Skip to content

Latest commit

 

History

History
277 lines (208 loc) · 5.81 KB

File metadata and controls

277 lines (208 loc) · 5.81 KB

Commander Integration

Commando uses Commander.js for rich CLI features while keeping command definitions simple.


Architecture

Commando commands (simple) → Framework bridgeCommander (rich CLI)

┌──────────────────┐
│ CommandoCommand  │  Simple command definition
│ - description    │
│ - execute()      │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ Framework        │  Translates to Commander
│ buildCommander() │
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ Commander.js     │  Handles parsing, help, validation
│ - parse()        │
│ - help()         │
└──────────────────┘

How It Works

1. Define Command (Simple)

export const deploy: CommandoCommand = {
  description: 'Deploy website',
  execute: async (options, args, context) => {
    console.log('Deploying...');
  }
};

2. Framework Translates

// lib/core.ts
function buildCommanderCommand(
  name: string,
  cmdoCmd: CommandoCommand,
  context: CommandoContext
): Command {
  const cmd = new Command(name);
  cmd.description(cmdoCmd.description);

  // Let command customize if needed
  if (cmdoCmd.defineCommand) {
    cmdoCmd.defineCommand(cmd);
  }

  // Install action handler
  cmd.action(async (...args) => {
    const options = args[args.length - 2];
    const positionalArgs = args.slice(0, -2);
    await cmdoCmd.execute(options, positionalArgs, context);
  });

  return cmd;
}

3. Commander Parses

User runs: cmdo deploy staging --skip-tests

Commander:

  • Matches deploy command
  • Parses staging as argument
  • Parses --skip-tests as flag
  • Invokes action handler

4. Commando Command Executes

execute: async (options, args, context) => {
  // options.skipTests = true
  // args[0] = 'staging'
  // Already parsed!
}

Custom Options

Use defineCommand() for Commander-specific options:

export const deploy: CommandoCommand = {
  description: 'Deploy website',

  defineCommand: (cmd) => {
    cmd
      .argument('<environment>', 'Target environment')
      .option('-s, --skip-tests', 'Skip tests')
      .option('--dry-run', 'Preview only');
  },

  execute: async (options, args) => {
    const env = args[0];              // 'staging'
    if (options.skipTests) { ... }    // true/false
    if (options.dryRun) { ... }       // true/false
  }
};

Features You Get

Auto-Generated Help

cmdo deploy --help

Output:

Usage: cmdo deploy [options] <environment>

Deploy website

Arguments:
  environment         Target environment

Options:
  -s, --skip-tests   Skip tests
  --dry-run          Preview only
  -h, --help         display help

Validation

cmdo deploy  # Missing required argument
# Error: missing required argument 'environment'

Type Safety

import { Command } from 'commander';

defineCommand: (cmd: Command) => {
  cmd.option('--count <n>', 'Count', parseInt);
  //                         ^^^^^^^ Type coercion
}

Global Flags

Framework adds global flags automatically:

cmdo --version              # Show version
cmdo --root /path/to/proj   # Override project root
cmdo -d                     # Debug mode (verbose logging)
cmdo -q                     # Quiet mode
cmdo -s                     # Silent mode
cmdo --no-color             # Disable colors
cmdo --log-format json      # JSON logs

These are configured in lib/cli.ts before commands load.


Subcommand Groups

Groups are Commander subcommands:

// Framework creates:
const program = new Command('cmdo');

const websiteCmd = new Command('website');
websiteCmd.addCommand(buildCommand('build', ...));
websiteCmd.addCommand(buildCommand('deploy', ...));

program.addCommand(websiteCmd);

Result: cmdo website build, cmdo website deploy


When To Use defineCommand()

You need it if:

  • Custom arguments (required, optional, variadic)
  • Options with choices/validation
  • Default values
  • Type coercion (parse numbers, dates)

You don't need it if:

  • Simple command with no options
  • Just needs description and execute

Examples

Simple (No defineCommand)

export const version = {
  description: 'Show version',
  execute: async () => console.log('v2.0.0')
};

With Options

export const build: CommandoCommand = {
  description: 'Build website',

  defineCommand: (cmd) => {
    cmd
      .option('-c, --clean', 'Clean first')
      .option('--no-optimize', 'Skip optimization');
  },

  execute: async (options) => {
    if (options.clean) await clean();
    if (options.optimize) await optimize();  // --no-optimize negates
    await build();
  }
};

With Arguments

export const deploy: CommandoCommand = {
  description: 'Deploy to environment',

  defineCommand: (cmd) => {
    cmd.argument('<env>', 'Environment name');
  },

  execute: async (options, args) => {
    const env = args[0];  // Required
    await deploy(env);
  }
};

Benefits

Simple commands stay simple - Just description + execute ✅ Rich features available - Full Commander power when needed ✅ Auto-help - Generated from definitions ✅ Type-safe - TypeScript validates everything ✅ Separation - Commando commands don't know about Commander


See Also