Skip to content

How Execution Works

How command execution works under the hood, step by step.

Pipeline

argv

  ├─ 1. Route ──────────── Match command from the nested command tree
  ├─ 2. Parse ──────────── Extract positional args and options (resolve aliases)
  ├─ 3. Plugin (pre) ────── onBeforeCommandExecution (global + command plugins; raw parsed args)
  ├─ 4. Middleware ─────── Matched command only; build ctx via next()
  ├─ 5. Validate ───────── Unknown-option policy, bequeath handlers, Zod positional & options
  ├─ 6. Handler ────────── Execute command handler with validated params
  └─ 7. Plugin (post) ──── onAfterCommandExecution (always runs, even on error)

Entry Point

executeCli() receives { cli, args } (args default to process.argv.slice(2)).

  1. Resolves the command from the tree
  2. Runs the command pipeline
  3. On error: formats and logs via handleError(), then process.exit(1)
  4. Finally: runs onDestroy plugin hooks (always, even on error)

Deprecation warnings are not emitted by the core execution layer. If you install the standard library deprecationPlugin, it will emit warnings via plugin hooks (e.g. onInit for the CLI and onBeforeCommandExecution for the matched command).

Routing

Walks the nested command tree by consuming argv segments:

  • Each non-flag arg (!startsWith('-')) is matched against command paths
  • On match: descend into that subcommand, consume the segment
  • On no match or flag: stop — current command is the target
  • Remaining argv is forwarded to the command as its own args

Command Pipeline

This is the core of execution. Runs in this exact order:

1. Alias Map

Merges command option aliases (from Zod .meta({ aliases })) with bequeath option aliases into a single lookup map.

2. Argument Parsing

Splits raw argv into { positional: string[], options: Record<string, any> } using mri with the alias map.

3. Pre-Execution Plugin Hooks

Runs onBeforeCommandExecution for global plugins, then command plugins, in order. Hooks receive unvalidated parsedOptions and parsedPositionals. If a hook throws, the rest of the pipeline (including middleware and handler) does not run.

4. Middleware

Runs the matched command’s middleware array only (not parent commands’ arrays), in order. Each step receives frozen params { ctx, next, cli, command, halt }. Context grows when a middleware returns next({ ctx: { ... } }) (deep-merge via defu). Each middleware must return the result of next(). The final context is passed to bequeath option handlers and the command handler as ctx.

5. Extraneous Options

Checks parsed options against the schema + bequeath option names. Behavior depends on throwOnExtrageousOptions:

  • 'throw' (default): throws InvalidOptionsError
  • 'filter-out': silently drops unknown options
  • 'pass-through': keeps them for the handler

6. Bequeath Option Handlers

Iterates bequeath options. If a bequeath option is present in parsed args:

  • Validates its value against its Zod schema
  • If it has a handler (e.g. --help, --version): executes it and returns early (short-circuits the rest of the pipeline)

7. Positional Validation

Extracts the positional value, then runs schema.parse(). Throws InvalidPositionalError on failure.

8. Options Validation

Separates valid vs. extra options, then runs schema.parse() on valid options. For 'pass-through' mode, extra options are merged back into the result.

9. Handler

Calls the command handler with:

{ positional, options, ctx, command, cli }

10. Post-Execution Plugin Hooks

Runs onAfterCommandExecution in a finally block — always executes, even if the handler threw. Hook errors are logged but don't override the original error.

Error Handling

Errors are handled by handleError(), which distinguishes between:

  • Validation errors (InvalidPositionalError, InvalidOptionsError): structured Zod issues — each is logged with the field name and description extracted from the manifest
  • Standard errors: logs the message
  • Unknown errors: logs a generic message

After logging, executeCli() calls process.exit(1).