Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: tj/commander.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v11.0.0
Choose a base ref
...
head repository: tj/commander.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v11.1.0
Choose a head ref

Commits on Jun 26, 2023

  1. Add example of displaying custom usage in subcommand list (#1896)

    * Add example of displaying custom usage in subcommand list
    
    * Make example more focused
    shadowspawn authored Jun 26, 2023
    Copy the full SHA
    7fe7831 View commit details

Commits on Jul 11, 2023

  1. Copy the full SHA
    d038570 View commit details

Commits on Aug 5, 2023

  1. Fix grammar in docs

    aweebit committed Aug 5, 2023
    Copy the full SHA
    3b31a1f View commit details

Commits on Aug 8, 2023

  1. Fix indentation

    Signed-off-by: abetomo <abe@enzou.tokyo>
    abetomo committed Aug 8, 2023
    Copy the full SHA
    2dc7458 View commit details
  2. Watch for npm package updates with dependabot

    Signed-off-by: abetomo <abe@enzou.tokyo>
    abetomo committed Aug 8, 2023
    Copy the full SHA
    dde6bb5 View commit details
  3. Add note on inherited settings to docs

    (cherry picked from commit ac955dc)
    aweebit authored and abetomo committed Aug 8, 2023
    Copy the full SHA
    2e2f139 View commit details
  4. Improve docs about inherited settings

    (cherry picked from commit dfe2fc7)
    
    Co-authored-by: John Gee <john@ruru.gen.nz>
    2 people authored and abetomo committed Aug 8, 2023
    Copy the full SHA
    880b028 View commit details

Commits on Aug 19, 2023

  1. Fix executableDir() return type

    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    68a4276 View commit details
  2. Fix version() parameter type

    Borrowed from a3f0e28 that was supposed to land in the now-closed #1921.
    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    14fb529 View commit details
  3. Change initial variable values in test for better error messages

    (cherry picked from commit 87db4ba)
    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    401b0d6 View commit details
  4. Copy the full SHA
    9c38a95 View commit details
  5. Revert "Fix version() parameter type"

    This reverts commit e8bea4a.
    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    f3431d1 View commit details
  6. Copy the full SHA
    c117172 View commit details
  7. Throw error on options-as-properties config after setting option values

    (cherry picked from commit 20c7cfa)
    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    ff0fd9e View commit details
  8. Copy the full SHA
    6871e68 View commit details
  9. Copy the full SHA
    96f076d View commit details
  10. Introduce _getCommandAndAncestors()

    Replaces getCommandAndParents():
    - turned into an instance method
    - used "ancestors" instead of "parents" because it is more precise
    
    (cherry picked from commit aa280af)
    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    1f90b11 View commit details
  11. Use _getCommandAndAncestors() consistently

    (cherry picked from commit 777a452)
    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    25ebead View commit details
  12. Use _getCommandAndAncestors() less aggressively

    Only call when all elements are to be iterated.
    Resort to manual iteration via .parent in other cases.
    aweebit authored and abetomo committed Aug 19, 2023
    Copy the full SHA
    f76a734 View commit details

Commits on Aug 22, 2023

  1. Copy the full SHA
    5740e59 View commit details

Commits on Aug 24, 2023

  1. Refactor to wrap invalid argument (#1977)

    Wrap handling command.invalidArgument to simplify calling code
    shadowspawn authored Aug 24, 2023
    Copy the full SHA
    4c095d1 View commit details

Commits on Aug 26, 2023

  1. Types for version getter (#1982)

    * Update types since .version() returns version.
    
    * Remove extra space
    shadowspawn authored Aug 26, 2023
    Copy the full SHA
    f5413db View commit details
  2. Copy the full SHA
    0d432ba View commit details

Commits on Sep 3, 2023

  1. Copy the full SHA
    c7d39ca View commit details

Commits on Sep 10, 2023

  1. Copy the full SHA
    e85e05a View commit details
  2. Work-around bug in Jest (#2011)

    Co-authored-by: Wee Bit <aweebit64@gmail.com>
    shadowspawn and aweebit authored Sep 10, 2023
    Copy the full SHA
    8edcfd9 View commit details

Commits on Sep 11, 2023

  1. Add public Arguments property (#2010)

    Co-authored-by: Wee Bit <aweebit64@gmail.com>
    shadowspawn and aweebit authored Sep 11, 2023
    Copy the full SHA
    7d23437 View commit details
  2. Bump actions/checkout from 3 to 4 (#2012)

    Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
    - [Release notes](https://github.com/actions/checkout/releases)
    - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
    - [Commits](actions/checkout@v3...v4)
    
    ---
    updated-dependencies:
    - dependency-name: actions/checkout
      dependency-type: direct:production
      update-type: version-update:semver-major
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Sep 11, 2023
    Copy the full SHA
    58820a4 View commit details

Commits on Sep 12, 2023

  1. Explicitly export factory functions (#2013)

    Co-authored-by: Wee Bit <aweebit64@gmail.com>
    shadowspawn and aweebit authored Sep 12, 2023
    Copy the full SHA
    384f17b View commit details
  2. Copy the full SHA
    14edbca View commit details

Commits on Sep 14, 2023

  1. Copy the full SHA
    26a34e6 View commit details

Commits on Sep 21, 2023

  1. Copy the full SHA
    744ee3f View commit details

Commits on Oct 4, 2023

  1. Refactor type-checking setup (#1969)

    * Refactor type-checking setup
    
    * Refactor tsconfig particularly to enable loose check in VSCode, strict checks run separately for type definitions
    
    * Simplify includes for tsconfig
    
    * Explicitly separate the tsconfig for use with npm run-scripts
    
    * Improve comment
    
    * Resolved couple of work-in-progress comments
    
    * Update tsconfig to recommended node16 lib/module/target
    
    * Make checks strict by default and opt-out
    
    * Restore broken code to merge later changes
    
    * Updates after merge
    
    ---------
    
    
    
    Co-authored-by: Wee Bit <aweebit64@gmail.com>
    shadowspawn and aweebit authored Oct 4, 2023
    Copy the full SHA
    96c6c25 View commit details

Commits on Oct 7, 2023

  1. Copy the full SHA
    67c9180 View commit details
  2. Copy the full SHA
    03dea00 View commit details

Commits on Oct 8, 2023

  1. Copy the full SHA
    591fc4b View commit details

Commits on Oct 13, 2023

  1. Update CHANGELOG for 11.1.0 (#2025)

    * Update CHANGELOG for 11.1.0
    
    * add tsconfig refactor to CHANGELOG
    
    * Add tentative release date
    
    * Add PR reference
    
    * Bump version
    shadowspawn authored Oct 13, 2023
    3
    Copy the full SHA
    f1ae2db View commit details
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ const javascriptSettings = {
const typescriptSettings = {
files: ['*.ts', '*.mts'],
parserOptions: {
project: './tsconfig.json'
project: './tsconfig.ts.json'
},
plugins: [
'@typescript-eslint'
13 changes: 9 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
version: 2
updates:
- package-ecosystem: "github-actions"
target-branch: "develop"
directory: "/"
schedule:
- package-ecosystem: "npm"
target-branch: "develop"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
target-branch: "develop"
directory: "/"
schedule:
interval: "weekly"
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ jobs:
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,30 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
<!-- markdownlint-disable MD024 -->
<!-- markdownlint-disable MD004 -->

## [11.1.0] (2023-10-13)

### Fixed

- TypeScript: update `OptionValueSource` to allow any string, to match supported use of custom sources ([#1983])
- TypeScript: add that `Command.version()` can also be used as getter ([#1982])
- TypeScript: add null return type to `Commands.executableDir()`, for when not configured ([#1965])
- subcommands with an executable handler and only a short help flag are now handled correctly by the parent's help command ([#1930])

### Added

- `registeredArguments` property on `Command` with the array of defined `Argument` (like `Command.options` for `Option`) ([#2010])
- TypeScript declarations for Option properties: `envVar`, `presetArg` ([#2019])
- TypeScript declarations for Argument properties: `argChoices`, `defaultValue`, `defaultValueDescription` ([#2019])
- example file which shows how to configure help to display any custom usage in the list of subcommands ([#1896])

### Changed

- (developer) refactor TypeScript configs for multiple use-cases, and enable checks in JavaScript files in supporting editors ([#1969])

### Deprecated

- `Command._args` was private anyway, but now available as `registeredArguments` ([#2010])

## [11.0.0] (2023-06-16)

### Fixed
@@ -1163,6 +1187,14 @@ program
[#1864]: https://github.com/tj/commander.js/pull/1864
[#1874]: https://github.com/tj/commander.js/pull/1874
[#1886]: https://github.com/tj/commander.js/pull/1886
[#1896]: https://github.com/tj/commander.js/pull/1896
[#1930]: https://github.com/tj/commander.js/pull/1930
[#1965]: https://github.com/tj/commander.js/pull/1965
[#1969]: https://github.com/tj/commander.js/pull/1969
[#1982]: https://github.com/tj/commander.js/pull/1982
[#1983]: https://github.com/tj/commander.js/pull/1983
[#2010]: https://github.com/tj/commander.js/pull/2010
[#2019]: https://github.com/tj/commander.js/pull/2019

<!-- Referenced in 5.x -->
[#1]: https://github.com/tj/commander.js/issues/1
@@ -1242,6 +1274,7 @@ program
[#1028]: https://github.com/tj/commander.js/pull/1028

[Unreleased]: https://github.com/tj/commander.js/compare/master...develop
[11.1.0]: https://github.com/tj/commander.js/compare/v11.0.0...v11.1.0
[11.0.0]: https://github.com/tj/commander.js/compare/v10.0.1...v11.0.0
[10.0.1]: https://github.com/tj/commander.js/compare/v10.0.0...v10.0.1
[10.0.0]: https://github.com/tj/commander.js/compare/v9.5.0...v10.0.0
44 changes: 23 additions & 21 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -191,7 +191,7 @@ serve --port=80

You can use `--` to indicate the end of the options, and any remaining arguments will be used without being interpreted.

By default options on the command line are not positional, and can be specified before or after other arguments.
By default, options on the command line are not positional, and can be specified before or after other arguments.

There are additional related routines for when `.opts()` is not enough:

@@ -234,7 +234,7 @@ pizza details:
- cheese
```

Multiple boolean short options may be combined together following the dash, and may be followed by a single short option taking a value.
Multiple boolean short options may be combined following the dash, and may be followed by a single short option taking a value.
For example `-d -s -p cheese` may be written as `-ds -p cheese` or even `-dsp cheese`.

Options with an expected option-argument are greedy and will consume the following argument whatever the value.
@@ -327,7 +327,7 @@ add cheese type mozzarella
Options with an optional option-argument are not greedy and will ignore arguments starting with a dash.
So `id` behaves as a boolean option for `--id -5`, but you can use a combined form if needed like `--id=-5`.

For information about possible ambiguous cases, see [options taking varying arguments](./docs/options-taking-varying-arguments.md).
For information about possible ambiguous cases, see [options taking varying arguments](./docs/options-in-depth.md).

### Required option

@@ -379,7 +379,7 @@ Options: { number: [ '1', '2', '3' ], letter: true }
Remaining arguments: [ 'operand' ]
```

For information about possible ambiguous cases, see [options taking varying arguments](./docs/options-taking-varying-arguments.md).
For information about possible ambiguous cases, see [options taking varying arguments](./docs/options-in-depth.md).

### Version option

@@ -546,6 +546,8 @@ subcommand is specified ([example](./examples/defaultCommand.js)).

You can add alternative names for a command with `.alias()`. ([example](./examples/alias.js))

`.command()` automatically copies the inherited settings from the parent command to the newly created subcommand. This is only done during creation, any later setting changes to the parent are not inherited.

For safety, `.addCommand()` does not automatically copy the inherited settings from the parent command. There is a helper routine `.copyInheritedSettings()` for copying the settings when they are wanted.

### Command-arguments
@@ -676,7 +678,7 @@ async function main() {
}
```

A command's options and arguments on the command line are validated when the command is used. Any unknown options or missing arguments will be reported as an error. You can suppress the unknown option checks with `.allowUnknownOption()`. By default it is not an error to
A command's options and arguments on the command line are validated when the command is used. Any unknown options or missing arguments will be reported as an error. You can suppress the unknown option checks with `.allowUnknownOption()`. By default, it is not an error to
pass more arguments than declared, but you can make this an error with `.allowExcessArguments(false)`.

### Stand-alone executable (sub)commands
@@ -764,7 +766,7 @@ shell help spawn
shell spawn --help
```

Long descriptions are wrapped to fit the available width. (However, a description that includes a line-break followed by whitespace is assumed to be pre-formatted and not wrapped.)
Long descriptions are wrapped to fit the available width. (However, a description that includes a line-break followed by whitespace is assumed to be pre-formatted and not wrapped.)

### Custom help

@@ -852,8 +854,8 @@ error: unknown option '--hepl'
The command name appears in the help, and is also used for locating stand-alone executable subcommands.

You may specify the program name using `.name()` or in the Command constructor. For the program, Commander will
fallback to using the script name from the full arguments passed into `.parse()`. However, the script name varies
depending on how your program is launched so you may wish to specify it explicitly.
fall back to using the script name from the full arguments passed into `.parse()`. However, the script name varies
depending on how your program is launched, so you may wish to specify it explicitly.

```js
program.name('pizza');
@@ -895,7 +897,7 @@ This may require additional disk space.

### .helpOption(flags, description)

By default every command has a help option. You may change the default help flags and description. Pass false to disable the built-in help option.
By default, every command has a help option. You may change the default help flags and description. Pass false to disable the built-in help option.

```js
program
@@ -969,7 +971,7 @@ program.parse(['-f', 'filename'], { from: 'user' });

If the default parsing does not suit your needs, there are some behaviours to support other usage patterns.

By default program options are recognised before and after subcommands. To only look for program options before subcommands, use `.enablePositionalOptions()`. This lets you use
By default, program options are recognised before and after subcommands. To only look for program options before subcommands, use `.enablePositionalOptions()`. This lets you use
an option for a different purpose in subcommands.

Example file: [positional-options.js](./examples/positional-options.js)
@@ -981,8 +983,8 @@ program -b subcommand
program subcommand -b
```

By default options are recognised before and after command-arguments. To only process options that come
before the command-arguments, use `.passThroughOptions()`. This lets you pass the arguments and following options through to another program
By default, options are recognised before and after command-arguments. To only process options that come
before the command-arguments, use `.passThroughOptions()`. This lets you pass the arguments and following options through to another program
without needing to use `--` to end the option processing.
To use pass through options in a subcommand, the program needs to enable positional options.

@@ -995,15 +997,15 @@ program --port=80 arg
program arg --port=80
```

By default the option processing shows an error for an unknown option. To have an unknown option treated as an ordinary command-argument and continue looking for options, use `.allowUnknownOption()`. This lets you mix known and unknown options.
By default, the option processing shows an error for an unknown option. To have an unknown option treated as an ordinary command-argument and continue looking for options, use `.allowUnknownOption()`. This lets you mix known and unknown options.

By default the argument processing does not display an error for more command-arguments than expected.
By default, the argument processing does not display an error for more command-arguments than expected.
To display an error for excess arguments, use`.allowExcessArguments(false)`.

### Legacy options as properties

Before Commander 7, the option values were stored as properties on the command.
This was convenient to code but the downside was possible clashes with
This was convenient to code, but the downside was possible clashes with
existing properties of `Command`. You can revert to the old behaviour to run unmodified legacy code by using `.storeOptionsAsProperties()`.

```js
@@ -1027,7 +1029,7 @@ See [commander-js/extra-typings](https://github.com/commander-js/extra-typings)
import { Command } from '@commander-js/extra-typings';
```

ts-node: If you use `ts-node` and stand-alone executable subcommands written as `.ts` files, you need to call your program through node to get the subcommands called correctly. e.g.
ts-node: If you use `ts-node` and stand-alone executable subcommands written as `.ts` files, you need to call your program through node to get the subcommands called correctly. e.g.

```sh
node -r ts-node/register pm.ts
@@ -1057,14 +1059,14 @@ You can enable `--harmony` option in two ways:

An executable subcommand is launched as a separate child process.

If you are using the node inspector for [debugging](https://nodejs.org/en/docs/guides/debugging-getting-started/) executable subcommands using `node --inspect` et al,
If you are using the node inspector for [debugging](https://nodejs.org/en/docs/guides/debugging-getting-started/) executable subcommands using `node --inspect` et al.,
the inspector port is incremented by 1 for the spawned subcommand.

If you are using VSCode to debug executable subcommands you need to set the `"autoAttachChildProcesses": true` flag in your launch.json configuration.

### npm run-script

By default when you call your program using run-script, `npm` will parse any options on the command-line and they will not reach your program. Use
By default, when you call your program using run-script, `npm` will parse any options on the command-line and they will not reach your program. Use
`--` to stop the npm option parsing and pass through all the arguments.

The synopsis for [npm run-script](https://docs.npmjs.com/cli/v9/commands/npm-run-script) explicitly shows the `--` for this reason:
@@ -1087,7 +1089,7 @@ program.error('Custom processing has failed', { exitCode: 2, code: 'my.custom.er

### Override exit and output handling

By default Commander calls `process.exit` when it detects errors, or after displaying the help or version. You can override
By default, Commander calls `process.exit` when it detects errors, or after displaying the help or version. You can override
this behaviour and optionally supply a callback. The default override throws a `CommanderError`.

The override callback is passed a `CommanderError` with properties `exitCode` number, `code` string, and `message`. The default override behaviour is to throw the error, except for async handling of executable subcommand completion which carries on. The normal display of error messages or version or help
@@ -1103,7 +1105,7 @@ try {
}
```

By default Commander is configured for a command-line application and writes to stdout and stderr.
By default, Commander is configured for a command-line application and writes to stdout and stderr.
You can modify this behaviour for custom applications. In addition, you can modify the display of error messages.

Example file: [configure-output.js](./examples/configure-output.js)
@@ -1129,7 +1131,7 @@ program
There is more information available about:

- [deprecated](./docs/deprecated.md) features still supported for backwards compatibility
- [options taking varying arguments](./docs/options-taking-varying-arguments.md)
- [options taking varying arguments](./docs/options-in-depth.md)
- [parsing life cycle and hooks](./docs/parsing-and-hooks.md)

## Support
19 changes: 18 additions & 1 deletion docs/deprecated.md
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ They are currently still available for backwards compatibility, but should not b
- [InvalidOptionArgumentError](#invalidoptionargumenterror)
- [Short option flag longer than a single character](#short-option-flag-longer-than-a-single-character)
- [Import from `commander/esm.mjs`](#import-from-commanderesmmjs)
- [cmd.\_args](#cmd_args)

## RegExp .option() parameter

@@ -33,7 +34,7 @@ The newer functionality is the Option `.choices()` method, or using a custom opt
This was an option passed to `.command()` to hide the command from the built-in help:

```js
program.command('example', 'examnple command', { noHelp: true });
program.command('example', 'example command', { noHelp: true });
```

The option was renamed `hidden` in Commander v5.1. Deprecated from Commander v7.
@@ -207,3 +208,19 @@ import { Command } from 'commander';
```

README updated in Commander v9. Deprecated from Commander v9.

## cmd._args

This was always private, but was previously the only way to access the command `Argument` array.

```js
const registeredArguments = program._args;
```

The registered command arguments are now accessible via `.registeredArguments`.

```js
const registeredArguments = program.registeredArguments;
```

Deprecated from Commander v11.
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
# Options taking varying numbers of option-arguments
<!-- omit from toc -->
# Options in Depth

The README covers declaring and using options, and mostly parsing will work the way you and your users expect. This page covers some special cases
and subtle issues in depth.

- [Options taking varying numbers of option-arguments](#options-taking-varying-numbers-of-option-arguments)
- [Parsing ambiguity](#parsing-ambiguity)
- [Alternative: Make `--` part of your syntax](#alternative-make-----part-of-your-syntax)
- [Alternative: Make `--` part of your syntax](#alternative-make----part-of-your-syntax)
- [Alternative: Put options last](#alternative-put-options-last)
- [Alternative: Use options instead of command-arguments](#alternative-use-options-instead-of-command-arguments)
- [Combining short options, and options taking arguments](#combining-short-options-and-options-taking-arguments)
- [Combining short options as if boolean](#combining-short-options-as-if-boolean)
- [Combining short options, and options taking arguments](#combining-short-options-and-options-taking-arguments)
- [Combining short options as if boolean](#combining-short-options-as-if-boolean)

## Options taking varying numbers of option-arguments

Certain options take a varying number of arguments:

@@ -20,11 +23,11 @@ program
.option('--test [name...]') // 0 or more
```

This page uses examples with options taking 0 or 1 arguments, but the discussions also apply to variadic options taking more arguments.
This section uses examples with options taking 0 or 1 arguments, but the discussions also apply to variadic options taking more arguments.

For information about terms used in this document see: [terminology](./terminology.md)

## Parsing ambiguity
### Parsing ambiguity

There is a potential downside to be aware of. If a command has both
command-arguments and options with varying option-arguments, this introduces a parsing ambiguity which may affect the user of your program.
@@ -73,7 +76,7 @@ ingredient: cheese

If you want to avoid your users needing to learn when to use `--`, there are a few approaches you could take.

### Alternative: Make `--` part of your syntax
#### Alternative: Make `--` part of your syntax

Rather than trying to teach your users what `--` does, you could just make it part of your syntax.

@@ -98,7 +101,7 @@ technique: scrambled
ingredient: cheese
```

### Alternative: Put options last
#### Alternative: Put options last

Commander follows the GNU convention for parsing and allows options before or after the command-arguments, or intermingled.
So by putting the options last, the command-arguments do not get confused with the option-arguments.
@@ -120,7 +123,7 @@ technique: scrambled
ingredient: cheese
```

### Alternative: Use options instead of command-arguments
#### Alternative: Use options instead of command-arguments

This is a bit more radical, but completely avoids the parsing ambiguity!

@@ -167,7 +170,7 @@ if (opts.halal) console.log(`halal servings: ${opts.halal}`);
```sh
$ collect -o 3
other servings: 3
$ collect -o3
$ collect -o3
other servings: 3
$ collect -l -v
vegan servings: true
@@ -178,7 +181,7 @@ halal servings: v

If you wish to use options taking varying arguments as boolean options, you need to specify them separately.

```
```console
$ collect -a -v -l
any servings: true
vegan servings: true
@@ -190,8 +193,8 @@ halal servings: true
Before Commander v5, combining a short option and the value was not supported, and combined short flags were always expanded.
So `-avl` expanded to `-a -v -l`.

If you want backwards compatible behaviour, or prefer combining short options as booleans to combining short option and value,
you may change the behavior.
If you want backwards compatible behaviour, or prefer combining short options as booleans to combining short option and value,
you may change the behaviour.

To modify the parsing of options taking an optional value:

29 changes: 29 additions & 0 deletions examples/help-subcommands-usage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo

// By default the subcommand list includes a fairly simple usage. If you have added a custom usage
// to the subcommands you may wish to configure the help to show these instead.
//
// See also ./configure-help.js which shows configuring the subcommand list to have no usage at all
// and just the subcommand name.

const program = new commander.Command()
.configureHelp({ subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage() });

program.command('make <FILENAME>')
.usage('-root ROOTDIR [-abc] <FILENAME>')
.requiredOption('--root <ROOTDIR>')
.option('-a')
.option('-b')
.option('-c')
.summary('example command with custom usage')
.description('this full description is displayed in the full help and not in the list of subcommands');

program.command('serve <SERVICE>')
.option('--port <PORTNUMBER>')
.description('example command with default simple usage');

program.parse();

// Try the following:
// node help-subcommands-usage help
11 changes: 5 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
@@ -4,24 +4,23 @@ const { CommanderError, InvalidArgumentError } = require('./lib/error.js');
const { Help } = require('./lib/help.js');
const { Option } = require('./lib/option.js');

// @ts-check

/**
* Expose the root command.
*/

exports = module.exports = new Command();
exports.program = exports; // More explicit access to global command.
// Implicit export of createArgument, createCommand, and createOption.
// createArgument, createCommand, and createOption are implicitly available as they are methods on program.

/**
* Expose classes
*/

exports.Argument = Argument;
exports.Command = Command;
exports.CommanderError = CommanderError;
exports.Option = Option;
exports.Argument = Argument;
exports.Help = Help;

exports.CommanderError = CommanderError;
exports.InvalidArgumentError = InvalidArgumentError;
exports.InvalidOptionArgumentError = InvalidArgumentError; // Deprecated
exports.Option = Option;
12 changes: 12 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const config = {
testEnvironment: 'node',
collectCoverage: true,
transform: {
'^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.ts.json' }]
},
testPathIgnorePatterns: [
'/node_modules/'
]
};

module.exports = config;
4 changes: 1 addition & 3 deletions lib/argument.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const { InvalidArgumentError } = require('./error.js');

// @ts-check

class Argument {
/**
* Initialize a new command argument with the given name and description.
@@ -66,7 +64,7 @@ class Argument {
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*
* @param {any} value
* @param {*} value
* @param {string} [description]
* @return {Argument}
*/
223 changes: 103 additions & 120 deletions lib/command.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions lib/error.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// @ts-check

/**
* CommanderError class
* @class
22 changes: 10 additions & 12 deletions lib/help.js
Original file line number Diff line number Diff line change
@@ -8,8 +8,6 @@ const { humanReadableArgName } = require('./argument.js');
* @typedef { import("./option.js").Option } Option
*/

// @ts-check

// Although this is a class, methods are static in style to allow override using subclass or just functions.
class Help {
constructor() {
@@ -101,8 +99,8 @@ class Help {
if (!this.showGlobalOptions) return [];

const globalOptions = [];
for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) {
const visibleOptions = parentCmd.options.filter((option) => !option.hidden);
for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) {
const visibleOptions = ancestorCmd.options.filter((option) => !option.hidden);
globalOptions.push(...visibleOptions);
}
if (this.sortOptions) {
@@ -121,14 +119,14 @@ class Help {
visibleArguments(cmd) {
// Side effect! Apply the legacy descriptions before the arguments are displayed.
if (cmd._argsDescription) {
cmd._args.forEach(argument => {
cmd.registeredArguments.forEach(argument => {
argument.description = argument.description || cmd._argsDescription[argument.name()] || '';
});
}

// If there are any arguments with a description then return all the arguments.
if (cmd._args.find(argument => argument.description)) {
return cmd._args;
if (cmd.registeredArguments.find(argument => argument.description)) {
return cmd.registeredArguments;
}
return [];
}
@@ -142,7 +140,7 @@ class Help {

subcommandTerm(cmd) {
// Legacy. Ignores custom usage string, and nested commands.
const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' ');
const args = cmd.registeredArguments.map(arg => humanReadableArgName(arg)).join(' ');
return cmd._name +
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
(cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option
@@ -240,11 +238,11 @@ class Help {
if (cmd._aliases[0]) {
cmdName = cmdName + '|' + cmd._aliases[0];
}
let parentCmdNames = '';
for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) {
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
let ancestorCmdNames = '';
for (let ancestorCmd = cmd.parent; ancestorCmd; ancestorCmd = ancestorCmd.parent) {
ancestorCmdNames = ancestorCmd.name() + ' ' + ancestorCmdNames;
}
return parentCmdNames + cmdName + ' ' + cmd.usage();
return ancestorCmdNames + cmdName + ' ' + cmd.usage();
}

/**
8 changes: 3 additions & 5 deletions lib/option.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
const { InvalidArgumentError } = require('./error.js');

// @ts-check

class Option {
/**
* Initialize a new `Option` with the given `flags` and `description`.
@@ -40,7 +38,7 @@ class Option {
/**
* Set the default value, and optionally supply the description to be displayed in the help.
*
* @param {any} value
* @param {*} value
* @param {string} [description]
* @return {Option}
*/
@@ -59,7 +57,7 @@ class Option {
* new Option('--color').default('GREYSCALE').preset('RGB');
* new Option('--donate [amount]').preset('20').argParser(parseFloat);
*
* @param {any} arg
* @param {*} arg
* @return {Option}
*/

@@ -275,7 +273,7 @@ class DualOptions {
/**
* Did the value come from the option, and not from possible matching dual option?
*
* @param {any} value
* @param {*} value
* @param {Option} option
* @returns {boolean}
*/
4 changes: 2 additions & 2 deletions package-lock.json
20 changes: 5 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "commander",
"version": "11.0.0",
"version": "11.1.0",
"description": "the complete solution for node.js command-line programs",
"keywords": [
"commander",
@@ -22,11 +22,11 @@
"lint": "npm run lint:javascript && npm run lint:typescript",
"lint:javascript": "eslint index.js esm.mjs \"lib/*.js\" \"tests/**/*.js\"",
"lint:typescript": "eslint typings/*.ts tests/*.ts",
"test": "jest && npm run test-typings",
"test": "jest && npm run typecheck-ts",
"test-esm": "node ./tests/esm-imports-test.mjs",
"test-typings": "tsd",
"typescript-checkJS": "tsc --allowJS --checkJS index.js lib/*.js --noEmit",
"test-all": "npm run test && npm run lint && npm run typescript-checkJS && npm run test-esm"
"typecheck-ts": "tsd && tsc -p tsconfig.ts.json",
"typecheck-js": "tsc -p tsconfig.js.json",
"test-all": "npm run test && npm run lint && npm run typecheck-js && npm run test-esm"
},
"files": [
"index.js",
@@ -73,16 +73,6 @@
"typescript": "^5.0.4"
},
"types": "typings/index.d.ts",
"jest": {
"testEnvironment": "node",
"collectCoverage": true,
"transform": {
"^.+\\.tsx?$": "ts-jest"
},
"testPathIgnorePatterns": [
"/node_modules/"
]
},
"engines": {
"node": ">=16"
},
10 changes: 5 additions & 5 deletions tests/command.addHelpText.test.js
Original file line number Diff line number Diff line change
@@ -181,7 +181,7 @@ describe('context checks with full parse', () => {
});

test('when help requested then context.error is false', () => {
let context = {};
let context;
const program = new commander.Command();
program
.exitOverride()
@@ -193,7 +193,7 @@ describe('context checks with full parse', () => {
});

test('when help for error then context.error is true', () => {
let context = {};
let context;
const program = new commander.Command();
program
.exitOverride()
@@ -206,7 +206,7 @@ describe('context checks with full parse', () => {
});

test('when help on program then context.command is program', () => {
let context = {};
let context;
const program = new commander.Command();
program
.exitOverride()
@@ -218,7 +218,7 @@ describe('context checks with full parse', () => {
});

test('when help on subcommand and "before" subcommand then context.command is subcommand', () => {
let context = {};
let context;
const program = new commander.Command();
program
.exitOverride();
@@ -231,7 +231,7 @@ describe('context checks with full parse', () => {
});

test('when help on subcommand and "beforeAll" on program then context.command is subcommand', () => {
let context = {};
let context;
const program = new commander.Command();
program
.exitOverride()
18 changes: 9 additions & 9 deletions tests/command.argumentVariations.test.js
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ const commander = require('../');
// and not exhaustively testing all methods elsewhere.

test.each(getSingleArgCases('<explicit-required>'))('when add "<arg>" using %s then argument required', (methodName, cmd) => {
const argument = cmd._args[0];
const argument = cmd.registeredArguments[0];
const expectedShape = {
_name: 'explicit-required',
required: true,
@@ -15,7 +15,7 @@ test.each(getSingleArgCases('<explicit-required>'))('when add "<arg>" using %s t
});

test.each(getSingleArgCases('implicit-required'))('when add "arg" using %s then argument required', (methodName, cmd) => {
const argument = cmd._args[0];
const argument = cmd.registeredArguments[0];
const expectedShape = {
_name: 'implicit-required',
required: true,
@@ -26,7 +26,7 @@ test.each(getSingleArgCases('implicit-required'))('when add "arg" using %s then
});

test.each(getSingleArgCases('[optional]'))('when add "[arg]" using %s then argument optional', (methodName, cmd) => {
const argument = cmd._args[0];
const argument = cmd.registeredArguments[0];
const expectedShape = {
_name: 'optional',
required: false,
@@ -37,7 +37,7 @@ test.each(getSingleArgCases('[optional]'))('when add "[arg]" using %s then argum
});

test.each(getSingleArgCases('<explicit-required...>'))('when add "<arg...>" using %s then argument required and variadic', (methodName, cmd) => {
const argument = cmd._args[0];
const argument = cmd.registeredArguments[0];
const expectedShape = {
_name: 'explicit-required',
required: true,
@@ -48,7 +48,7 @@ test.each(getSingleArgCases('<explicit-required...>'))('when add "<arg...>" usin
});

test.each(getSingleArgCases('implicit-required...'))('when add "arg..." using %s then argument required and variadic', (methodName, cmd) => {
const argument = cmd._args[0];
const argument = cmd.registeredArguments[0];
const expectedShape = {
_name: 'implicit-required',
required: true,
@@ -59,7 +59,7 @@ test.each(getSingleArgCases('implicit-required...'))('when add "arg..." using %s
});

test.each(getSingleArgCases('[optional...]'))('when add "[arg...]" using %s then argument optional and variadic', (methodName, cmd) => {
const argument = cmd._args[0];
const argument = cmd.registeredArguments[0];
const expectedShape = {
_name: 'optional',
required: false,
@@ -79,8 +79,8 @@ function getSingleArgCases(arg) {
}

test.each(getMultipleArgCases('<first>', '[second]'))('when add two arguments using %s then two arguments', (methodName, cmd) => {
expect(cmd._args[0].name()).toEqual('first');
expect(cmd._args[1].name()).toEqual('second');
expect(cmd.registeredArguments[0].name()).toEqual('first');
expect(cmd.registeredArguments[1].name()).toEqual('second');
});

function getMultipleArgCases(arg1, arg2) {
@@ -99,6 +99,6 @@ test('when add arguments using multiple methods then all added', () => {
cmd.arguments('<arg3> <arg4>');
cmd.argument('<arg5>');
cmd.addArgument(new commander.Argument('arg6'));
const argNames = cmd._args.map(arg => arg.name());
const argNames = cmd.registeredArguments.map(arg => arg.name());
expect(argNames).toEqual(['arg1', 'arg2', 'arg3', 'arg4', 'arg5', 'arg6']);
});
12 changes: 6 additions & 6 deletions tests/command.createArgument.test.js
Original file line number Diff line number Diff line change
@@ -21,20 +21,20 @@ class MyCommand extends commander.Command {
test('when override createArgument then used for argument()', () => {
const program = new MyCommand();
program.argument('<file>');
expect(program._args.length).toEqual(1);
expect(program._args[0].myProperty).toEqual('MyArgument');
expect(program.registeredArguments.length).toEqual(1);
expect(program.registeredArguments[0].myProperty).toEqual('MyArgument');
});

test('when override createArgument then used for arguments()', () => {
const program = new MyCommand();
program.arguments('<file>');
expect(program._args.length).toEqual(1);
expect(program._args[0].myProperty).toEqual('MyArgument');
expect(program.registeredArguments.length).toEqual(1);
expect(program.registeredArguments[0].myProperty).toEqual('MyArgument');
});

test('when override createArgument and createCommand then used for argument of command()', () => {
const program = new MyCommand();
const sub = program.command('sub <file>');
expect(sub._args.length).toEqual(1);
expect(sub._args[0].myProperty).toEqual('MyArgument');
expect(sub.registeredArguments.length).toEqual(1);
expect(sub.registeredArguments[0].myProperty).toEqual('MyArgument');
});
10 changes: 9 additions & 1 deletion tests/commander.configureCommand.test.js
Original file line number Diff line number Diff line change
@@ -81,6 +81,14 @@ test('when storeOptionsAsProperties() after adding option then throw', () => {
const program = new commander.Command();
program.option('--port <number>', 'port number', '80');
expect(() => {
program.storeOptionsAsProperties(false);
program.storeOptionsAsProperties();
}).toThrow();
});

// test('when storeOptionsAsProperties() after setting option value then throw', () => {
// const program = new commander.Command();
// program.setOptionValue('foo', 'bar');
// expect(() => {
// program.storeOptionsAsProperties();
// }).toThrow();
// });
2 changes: 2 additions & 0 deletions tests/fixtures/inspect.js
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

const program = require('../../');

process.env.FORCE_COLOR = 0; // work-around bug in Jest: https://github.com/jestjs/jest/issues/14391

program
.command('sub', 'install one or more packages')
.parse(process.argv);
2 changes: 2 additions & 0 deletions tests/fixtures/pm
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env node

process.env.FORCE_COLOR = 0; // work-around bug in Jest: https://github.com/jestjs/jest/issues/14391

var program = require('../../');

program
125 changes: 98 additions & 27 deletions tests/ts-imports.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { program, Command, Option, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Help, createCommand } from '../';
import {
program,
Command,
Option,
Argument,
Help,
CommanderError,
InvalidArgumentError,
InvalidOptionArgumentError,
createCommand,
createOption,
createArgument
} from '../';

import * as commander from '../';
import * as commander from '../'; // This does interesting things when esModuleInterop is true!

// Do some simple checks that expected imports are available at runtime.
// Similar tests to esm-imports-test.js
@@ -11,38 +23,97 @@ function checkClass(obj: object, name: string): void {
expect(obj.constructor.name).toEqual(name);
}

test('legacy default export of global Command', () => {
checkClass(commander, 'Command');
});
describe('named imports', () => {
test('program', () => {
checkClass(program, 'Command');
});

test('program', () => {
checkClass(program, 'Command');
});
test('Command', () => {
checkClass(new Command('name'), 'Command');
});

test('createCommand', () => {
checkClass(createCommand(), 'Command');
});
test('Option', () => {
checkClass(new Option('-e, --example', 'description'), 'Option');
});

test('Command', () => {
checkClass(new Command('name'), 'Command');
});
test('Argument', () => {
checkClass(new Argument('<foo>', 'description'), 'Argument');
});

test('Option', () => {
checkClass(new Option('-e, --example', 'description'), 'Option');
});
test('Help', () => {
checkClass(new Help(), 'Help');
});

test('CommanderError', () => {
checkClass(new CommanderError(1, 'code', 'failed'), 'CommanderError');
});
test('CommanderError', () => {
checkClass(new CommanderError(1, 'code', 'failed'), 'CommanderError');
});

test('InvalidArgumentError', () => {
checkClass(new InvalidArgumentError('failed'), 'InvalidArgumentError');
});
test('InvalidArgumentError', () => {
checkClass(new InvalidArgumentError('failed'), 'InvalidArgumentError');
});

test('InvalidOptionArgumentError', () => { // Deprecated
checkClass(new InvalidOptionArgumentError('failed'), 'InvalidArgumentError');
});

test('createCommand', () => {
checkClass(createCommand('foo'), 'Command');
});

test('InvalidOptionArgumentError', () => { // Deprecated
checkClass(new InvalidOptionArgumentError('failed'), 'InvalidArgumentError');
test('createOption', () => {
checkClass(createOption('-e, --example', 'description'), 'Option');
});

test('createArgument', () => {
checkClass(createArgument('<foo>', 'description'), 'Argument');
});
});

test('Help', () => {
checkClass(new Help(), 'Help');
describe('import * as commander', () => {
test('program', () => {
checkClass(commander.program, 'Command');
});

test('Command', () => {
checkClass(new commander.Command('name'), 'Command');
});

test('Option', () => {
checkClass(new commander.Option('-e, --example', 'description'), 'Option');
});

test('Argument', () => {
checkClass(new commander.Argument('<foo>', 'description'), 'Argument');
});

test('Help', () => {
checkClass(new commander.Help(), 'Help');
});

test('CommanderError', () => {
checkClass(new commander.CommanderError(1, 'code', 'failed'), 'CommanderError');
});

test('InvalidArgumentError', () => {
checkClass(new commander.InvalidArgumentError('failed'), 'InvalidArgumentError');
});

test('InvalidOptionArgumentError', () => { // Deprecated
checkClass(new commander.InvalidOptionArgumentError('failed'), 'InvalidArgumentError');
});

// Factory functions are not found if esModuleInterop is true, so comment out tests for now.
// Can uncomment these again when we drop the default export of global program and add the factory functions explicitly.

// test('createCommand', () => {
// checkClass(commander.createCommand('foo'), 'Command');
// });

// test('createOption', () => {
// checkClass(commander.createOption('-e, --example', 'description'), 'Option');
// });

// test('createArgument', () => {
// checkClass(commander.createArgument('<foo>', 'description'), 'Argument');
// });
});
13 changes: 13 additions & 0 deletions tsconfig.js.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{ /*
Simple override including just JavaScript files.
Used by npm run-script typecheck-js
*/
/* Visit https://aka.ms/tsconfig to read more about tsconfig configuration. */
"extends": "./tsconfig.json",
"include": [
/* All JavaScript targets from tsconfig.json include. */
"*.js",
"*.mjs",
"lib/**/*.js"
],
}
61 changes: 45 additions & 16 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,47 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": [
"es6"
],
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"types": [
"node",
"jest"
],
"noEmit": true,
"forceConsistentCasingInFileNames": true
},
"include": ["**/*.ts"],
/*
TypeScript is being used to do type checking across both JavaScript and TypeScript files.
In particular, this picks up some problems in the JSDoc in the JavaScript files, and validates the code
is consistent with the JSDoc.
The settings here are used by VSCode.
See also tsconfig.js.json and tsconfig.ts.json.
*/
/* Visit https://aka.ms/tsconfig to read more about tsconfig configuration. */
"compilerOptions": {
"lib": ["es2021"],
"module": "node16",
"target": "es2021",

"allowJs": true,
"checkJs": true,

/* Strict by default, but dial it down to reduce churn in our JavaScript code. */
"strict": true,
"noImplicitAny": false,
"strictNullChecks": false,
"useUnknownInCatchVariables": false,

"types": [
"node",
"jest"
],
"noEmit": true, /* just type checking and not emitting transpiled files */
"skipLibCheck": false, /* we want to check our hand crafted definitions */
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true /* common TypeScript config */
},
"include": [
/* JavaScript. Should match includes in tsconfig.js.json. */
"*.js",
"*.mjs",
"lib/**/*.js",
/* TypeScript. Should match includes in tsconfig.ts.json. */
"**/*.ts",
"**/*.mts"
],
"exclude": [
"node_modules"
]
}
20 changes: 20 additions & 0 deletions tsconfig.ts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{ /*
Override to include just TypeScript files and use stricter settings than we do with JavaScript.
Used by:
- npm run-script typecheck-ts
- eslint
*/
/* Visit https://aka.ms/tsconfig to read more about tsconfig configuration. */
"extends": "./tsconfig.json",
"compilerOptions": {
/* Full strict is fine for the TypeScript files, so turn back on the checks we turned off for mixed-use. */
"noImplicitAny": true,
"strictNullChecks": true,
"useUnknownInCatchVariables": true,
},
"include": [
/* All TypeScript targets from tsconfig.json include. */
"**/*.ts",
"**/*.mts"
],
}
71 changes: 33 additions & 38 deletions typings/index.d.ts
Original file line number Diff line number Diff line change
@@ -5,6 +5,14 @@
/* eslint-disable @typescript-eslint/method-signature-style */
/* eslint-disable @typescript-eslint/no-explicit-any */

// This is a trick to encourage editor to suggest the known literals while still
// allowing any BaseType value.
// References:
// - https://github.com/microsoft/TypeScript/issues/29729
// - https://github.com/sindresorhus/type-fest/blob/main/source/literal-union.d.ts
// - https://github.com/sindresorhus/type-fest/blob/main/source/primitive.d.ts
type LiteralUnion<LiteralType, BaseType extends string | number> = LiteralType | (BaseType & Record<never, never>);

export class CommanderError extends Error {
code: string;
exitCode: number;
@@ -42,6 +50,9 @@ export class Argument {
description: string;
required: boolean;
variadic: boolean;
defaultValue?: any;
defaultValueDescription?: string;
argChoices?: string[];

/**
* Initialize a new command argument with the given name and description.
@@ -94,6 +105,8 @@ export class Option {
negate: boolean;
defaultValue?: any;
defaultValueDescription?: string;
presetArg?: unknown;
envVar?: string;
parseArg?: <T>(value: string, previous: T) => T;
hidden: boolean;
argChoices?: string[];
@@ -272,7 +285,8 @@ export interface OutputConfiguration {

export type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll';
export type HookEvent = 'preSubcommand' | 'preAction' | 'postAction';
export type OptionValueSource = 'default' | 'config' | 'env' | 'cli' | 'implied';
// The source is a string so author can define their own too.
export type OptionValueSource = LiteralUnion<'default' | 'config' | 'env' | 'cli' | 'implied', string> | undefined;

export type OptionValues = Record<string, any>;

@@ -281,6 +295,7 @@ export class Command {
processedArgs: any[];
readonly commands: readonly Command[];
readonly options: readonly Option[];
readonly registeredArguments: readonly Argument[];
parent: Command | null;

constructor(name?: string);
@@ -294,6 +309,10 @@ export class Command {
* You can optionally supply the flags and description to override the defaults.
*/
version(str: string, flags?: string, description?: string): this;
/**
* Get the program version.
*/
version(): string | undefined;

/**
* Define a command, implemented using an action handler.
@@ -497,51 +516,27 @@ export class Command {
action(fn: (...args: any[]) => void | Promise<void>): this;

/**
* Define option with `flags`, `description` and optional
* coercion `fn`.
* Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both.
*
* The `flags` string contains the short and/or long flags,
* separated by comma, a pipe or space. The following are all valid
* all will output this way when `--help` is used.
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. A required
* option-argument is indicated by `<>` and an optional option-argument by `[]`.
*
* "-p, --pepper"
* "-p|--pepper"
* "-p --pepper"
* See the README for more details, and see also addOption() and requiredOption().
*
* @example
* ```
* // simple boolean defaulting to false
* program.option('-p, --pepper', 'add pepper');
*
* --pepper
* program.pepper
* // => Boolean
*
* // simple boolean defaulting to true
* program.option('-C, --no-cheese', 'remove cheese');
*
* program.cheese
* // => true
*
* --no-cheese
* program.cheese
* // => false
*
* // required argument
* program.option('-C, --chdir <path>', 'change the working directory');
*
* --chdir /tmp
* program.chdir
* // => "/tmp"
*
* // optional argument
* program.option('-c, --cheese [type]', 'add cheese [marble]');
* ```js
* program
* .option('-p, --pepper', 'add pepper')
* .option('-p, --pizza-type <TYPE>', 'type of pizza') // required option-argument
* .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
* .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
* ```
*
* @returns `this` command for chaining
*/
option(flags: string, description?: string, defaultValue?: string | boolean | string[]): this;
option<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
option<T>(flags: string, description: string, parseArg: (value: string, previous: T) => T, defaultValue?: T): this;
/** @deprecated since v7, instead use choices or a custom function */
option(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean | string[]): this;

@@ -552,7 +547,7 @@ export class Command {
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
*/
requiredOption(flags: string, description?: string, defaultValue?: string | boolean | string[]): this;
requiredOption<T>(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this;
requiredOption<T>(flags: string, description: string, parseArg: (value: string, previous: T) => T, defaultValue?: T): this;
/** @deprecated since v7, instead use choices or a custom function */
requiredOption(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean | string[]): this;

@@ -819,7 +814,7 @@ export class Command {
/**
* Get the executable search directory.
*/
executableDir(): string;
executableDir(): string | null;

/**
* Output help information for this command.
27 changes: 24 additions & 3 deletions typings/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -30,12 +30,14 @@ expectType<string[]>(program.args);
expectType<any[]>(program.processedArgs);
expectType<readonly commander.Command[]>(program.commands);
expectType<readonly commander.Option[]>(program.options);
expectType<readonly commander.Argument[]>(program.registeredArguments);
expectType<commander.Command | null>(program.parent);

// version
expectType<commander.Command>(program.version('1.2.3'));
expectType<commander.Command>(program.version('1.2.3', '-r,--revision'));
expectType<commander.Command>(program.version('1.2.3', '-r,--revision', 'show revision information'));
expectType<string | undefined>(program.version());

// command (and CommandOptions)
expectType<commander.Command>(program.command('action'));
@@ -275,7 +277,7 @@ expectType<commander.Command>(program.nameFromFilename(__filename));

// executableDir
expectType<commander.Command>(program.executableDir(__dirname));
expectType<string>(program.executableDir());
expectType<string | null>(program.executableDir());

// outputHelp
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type, @typescript-eslint/no-confusing-void-expression
@@ -396,9 +398,25 @@ expectType<string>(helper.wrap('a b c', 50, 3));

expectType<string>(helper.formatHelp(helperCommand, helper));

// Option methods

// Option properties
const baseOption = new commander.Option('-f,--foo', 'foo description');
expectType<string>(baseOption.flags);
expectType<string>(baseOption.description);
expectType<boolean>(baseOption.required);
expectType<boolean>(baseOption.optional);
expectType<boolean>(baseOption.variadic);
expectType<boolean>(baseOption.mandatory);
expectType<string | undefined>(baseOption.short);
expectType<string | undefined>(baseOption.long);
expectType<boolean>(baseOption.negate);
expectType<any>(baseOption.defaultValue);
expectType<string | undefined>(baseOption.defaultValueDescription);
expectType<unknown>(baseOption.presetArg);
expectType<string | undefined>(baseOption.envVar);
expectType<boolean>(baseOption.hidden);
expectType<string[] | undefined>(baseOption.argChoices);

// Option methods

// default
expectType<commander.Option>(baseOption.default(3));
@@ -452,6 +470,9 @@ const baseArgument = new commander.Argument('<foo');
expectType<string>(baseArgument.description);
expectType<boolean>(baseArgument.required);
expectType<boolean>(baseArgument.variadic);
expectType<any>(baseArgument.defaultValue);
expectType<string | undefined>(baseArgument.defaultValueDescription);
expectType<string[] | undefined>(baseArgument.argChoices);

// Argument methods