Creating Commands
This guide covers advanced techniques for creating and managing commands in your clapp applications.
Command Architecture
Understanding the architecture behind clapp commands will help you build more sophisticated CLI applications.
Command Lifecycle
Each command goes through a lifecycle:
- Registration: Command is defined and registered with the CLI
- Parsing: Command arguments and options are parsed
- Validation: Inputs are validated against defined rules
- Execution: Command action is executed with provided inputs
- Completion: Command completes and returns results
Advanced Arguments
Variadic Arguments
Collect multiple arguments with variadic parameters:
command('install')
.description('Install packages')
.argument('<...packages>', 'Packages to install')
.action((packages) => {
console.log(`Installing packages: ${packages.join(', ')}`)
})
Typed Arguments
Define argument types for automatic conversion and validation:
command('launch')
.description('Launch application')
.argument('<port:number>', 'Port to listen on')
.argument('[hostname:string]', 'Hostname', 'localhost')
.action((port, hostname) => {
// port is automatically converted to a number
console.log(`Launching on ${hostname}:${port}`)
})
Advanced Options
Negatable Options
Create options that can be negated:
command('build')
.description('Build the project')
.option('--minify/--no-minify', 'Minify output', true)
.action((options) => {
console.log(`Minification: ${options.minify ? 'enabled' : 'disabled'}`)
})
Option Choices
Restrict option values to a predefined set:
command('deploy')
.description('Deploy application')
.option('-e, --environment <env>', 'Deployment environment', {
choices: ['development', 'staging', 'production'],
default: 'development',
})
.action((options) => {
console.log(`Deploying to ${options.environment}`)
})
Coercion
Convert option values to specific types:
command('resize')
.description('Resize image')
.option('-w, --width <width>', 'Width in pixels', {
coerce: value => Number.parseInt(value, 10),
})
.option('-h, --height <height>', 'Height in pixels', {
coerce: value => Number.parseInt(value, 10),
})
.action((options) => {
// options.width and options.height are numbers
console.log(`Resizing to ${options.width}x${options.height}`)
})
Option Dependencies
Create options that depend on other options:
command('deploy')
.description('Deploy application')
.option('-e, --environment <env>', 'Deployment environment')
.option('-c, --config <path>', 'Config file')
.dependsOn('config', ['environment'])
.action((options) => {
// Requires --environment if --config is provided
console.log(`Deploying with config: ${options.config}`)
})
Command Groups
Command Group Structure
Create hierarchical command structures:
// Main parent command
const db = command('db')
.description('Database operations')
// First-level subcommands
db.command('migrate')
.description('Database migrations')
db.command('seed')
.description('Seed database')
// Second-level subcommands
const migrateCmd = db.commands.find(cmd => cmd.name === 'migrate')
migrateCmd.command('up')
.description('Run migrations')
migrateCmd.command('down')
.description('Rollback migrations')
Shared Options
Share options across related commands:
import { cli, command, createOption } from '@stacksjs/clapp'
const app = cli({
name: 'mycli',
})
// Create a shared option
const verboseOption = createOption('-v, --verbose', 'Enable verbose output')
// Apply to multiple commands
command('build')
.description('Build project')
.addOption(verboseOption)
.action((options) => {
if (options.verbose)
console.log('Verbose mode enabled for build')
// Build logic...
})
command('deploy')
.description('Deploy project')
.addOption(verboseOption)
.action((options) => {
if (options.verbose)
console.log('Verbose mode enabled for deploy')
// Deploy logic...
})
Command Middleware
Use middleware to run code before or after commands:
import { cli, command } from '@stacksjs/clapp'
const app = cli({
name: 'mycli',
})
// Authentication middleware
async function requireAuth(next) {
console.log('Checking authentication...')
// Check authentication logic here
const isAuthenticated = true
if (isAuthenticated) {
// Continue to the command
await next()
}
else {
console.error('Authentication failed')
process.exit(1)
}
}
// Timing middleware
async function timeExecution(next) {
const start = Date.now()
await next()
const end = Date.now()
console.log(`Command executed in ${end - start}ms`)
}
// Apply middleware to a command
command('deploy')
.description('Deploy application')
.middleware([requireAuth, timeExecution])
.action(() => {
console.log('Deploying application...')
})
Dynamic Commands
Create commands dynamically at runtime:
import * as fs from 'node:fs'
import { cli, command } from '@stacksjs/clapp'
const app = cli({
name: 'mycli',
})
// Generate commands from configuration
function loadCommands() {
const config = JSON.parse(fs.readFileSync('./commands.json', 'utf8'))
config.commands.forEach((cmd) => {
command(cmd.name)
.description(cmd.description)
.action(() => {
console.log(`Executing ${cmd.name}: ${cmd.script}`)
// Execute cmd.script
})
})
}
loadCommands()
app.run()
Command Aliases
Create aliases for commonly used commands:
import { cli, command } from '@stacksjs/clapp'
const app = cli({
name: 'mycli',
})
command('install')
.alias('i') // shorthand alias
.description('Install dependencies')
.action(() => {
console.log('Installing dependencies...')
})
// Users can now use either:
// $ mycli install
// $ mycli i
Command Hooks
Register hooks that run at different points in the command lifecycle:
import { cli, command } from '@stacksjs/clapp'
const app = cli({
name: 'mycli',
})
command('deploy')
.description('Deploy application')
.beforeRun(() => {
console.log('Before deploy...')
// Run setup tasks
})
.afterRun(() => {
console.log('After deploy...')
// Run cleanup tasks
})
.action(() => {
console.log('Deploying application...')
})
Command Composition
Compose complex commands from smaller, reusable parts:
import { cli, command, createOption } from '@stacksjs/clapp'
// Reusable command parts
function withVerboseOption(cmd) {
return cmd.option('-v, --verbose', 'Enable verbose output')
}
function withForceOption(cmd) {
return cmd.option('-f, --force', 'Force operation without confirmation')
}
function withLogging(cmd) {
return cmd
.beforeRun(() => {
console.log(`Running ${cmd.name}...`)
})
.afterRun(() => {
console.log(`${cmd.name} completed`)
})
}
// Create a command with composition
const buildCmd = command('build')
.description('Build the project')
.action((options) => {
console.log(`Building with options:`, options)
})
// Apply composable parts
withVerboseOption(buildCmd)
withForceOption(buildCmd)
withLogging(buildCmd)
Error Handling
Handle command errors gracefully:
import { cli, command } from '@stacksjs/clapp'
const app = cli({
name: 'mycli',
})
command('risky')
.description('Run a risky operation')
.action(async () => {
try {
// Attempt risky operation
const result = await riskyOperation()
console.log('Operation succeeded:', result)
}
catch (err) {
// Handle specific errors
if (err.code === 'NETWORK_ERROR') {
console.error('Network error occurred. Check your connection.')
process.exit(1)
}
// Handle other errors
console.error('An unexpected error occurred:', err.message)
process.exit(1)
}
})
app.run()
Command Customization
Customize command appearance and behavior:
import { cli, command, style } from '@stacksjs/clapp'
const app = cli({
name: 'mycli',
})
command('status')
.description('Show system status')
.help({
header: style.blue('STATUS COMMAND'),
usage: 'mycli status [options]',
examples: [
'mycli status',
'mycli status --json',
'mycli status --detailed',
],
footer: style.dim('For more information, visit our docs.'),
})
.action(() => {
console.log('System is running')
})
For more information, see the Commands API Reference.