-
Notifications
You must be signed in to change notification settings - Fork 0
Developer Documentation
This documentation provides guidance for developers who want to contribute to or extend the pawnctl CLI tool. Here, we'll focus on the architecture, how to add new commands, and how to use the available utilities.
pawnctl/
├── src/
│ ├── commands/ # CLI commands
│ │ ├── build/ # build command
│ │ ├── init/ # init command
│ │ ├── start/ # start command
│ │ └── index.ts # command registration system
│ ├── core/ # Core functionality
│ │ └── manifest.ts # Project manifest handler
│ ├── templates/ # Template files
│ │ ├── common/ # Common templates
│ │ ├── projects/ # Project templates
│ │ └── vscode/ # VS Code templates
│ ├── utils/ # Utility functions
│ │ ├── banner.ts # CLI banner
│ │ ├── config.ts # Configuration manager
│ │ ├── logger.ts # Logging system
│ │ └── serverState.ts # Server state tracking
│ └── index.ts # Main entry point
├── scripts/ # Build scripts
└── dist/ # Compiled output
The command registration system is defined in src/commands/index.ts. It automatically discovers and registers commands based on the file structure.
-
The system looks for commands in two ways:
- Folders in
src/commands/that contain a file with the same name as the folder - Standalone files in
src/commands/
- Folders in
-
For each potential command, it tries to import the module and find a function to register the command.
Each command module must export a function that takes a Commander instance and registers the command. The function can be:
- The default export
- Named export matching the pattern
{commandName}Command - Named export called
registerCommand
-
Create a folder for your command:
src/commands/deploy/ -
Create a file with the same name as the folder:
src/commands/deploy/deploy.ts -
Implement your command:
import { Command } from 'commander';
import { logger } from '../../utils/logger';
import { showBanner } from '../../utils/banner';
export default function(program: Command): void {
program
.command('deploy')
.description('Deploy your project to a server')
.option('-s, --server <server>', 'server to deploy to')
.option('-p, --port <port>', 'port to use for deployment', '22')
.action(async (options) => {
showBanner(false);
try {
logger.info('Deploying project...');
// Your command implementation goes here
logger.success('Deployment completed successfully!');
} catch (error) {
logger.error(
`Failed to deploy: ${error instanceof Error ? error.message : 'unknown error'}`
);
process.exit(1);
}
});
}-
Create a single file in the
src/commands/directory:src/commands/deploy.ts -
Implement your command using the same structure as above.
pawnctl provides several utilities to help when creating commands.
The logger system in src/utils/logger.ts provides consistent output formatting with different verbosity levels.
import { logger } from '../../utils/logger';
// Basic logging (shown in normal and verbose mode)
logger.plain("Regular message");
logger.info("Informational message");
logger.success("Success message");
logger.warn("Warning message");
logger.error("Error message");
logger.finalSuccess("Final success message (highlighted)");
// Detailed logging (only shown in verbose mode)
logger.detail("Detailed information");
logger.routine("Routine operations");
// Set verbosity level
logger.setVerbosity('normal'); // Default
logger.setVerbosity('verbose'); // More detailed logs
logger.setVerbosity('quiet'); // Only essential outputThe banner utility in src/utils/banner.ts displays the pawnctl logo.
import { showBanner } from '../../utils/banner';
// Show minimized banner (just the text)
showBanner(false);
// Show full ASCII art banner
showBanner(true);The configuration manager in src/utils/config.ts handles user preferences.
import { configManager } from '../../utils/config';
// Get user configuration
const githubToken = configManager.getGitHubToken();
const defaultAuthor = configManager.getDefaultAuthor();
// Set user configuration
configManager.setGitHubToken('your-token');
configManager.setDefaultAuthor('Your Name');The server state utilities in src/utils/serverState.ts manage the state of running servers.
import {
loadServerState,
saveServerState,
clearServerState,
isServerRunning
} from '../../utils/serverState';
// Check if server is running
if (isServerRunning()) {
logger.info('Server is running');
}
// Save server state
saveServerState({
pid: process.pid,
serverPath: '/path/to/server'
});
// Load server state
const state = loadServerState();
// Clear server state
clearServerState();The manifest system in src/core/manifest.ts handles the project manifest file (pawn.json).
import {
generatePackageManifest,
loadManifest,
updateManifest
} from '../../core/manifest';
// Create a new manifest
await generatePackageManifest({
name: 'my-project',
description: 'My awesome project',
author: 'John Doe',
projectType: 'gamemode',
addStdLib: true
});
// Load existing manifest
const manifest = await loadManifest();
// Update manifest
await updateManifest({
version: '1.1.0',
compiler: {
constants: {
MAX_PLAYERS: 100
}
}
});-
Structure:
- Use the folder approach for complex commands
- Use standalone files for simpler commands
-
Error Handling:
- Wrap command actions in try/catch blocks
- Use logger.error for error messages
- Set appropriate exit codes (0 for success, non-zero for failure)
-
User Experience:
- Show the banner at the start of your command
- Provide clear progress indicators
- Support both normal and verbose output modes
- Follow existing command patterns for consistency
-
Documentation:
- Add a clear description to your command
- Document each option with helpful descriptions
- Include examples in the command help text
-
Testing:
- Build and test your command locally before committing
- Test on all supported platforms when possible
- Test with both default and custom options
Follow these patterns when developing new commands:
program
.command('command-name')
.description('Clear description of what the command does')
.option('-a, --option-a <value>', 'Description of option A')
.option('-b, --option-b', 'Description of flag option B')
.action(async (options) => {
// Command implementation
});.action(async (options) => {
showBanner(false);
try {
logger.info('Starting command...');
// Implement command logic here
logger.success('Command completed successfully');
} catch (error) {
logger.error(
`Command failed: ${error instanceof Error ? error.message : 'unknown error'}`
);
process.exit(1);
}
});After adding a new command:
-
Build the project:
npm run build
-
Test your command:
pawnctl your-command