sticky.codes

commander.js

A Node.js library for building command-line interfaces, providing declarative option and argument parsing, subcommand routing, and automatic help generation.

Overview

Commander.js is a zero-dependency library for constructing CLI programs in Node.js. It handles the low-level work of parsing process.argv, validating inputs, and dispatching to action handlers so that authors can focus on program logic rather than argument wrangling.

The library is aimed at Node.js developers who need anything from a simple single-command script to a multi-level subcommand tree (like git or npm). It ships with full TypeScript typings, supports both CommonJS and ESM imports, and covers a wide surface area: required and optional arguments, variadic options, environment variable fallbacks, conflict detection, implied values, custom parsers, executable subcommands, and a fully customisable help formatter.

Commander follows semantic versioning and targets actively supported Node.js LTS versions. As of v14, the minimum required Node.js version is 20. The project explicitly maintains zero production dependencies; requests that add one are unlikely to be accepted.

Architecture

The library is a small set of cooperating classes. All public symbols are re-exported from index.js, which is the CommonJS entry point. esm.mjs wraps that same module to provide named ESM exports.

index.js  (CommonJS entry, exports all public symbols)
esm.mjs   (ESM wrapper, re-exports from index.js)

lib/
  command.js        – Command class, the central orchestrator
  option.js         – Option + DualOptions classes, flag parsing helpers
  argument.js       – Argument class, positional parameter descriptors
  help.js           – Help class, formats and renders help text
  error.js          – CommanderError + InvalidArgumentError
  suggestSimilar.js – Damerau-Levenshtein typo suggestions

typings/
  index.d.ts        – TypeScript declarations for CJS
  esm.d.mts         – TypeScript declarations for ESM

At runtime, a user creates a Command instance (or uses the shared program singleton exported from index.js), attaches Option and Argument objects, registers action handlers, and calls .parse(). Command drives all parsing: it strips options it understands, resolves environment variables, applies implied values, checks constraints, then dispatches to the action handler or routes to a subcommand. The Help class is invoked separately when --help is requested.

Key modules

lib/command.js

The heart of the library. Command manages the full lifecycle described in docs/parsing-and-hooks.md:

  1. Parse options from the args array.
  2. Read environment variable fallbacks.
  3. Apply implied option values.
  4. Route to a subcommand or process command-arguments for the leaf command.
  5. Check mandatory options and conflicts.
  6. Invoke preAction / postAction hooks, then the action handler.

Command also owns the subcommand tree. Calling .command('name') either registers an inline subcommand (returns a child Command) or registers an executable subcommand that is spawned as a separate process. The createCommand, createHelp, and createOption factory methods exist specifically to be overridden in subclasses, as shown in examples/custom-command-class.js.

lib/option.js

Defines Option, which parses a flags string like '-p, --port <number>' into structured fields (short, long, required, optional, variadic, negate). Key methods:

  • .default(value, description) — sets a default and its help description.
  • .env(name) — reads from an environment variable when the option is not supplied.
  • .conflicts(names) — registers names that cannot be used alongside this option.
  • .implies(values) — sets other option values when this option is present.
  • .choices(values) — restricts the option to an enumerated set.
  • .argParser(fn) — attaches a custom value transformer.
  • .preset(arg) — value to use when the option flag is given but no argument follows (useful for optional-argument options).
  • .helpGroup(heading) — assigns the option to a named group in the help output.

Also exports DualOptions, which helps Command handle paired --foo / --no-foo options that share a single attribute name.

The private splitOptionFlags function parses the flag string and enforces the constraint that short flags are exactly one character. Since v13, the library supports two long flags for the same option (e.g. '--ws, --workspace') as an alternative to short flags.

lib/argument.js

Defines Argument, which represents a positional command-argument. The constructor infers required vs. optional from angle-bracket or square-bracket notation (<required> vs. [optional]), and variadic from a trailing .... Supports .default(), .choices(), and .argParser() in the same style as Option. Exports the humanReadableArgName helper used by Help to reconstruct the bracket notation for display.

lib/help.js

