Initial commit
This commit is contained in:
19
skills/commander-patterns/templates/basic-commander.js
Normal file
19
skills/commander-patterns/templates/basic-commander.js
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
import { Command } from 'commander';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('A simple CLI tool')
|
||||
.version('1.0.0');
|
||||
|
||||
program
|
||||
.command('hello')
|
||||
.description('Say hello')
|
||||
.option('-n, --name <name>', 'name to greet', 'World')
|
||||
.action((options) => {
|
||||
console.log(`Hello, ${options.name}!`);
|
||||
});
|
||||
|
||||
program.parse();
|
||||
18
skills/commander-patterns/templates/basic-commander.ts
Normal file
18
skills/commander-patterns/templates/basic-commander.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Command } from 'commander';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('A simple CLI tool')
|
||||
.version('1.0.0');
|
||||
|
||||
program
|
||||
.command('hello')
|
||||
.description('Say hello')
|
||||
.option('-n, --name <name>', 'name to greet', 'World')
|
||||
.action((options) => {
|
||||
console.log(`Hello, ${options.name}!`);
|
||||
});
|
||||
|
||||
program.parse();
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('CLI with various argument types')
|
||||
.version('1.0.0');
|
||||
|
||||
// Command with required argument
|
||||
program
|
||||
.command('deploy <environment>')
|
||||
.description('Deploy to specified environment')
|
||||
.argument('<environment>', 'target environment (dev, staging, prod)')
|
||||
.action((environment) => {
|
||||
console.log(chalk.blue(`Deploying to ${environment}...`));
|
||||
});
|
||||
|
||||
// Command with required and optional arguments
|
||||
program
|
||||
.command('create <name> [description]')
|
||||
.description('Create new item')
|
||||
.argument('<name>', 'item name')
|
||||
.argument('[description]', 'item description', 'No description provided')
|
||||
.action((name, description) => {
|
||||
console.log(`Creating: ${name}`);
|
||||
console.log(`Description: ${description}`);
|
||||
});
|
||||
|
||||
// Command with variadic arguments
|
||||
program
|
||||
.command('add <items...>')
|
||||
.description('Add multiple items')
|
||||
.argument('<items...>', 'items to add')
|
||||
.action((items) => {
|
||||
console.log(chalk.blue('Adding items:'));
|
||||
items.forEach((item, index) => {
|
||||
console.log(` ${index + 1}. ${item}`);
|
||||
});
|
||||
console.log(chalk.green(`✓ Added ${items.length} items`));
|
||||
});
|
||||
|
||||
// Command with custom argument parser
|
||||
program
|
||||
.command('wait <seconds>')
|
||||
.description('Wait for specified time')
|
||||
.argument('<seconds>', 'seconds to wait', parseFloat)
|
||||
.action(async (seconds) => {
|
||||
console.log(chalk.blue(`Waiting ${seconds} seconds...`));
|
||||
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
||||
console.log(chalk.green('✓ Done'));
|
||||
});
|
||||
|
||||
// Command with arguments and options
|
||||
program
|
||||
.command('copy <source> <destination>')
|
||||
.description('Copy file from source to destination')
|
||||
.argument('<source>', 'source file path')
|
||||
.argument('<destination>', 'destination file path')
|
||||
.option('-f, --force', 'overwrite if exists', false)
|
||||
.option('-r, --recursive', 'copy recursively', false)
|
||||
.action((source, destination, options) => {
|
||||
console.log(`Copying ${source} to ${destination}`);
|
||||
console.log('Options:', options);
|
||||
if (options.force) {
|
||||
console.log(chalk.yellow('⚠ Force mode: will overwrite existing files'));
|
||||
}
|
||||
if (options.recursive) {
|
||||
console.log(chalk.blue('Recursive copy enabled'));
|
||||
}
|
||||
console.log(chalk.green('✓ Copy complete'));
|
||||
});
|
||||
|
||||
// Command with argument validation
|
||||
program
|
||||
.command('set-port <port>')
|
||||
.description('Set application port')
|
||||
.argument('<port>', 'port number', (value) => {
|
||||
const port = parseInt(value, 10);
|
||||
if (isNaN(port)) {
|
||||
throw new Error('Port must be a number');
|
||||
}
|
||||
if (port < 1 || port > 65535) {
|
||||
throw new Error('Port must be between 1 and 65535');
|
||||
}
|
||||
return port;
|
||||
})
|
||||
.action((port) => {
|
||||
console.log(chalk.green(`✓ Port set to ${port}`));
|
||||
});
|
||||
|
||||
program.parse();
|
||||
60
skills/commander-patterns/templates/command-with-options.ts
Normal file
60
skills/commander-patterns/templates/command-with-options.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Command, Option } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('CLI with various option types')
|
||||
.version('1.0.0');
|
||||
|
||||
program
|
||||
.command('deploy')
|
||||
.description('Deploy application')
|
||||
// Boolean flag
|
||||
.option('-v, --verbose', 'verbose output', false)
|
||||
// Option with required value
|
||||
.option('-p, --port <port>', 'port number', '3000')
|
||||
// Option with optional value
|
||||
.option('-c, --config [path]', 'config file path')
|
||||
// Negatable option
|
||||
.option('--no-build', 'skip build step')
|
||||
// Multiple choice option using Option class
|
||||
.addOption(
|
||||
new Option('-e, --env <environment>', 'target environment')
|
||||
.choices(['dev', 'staging', 'prod'])
|
||||
.default('dev')
|
||||
)
|
||||
// Option with custom parser
|
||||
.addOption(
|
||||
new Option('-r, --replicas <count>', 'number of replicas')
|
||||
.argParser(parseInt)
|
||||
.default(3)
|
||||
)
|
||||
// Mandatory option
|
||||
.addOption(
|
||||
new Option('-t, --token <token>', 'API token')
|
||||
.makeOptionMandatory()
|
||||
.env('API_TOKEN')
|
||||
)
|
||||
// Variadic option
|
||||
.option('--tags <tags...>', 'deployment tags')
|
||||
.action((options) => {
|
||||
console.log(chalk.blue('Deploying with options:'));
|
||||
console.log('Verbose:', options.verbose);
|
||||
console.log('Port:', options.port);
|
||||
console.log('Config:', options.config);
|
||||
console.log('Build:', options.build);
|
||||
console.log('Environment:', options.env);
|
||||
console.log('Replicas:', options.replicas);
|
||||
console.log('Token:', options.token ? '***' : 'not set');
|
||||
console.log('Tags:', options.tags);
|
||||
|
||||
if (options.verbose) {
|
||||
console.log(chalk.gray('Verbose mode enabled'));
|
||||
}
|
||||
|
||||
console.log(chalk.green('✓ Deployment complete'));
|
||||
});
|
||||
|
||||
program.parse();
|
||||
341
skills/commander-patterns/templates/commander-with-inquirer.ts
Normal file
341
skills/commander-patterns/templates/commander-with-inquirer.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
import { Command } from 'commander';
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('Interactive CLI with Inquirer.js integration')
|
||||
.version('1.0.0');
|
||||
|
||||
// Interactive init command
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize project interactively')
|
||||
.option('-y, --yes', 'skip prompts and use defaults', false)
|
||||
.action(async (options) => {
|
||||
if (options.yes) {
|
||||
console.log(chalk.blue('Using default configuration...'));
|
||||
await initProject({
|
||||
name: 'my-project',
|
||||
template: 'basic',
|
||||
features: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Interactive prompts
|
||||
const answers = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Project name:',
|
||||
default: 'my-project',
|
||||
validate: (input) => {
|
||||
if (!input.trim()) {
|
||||
return 'Project name is required';
|
||||
}
|
||||
if (!/^[a-z0-9-]+$/.test(input)) {
|
||||
return 'Project name must contain only lowercase letters, numbers, and hyphens';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'template',
|
||||
message: 'Choose a template:',
|
||||
choices: ['basic', 'advanced', 'enterprise'],
|
||||
default: 'basic',
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'features',
|
||||
message: 'Select features:',
|
||||
choices: [
|
||||
{ name: 'TypeScript', value: 'typescript', checked: true },
|
||||
{ name: 'ESLint', value: 'eslint', checked: true },
|
||||
{ name: 'Prettier', value: 'prettier', checked: true },
|
||||
{ name: 'Testing (Jest)', value: 'jest' },
|
||||
{ name: 'CI/CD', value: 'cicd' },
|
||||
{ name: 'Docker', value: 'docker' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'install',
|
||||
message: 'Install dependencies now?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
await initProject(answers);
|
||||
});
|
||||
|
||||
// Interactive deploy command
|
||||
program
|
||||
.command('deploy')
|
||||
.description('Deploy with interactive configuration')
|
||||
.option('-e, --env <environment>', 'skip environment prompt')
|
||||
.action(async (options) => {
|
||||
const questions: any[] = [];
|
||||
|
||||
// Conditionally add environment question
|
||||
if (!options.env) {
|
||||
questions.push({
|
||||
type: 'list',
|
||||
name: 'environment',
|
||||
message: 'Select deployment environment:',
|
||||
choices: ['dev', 'staging', 'prod'],
|
||||
});
|
||||
}
|
||||
|
||||
questions.push(
|
||||
{
|
||||
type: 'list',
|
||||
name: 'strategy',
|
||||
message: 'Deployment strategy:',
|
||||
choices: ['rolling', 'blue-green', 'canary'],
|
||||
default: 'rolling',
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'runTests',
|
||||
message: 'Run tests before deployment?',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'createBackup',
|
||||
message: 'Create backup before deployment?',
|
||||
default: true,
|
||||
when: (answers) => answers.environment === 'prod',
|
||||
},
|
||||
{
|
||||
type: 'password',
|
||||
name: 'token',
|
||||
message: 'Enter deployment token:',
|
||||
mask: '*',
|
||||
validate: (input) => (input.length > 0 ? true : 'Token is required'),
|
||||
}
|
||||
);
|
||||
|
||||
const answers = await inquirer.prompt(questions);
|
||||
|
||||
const deployConfig = {
|
||||
environment: options.env || answers.environment,
|
||||
...answers,
|
||||
};
|
||||
|
||||
console.log(chalk.blue('\nDeployment configuration:'));
|
||||
console.log(JSON.stringify(deployConfig, null, 2));
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: 'Proceed with deployment?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!confirm) {
|
||||
console.log(chalk.yellow('Deployment cancelled'));
|
||||
return;
|
||||
}
|
||||
|
||||
await deploy(deployConfig);
|
||||
});
|
||||
|
||||
// Interactive config command
|
||||
program
|
||||
.command('config')
|
||||
.description('Configure application interactively')
|
||||
.action(async () => {
|
||||
const mainMenu = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'action',
|
||||
message: 'What would you like to do?',
|
||||
choices: [
|
||||
{ name: 'View configuration', value: 'view' },
|
||||
{ name: 'Edit configuration', value: 'edit' },
|
||||
{ name: 'Reset to defaults', value: 'reset' },
|
||||
{ name: 'Exit', value: 'exit' },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
switch (mainMenu.action) {
|
||||
case 'view':
|
||||
console.log(chalk.blue('\nCurrent configuration:'));
|
||||
console.log(JSON.stringify(getConfig(), null, 2));
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
const config = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'apiUrl',
|
||||
message: 'API URL:',
|
||||
default: getConfig().apiUrl,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
name: 'timeout',
|
||||
message: 'Request timeout (ms):',
|
||||
default: getConfig().timeout,
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
name: 'logLevel',
|
||||
message: 'Log level:',
|
||||
choices: ['debug', 'info', 'warn', 'error'],
|
||||
default: getConfig().logLevel,
|
||||
},
|
||||
]);
|
||||
saveConfig(config);
|
||||
console.log(chalk.green('✓ Configuration saved'));
|
||||
break;
|
||||
|
||||
case 'reset':
|
||||
const { confirmReset } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirmReset',
|
||||
message: 'Reset to default configuration?',
|
||||
default: false,
|
||||
},
|
||||
]);
|
||||
if (confirmReset) {
|
||||
resetConfig();
|
||||
console.log(chalk.green('✓ Configuration reset'));
|
||||
}
|
||||
break;
|
||||
|
||||
case 'exit':
|
||||
console.log(chalk.gray('Goodbye!'));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Multi-step wizard
|
||||
program
|
||||
.command('wizard')
|
||||
.description('Run setup wizard')
|
||||
.action(async () => {
|
||||
console.log(chalk.blue('🧙 Welcome to the setup wizard\n'));
|
||||
|
||||
// Step 1: Basic info
|
||||
console.log(chalk.bold('Step 1: Basic Information'));
|
||||
const step1 = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'projectName',
|
||||
message: 'Project name:',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'description',
|
||||
message: 'Description:',
|
||||
},
|
||||
]);
|
||||
|
||||
// Step 2: Technology stack
|
||||
console.log(chalk.bold('\nStep 2: Technology Stack'));
|
||||
const step2 = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'language',
|
||||
message: 'Primary language:',
|
||||
choices: ['TypeScript', 'JavaScript', 'Python', 'Go', 'Rust'],
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'frameworks',
|
||||
message: 'Select frameworks:',
|
||||
choices: (answers) => {
|
||||
const frameworksByLanguage: Record<string, string[]> = {
|
||||
TypeScript: ['Next.js', 'Express', 'NestJS'],
|
||||
JavaScript: ['React', 'Vue', 'Express'],
|
||||
Python: ['FastAPI', 'Django', 'Flask'],
|
||||
Go: ['Gin', 'Echo', 'Fiber'],
|
||||
Rust: ['Actix', 'Rocket', 'Axum'],
|
||||
};
|
||||
return frameworksByLanguage[answers.language] || [];
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// Step 3: Infrastructure
|
||||
console.log(chalk.bold('\nStep 3: Infrastructure'));
|
||||
const step3 = await inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'database',
|
||||
message: 'Database:',
|
||||
choices: ['PostgreSQL', 'MySQL', 'MongoDB', 'SQLite', 'None'],
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'services',
|
||||
message: 'Additional services:',
|
||||
choices: ['Redis', 'ElasticSearch', 'RabbitMQ', 'S3'],
|
||||
},
|
||||
]);
|
||||
|
||||
// Summary
|
||||
const config = { ...step1, ...step2, ...step3 };
|
||||
|
||||
console.log(chalk.bold('\n📋 Configuration Summary:'));
|
||||
console.log(JSON.stringify(config, null, 2));
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: 'Create project with this configuration?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (confirm) {
|
||||
console.log(chalk.green('\n✓ Project created successfully!'));
|
||||
} else {
|
||||
console.log(chalk.yellow('\n✗ Project creation cancelled'));
|
||||
}
|
||||
});
|
||||
|
||||
// Helper functions
|
||||
async function initProject(config: any) {
|
||||
console.log(chalk.blue('\nInitializing project...'));
|
||||
console.log('Name:', config.name);
|
||||
console.log('Template:', config.template);
|
||||
console.log('Features:', config.features.join(', '));
|
||||
console.log(chalk.green('\n✓ Project initialized!'));
|
||||
}
|
||||
|
||||
async function deploy(config: any) {
|
||||
console.log(chalk.blue('\nDeploying...'));
|
||||
console.log(JSON.stringify(config, null, 2));
|
||||
console.log(chalk.green('\n✓ Deployment complete!'));
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
return {
|
||||
apiUrl: 'https://api.example.com',
|
||||
timeout: 5000,
|
||||
logLevel: 'info',
|
||||
};
|
||||
}
|
||||
|
||||
function saveConfig(config: any) {
|
||||
console.log('Saving config:', config);
|
||||
}
|
||||
|
||||
function resetConfig() {
|
||||
console.log('Resetting config to defaults');
|
||||
}
|
||||
|
||||
program.parse();
|
||||
266
skills/commander-patterns/templates/commander-with-validation.ts
Normal file
266
skills/commander-patterns/templates/commander-with-validation.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { Command, Option } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import { z } from 'zod';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('CLI with comprehensive input validation')
|
||||
.version('1.0.0');
|
||||
|
||||
// Zod schemas for validation
|
||||
const EmailSchema = z.string().email();
|
||||
const UrlSchema = z.string().url();
|
||||
const PortSchema = z.number().int().min(1).max(65535);
|
||||
const EnvironmentSchema = z.enum(['dev', 'staging', 'prod']);
|
||||
|
||||
// Validation helper
|
||||
function validateOrThrow<T>(schema: z.ZodSchema<T>, value: unknown, fieldName: string): T {
|
||||
try {
|
||||
return schema.parse(value);
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
throw new Error(`Invalid ${fieldName}: ${error.errors[0].message}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Command with validated arguments
|
||||
program
|
||||
.command('create-user <email> <username>')
|
||||
.description('Create user with validated inputs')
|
||||
.argument('<email>', 'user email', (value) => {
|
||||
return validateOrThrow(EmailSchema, value, 'email');
|
||||
})
|
||||
.argument('<username>', 'username', (value) => {
|
||||
const UsernameSchema = z
|
||||
.string()
|
||||
.min(3, 'Username must be at least 3 characters')
|
||||
.max(20, 'Username must be at most 20 characters')
|
||||
.regex(/^[a-z0-9_]+$/, 'Username must contain only lowercase letters, numbers, and underscores');
|
||||
|
||||
return validateOrThrow(UsernameSchema, value, 'username');
|
||||
})
|
||||
.option('-a, --age <age>', 'user age', (value) => {
|
||||
const age = parseInt(value, 10);
|
||||
const AgeSchema = z.number().int().min(13).max(120);
|
||||
return validateOrThrow(AgeSchema, age, 'age');
|
||||
})
|
||||
.action((email, username, options) => {
|
||||
console.log(chalk.green('✓ User validation passed'));
|
||||
console.log('Email:', email);
|
||||
console.log('Username:', username);
|
||||
if (options.age) {
|
||||
console.log('Age:', options.age);
|
||||
}
|
||||
});
|
||||
|
||||
// Command with validated options
|
||||
program
|
||||
.command('deploy')
|
||||
.description('Deploy with validated configuration')
|
||||
.addOption(
|
||||
new Option('-e, --env <environment>', 'deployment environment')
|
||||
.argParser((value) => {
|
||||
return validateOrThrow(EnvironmentSchema, value, 'environment');
|
||||
})
|
||||
.makeOptionMandatory()
|
||||
)
|
||||
.addOption(
|
||||
new Option('-u, --url <url>', 'deployment URL')
|
||||
.argParser((value) => {
|
||||
return validateOrThrow(UrlSchema, value, 'URL');
|
||||
})
|
||||
.makeOptionMandatory()
|
||||
)
|
||||
.addOption(
|
||||
new Option('-p, --port <port>', 'server port')
|
||||
.argParser((value) => {
|
||||
const port = parseInt(value, 10);
|
||||
return validateOrThrow(PortSchema, port, 'port');
|
||||
})
|
||||
.default(3000)
|
||||
)
|
||||
.addOption(
|
||||
new Option('-r, --replicas <count>', 'replica count')
|
||||
.argParser((value) => {
|
||||
const count = parseInt(value, 10);
|
||||
const ReplicaSchema = z.number().int().min(1).max(100);
|
||||
return validateOrThrow(ReplicaSchema, count, 'replicas');
|
||||
})
|
||||
.default(3)
|
||||
)
|
||||
.action((options) => {
|
||||
console.log(chalk.green('✓ Deployment configuration validated'));
|
||||
console.log('Environment:', options.env);
|
||||
console.log('URL:', options.url);
|
||||
console.log('Port:', options.port);
|
||||
console.log('Replicas:', options.replicas);
|
||||
});
|
||||
|
||||
// Command with complex object validation
|
||||
program
|
||||
.command('configure')
|
||||
.description('Configure application with JSON validation')
|
||||
.option('-c, --config <json>', 'configuration as JSON string', (value) => {
|
||||
// Parse JSON
|
||||
let parsed;
|
||||
try {
|
||||
parsed = JSON.parse(value);
|
||||
} catch {
|
||||
throw new Error('Invalid JSON format');
|
||||
}
|
||||
|
||||
// Validate with Zod schema
|
||||
const ConfigSchema = z.object({
|
||||
api: z.object({
|
||||
url: z.string().url(),
|
||||
timeout: z.number().int().positive(),
|
||||
retries: z.number().int().min(0).max(5),
|
||||
}),
|
||||
database: z.object({
|
||||
host: z.string(),
|
||||
port: z.number().int().min(1).max(65535),
|
||||
name: z.string(),
|
||||
}),
|
||||
features: z.object({
|
||||
enableCache: z.boolean(),
|
||||
enableMetrics: z.boolean(),
|
||||
}),
|
||||
});
|
||||
|
||||
return validateOrThrow(ConfigSchema, parsed, 'configuration');
|
||||
})
|
||||
.action((options) => {
|
||||
if (options.config) {
|
||||
console.log(chalk.green('✓ Configuration validated'));
|
||||
console.log(JSON.stringify(options.config, null, 2));
|
||||
}
|
||||
});
|
||||
|
||||
// Command with file path validation
|
||||
program
|
||||
.command('process <inputFile> <outputFile>')
|
||||
.description('Process file with path validation')
|
||||
.argument('<inputFile>', 'input file path', (value) => {
|
||||
const FilePathSchema = z.string().refine(
|
||||
(path) => {
|
||||
// Check file extension
|
||||
return path.endsWith('.json') || path.endsWith('.yaml') || path.endsWith('.yml');
|
||||
},
|
||||
{ message: 'File must be .json, .yaml, or .yml' }
|
||||
);
|
||||
|
||||
return validateOrThrow(FilePathSchema, value, 'input file');
|
||||
})
|
||||
.argument('<outputFile>', 'output file path', (value) => {
|
||||
const FilePathSchema = z.string().min(1);
|
||||
return validateOrThrow(FilePathSchema, value, 'output file');
|
||||
})
|
||||
.action((inputFile, outputFile) => {
|
||||
console.log(chalk.green('✓ File paths validated'));
|
||||
console.log('Input:', inputFile);
|
||||
console.log('Output:', outputFile);
|
||||
});
|
||||
|
||||
// Command with date/time validation
|
||||
program
|
||||
.command('schedule <datetime>')
|
||||
.description('Schedule task with datetime validation')
|
||||
.argument('<datetime>', 'ISO 8601 datetime', (value) => {
|
||||
const date = new Date(value);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
throw new Error('Invalid datetime format (use ISO 8601)');
|
||||
}
|
||||
|
||||
if (date < new Date()) {
|
||||
throw new Error('Datetime must be in the future');
|
||||
}
|
||||
|
||||
return date;
|
||||
})
|
||||
.option('-d, --duration <minutes>', 'duration in minutes', (value) => {
|
||||
const minutes = parseInt(value, 10);
|
||||
const DurationSchema = z.number().int().min(1).max(1440); // 1 min to 24 hours
|
||||
return validateOrThrow(DurationSchema, minutes, 'duration');
|
||||
})
|
||||
.action((datetime, options) => {
|
||||
console.log(chalk.green('✓ Schedule validated'));
|
||||
console.log('Start time:', datetime.toISOString());
|
||||
if (options.duration) {
|
||||
const endTime = new Date(datetime.getTime() + options.duration * 60000);
|
||||
console.log('End time:', endTime.toISOString());
|
||||
}
|
||||
});
|
||||
|
||||
// Command with array validation
|
||||
program
|
||||
.command('tag <items...>')
|
||||
.description('Tag items with validation')
|
||||
.argument('<items...>', 'items to tag')
|
||||
.option('-t, --tags <tags...>', 'tags to apply', (values) => {
|
||||
const TagSchema = z.array(
|
||||
z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(20)
|
||||
.regex(/^[a-z0-9-]+$/, 'Tags must contain only lowercase letters, numbers, and hyphens')
|
||||
);
|
||||
|
||||
return validateOrThrow(TagSchema, values, 'tags');
|
||||
})
|
||||
.action((items, options) => {
|
||||
console.log(chalk.green('✓ Items and tags validated'));
|
||||
console.log('Items:', items);
|
||||
if (options.tags) {
|
||||
console.log('Tags:', options.tags);
|
||||
}
|
||||
});
|
||||
|
||||
// Command with custom validation logic
|
||||
program
|
||||
.command('migrate')
|
||||
.description('Database migration with version validation')
|
||||
.option('-f, --from <version>', 'migrate from version', (value) => {
|
||||
const VersionSchema = z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be in format X.Y.Z');
|
||||
return validateOrThrow(VersionSchema, value, 'from version');
|
||||
})
|
||||
.option('-t, --to <version>', 'migrate to version', (value) => {
|
||||
const VersionSchema = z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be in format X.Y.Z');
|
||||
return validateOrThrow(VersionSchema, value, 'to version');
|
||||
})
|
||||
.action((options) => {
|
||||
// Additional cross-field validation
|
||||
if (options.from && options.to) {
|
||||
const fromParts = options.from.split('.').map(Number);
|
||||
const toParts = options.to.split('.').map(Number);
|
||||
|
||||
const fromNum = fromParts[0] * 10000 + fromParts[1] * 100 + fromParts[2];
|
||||
const toNum = toParts[0] * 10000 + toParts[1] * 100 + toParts[2];
|
||||
|
||||
if (fromNum >= toNum) {
|
||||
throw new Error('Target version must be higher than source version');
|
||||
}
|
||||
|
||||
console.log(chalk.green('✓ Migration versions validated'));
|
||||
console.log(`Migrating from ${options.from} to ${options.to}`);
|
||||
}
|
||||
});
|
||||
|
||||
// Global error handling
|
||||
program.exitOverride();
|
||||
|
||||
try {
|
||||
program.parse();
|
||||
} catch (error: any) {
|
||||
if (error.code === 'commander.help' || error.code === 'commander.version') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error(chalk.red('Validation Error:'), error.message);
|
||||
console.error(chalk.gray('\nUse --help for usage information'));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
30
skills/commander-patterns/templates/commonjs-commander.js
Normal file
30
skills/commander-patterns/templates/commonjs-commander.js
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env node
|
||||
const { Command } = require('commander');
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('A simple CLI tool (CommonJS)')
|
||||
.version('1.0.0');
|
||||
|
||||
program
|
||||
.command('hello')
|
||||
.description('Say hello')
|
||||
.option('-n, --name <name>', 'name to greet', 'World')
|
||||
.action((options) => {
|
||||
console.log(`Hello, ${options.name}!`);
|
||||
});
|
||||
|
||||
program
|
||||
.command('deploy <environment>')
|
||||
.description('Deploy to environment')
|
||||
.option('-f, --force', 'force deployment', false)
|
||||
.action((environment, options) => {
|
||||
console.log(`Deploying to ${environment}...`);
|
||||
if (options.force) {
|
||||
console.log('Force mode enabled');
|
||||
}
|
||||
});
|
||||
|
||||
program.parse();
|
||||
282
skills/commander-patterns/templates/full-cli-example.ts
Normal file
282
skills/commander-patterns/templates/full-cli-example.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env node
|
||||
import { Command, Option } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
// Configure main program
|
||||
program
|
||||
.name('mycli')
|
||||
.description('A powerful CLI tool with all Commander.js features')
|
||||
.version('1.0.0')
|
||||
.option('-c, --config <path>', 'config file path', './config.json')
|
||||
.option('-v, --verbose', 'verbose output', false)
|
||||
.option('--no-color', 'disable colored output');
|
||||
|
||||
// Init command
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize a new project')
|
||||
.option('-t, --template <type>', 'project template', 'basic')
|
||||
.option('-d, --directory <path>', 'target directory', '.')
|
||||
.option('-f, --force', 'overwrite existing files', false)
|
||||
.action(async (options) => {
|
||||
const spinner = ora('Initializing project...').start();
|
||||
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
spinner.text = 'Creating directory structure...';
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
spinner.text = 'Copying template files...';
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
spinner.text = 'Installing dependencies...';
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
spinner.succeed(chalk.green('Project initialized successfully!'));
|
||||
|
||||
console.log(chalk.blue('\nNext steps:'));
|
||||
console.log(` cd ${options.directory}`);
|
||||
console.log(' mycli dev');
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Initialization failed'));
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
// Dev command
|
||||
program
|
||||
.command('dev')
|
||||
.description('Start development server')
|
||||
.option('-p, --port <port>', 'server port', '3000')
|
||||
.option('-h, --host <host>', 'server host', 'localhost')
|
||||
.option('--open', 'open browser automatically', false)
|
||||
.action((options) => {
|
||||
console.log(chalk.blue('Starting development server...'));
|
||||
console.log(`Server running at http://${options.host}:${options.port}`);
|
||||
|
||||
if (options.open) {
|
||||
console.log(chalk.gray('Opening browser...'));
|
||||
}
|
||||
});
|
||||
|
||||
// Build command
|
||||
program
|
||||
.command('build')
|
||||
.description('Build for production')
|
||||
.addOption(
|
||||
new Option('-m, --mode <mode>', 'build mode')
|
||||
.choices(['development', 'production'])
|
||||
.default('production')
|
||||
)
|
||||
.option('--analyze', 'analyze bundle size', false)
|
||||
.option('--sourcemap', 'generate source maps', false)
|
||||
.action(async (options) => {
|
||||
const spinner = ora('Building for production...').start();
|
||||
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
spinner.succeed(chalk.green('Build complete!'));
|
||||
|
||||
console.log(chalk.blue('\nBuild info:'));
|
||||
console.log('Mode:', options.mode);
|
||||
console.log('Source maps:', options.sourcemap ? 'enabled' : 'disabled');
|
||||
|
||||
if (options.analyze) {
|
||||
console.log(chalk.gray('\nBundle analysis:'));
|
||||
console.log(' main.js: 245 KB');
|
||||
console.log(' vendor.js: 892 KB');
|
||||
}
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Build failed'));
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
// Deploy command with nested subcommands
|
||||
const deploy = program.command('deploy').description('Deployment operations');
|
||||
|
||||
deploy
|
||||
.command('start <environment>')
|
||||
.description('Deploy to specified environment')
|
||||
.argument('<environment>', 'target environment (dev, staging, prod)')
|
||||
.addOption(
|
||||
new Option('-s, --strategy <strategy>', 'deployment strategy')
|
||||
.choices(['rolling', 'blue-green', 'canary'])
|
||||
.default('rolling')
|
||||
)
|
||||
.option('-f, --force', 'force deployment', false)
|
||||
.option('--dry-run', 'simulate deployment', false)
|
||||
.action(async (environment, options) => {
|
||||
if (options.dryRun) {
|
||||
console.log(chalk.yellow('🔍 Dry run mode - no actual deployment'));
|
||||
}
|
||||
|
||||
const spinner = ora(`Deploying to ${environment}...`).start();
|
||||
|
||||
try {
|
||||
spinner.text = 'Running tests...';
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
|
||||
spinner.text = 'Building application...';
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500));
|
||||
|
||||
spinner.text = `Deploying with ${options.strategy} strategy...`;
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
spinner.succeed(chalk.green(`Deployed to ${environment}!`));
|
||||
|
||||
console.log(chalk.blue('\nDeployment details:'));
|
||||
console.log('Environment:', environment);
|
||||
console.log('Strategy:', options.strategy);
|
||||
console.log('URL:', `https://${environment}.example.com`);
|
||||
} catch (error) {
|
||||
spinner.fail(chalk.red('Deployment failed'));
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
deploy
|
||||
.command('rollback [version]')
|
||||
.description('Rollback to previous version')
|
||||
.argument('[version]', 'version to rollback to', 'previous')
|
||||
.option('-f, --force', 'skip confirmation', false)
|
||||
.action((version, options) => {
|
||||
if (!options.force) {
|
||||
console.log(chalk.yellow('⚠ This will rollback your deployment. Use --force to confirm.'));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`Rolling back to ${version}...`));
|
||||
console.log(chalk.green('✓ Rollback complete'));
|
||||
});
|
||||
|
||||
deploy
|
||||
.command('status')
|
||||
.description('Check deployment status')
|
||||
.option('-e, --env <environment>', 'check specific environment')
|
||||
.action((options) => {
|
||||
console.log(chalk.blue('Deployment status:'));
|
||||
|
||||
const envs = options.env ? [options.env] : ['dev', 'staging', 'prod'];
|
||||
|
||||
envs.forEach((env) => {
|
||||
console.log(`\n${env}:`);
|
||||
console.log(' Status:', chalk.green('healthy'));
|
||||
console.log(' Version:', '1.2.3');
|
||||
console.log(' Uptime:', '5d 12h 34m');
|
||||
});
|
||||
});
|
||||
|
||||
// Config command group
|
||||
const config = program.command('config').description('Configuration management');
|
||||
|
||||
config
|
||||
.command('get <key>')
|
||||
.description('Get configuration value')
|
||||
.action((key) => {
|
||||
console.log(`${key}: value`);
|
||||
});
|
||||
|
||||
config
|
||||
.command('set <key> <value>')
|
||||
.description('Set configuration value')
|
||||
.action((key, value) => {
|
||||
console.log(chalk.green(`✓ Set ${key} = ${value}`));
|
||||
});
|
||||
|
||||
config
|
||||
.command('list')
|
||||
.description('List all configuration')
|
||||
.option('-f, --format <format>', 'output format (json, yaml, table)', 'table')
|
||||
.action((options) => {
|
||||
console.log(`Configuration (format: ${options.format}):`);
|
||||
console.log('key1: value1');
|
||||
console.log('key2: value2');
|
||||
});
|
||||
|
||||
// Database command group
|
||||
const db = program.command('db').description('Database operations');
|
||||
|
||||
db.command('migrate')
|
||||
.description('Run database migrations')
|
||||
.option('-d, --dry-run', 'show migrations without running')
|
||||
.action((options) => {
|
||||
if (options.dryRun) {
|
||||
console.log(chalk.blue('Migrations to run:'));
|
||||
console.log(' 001_create_users.sql');
|
||||
console.log(' 002_add_email_index.sql');
|
||||
} else {
|
||||
console.log(chalk.blue('Running migrations...'));
|
||||
console.log(chalk.green('✓ 2 migrations applied'));
|
||||
}
|
||||
});
|
||||
|
||||
db.command('seed')
|
||||
.description('Seed database with data')
|
||||
.option('-e, --env <env>', 'environment', 'dev')
|
||||
.action((options) => {
|
||||
console.log(chalk.blue(`Seeding ${options.env} database...`));
|
||||
console.log(chalk.green('✓ Database seeded'));
|
||||
});
|
||||
|
||||
// Test command
|
||||
program
|
||||
.command('test [pattern]')
|
||||
.description('Run tests')
|
||||
.argument('[pattern]', 'test file pattern', '**/*.test.ts')
|
||||
.option('-w, --watch', 'watch mode', false)
|
||||
.option('-c, --coverage', 'collect coverage', false)
|
||||
.option('--verbose', 'verbose output', false)
|
||||
.action((pattern, options) => {
|
||||
console.log(chalk.blue('Running tests...'));
|
||||
console.log('Pattern:', pattern);
|
||||
|
||||
if (options.watch) {
|
||||
console.log(chalk.gray('Watch mode enabled'));
|
||||
}
|
||||
|
||||
if (options.coverage) {
|
||||
console.log(chalk.gray('\nCoverage:'));
|
||||
console.log(' Statements: 85%');
|
||||
console.log(' Branches: 78%');
|
||||
console.log(' Functions: 92%');
|
||||
console.log(' Lines: 87%');
|
||||
}
|
||||
|
||||
console.log(chalk.green('\n✓ 42 tests passed'));
|
||||
});
|
||||
|
||||
// Global error handling
|
||||
program.exitOverride();
|
||||
|
||||
try {
|
||||
program.parse();
|
||||
} catch (error: any) {
|
||||
if (error.code === 'commander.help') {
|
||||
// Help was displayed, exit normally
|
||||
process.exit(0);
|
||||
} else if (error.code === 'commander.version') {
|
||||
// Version was displayed, exit normally
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.error(chalk.red('Error:'), error.message);
|
||||
|
||||
const globalOpts = program.opts();
|
||||
if (globalOpts.verbose) {
|
||||
console.error(chalk.gray('\nStack trace:'));
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle no command
|
||||
if (process.argv.length <= 2) {
|
||||
program.help();
|
||||
}
|
||||
150
skills/commander-patterns/templates/nested-subcommands.ts
Normal file
150
skills/commander-patterns/templates/nested-subcommands.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Command } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('CLI with nested subcommands')
|
||||
.version('1.0.0');
|
||||
|
||||
// Config command group
|
||||
const config = program.command('config').description('Manage configuration');
|
||||
|
||||
config
|
||||
.command('get <key>')
|
||||
.description('Get configuration value')
|
||||
.action((key) => {
|
||||
const value = getConfig(key);
|
||||
if (value) {
|
||||
console.log(`${key} = ${value}`);
|
||||
} else {
|
||||
console.log(chalk.yellow(`Config key '${key}' not found`));
|
||||
}
|
||||
});
|
||||
|
||||
config
|
||||
.command('set <key> <value>')
|
||||
.description('Set configuration value')
|
||||
.action((key, value) => {
|
||||
setConfig(key, value);
|
||||
console.log(chalk.green(`✓ Set ${key} = ${value}`));
|
||||
});
|
||||
|
||||
config
|
||||
.command('list')
|
||||
.description('List all configuration')
|
||||
.option('-f, --format <format>', 'output format (json, table)', 'table')
|
||||
.action((options) => {
|
||||
const allConfig = getAllConfig();
|
||||
if (options.format === 'json') {
|
||||
console.log(JSON.stringify(allConfig, null, 2));
|
||||
} else {
|
||||
Object.entries(allConfig).forEach(([key, value]) => {
|
||||
console.log(`${key.padEnd(20)} ${value}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
config
|
||||
.command('delete <key>')
|
||||
.description('Delete configuration value')
|
||||
.option('-f, --force', 'skip confirmation', false)
|
||||
.action((key, options) => {
|
||||
if (!options.force) {
|
||||
console.log(chalk.yellow(`Are you sure? Use --force to confirm`));
|
||||
return;
|
||||
}
|
||||
deleteConfig(key);
|
||||
console.log(chalk.green(`✓ Deleted ${key}`));
|
||||
});
|
||||
|
||||
// Database command group
|
||||
const db = program.command('db').description('Database operations');
|
||||
|
||||
db.command('migrate')
|
||||
.description('Run database migrations')
|
||||
.option('-d, --dry-run', 'show what would be migrated')
|
||||
.action((options) => {
|
||||
if (options.dryRun) {
|
||||
console.log(chalk.blue('Dry run: showing migrations...'));
|
||||
} else {
|
||||
console.log(chalk.blue('Running migrations...'));
|
||||
}
|
||||
console.log(chalk.green('✓ Migrations complete'));
|
||||
});
|
||||
|
||||
db.command('seed')
|
||||
.description('Seed database with data')
|
||||
.option('-e, --env <env>', 'environment', 'dev')
|
||||
.action((options) => {
|
||||
console.log(chalk.blue(`Seeding ${options.env} database...`));
|
||||
console.log(chalk.green('✓ Seeding complete'));
|
||||
});
|
||||
|
||||
db.command('reset')
|
||||
.description('Reset database')
|
||||
.option('-f, --force', 'skip confirmation')
|
||||
.action((options) => {
|
||||
if (!options.force) {
|
||||
console.log(chalk.red('⚠ This will delete all data! Use --force to confirm'));
|
||||
return;
|
||||
}
|
||||
console.log(chalk.yellow('Resetting database...'));
|
||||
console.log(chalk.green('✓ Database reset'));
|
||||
});
|
||||
|
||||
// User command group with nested subcommands
|
||||
const user = program.command('user').description('User management');
|
||||
|
||||
const userList = user.command('list').description('List users');
|
||||
userList
|
||||
.option('-p, --page <page>', 'page number', '1')
|
||||
.option('-l, --limit <limit>', 'items per page', '10')
|
||||
.action((options) => {
|
||||
console.log(`Listing users (page ${options.page}, limit ${options.limit})`);
|
||||
});
|
||||
|
||||
user
|
||||
.command('create <username> <email>')
|
||||
.description('Create new user')
|
||||
.option('-r, --role <role>', 'user role', 'user')
|
||||
.action((username, email, options) => {
|
||||
console.log(chalk.green(`✓ Created user ${username} (${email}) with role ${options.role}`));
|
||||
});
|
||||
|
||||
user
|
||||
.command('delete <userId>')
|
||||
.description('Delete user')
|
||||
.option('-f, --force', 'skip confirmation')
|
||||
.action((userId, options) => {
|
||||
if (!options.force) {
|
||||
console.log(chalk.red('Use --force to confirm deletion'));
|
||||
return;
|
||||
}
|
||||
console.log(chalk.green(`✓ Deleted user ${userId}`));
|
||||
});
|
||||
|
||||
// Helper functions (mock implementations)
|
||||
const configStore: Record<string, string> = {
|
||||
apiUrl: 'https://api.example.com',
|
||||
timeout: '5000',
|
||||
};
|
||||
|
||||
function getConfig(key: string): string | undefined {
|
||||
return configStore[key];
|
||||
}
|
||||
|
||||
function setConfig(key: string, value: string): void {
|
||||
configStore[key] = value;
|
||||
}
|
||||
|
||||
function getAllConfig(): Record<string, string> {
|
||||
return { ...configStore };
|
||||
}
|
||||
|
||||
function deleteConfig(key: string): void {
|
||||
delete configStore[key];
|
||||
}
|
||||
|
||||
program.parse();
|
||||
213
skills/commander-patterns/templates/option-class-advanced.ts
Normal file
213
skills/commander-patterns/templates/option-class-advanced.ts
Normal file
@@ -0,0 +1,213 @@
|
||||
import { Command, Option } from 'commander';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('mycli')
|
||||
.description('Advanced Option class usage')
|
||||
.version('1.0.0');
|
||||
|
||||
program
|
||||
.command('deploy')
|
||||
.description('Deploy with advanced option patterns')
|
||||
|
||||
// Option with choices
|
||||
.addOption(
|
||||
new Option('-e, --env <environment>', 'deployment environment')
|
||||
.choices(['dev', 'staging', 'prod'])
|
||||
.default('dev')
|
||||
)
|
||||
|
||||
// Option with custom parser and validation
|
||||
.addOption(
|
||||
new Option('-p, --port <port>', 'server port')
|
||||
.argParser((value) => {
|
||||
const port = parseInt(value, 10);
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
throw new Error('Port must be between 1 and 65535');
|
||||
}
|
||||
return port;
|
||||
})
|
||||
.default(3000)
|
||||
)
|
||||
|
||||
// Mandatory option
|
||||
.addOption(
|
||||
new Option('-t, --token <token>', 'API authentication token')
|
||||
.makeOptionMandatory()
|
||||
.env('API_TOKEN')
|
||||
)
|
||||
|
||||
// Option from environment variable
|
||||
.addOption(
|
||||
new Option('-u, --api-url <url>', 'API base URL')
|
||||
.env('API_URL')
|
||||
.default('https://api.example.com')
|
||||
)
|
||||
|
||||
// Conflicting options
|
||||
.addOption(
|
||||
new Option('--use-cache', 'enable caching')
|
||||
.conflicts('noCache')
|
||||
)
|
||||
.addOption(
|
||||
new Option('--no-cache', 'disable caching')
|
||||
.conflicts('useCache')
|
||||
)
|
||||
|
||||
// Implies relationship
|
||||
.addOption(
|
||||
new Option('--ssl', 'enable SSL')
|
||||
.implies({ sslVerify: true })
|
||||
)
|
||||
.addOption(
|
||||
new Option('--ssl-verify', 'verify SSL certificates')
|
||||
)
|
||||
|
||||
// Preset configurations
|
||||
.addOption(
|
||||
new Option('--preset <preset>', 'use preset configuration')
|
||||
.choices(['minimal', 'standard', 'complete'])
|
||||
.argParser((value) => {
|
||||
const presets = {
|
||||
minimal: { replicas: 1, timeout: 30 },
|
||||
standard: { replicas: 3, timeout: 60 },
|
||||
complete: { replicas: 5, timeout: 120 },
|
||||
};
|
||||
return presets[value as keyof typeof presets];
|
||||
})
|
||||
)
|
||||
|
||||
// Hidden option (for debugging)
|
||||
.addOption(
|
||||
new Option('--debug', 'enable debug mode')
|
||||
.hideHelp()
|
||||
)
|
||||
|
||||
// Custom option processing
|
||||
.addOption(
|
||||
new Option('-r, --replicas <count>', 'number of replicas')
|
||||
.argParser((value, previous) => {
|
||||
const count = parseInt(value, 10);
|
||||
if (count < 1) {
|
||||
throw new Error('Replicas must be at least 1');
|
||||
}
|
||||
return count;
|
||||
})
|
||||
.default(3)
|
||||
)
|
||||
|
||||
.action((options) => {
|
||||
console.log(chalk.blue('Deployment configuration:'));
|
||||
console.log('Environment:', chalk.yellow(options.env));
|
||||
console.log('Port:', options.port);
|
||||
console.log('Token:', options.token ? chalk.green('***set***') : chalk.red('not set'));
|
||||
console.log('API URL:', options.apiUrl);
|
||||
console.log('Cache:', options.useCache ? 'enabled' : 'disabled');
|
||||
console.log('SSL:', options.ssl ? 'enabled' : 'disabled');
|
||||
console.log('SSL Verify:', options.sslVerify ? 'enabled' : 'disabled');
|
||||
|
||||
if (options.preset) {
|
||||
console.log('Preset configuration:', options.preset);
|
||||
}
|
||||
|
||||
console.log('Replicas:', options.replicas);
|
||||
|
||||
if (options.debug) {
|
||||
console.log(chalk.gray('\nDebug mode enabled'));
|
||||
console.log(chalk.gray('All options:'), options);
|
||||
}
|
||||
|
||||
console.log(chalk.green('\n✓ Deployment configuration validated'));
|
||||
});
|
||||
|
||||
// Command demonstrating option dependencies
|
||||
program
|
||||
.command('backup')
|
||||
.description('Backup data with option dependencies')
|
||||
.addOption(
|
||||
new Option('-m, --method <method>', 'backup method')
|
||||
.choices(['full', 'incremental', 'differential'])
|
||||
.makeOptionMandatory()
|
||||
)
|
||||
.addOption(
|
||||
new Option('--base-backup <path>', 'base backup for incremental')
|
||||
.conflicts('full')
|
||||
)
|
||||
.addOption(
|
||||
new Option('--compression <level>', 'compression level')
|
||||
.choices(['none', 'low', 'medium', 'high'])
|
||||
.default('medium')
|
||||
)
|
||||
.action((options) => {
|
||||
if (options.method === 'incremental' && !options.baseBackup) {
|
||||
console.log(chalk.red('Error: --base-backup required for incremental backup'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`Running ${options.method} backup...`));
|
||||
console.log('Compression:', options.compression);
|
||||
if (options.baseBackup) {
|
||||
console.log('Base backup:', options.baseBackup);
|
||||
}
|
||||
console.log(chalk.green('✓ Backup complete'));
|
||||
});
|
||||
|
||||
// Command with complex validation
|
||||
program
|
||||
.command('scale')
|
||||
.description('Scale application with complex validation')
|
||||
.addOption(
|
||||
new Option('-r, --replicas <count>', 'number of replicas')
|
||||
.argParser((value) => {
|
||||
const count = parseInt(value, 10);
|
||||
if (isNaN(count)) {
|
||||
throw new Error('Replicas must be a number');
|
||||
}
|
||||
if (count < 1 || count > 100) {
|
||||
throw new Error('Replicas must be between 1 and 100');
|
||||
}
|
||||
return count;
|
||||
})
|
||||
.makeOptionMandatory()
|
||||
)
|
||||
.addOption(
|
||||
new Option('--cpu <value>', 'CPU limit (millicores)')
|
||||
.argParser((value) => {
|
||||
const cpu = parseInt(value, 10);
|
||||
if (cpu < 100 || cpu > 8000) {
|
||||
throw new Error('CPU must be between 100 and 8000 millicores');
|
||||
}
|
||||
return cpu;
|
||||
})
|
||||
.default(1000)
|
||||
)
|
||||
.addOption(
|
||||
new Option('--memory <value>', 'Memory limit (MB)')
|
||||
.argParser((value) => {
|
||||
const mem = parseInt(value, 10);
|
||||
if (mem < 128 || mem > 16384) {
|
||||
throw new Error('Memory must be between 128 and 16384 MB');
|
||||
}
|
||||
return mem;
|
||||
})
|
||||
.default(512)
|
||||
)
|
||||
.action((options) => {
|
||||
console.log(chalk.blue('Scaling application:'));
|
||||
console.log('Replicas:', chalk.yellow(options.replicas));
|
||||
console.log('CPU:', `${options.cpu}m`);
|
||||
console.log('Memory:', `${options.memory}MB`);
|
||||
|
||||
const totalCpu = options.replicas * options.cpu;
|
||||
const totalMemory = options.replicas * options.memory;
|
||||
|
||||
console.log(chalk.gray('\nTotal resources:'));
|
||||
console.log(chalk.gray(`CPU: ${totalCpu}m`));
|
||||
console.log(chalk.gray(`Memory: ${totalMemory}MB`));
|
||||
|
||||
console.log(chalk.green('\n✓ Scaling complete'));
|
||||
});
|
||||
|
||||
program.parse();
|
||||
46
skills/commander-patterns/templates/package.json.template
Normal file
46
skills/commander-patterns/templates/package.json.template
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "{{CLI_NAME}}",
|
||||
"version": "1.0.0",
|
||||
"description": "{{DESCRIPTION}}",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
"{{CLI_NAME}}": "dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"start": "node dist/index.js",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"format": "prettier --write 'src/**/*.ts'",
|
||||
"test": "vitest",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"commander",
|
||||
"typescript"
|
||||
],
|
||||
"author": "{{AUTHOR}}",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^12.0.0",
|
||||
"chalk": "^5.3.0",
|
||||
"ora": "^8.0.1",
|
||||
"inquirer": "^9.2.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.24",
|
||||
"@types/inquirer": "^9.0.7",
|
||||
"typescript": "^5.3.3",
|
||||
"tsx": "^4.7.1",
|
||||
"eslint": "^8.57.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.0",
|
||||
"@typescript-eslint/parser": "^7.1.0",
|
||||
"prettier": "^3.2.5",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
}
|
||||
26
skills/commander-patterns/templates/tsconfig.commander.json
Normal file
26
skills/commander-patterns/templates/tsconfig.commander.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022"],
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"allowJs": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"removeComments": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user