Initial commit
This commit is contained in:
261
skills/yargs-patterns/SKILL.md
Normal file
261
skills/yargs-patterns/SKILL.md
Normal file
@@ -0,0 +1,261 @@
|
||||
---
|
||||
name: yargs-patterns
|
||||
description: Advanced yargs patterns for Node.js CLI argument parsing with subcommands, options, middleware, and validation
|
||||
tags: [nodejs, cli, yargs, argument-parsing, validation]
|
||||
---
|
||||
|
||||
# yargs Patterns Skill
|
||||
|
||||
Comprehensive patterns and templates for building CLI applications with yargs, the modern Node.js argument parsing library.
|
||||
|
||||
## Overview
|
||||
|
||||
yargs is a powerful argument parsing library for Node.js that provides:
|
||||
- Automatic help generation
|
||||
- Rich command syntax (positional args, options, flags)
|
||||
- Type coercion and validation
|
||||
- Subcommands with isolated option namespaces
|
||||
- Middleware for preprocessing
|
||||
- Completion scripts for bash/zsh
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Basic Setup
|
||||
|
||||
```javascript
|
||||
#!/usr/bin/env node
|
||||
const yargs = require('yargs/yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.command('* <name>', 'greet someone', (yargs) => {
|
||||
yargs.positional('name', {
|
||||
describe: 'Name to greet',
|
||||
type: 'string'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Hello, ${argv.name}!`);
|
||||
})
|
||||
.parse();
|
||||
```
|
||||
|
||||
### Subcommands
|
||||
|
||||
```javascript
|
||||
yargs(hideBin(process.argv))
|
||||
.command('init <project>', 'initialize a new project', (yargs) => {
|
||||
yargs
|
||||
.positional('project', {
|
||||
describe: 'Project name',
|
||||
type: 'string'
|
||||
})
|
||||
.option('template', {
|
||||
alias: 't',
|
||||
describe: 'Project template',
|
||||
choices: ['basic', 'advanced', 'minimal'],
|
||||
default: 'basic'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Initializing ${argv.project} with ${argv.template} template`);
|
||||
})
|
||||
.command('build [entry]', 'build the project', (yargs) => {
|
||||
yargs
|
||||
.positional('entry', {
|
||||
describe: 'Entry point file',
|
||||
type: 'string',
|
||||
default: 'index.js'
|
||||
})
|
||||
.option('output', {
|
||||
alias: 'o',
|
||||
describe: 'Output directory',
|
||||
type: 'string',
|
||||
default: 'dist'
|
||||
})
|
||||
.option('minify', {
|
||||
describe: 'Minify output',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Building from ${argv.entry} to ${argv.output}`);
|
||||
})
|
||||
.parse();
|
||||
```
|
||||
|
||||
### Options and Flags
|
||||
|
||||
```javascript
|
||||
yargs(hideBin(process.argv))
|
||||
.option('verbose', {
|
||||
alias: 'v',
|
||||
type: 'boolean',
|
||||
description: 'Run with verbose logging',
|
||||
default: false
|
||||
})
|
||||
.option('config', {
|
||||
alias: 'c',
|
||||
type: 'string',
|
||||
description: 'Path to config file',
|
||||
demandOption: true // Required option
|
||||
})
|
||||
.option('port', {
|
||||
alias: 'p',
|
||||
type: 'number',
|
||||
description: 'Port number',
|
||||
default: 3000
|
||||
})
|
||||
.option('env', {
|
||||
alias: 'e',
|
||||
type: 'string',
|
||||
choices: ['development', 'staging', 'production'],
|
||||
description: 'Environment'
|
||||
})
|
||||
.parse();
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
```javascript
|
||||
yargs(hideBin(process.argv))
|
||||
.command('deploy <service>', 'deploy a service', (yargs) => {
|
||||
yargs
|
||||
.positional('service', {
|
||||
describe: 'Service name',
|
||||
type: 'string'
|
||||
})
|
||||
.option('version', {
|
||||
describe: 'Version to deploy',
|
||||
type: 'string',
|
||||
coerce: (arg) => {
|
||||
// Custom validation
|
||||
if (!/^\d+\.\d+\.\d+$/.test(arg)) {
|
||||
throw new Error('Version must be in format X.Y.Z');
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
})
|
||||
.option('replicas', {
|
||||
describe: 'Number of replicas',
|
||||
type: 'number',
|
||||
default: 1
|
||||
})
|
||||
.check((argv) => {
|
||||
// Cross-field validation
|
||||
if (argv.replicas > 10 && argv.env === 'development') {
|
||||
throw new Error('Cannot deploy more than 10 replicas in development');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Deploying ${argv.service} v${argv.version} with ${argv.replicas} replicas`);
|
||||
})
|
||||
.parse();
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
```javascript
|
||||
yargs(hideBin(process.argv))
|
||||
.middleware((argv) => {
|
||||
// Preprocessing middleware
|
||||
if (argv.verbose) {
|
||||
console.log('Running in verbose mode');
|
||||
console.log('Arguments:', argv);
|
||||
}
|
||||
})
|
||||
.middleware((argv) => {
|
||||
// Load config file
|
||||
if (argv.config) {
|
||||
const config = require(path.resolve(argv.config));
|
||||
return { ...argv, ...config };
|
||||
}
|
||||
})
|
||||
.command('run', 'run the application', {}, (argv) => {
|
||||
console.log('Application running with config:', argv);
|
||||
})
|
||||
.parse();
|
||||
```
|
||||
|
||||
### Advanced Features
|
||||
|
||||
#### Conflicts and Implies
|
||||
|
||||
```javascript
|
||||
yargs(hideBin(process.argv))
|
||||
.option('json', {
|
||||
describe: 'Output as JSON',
|
||||
type: 'boolean'
|
||||
})
|
||||
.option('yaml', {
|
||||
describe: 'Output as YAML',
|
||||
type: 'boolean'
|
||||
})
|
||||
.conflicts('json', 'yaml') // Can't use both
|
||||
.option('output', {
|
||||
describe: 'Output file',
|
||||
type: 'string'
|
||||
})
|
||||
.option('format', {
|
||||
describe: 'Output format',
|
||||
choices: ['json', 'yaml'],
|
||||
implies: 'output' // format requires output
|
||||
})
|
||||
.parse();
|
||||
```
|
||||
|
||||
#### Array Options
|
||||
|
||||
```javascript
|
||||
yargs(hideBin(process.argv))
|
||||
.option('include', {
|
||||
describe: 'Files to include',
|
||||
type: 'array',
|
||||
default: []
|
||||
})
|
||||
.option('exclude', {
|
||||
describe: 'Files to exclude',
|
||||
type: 'array',
|
||||
default: []
|
||||
})
|
||||
.parse();
|
||||
|
||||
// Usage: cli --include file1.js file2.js --exclude test.js
|
||||
```
|
||||
|
||||
#### Count Options
|
||||
|
||||
```javascript
|
||||
yargs(hideBin(process.argv))
|
||||
.option('verbose', {
|
||||
alias: 'v',
|
||||
describe: 'Verbosity level',
|
||||
type: 'count' // -v, -vv, -vvv
|
||||
})
|
||||
.parse();
|
||||
```
|
||||
|
||||
## Templates
|
||||
|
||||
See `templates/` directory for:
|
||||
- `basic-cli.js` - Simple CLI with commands
|
||||
- `advanced-cli.js` - Full-featured CLI with validation
|
||||
- `config-cli.js` - CLI with configuration file support
|
||||
- `interactive-cli.js` - CLI with prompts
|
||||
- `plugin-cli.js` - Plugin-based CLI architecture
|
||||
|
||||
## Scripts
|
||||
|
||||
See `scripts/` directory for:
|
||||
- `generate-completion.sh` - Generate bash/zsh completion
|
||||
- `validate-args.js` - Argument validation helper
|
||||
- `test-cli.sh` - CLI testing script
|
||||
|
||||
## Examples
|
||||
|
||||
See `examples/` directory for complete working examples.
|
||||
|
||||
## Resources
|
||||
|
||||
- [yargs Documentation](https://yargs.js.org/)
|
||||
- [yargs GitHub](https://github.com/yargs/yargs)
|
||||
- [yargs Best Practices](https://github.com/yargs/yargs/blob/main/docs/tricks.md)
|
||||
89
skills/yargs-patterns/examples/subcommands-example.js
Normal file
89
skills/yargs-patterns/examples/subcommands-example.js
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Example: yargs with nested subcommands
|
||||
*
|
||||
* Usage:
|
||||
* node subcommands-example.js user create --name John --email john@example.com
|
||||
* node subcommands-example.js user list --limit 10
|
||||
* node subcommands-example.js user delete 123
|
||||
*/
|
||||
|
||||
const yargs = require('yargs/yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.command('user', 'manage users', (yargs) => {
|
||||
return yargs
|
||||
.command('create', 'create a new user', (yargs) => {
|
||||
yargs
|
||||
.option('name', {
|
||||
describe: 'User name',
|
||||
type: 'string',
|
||||
demandOption: true
|
||||
})
|
||||
.option('email', {
|
||||
describe: 'User email',
|
||||
type: 'string',
|
||||
demandOption: true
|
||||
})
|
||||
.option('role', {
|
||||
describe: 'User role',
|
||||
choices: ['admin', 'user', 'guest'],
|
||||
default: 'user'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Creating user: ${argv.name} (${argv.email}) with role ${argv.role}`);
|
||||
})
|
||||
.command('list', 'list all users', (yargs) => {
|
||||
yargs
|
||||
.option('limit', {
|
||||
alias: 'l',
|
||||
describe: 'Limit number of results',
|
||||
type: 'number',
|
||||
default: 10
|
||||
})
|
||||
.option('offset', {
|
||||
alias: 'o',
|
||||
describe: 'Offset for pagination',
|
||||
type: 'number',
|
||||
default: 0
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Listing users (limit: ${argv.limit}, offset: ${argv.offset})`);
|
||||
})
|
||||
.command('delete <id>', 'delete a user', (yargs) => {
|
||||
yargs.positional('id', {
|
||||
describe: 'User ID',
|
||||
type: 'number'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Deleting user ID: ${argv.id}`);
|
||||
})
|
||||
.demandCommand(1, 'You need to specify a user subcommand');
|
||||
})
|
||||
.command('project', 'manage projects', (yargs) => {
|
||||
return yargs
|
||||
.command('create <name>', 'create a new project', (yargs) => {
|
||||
yargs
|
||||
.positional('name', {
|
||||
describe: 'Project name',
|
||||
type: 'string'
|
||||
})
|
||||
.option('template', {
|
||||
alias: 't',
|
||||
describe: 'Project template',
|
||||
choices: ['basic', 'advanced'],
|
||||
default: 'basic'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Creating project: ${argv.name} with template ${argv.template}`);
|
||||
})
|
||||
.command('list', 'list all projects', {}, (argv) => {
|
||||
console.log('Listing all projects');
|
||||
})
|
||||
.demandCommand(1, 'You need to specify a project subcommand');
|
||||
})
|
||||
.demandCommand(1, 'You need at least one command')
|
||||
.strict()
|
||||
.help()
|
||||
.parse();
|
||||
37
skills/yargs-patterns/scripts/generate-completion.sh
Executable file
37
skills/yargs-patterns/scripts/generate-completion.sh
Executable file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate bash/zsh completion script for yargs-based CLI
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
CLI_NAME="${1:-mycli}"
|
||||
OUTPUT_FILE="${2:-${CLI_NAME}-completion.sh}"
|
||||
|
||||
cat > "$OUTPUT_FILE" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
# Bash completion script for ${CLI_NAME}
|
||||
# Source this file to enable completion: source ${OUTPUT_FILE}
|
||||
|
||||
_${CLI_NAME}_completions()
|
||||
{
|
||||
local cur prev opts
|
||||
COMPREPLY=()
|
||||
cur="\${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
# Get completion from yargs
|
||||
COMP_LINE=\$COMP_LINE COMP_POINT=\$COMP_POINT ${CLI_NAME} --get-yargs-completions "\${COMP_WORDS[@]:1}" 2>/dev/null | while read -r line; do
|
||||
COMPREPLY+=("\$line")
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -F _${CLI_NAME}_completions ${CLI_NAME}
|
||||
EOF
|
||||
|
||||
chmod +x "$OUTPUT_FILE"
|
||||
|
||||
echo "✅ Completion script generated: $OUTPUT_FILE"
|
||||
echo ""
|
||||
echo "To enable completion, add this to your ~/.bashrc or ~/.zshrc:"
|
||||
echo " source $(pwd)/$OUTPUT_FILE"
|
||||
99
skills/yargs-patterns/templates/advanced-cli.js
Normal file
99
skills/yargs-patterns/templates/advanced-cli.js
Normal file
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env node
|
||||
const yargs = require('yargs/yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.command('deploy <service>', 'deploy a service', (yargs) => {
|
||||
yargs
|
||||
.positional('service', {
|
||||
describe: 'Service name to deploy',
|
||||
type: 'string'
|
||||
})
|
||||
.option('environment', {
|
||||
alias: 'env',
|
||||
describe: 'Deployment environment',
|
||||
choices: ['development', 'staging', 'production'],
|
||||
demandOption: true
|
||||
})
|
||||
.option('version', {
|
||||
alias: 'v',
|
||||
describe: 'Version to deploy',
|
||||
type: 'string',
|
||||
coerce: (arg) => {
|
||||
if (!/^\d+\.\d+\.\d+$/.test(arg)) {
|
||||
throw new Error('Version must be in format X.Y.Z');
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
})
|
||||
.option('replicas', {
|
||||
alias: 'r',
|
||||
describe: 'Number of replicas',
|
||||
type: 'number',
|
||||
default: 1
|
||||
})
|
||||
.option('force', {
|
||||
alias: 'f',
|
||||
describe: 'Force deployment without confirmation',
|
||||
type: 'boolean',
|
||||
default: false
|
||||
})
|
||||
.check((argv) => {
|
||||
if (argv.replicas > 10 && argv.environment === 'development') {
|
||||
throw new Error('Cannot deploy more than 10 replicas in development');
|
||||
}
|
||||
if (argv.environment === 'production' && !argv.version) {
|
||||
throw new Error('Version is required for production deployments');
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Deploying ${argv.service} v${argv.version || 'latest'}`);
|
||||
console.log(`Environment: ${argv.environment}`);
|
||||
console.log(`Replicas: ${argv.replicas}`);
|
||||
|
||||
if (!argv.force) {
|
||||
console.log('Use --force to proceed without confirmation');
|
||||
}
|
||||
})
|
||||
.command('rollback <service>', 'rollback a service', (yargs) => {
|
||||
yargs
|
||||
.positional('service', {
|
||||
describe: 'Service name to rollback',
|
||||
type: 'string'
|
||||
})
|
||||
.option('environment', {
|
||||
alias: 'env',
|
||||
describe: 'Deployment environment',
|
||||
choices: ['development', 'staging', 'production'],
|
||||
demandOption: true
|
||||
})
|
||||
.option('version', {
|
||||
alias: 'v',
|
||||
describe: 'Version to rollback to',
|
||||
type: 'string'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Rolling back ${argv.service} in ${argv.environment}`);
|
||||
if (argv.version) {
|
||||
console.log(`Target version: ${argv.version}`);
|
||||
}
|
||||
})
|
||||
.middleware((argv) => {
|
||||
// Logging middleware
|
||||
if (argv.verbose) {
|
||||
console.log('[DEBUG] Arguments:', argv);
|
||||
}
|
||||
})
|
||||
.option('verbose', {
|
||||
describe: 'Enable verbose logging',
|
||||
type: 'boolean',
|
||||
global: true // Available to all commands
|
||||
})
|
||||
.demandCommand(1, 'You need at least one command')
|
||||
.strict()
|
||||
.help()
|
||||
.alias('help', 'h')
|
||||
.version()
|
||||
.alias('version', 'V')
|
||||
.parse();
|
||||
28
skills/yargs-patterns/templates/basic-cli.js
Normal file
28
skills/yargs-patterns/templates/basic-cli.js
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env node
|
||||
const yargs = require('yargs/yargs');
|
||||
const { hideBin } = require('yargs/helpers');
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.command('greet <name>', 'greet someone', (yargs) => {
|
||||
yargs.positional('name', {
|
||||
describe: 'Name to greet',
|
||||
type: 'string'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Hello, ${argv.name}!`);
|
||||
})
|
||||
.command('goodbye <name>', 'say goodbye', (yargs) => {
|
||||
yargs.positional('name', {
|
||||
describe: 'Name to say goodbye to',
|
||||
type: 'string'
|
||||
});
|
||||
}, (argv) => {
|
||||
console.log(`Goodbye, ${argv.name}!`);
|
||||
})
|
||||
.demandCommand(1, 'You need at least one command')
|
||||
.strict()
|
||||
.help()
|
||||
.alias('help', 'h')
|
||||
.version()
|
||||
.alias('version', 'V')
|
||||
.parse();
|
||||
Reference in New Issue
Block a user