The Help class formats the help page. It is designed to be customised without subclassing: Command.configureHelp(overrides) merges an object of property overrides or method replacements into the Help instance. Data properties control sorting (sortSubcommands, sortOptions) and layout (helpWidth, minWidthToWrap, showGlobalOptions). Stringify methods (optionTerm, subcommandTerm, argumentTerm, etc.) control how each item is converted to a string. Style methods (styleTitle, styleOptionText, styleDescriptionText, etc.) control ANSI colouring. The prepareContext method receives output-stream metadata so that the correct color enablement is applied to stdout vs. stderr. See docs/help-in-depth.md for the full method table.

lib/error.js

Two error classes:

  • CommanderError(exitCode, code, message) — base class carrying an exit code and a string error code (e.g. 'commander.unknownOption').
  • InvalidArgumentError(message) — subclass for errors thrown from custom argParser / parseArg functions; always exits with code 1 and code string 'commander.invalidArgument'. This is the replacement for the deprecated InvalidOptionArgumentError.

lib/suggestSimilar.js

Implements Damerau-Levenshtein optimal string alignment distance to suggest corrections when the user types an unknown option or command. Suggestions are filtered to a similarity ratio above 0.4 and an edit distance no greater than 3. Used by Command when .showSuggestionAfterError() is enabled.

index.js and esm.mjs

index.js is the CommonJS entry point. It instantiates a shared program singleton and re-exports all public classes and factory functions. esm.mjs destructures that same export and re-exports named bindings for ESM consumers, so import { Command } from 'commander' works without any special entry path.

Getting started

Requires Node.js 20 or later. Commander has no production dependencies.

Install:

npm install commander

CommonJS usage:

const { Command } = require('commander');
const program = new Command();

program
  .name('my-cli')
  .option('-d, --debug', 'enable debug output')
  .argument('<file>', 'file to process')
  .action((file, options) => {
    if (options.debug) console.log('debug on');
    console.log('processing', file);
  });

program.parse();

ESM usage:

import { Command } from 'commander';

Run the test suite:

npm test          # jest + TypeScript type-checking
npm run test-all  # jest + ESM smoke test + lint + format + type checks

Lint and format checks:

npm run check     # type-check (JS + TS), lint, and format
npm run fix       # auto-fix lint and formatting

The test runner is Jest. TypeScript checking uses tsc with separate configs: tsconfig.js.json covers the library's own JavaScript files (with relaxed strictness), and tsconfig.ts.json covers the TypeScript test and declaration files (full strict). tsd validates the hand-written declaration file in typings/index.d.ts.

Notable details

Parsing order matters for optional-argument options. When a command has both positional arguments and options with optional option-arguments (e.g. --flag [value]), Commander claims the following token as the option-argument, which can silently consume what the user intended as a command-argument. docs/options-in-depth.md describes this ambiguity and the recommended workarounds (-- separator, putting options last, or restructuring as required-argument options).

--no-* negation is built in. Declaring --no-foo automatically creates a negation counterpart to a boolean --foo option. If both are declared explicitly, DualOptions in lib/option.js tracks them as a dual pair to correctly attribute which flag set the current value.

Option names are camel-cased automatically. --output-file becomes opts().outputFile. This conversion is done by the camelcase function in lib/option.js and affects how you access values and how .conflicts() / .implies() names are resolved.

Dual long flags replace multi-character short flags. Since v13.1, the supported way to have a compact option flag that is not a single character is to declare two long flags: '--ws, --workspace'. The first acts as the "short" alias. Actual multi-character short flags (-ws) throw an exception as of v13.

Executable subcommands spawn a separate process. When .command('name', 'description') is called with a description string as the second argument, Commander treats the subcommand as executable and uses spawn/fork to run <program>-<name> from the PATH or configured search paths. The inline createCommand form (no description string) creates a child Command object in the same process.

The Help class is fully replaceable. Override createHelp() on a Command subclass to return a custom Help instance, as shown in examples/color-help-replacement.mjs. Alternatively, use the lighter-weight configureHelp() to patch individual methods on the default instance.

Zero production dependencies is a stated project constraint. The CONTRIBUTING.md notes that pull requests which add a production dependency are unlikely to be accepted.