Yargs
A feature-rich and highly extensible Node.js CLI library. Excels at handling complex command structures, validation, and customization.
GitHub Overview
yargs/yargs
yargs the modern, pirate-themed successor to optimist.
Topics
Star History
Framework
yargs
Overview
yargs is one of the most popular CLI libraries in the Node.js ecosystem. Designed for building interactive command-line tools, it parses arguments and generates elegant user interfaces. Developed as "the modern, pirate-themed successor to optimist," it has achieved widespread adoption in the Node.js community through its dynamic help menu generation, bash completion functionality, and comprehensive feature set.
Why is it the most popular in Node.js:
- Comprehensive feature set: Provides argument parsing, command support, validation, and automatic help generation in a single library
- Excellent DX (Developer Experience): Intuitive API design with rich documentation
- Active community: Over 12 years of development history, 282 contributors, millions of monthly downloads
- Cross-platform support: Consistent operation across Node.js, Deno, and browser environments
Detailed Description
History and Evolution
yargs development began over 12 years ago and has matured as a Node.js CLI library. Originally designed as a successor to "optimist," it has now evolved into a completely independent library with its own advanced feature set. Recent updates include full asynchronous processing support, enhanced TypeScript type inference, and performance improvements.
Position in the Ecosystem
In the Node.js ecosystem, yargs holds a special position for the following reasons:
- Most mature CLI library: Achieved stability and feature richness through years of development
- Adopted by many tools: Used by numerous renowned projects including Mocha, Webpack, and ESLint
- Modular design: Clear separation of concerns with CommandInstance, UsageInstance, ValidationInstance, and others
- Platform abstraction: Supports different JavaScript runtime environments through PlatformShim
Latest Trends (2024-2025)
- Full asynchronous processing support: Promise-based parsing with parseAsync() method
- Enhanced TypeScript support: More accurate type inference and type safety
- Expanded internationalization: Support for new locales including Czech and Ukrainian
- Improved middleware system: Support for global middleware and asynchronous middleware
- Enhanced completion features: Positional argument completion, alias support, and Zsh improvements
Key Features
Core Functionality
- Declarative Command Building: Define commands with intuitive, readable syntax
- Dynamic Help Generation: Automatic help menus based on defined commands and options
- Positional Arguments and Options: Flexible argument definition with
<required>
and[optional]
- Subcommand Support: Nested command structures and hierarchical CLI design
- Alias Functionality: Support for command and option shortcuts and aliases
Advanced Features
- Middleware System: Pipeline argument processing with customizable pre/post processing
- Validation Features: Type checking, range validation, and custom validation for arguments
- Configuration File Integration: Load settings from JSON, YAML, and package.json
- Environment Variable Integration: Parse arguments from environment variables (with prefix support)
- Internationalization (i18n): Multi-language help and error messages
- Shell Completion: Generate auto-completion scripts for Bash, Zsh, and Fish
Developer Experience
- Full TypeScript Support: Type-safe development with type definitions and IntelliSense support
- Rich API Methods: Detailed help customization with
.example()
,.usage()
,.epilog()
, etc. - Testing Support: Headless mode execution and mock support for testing
- Debug Features: Detailed error messages and parsing process visualization
Latest Features and Updates
Major Features in v17.x Series
- Complete Asynchronous Support: Promise-based parsing with
parseAsync()
method - Asynchronous Middleware: Middleware functions supporting async/await
- Asynchronous Command Handlers: Full support for command handlers that return Promises
- Asynchronous Validation: Validation with external service integration
Recent Improvements
- ES Modules Support: Modern usage with
import
/export
syntax - Enhanced TypeScript Type Inference: More accurate type checking and IntelliSense
- Performance Optimization: Reduced startup time and improved parsing speed
- Expanded Completion Features: Improved positional arguments, aliases, and Zsh support
- New Locale Support: Internationalization expansion including Czech and Ukrainian
- Middleware Improvements: Support for global middleware and command-specific middleware
Pros and Cons
Pros
Functional Advantages
- Comprehensive feature set: Unified provision of command definition, validation, help generation, and configuration management
- Strong type support: Type-safe development experience through perfect TypeScript integration
- Rich customization: Flexible API design accommodating detailed requirements
- Asynchronous processing support: Modern asynchronous command processing with async/await
- Cross-platform: Consistent operation across Node.js, Deno, and browsers
Community and Quality
- Mature ecosystem: Over 12 years of development track record with continuous maintenance
- Rich documentation: Official documentation, examples, and community resources
- High adoption rate: Proven track record in numerous renowned projects
- Testability: Automated testing support through headless mode
Cons
Complexity and Learning Cost
- Initial learning cost: Learning time required to handle rich functionality
- Configuration complexity: Many options can be difficult for beginners to understand
- Over-engineering: Features may be excessive for simple scripts
Performance Aspects
- Bundle size: Somewhat large file size for lightweight CLI tools
- Startup time: Slightly slower startup than lightweight alternatives due to rich functionality
- Memory usage: Memory consumption due to multi-functionality
Key Links
Official Resources
- Official Site - Main documentation and guides
- GitHub Repository - Source code, Issues, Discussions
- npm Package - Installation and basic information
- TypeScript Type Definitions - Type definitions for TypeScript developers
Documentation
- API Reference - Detailed API specifications
- Advanced Features - Advanced feature explanations
- Examples Collection - Practical code examples
- Migration Guide - Migration guide between versions
Code Examples
Basic Usage Examples
Simplest argument parsing:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
// hideBin() removes node/process name
const argv = yargs(hideBin(process.argv)).parse();
if (argv.ships > 3 && argv.distance < 53.5) {
console.log('Plunder more riffiwobbles!');
} else {
console.log('Retreat from the xupptumblers!');
}
# Usage example
./plunder.js --ships=4 --distance=22
# Output: Plunder more riffiwobbles!
# Automatic help generation
./plunder.js --help
# Automatically displays help menu
Option definition and type specification:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
const argv = yargs(hideBin(process.argv))
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging'
})
.option('port', {
alias: 'p',
type: 'number',
default: 3000,
description: 'Server port number'
})
.help()
.alias('help', 'h')
.parse();
console.log(`Starting server on port ${argv.port}...`);
if (argv.verbose) {
console.log('Verbose mode enabled');
}
Command and Subcommand Definition
Basic command definition:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
yargs(hideBin(process.argv))
.command(
'serve [port]', // Command name and positional arguments
'start the server', // Description
(yargs) => {
// Builder function: Define command-specific options
return yargs
.positional('port', {
describe: 'port to bind on',
default: 5000,
type: 'number'
})
.option('daemon', {
alias: 'd',
type: 'boolean',
description: 'Run in background'
})
},
(argv) => {
// Handler function: Process command execution
if (argv.verbose) {
console.info(`Starting server on port ${argv.port}`);
}
startServer(argv.port, argv.daemon);
}
)
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging',
global: true // Available across all commands
})
.demandCommand(1, 'Please specify a command')
.help()
.parse();
function startServer(port, daemon) {
const mode = daemon ? 'daemon mode' : 'normal mode';
console.log(`Server started in ${mode} on port ${port}`);
}
Multiple commands and aliases:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
yargs(hideBin(process.argv))
// Command with multiple aliases
.command(['start [app]', 'run', 'up'], 'Start up an application', {}, (argv) => {
console.log(`Starting up ${argv.app || 'default'} app...`);
})
// Object-style command definition
.command({
command: 'config <action> [key] [value]',
aliases: ['cfg', 'configure'],
describe: 'Manage configuration',
builder: (yargs) => {
return yargs
.positional('action', {
describe: 'Action to perform',
choices: ['get', 'set', 'list', 'delete']
})
.positional('key', {
describe: 'Configuration key',
type: 'string'
})
.positional('value', {
describe: 'Configuration value',
type: 'string'
})
},
handler: (argv) => {
switch (argv.action) {
case 'set':
console.log(`Setting ${argv.key} = ${argv.value}`);
break;
case 'get':
console.log(`Getting value for ${argv.key}`);
break;
case 'list':
console.log('Listing all configurations');
break;
case 'delete':
console.log(`Deleting ${argv.key}`);
break;
}
}
})
.demandCommand()
.help()
.wrap(72)
.parse();
Asynchronous Processing and Promises
Asynchronous command handlers:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { promises as fs } from 'fs';
import https from 'https';
yargs(hideBin(process.argv))
.command(
'fetch <url> [output]',
'Fetch data from URL and save to file',
(yargs) => {
return yargs
.positional('url', {
describe: 'URL to fetch',
type: 'string'
})
.positional('output', {
describe: 'Output filename',
type: 'string',
default: 'output.txt'
})
.option('timeout', {
alias: 't',
type: 'number',
default: 10000,
description: 'Timeout in milliseconds'
})
},
async (argv) => {
try {
console.log(`Fetching data from ${argv.url}...`);
// Asynchronously fetch data
const data = await fetchData(argv.url, argv.timeout);
// Asynchronously write to file
await fs.writeFile(argv.output, data, 'utf8');
console.log(`Data saved to ${argv.output}`);
} catch (error) {
console.error('Error occurred:', error.message);
process.exit(1);
}
}
)
.command(
'process <input>',
'Process file asynchronously',
(yargs) => {
return yargs
.positional('input', {
describe: 'Input file',
type: 'string'
})
},
async (argv) => {
try {
// Asynchronously read file
const content = await fs.readFile(argv.input, 'utf8');
// Process data (e.g., count lines)
const lineCount = content.split('\n').length;
console.log(`${argv.input}: ${lineCount} lines`);
} catch (error) {
console.error('File processing error:', error.message);
process.exit(1);
}
}
)
.demandCommand()
.help()
.parseAsync(); // Asynchronous parsing
// Helper function
function fetchData(url, timeout) {
return new Promise((resolve, reject) => {
const request = https.get(url, (response) => {
let data = '';
response.on('data', (chunk) => data += chunk);
response.on('end', () => resolve(data));
});
request.setTimeout(timeout, () => {
request.destroy();
reject(new Error('Request timeout'));
});
request.on('error', reject);
});
}
Type-Safe Development with TypeScript
Basic type definitions:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
// Argument type definitions
interface UserArguments {
username: string;
email?: string;
age: number;
verbose: boolean;
role: 'admin' | 'user' | 'guest';
}
const argv = yargs(hideBin(process.argv))
.options({
username: {
type: 'string',
demandOption: true,
describe: 'Username',
alias: 'u'
},
email: {
type: 'string',
describe: 'Email address',
alias: 'e'
},
age: {
type: 'number',
default: 25,
describe: 'Age'
},
verbose: {
type: 'boolean',
default: false,
alias: 'v',
describe: 'Verbose output'
},
role: {
type: 'string',
choices: ['admin', 'user', 'guest'] as const,
default: 'user' as const,
describe: 'User role'
}
})
.parseSync() as UserArguments;
// Type-safe processing
function processUser(args: UserArguments): void {
console.log(`User: ${args.username} (${args.age} years old, ${args.role})`);
if (args.email) {
console.log(`Email: ${args.email}`);
}
if (args.verbose) {
console.log('Verbose mode enabled');
}
// Type-based branching
switch (args.role) {
case 'admin':
console.log('Running with admin privileges');
break;
case 'user':
console.log('Running as regular user');
break;
case 'guest':
console.log('Running in guest mode');
break;
}
}
processUser(argv);
Advanced command type definitions:
#!/usr/bin/env node
import yargs, { Arguments, CommandBuilder } from 'yargs';
import { hideBin } from 'yargs/helpers';
// Command-specific argument types
interface DeployArgs {
environment: 'dev' | 'staging' | 'production';
force: boolean;
dryRun: boolean;
version?: string;
}
interface BuildArgs {
target: 'web' | 'mobile' | 'all';
minify: boolean;
sourcemap: boolean;
}
// Command builder type definitions
const deployBuilder: CommandBuilder<{}, DeployArgs> = (yargs) => {
return yargs
.option('environment', {
alias: 'e',
type: 'string',
choices: ['dev', 'staging', 'production'] as const,
demandOption: true,
describe: 'Deployment environment'
})
.option('force', {
alias: 'f',
type: 'boolean',
default: false,
describe: 'Force deployment'
})
.option('dry-run', {
type: 'boolean',
default: false,
describe: 'Dry run execution'
})
.option('version', {
alias: 'v',
type: 'string',
describe: 'Version to deploy'
});
};
const buildBuilder: CommandBuilder<{}, BuildArgs> = (yargs) => {
return yargs
.option('target', {
alias: 't',
type: 'string',
choices: ['web', 'mobile', 'all'] as const,
default: 'all' as const,
describe: 'Build target'
})
.option('minify', {
alias: 'm',
type: 'boolean',
default: true,
describe: 'Minify code'
})
.option('sourcemap', {
alias: 's',
type: 'boolean',
default: false,
describe: 'Generate sourcemap'
});
};
yargs(hideBin(process.argv))
.command<DeployArgs>(
'deploy',
'Deploy application',
deployBuilder,
async (argv: Arguments<DeployArgs>) => {
console.log(`Deploying to ${argv.environment} environment...`);
if (argv.dryRun) {
console.log('Dry run: No actual deployment will occur');
return;
}
if (argv.force) {
console.log('Force deployment mode enabled');
}
// Type-safe deployment processing
await deployToEnvironment(argv.environment, {
force: argv.force,
version: argv.version
});
}
)
.command<BuildArgs>(
'build',
'Build application',
buildBuilder,
(argv: Arguments<BuildArgs>) => {
console.log(`Building ${argv.target}...`);
const options = {
minify: argv.minify,
sourcemap: argv.sourcemap
};
buildApplication(argv.target, options);
}
)
.demandCommand()
.help()
.parseAsync();
// Type-safe helper functions
async function deployToEnvironment(
env: 'dev' | 'staging' | 'production',
options: { force: boolean; version?: string }
): Promise<void> {
// Environment-specific deployment logic
console.log(`Executing ${options.force ? 'forced' : 'normal'} deployment to ${env}`);
if (options.version) {
console.log(`Version: ${options.version}`);
}
}
function buildApplication(
target: 'web' | 'mobile' | 'all',
options: { minify: boolean; sourcemap: boolean }
): void {
// Build logic
console.log(`${target} build completed (minify: ${options.minify}, sourcemap: ${options.sourcemap})`);
}
Middleware and Plugin System
Advanced middleware functionality:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';
// Configuration file loading middleware
const loadConfig = async (argv) => {
const configPaths = [
path.join(process.cwd(), '.myapp.json'),
path.join(os.homedir(), '.myapp.json'),
'/etc/myapp/config.json'
];
for (const configPath of configPaths) {
try {
const configData = await fs.readFile(configPath, 'utf8');
const config = JSON.parse(configData);
console.log(`Loaded configuration from: ${configPath}`);
return { ...config, ...argv }; // CLI options take precedence
} catch (error) {
// Ignore if file doesn't exist
}
}
return argv;
};
// Credential normalization middleware
const normalizeCredentials = async (argv) => {
if (!argv.apiKey && !argv.username) {
try {
const credentialsPath = path.join(os.homedir(), '.myapp-credentials.json');
const credentialsData = await fs.readFile(credentialsPath, 'utf8');
const credentials = JSON.parse(credentialsData);
console.log('Loaded settings from credentials file');
return { ...argv, ...credentials };
} catch (error) {
console.warn('Credentials file not found. Please set authentication info manually.');
}
}
return argv;
};
// Log level setup middleware
const setupLogging = (argv) => {
const logLevel = argv.verbose ? 'debug' : argv.quiet ? 'error' : 'info';
global.logLevel = logLevel;
// Set up global log functions
global.log = {
debug: (msg) => logLevel === 'debug' && console.log(`[DEBUG] ${msg}`),
info: (msg) => ['debug', 'info'].includes(logLevel) && console.log(`[INFO] ${msg}`),
warn: (msg) => ['debug', 'info', 'warn'].includes(logLevel) && console.warn(`[WARN] ${msg}`),
error: (msg) => console.error(`[ERROR] ${msg}`)
};
return argv;
};
// Validation middleware
const validateEnvironment = (argv) => {
if (argv.env === 'production' && !argv.confirm) {
throw new Error('Production environment operations require --confirm flag');
}
if (argv.env === 'production' && argv.force) {
global.log.warn('Force mode is enabled in production environment');
}
return argv;
};
yargs(hideBin(process.argv))
.usage('Usage: $0 <command> [options]')
// Global options
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Verbose logging',
global: true
})
.option('quiet', {
alias: 'q',
type: 'boolean',
description: 'Error messages only',
global: true
})
.option('config', {
alias: 'c',
type: 'string',
description: 'Configuration file path',
global: true
})
// Global middleware (applies to all commands)
.middleware([setupLogging, loadConfig, normalizeCredentials])
.command(
'deploy <env>',
'Deploy application',
(yargs) => {
return yargs
.positional('env', {
describe: 'Deployment environment',
choices: ['dev', 'staging', 'production']
})
.option('force', {
type: 'boolean',
description: 'Force deployment'
})
.option('confirm', {
type: 'boolean',
description: 'Confirm production deployment'
})
.option('dry-run', {
type: 'boolean',
description: 'Dry run execution'
})
.middleware(validateEnvironment); // Command-specific middleware
},
async (argv) => {
global.log.info(`Starting deployment to ${argv.env} environment`);
if (argv.dryRun) {
global.log.info('Dry run mode: No actual deployment will occur');
return;
}
try {
await performDeploy(argv.env, {
force: argv.force,
apiKey: argv.apiKey
});
global.log.info('Deployment completed successfully');
} catch (error) {
global.log.error(`Deployment error: ${error.message}`);
process.exit(1);
}
}
)
.command(
'status [service]',
'Check service status',
(yargs) => {
return yargs
.positional('service', {
describe: 'Service name to check',
type: 'string'
})
.option('watch', {
alias: 'w',
type: 'boolean',
description: 'Continuous monitoring'
})
},
async (argv) => {
global.log.info('Checking service status...');
if (argv.watch) {
global.log.info('Starting monitoring mode (Ctrl+C to exit)');
// Continuous monitoring processing
} else {
// One-time status check
const status = await checkServiceStatus(argv.service);
console.log(JSON.stringify(status, null, 2));
}
}
)
.demandCommand(1, 'Please specify a command')
.help('h')
.alias('h', 'help')
.version('2.0.0')
.epilog('For more detailed information, visit https://example.com/docs')
.parseAsync(); // Use parseAsync for asynchronous middleware
// Helper functions
async function performDeploy(environment, options) {
global.log.debug(`Deploy options: ${JSON.stringify(options)}`);
// Deployment logic implementation
await new Promise(resolve => setTimeout(resolve, 2000));
}
async function checkServiceStatus(service) {
global.log.debug(`Checking status of service ${service || 'all'}`);
// Status check logic implementation
return {
service: service || 'all',
status: 'running',
uptime: '2 hours',
lastDeploy: new Date().toISOString()
};
}
Modular Design and Command Directory
Structured command modules:
// commands/server.js - Basic command module
export const command = 'server [port]';
export const describe = 'Start web server';
export const aliases = ['serve', 'start-server'];
export const builder = (yargs) => {
return yargs
.positional('port', {
default: 3000,
describe: 'Server port',
type: 'number'
})
.option('host', {
default: 'localhost',
describe: 'Server host',
type: 'string',
alias: 'h'
})
.option('ssl', {
type: 'boolean',
description: 'Enable HTTPS',
default: false
})
.option('workers', {
type: 'number',
description: 'Number of worker processes',
default: require('os').cpus().length
})
.example('$0 server 8080', 'Start server on port 8080')
.example('$0 server --ssl', 'Start server with HTTPS enabled');
};
export const handler = async (argv) => {
const protocol = argv.ssl ? 'https' : 'http';
console.log(`Starting server at ${protocol}://${argv.host}:${argv.port}...`);
console.log(`Worker processes: ${argv.workers}`);
try {
await startServer({
port: argv.port,
host: argv.host,
ssl: argv.ssl,
workers: argv.workers
});
console.log('Server startup completed');
} catch (error) {
console.error('Server startup error:', error.message);
process.exit(1);
}
};
// Server startup logic
async function startServer(options) {
// Actual server startup processing
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Server running with ${options.workers} workers`);
resolve();
}, 1000);
});
}
// commands/database.js - Database-related commands
export const command = 'db <action>';
export const describe = 'Database operations';
export const builder = (yargs) => {
return yargs
.positional('action', {
describe: 'Action to perform',
choices: ['migrate', 'seed', 'reset', 'backup', 'restore']
})
.option('env', {
describe: 'Target environment',
choices: ['development', 'test', 'production'],
default: 'development'
})
.option('force', {
type: 'boolean',
description: 'Force execution',
default: false
})
.option('backup-file', {
type: 'string',
description: 'Backup file path (for restore)',
implies: 'action'
})
.check((argv) => {
if (argv.action === 'restore' && !argv.backupFile) {
throw new Error('restore action requires --backup-file');
}
if (argv.env === 'production' && !argv.force) {
throw new Error('Production environment requires --force flag');
}
return true;
});
};
export const handler = async (argv) => {
console.log(`Executing database ${argv.action} on ${argv.env} environment...`);
switch (argv.action) {
case 'migrate':
await runMigrations(argv.env);
break;
case 'seed':
await seedData(argv.env);
break;
case 'reset':
if (argv.force) {
await resetDatabase(argv.env);
}
break;
case 'backup':
await createBackup(argv.env);
break;
case 'restore':
await restoreFromBackup(argv.env, argv.backupFile);
break;
}
};
// Database operation functions
async function runMigrations(env) {
console.log(`Running migrations on ${env} environment...`);
}
async function seedData(env) {
console.log(`Seeding data on ${env} environment...`);
}
async function resetDatabase(env) {
console.log(`Resetting database on ${env} environment...`);
}
async function createBackup(env) {
console.log(`Creating backup for ${env} environment...`);
}
async function restoreFromBackup(env, backupFile) {
console.log(`Restoring from ${backupFile} to ${env} environment...`);
}
// cli.js - Main CLI entry point
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
yargs(hideBin(process.argv))
// Global settings
.scriptName('myapp')
.usage('$0 <command> [options]')
// Global options
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Verbose output',
global: true
})
.option('config', {
alias: 'c',
type: 'string',
description: 'Configuration file path',
global: true
})
// Auto-load from command directory
.commandDir(join(__dirname, 'commands'), {
recurse: true, // Recursively load subdirectories
extensions: ['js', 'mjs'] // Target file extensions
})
// Basic settings
.demandCommand(1, 'Please specify a command')
.recommendCommands() // Recommend similar command names
.strict() // Error on unknown commands or options
.help('help')
.alias('help', 'h')
.version('3.0.0')
.epilog('Detailed documentation: https://myapp.example.com/docs')
// Global middleware
.middleware([(argv) => {
if (argv.verbose) {
console.log('Executing command:', argv._);
console.log('Options:', Object.keys(argv).filter(k => k !== '_' && k !== '$0'));
}
}])
.parseAsync();
Directory structure:
myapp/
├── cli.js # Main entry point
├── commands/
│ ├── server.js # Server-related commands
│ ├── database.js # Database-related commands
│ ├── user/
│ │ ├── create.js # user create command
│ │ ├── delete.js # user delete command
│ │ └── list.js # user list command
│ └── deploy/
│ ├── staging.js # deploy staging command
│ └── production.js # deploy production command
├── lib/
│ ├── server.js # Server-related helpers
│ ├── database.js # Database-related helpers
│ └── utils.js # Common utilities
└── package.json
Advanced Validation and Configuration Management
Comprehensive validation example:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import fs from 'fs/promises';
import path from 'path';
const argv = yargs(hideBin(process.argv))
.usage('Usage: $0 [options]')
.option('width', {
alias: 'w',
describe: 'Width (pixels)',
type: 'number',
demandOption: true
})
.option('height', {
alias: 'h',
describe: 'Height (pixels)',
type: 'number',
demandOption: true
})
.option('format', {
alias: 'f',
describe: 'Output format',
choices: ['json', 'xml', 'yaml', 'csv'],
default: 'json'
})
.option('output', {
alias: 'o',
describe: 'Output file path',
type: 'string'
})
.option('quality', {
alias: 'q',
describe: 'Quality (1-100)',
type: 'number',
default: 80
})
.option('color-depth', {
describe: 'Color depth',
choices: [8, 16, 24, 32],
default: 24
})
.option('config', {
alias: 'c',
describe: 'Configuration file path',
type: 'string',
config: true // Treat this option as a configuration file
})
// Multiple validation functions
.check((argv) => {
// Basic numeric validation
if (argv.width <= 0 || argv.height <= 0) {
throw new Error('Width and height must be positive numbers');
}
// Range check
if (argv.width > 10000 || argv.height > 10000) {
throw new Error('Width and height must be 10000 pixels or less');
}
// Quality validation
if (argv.quality < 1 || argv.quality > 100) {
throw new Error('Quality must be in the range 1-100');
}
return true;
})
.check((argv) => {
// Aspect ratio check
const aspectRatio = argv.width / argv.height;
if (aspectRatio > 10 || aspectRatio < 0.1) {
console.warn('Warning: Extreme aspect ratio specified');
}
return true;
})
// Asynchronous validation
.check(async (argv) => {
if (argv.output) {
const outputDir = path.dirname(argv.output);
try {
await fs.access(outputDir);
} catch (error) {
throw new Error(`Output directory does not exist: ${outputDir}`);
}
// Warning if file already exists
try {
await fs.access(argv.output);
console.warn(`Warning: Output file ${argv.output} already exists`);
} catch (error) {
// File doesn't exist, which is normal
}
}
return true;
})
// Dependency checks
.implies('quality', 'format') // quality requires format
.conflicts('width', 'config') // width and config cannot be specified together (example)
// Usage examples
.example('$0 -w 800 -h 600', 'Generate 800x600 image')
.example('$0 -w 1920 -h 1080 --format xml', 'Full HD size with XML output')
.example('$0 --config config.json', 'Load from configuration file')
.example('$0 -w 500 -h 300 -q 95 -o image.json', 'High quality output to specified file')
.help()
.alias('help', '?')
.parseAsync(); // Use parseAsync for asynchronous validation
// Configuration calculation and output
const area = argv.width * argv.height;
const memoryEstimate = area * (argv.colorDepth / 8); // in bytes
const result = {
dimensions: {
width: argv.width,
height: argv.height,
area: area,
aspectRatio: +(argv.width / argv.height).toFixed(2)
},
settings: {
format: argv.format,
quality: argv.quality,
colorDepth: argv.colorDepth,
estimatedMemory: `${(memoryEstimate / 1024 / 1024).toFixed(2)} MB`
},
output: argv.output || `output.${argv.format}`
};
// Format-specific output
switch (argv.format) {
case 'json':
console.log(JSON.stringify(result, null, 2));
break;
case 'yaml':
// YAML format output (actual yaml library required)
console.log('# Generated settings');
console.log(`width: ${result.dimensions.width}`);
console.log(`height: ${result.dimensions.height}`);
console.log(`format: ${result.settings.format}`);
break;
case 'xml':
console.log('<?xml version="1.0" encoding="UTF-8"?>');
console.log('<image>');
console.log(` <width>${result.dimensions.width}</width>`);
console.log(` <height>${result.dimensions.height}</height>`);
console.log(` <format>${result.settings.format}</format>`);
console.log('</image>');
break;
case 'csv':
console.log('width,height,format,quality');
console.log(`${result.dimensions.width},${result.dimensions.height},${result.settings.format},${result.settings.quality}`);
break;
}
// File output if specified
if (argv.output) {
try {
await fs.writeFile(argv.output, JSON.stringify(result, null, 2));
console.log(`Results saved to ${argv.output}`);
} catch (error) {
console.error('File save error:', error.message);
process.exit(1);
}
}
Configuration file example (config.json):
{
"width": 1920,
"height": 1080,
"format": "json",
"quality": 90,
"color-depth": 24,
"output": "generated/result.json"
}
Best Practices and Practical Tips
1. Environment Variable Integration:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
// Set environment variable prefix
const argv = yargs(hideBin(process.argv))
.env('MYAPP') // Auto-load environment variables starting with MYAPP_
.option('database-url', {
alias: 'db',
type: 'string',
description: 'Database connection URL',
default: 'sqlite://./app.db'
})
.option('port', {
alias: 'p',
type: 'number',
description: 'Server port',
default: 3000
})
.parse();
// MYAPP_DATABASE_URL, MYAPP_PORT environment variables are automatically loaded
console.log(`Database: ${argv.databaseUrl}`);
console.log(`Port: ${argv.port}`);
2. Headless Mode for Testing:
// testable-cli.js
import yargs from 'yargs';
export function createParser() {
return yargs()
.command('greet <name>', 'Greet someone', {}, (argv) => {
return `Hello, ${argv.name}!`;
})
.help();
}
// Main CLI execution
if (import.meta.url === `file://${process.argv[1]}`) {
createParser().parse(process.argv.slice(2));
}
// test/cli.test.js
import { createParser } from '../testable-cli.js';
import { jest } from '@jest/globals';
describe('CLI Tests', () => {
test('greet command works correctly', async () => {
const parser = createParser();
const result = await new Promise((resolve) => {
parser.parse('greet Alice', (err, argv, output) => {
resolve(output || argv);
});
});
expect(result).toContain('Hello, Alice!');
});
});
3. Internationalization (i18n) Implementation:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
// Locale settings
const argv = yargs(hideBin(process.argv))
.locale('en') // English locale
.option('lang', {
choices: ['ja', 'en', 'fr'],
default: 'en',
description: 'Display language'
})
.command('hello', 'Say hello', {}, (argv) => {
const greetings = {
ja: 'こんにちは!',
en: 'Hello!',
fr: 'Bonjour!'
};
console.log(greetings[argv.lang] || greetings.en);
})
.help()
.parse();
4. Dynamic Completion and Interactive Features:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { input, select } from '@inquirer/prompts';
const argv = yargs(hideBin(process.argv))
.command(
'interactive',
'Interactive mode',
{},
async (argv) => {
const name = await input({ message: 'Please enter your name:' });
const action = await select({
message: 'Choose an action to perform:',
choices: [
{ name: 'Greeting', value: 'greet' },
{ name: 'Create File', value: 'create' },
{ name: 'Exit', value: 'exit' }
]
});
switch (action) {
case 'greet':
console.log(`Hello, ${name}!`);
break;
case 'create':
console.log(`Creating file for ${name}...`);
break;
case 'exit':
console.log('Goodbye!');
break;
}
}
)
.completion('completion', function(current, argv) {
// Dynamic completion implementation
if (current === 'user') {
return ['alice', 'bob', 'charlie'];
}
return [];
})
.help()
.parse();
5. Plugin System Implementation:
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { readdirSync } from 'fs';
import { join } from 'path';
// Dynamic plugin loading
function loadPlugins(pluginDir) {
const plugins = [];
try {
const files = readdirSync(pluginDir);
for (const file of files) {
if (file.endsWith('.js')) {
const plugin = require(join(pluginDir, file));
if (plugin.register) {
plugins.push(plugin);
}
}
}
} catch (error) {
console.warn('Plugin directory not found:', pluginDir);
}
return plugins;
}
const parser = yargs(hideBin(process.argv))
.scriptName('myapp')
.usage('$0 <command> [options]');
// Load plugins and register commands
const plugins = loadPlugins('./plugins');
for (const plugin of plugins) {
plugin.register(parser);
}
parser
.demandCommand()
.help()
.parse();
Plugin example (plugins/weather.js):
// plugins/weather.js
export function register(yargs) {
yargs.command(
'weather [city]',
'Get weather information',
(yargs) => {
return yargs
.positional('city', {
describe: 'City name',
default: 'Tokyo'
});
},
async (argv) => {
console.log(`Fetching weather information for ${argv.city}...`);
// Actual weather API call
}
);
}