diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 33c845339..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,67 +0,0 @@ -const javascriptSettings = { - files: ['*.js', '*.mjs'], - extends: [ - 'standard', - 'plugin:jest/recommended' - ], - rules: { - 'no-else-return': ['error', { allowElseIf: false }], - 'space-before-function-paren': ['error', 'never'], - // manual "semistandard" settings - semi: ['error', 'always'], - 'no-extra-semi': 'error' - } -}; - -const typescriptSettings = { - files: ['*.ts'], - parserOptions: { - project: './tsconfig.json' - }, - plugins: [ - '@typescript-eslint' - ], - extends: [ - 'standard-with-typescript' - ], - rules: { - 'no-else-return': ['error', { allowElseIf: false }], - 'space-before-function-paren': 'off', - '@typescript-eslint/space-before-function-paren': ['error', 'never'], - '@typescript-eslint/member-delimiter-style': [ - 'error', - { - multiline: { - delimiter: 'semi', - requireLast: true - }, - singleline: { - delimiter: 'semi', - requireLast: false - } - } - ], - // manual "semistandard" settings - semi: 'off', - '@typescript-eslint/semi': ['error', 'always'], - 'no-extra-semi': 'off', - '@typescript-eslint/no-extra-semi': ['error'] - } -}; - -module.exports = { - plugins: ['jest'], - parserOptions: { - ecmaVersion: 8 - }, - overrides: [ - javascriptSettings, - typescriptSettings, - { - files: ['*.mjs'], - parserOptions: { - sourceType: 'module' - } - } - ] -}; diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1323b94ff..b901673ae 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,9 +6,9 @@ and can be deleted. Please submit pull requests against the develop branch. -Follow the existing code style. Check the tests succeed, including lint. +Follow the existing code style. Check the tests succeed, including format and lint. npm run test - npm run lint + npm run check Don't update the CHANGELOG or command version number. That gets done by maintainers when preparing the release. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8ad6e7488..7998d5b15 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -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: "monthly" + - package-ecosystem: "github-actions" + target-branch: "develop" + directory: "/" + schedule: interval: "weekly" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a692b6749..a5c2042e3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,11 +25,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -40,7 +40,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -54,4 +54,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8d37d7f9c..2f6bce155 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,13 +12,13 @@ jobs: strategy: fail-fast: false matrix: - node-version: [12.x, 14.x, 16.x, 18.x] + node-version: [18.x, 20.x, 22.x] 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 + uses: actions/setup-node@v4 with: cache: 'npm' node-version: ${{ matrix.node-version }} @@ -27,5 +27,6 @@ jobs: run: npm ci - name: npm test run: npm test - - name: npm run lint - run: npm run lint + - name: npm run check:lint + # switch to full check when have run prettier on all files + run: npm run check:lint diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..78a5a22d2 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,10 @@ +# exclude everything, and opt-in to types we want to format +**.* +# add the filetypes we want to format +!**.js +!**.mjs +!**.cjs +!**.ts +!**.mts +!**.cts +!**.json diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 000000000..dc0a3dc83 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,12 @@ +const config = { + // plugins: ['prettier-plugin-jsdoc'], + singleQuote: true, + overrides: [ + { + files: ['tsconfig*.json'], + options: { parser: 'jsonc' }, + }, + ], +}; + +module.exports = config; diff --git a/CHANGELOG.md b/CHANGELOG.md index 9224605a2..efdba5257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,147 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. +## [12.1.0] (2024-05-18) + +### Added + +- auto-detect special node flags `node --eval` and `node --print` when call `.parse()` with no arguments ([#2164]) +- +### Changed + +- prefix require of Node.js core modules with `node:` ([#2170]) +- format source files with Prettier ([#2180]) +- switch from StandardJS to directly calling ESLint for linting ([#2153]) +- extend security support for previous major version of Commander ([#2150]) + +### Removed + +- removed unimplemented Option.fullDescription from TypeScript definition ([#2191]) + +## [12.0.0] (2024-02-03) + +### Added + +- `.addHelpOption()` as another way of configuring built-in help option ([#2006]) +- `.helpCommand()` for configuring built-in help command ([#2087]) + +### Fixed + +- *Breaking:* use non-zero exit code when spawned executable subcommand terminates due to a signal ([#2023]) +- *Breaking:* check `passThroughOptions` constraints when using `.addCommand` and throw if parent command does not have `.enablePositionalOptions()` enabled ([#1937]) + +### Changed + +- *Breaking:* Commander 12 requires Node.js v18 or higher ([#2027]) +- *Breaking:* throw an error if add an option with a flag which is already in use ([#2055]) +- *Breaking:* throw an error if add a command with name or alias which is already in use ([#2059]) +- *Breaking:* throw error when calling `.storeOptionsAsProperties()` after setting an option value ([#1928]) +- replace non-standard JSDoc of `@api private` with documented `@private` ([#1949]) +- `.addHelpCommand()` now takes a Command (passing string or boolean still works as before but deprecated) ([#2087]) +- refactor internal implementation of built-in help option ([#2006]) +- refactor internal implementation of built-in help command ([#2087]) + +### Deprecated + +- `.addHelpCommand()` passing string or boolean (use `.helpCommand()` or pass a Command) ([#2087]) + +### Removed + +- *Breaking:* removed default export of a global Command instance from CommonJS (use the named `program` export instead) ([#2017]) + +### Migration Tips + +**global program** + +If you are using the [deprecated](./docs/deprecated.md#default-import-of-global-command-object) default import of the global Command object, you need to switch to using a named import (or create a new `Command`). + +```js +// const program = require('commander'); +const { program } = require('commander'); +``` + +**option and command clashes** + +A couple of configuration problems now throw an error, which will pick up issues in existing programs: + +- adding an option which uses the same flag as a previous option +- adding a command which uses the same name or alias as a previous command + +## [12.0.0-1] (2024-01-20) + +(Released in 12.0.0) + +## [12.0.0-0] (2023-11-11) + +(Released in 12.0.0) + +## [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 + +- help command works when help option is disabled ([#1864]) + +### Changed + +- leading and trailing spaces are now ignored by the .arguments() method ([#1874]) +- refine "types" exports for ESM to follow TypeScript guidelines ([#1886]) +- *Breaking:* Commander 11 requires Node.js v16 or higher + +## [10.0.1] (2023-04-15) + +### Added + +- improvements to documentation ([#1858], [#1859], [#1860]) + +### Fixed + +- remove unused `Option.optionFlags` property from TypeScript definition ([#1844]) + +### Changed + +- assume boolean option intended if caller passes string instead of hash to `.implies()` ([#1854]) + +## [10.0.0] (2023-01-13) + +### Added + +- wrap command description in help ([#1804]) + +### Changed + +- *Breaking:* Commander 10 requires Node.js v14 or higher + +## [9.5.0] (2023-01-07) + +### Added + +- `.getOptionValueSourceWithGlobals()` ([#1832]) +- `showGlobalOptions` for `.configureHelp{}` and `Help` ([#1828]) + ## [9.4.1] (2022-09-30) ### Fixed @@ -93,8 +234,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - use command name as prefix for subcommand stand-alone executable name (with fallback to script name for backwards compatibility) ([#1571]) - allow absolute path with `executableFile` ([#1571]) - removed restriction that nested subcommands must specify `executableFile` ([#1571]) -- TypeScript: allow passing readonly string array to `.choices()` [(#1667)] -- TypeScript: allow passing readonly string array to `.parse()`, `.parseAsync()`, `.aliases()` [(#1669)] +- TypeScript: allow passing readonly string array to `.choices()` ([#1667]) +- TypeScript: allow passing readonly string array to `.parse()`, `.parseAsync()`, `.aliases()` ([#1669]) ### Fixed @@ -104,7 +245,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - *Breaking:* removed internal fallback to `require.main.filename` when script not known from arguments passed to `.parse()` (can supply details using `.name()`, and `.executableDir()` or `executableFile`) ([#1571]) - ## [9.0.0-1] (2022-01-15) (Released in 9.0.0) @@ -286,8 +426,8 @@ program.showHelpAfterError(); ### Changed - *Breaking:* options are stored safely by default, not as properties on the command ([#1409]) - - this especially affects accessing options on program, use `program.opts()` - - revert behaviour with `.storeOptionsAsProperties()` + - this especially affects accessing options on program, use `program.opts()` + - revert behaviour with `.storeOptionsAsProperties()` - *Breaking:* action handlers are passed options and command separately ([#1409]) - deprecated callback parameter to `.help()` and `.outputHelp()` (removed from README) ([#1296]) - *Breaking:* errors now displayed using `process.stderr.write()` instead of `console.error()` @@ -306,9 +446,9 @@ program.showHelpAfterError(); ### Deleted - *Breaking:* `.passCommandToAction()` ([#1409]) - - no longer needed as action handler is passed options and command + - no longer needed as action handler is passed options and command - *Breaking:* "extra arguments" parameter to action handler ([#1409]) - - if being used to detect excess arguments, there is now an error available by setting `.allowExcessArguments(false)` + - if being used to detect excess arguments, there is now an error available by setting `.allowExcessArguments(false)` ### Migration Tips @@ -393,7 +533,7 @@ program ### Fixed -- some tests failed if directory path included a space ([1390]) +- some tests failed if directory path included a space ([#1390]) ## [6.2.0] (2020-10-25) @@ -1065,6 +1205,7 @@ program [#1490]: https://github.com/tj/commander.js/pull/1490 [#1497]: https://github.com/tj/commander.js/pull/1497 [#1500]: https://github.com/tj/commander.js/pull/1500 +[#1502]: https://github.com/tj/commander.js/pull/1502 [#1508]: https://github.com/tj/commander.js/pull/1508 [#1513]: https://github.com/tj/commander.js/pull/1513 [#1514]: https://github.com/tj/commander.js/pull/1514 @@ -1109,6 +1250,41 @@ program [#1767]: https://github.com/tj/commander.js/pull/1767 [#1794]: https://github.com/tj/commander.js/pull/1794 [#1795]: https://github.com/tj/commander.js/pull/1795 +[#1804]: https://github.com/tj/commander.js/pull/1804 +[#1832]: https://github.com/tj/commander.js/pull/1832 +[#1828]: https://github.com/tj/commander.js/pull/1828 +[#1844]: https://github.com/tj/commander.js/pull/1844 +[#1854]: https://github.com/tj/commander.js/pull/1854 +[#1858]: https://github.com/tj/commander.js/pull/1858 +[#1859]: https://github.com/tj/commander.js/pull/1859 +[#1860]: https://github.com/tj/commander.js/pull/1860 +[#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 +[#1928]: https://github.com/tj/commander.js/pull/1928 +[#1930]: https://github.com/tj/commander.js/pull/1930 +[#1937]: https://github.com/tj/commander.js/pull/1937 +[#1949]: https://github.com/tj/commander.js/pull/1949 +[#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 +[#2006]: https://github.com/tj/commander.js/pull/2006 +[#2010]: https://github.com/tj/commander.js/pull/2010 +[#2017]: https://github.com/tj/commander.js/pull/2017 +[#2019]: https://github.com/tj/commander.js/pull/2019 +[#2023]: https://github.com/tj/commander.js/pull/2023 +[#2027]: https://github.com/tj/commander.js/pull/2027 +[#2055]: https://github.com/tj/commander.js/pull/2055 +[#2059]: https://github.com/tj/commander.js/pull/2059 +[#2087]: https://github.com/tj/commander.js/pull/2087 +[#2150]: https://github.com/tj/commander.js/pull/2150 +[#2153]: https://github.com/tj/commander.js/pull/2153 +[#2164]: https://github.com/tj/commander.js/pull/2164 +[#2170]: https://github.com/tj/commander.js/pull/2170 +[#2180]: https://github.com/tj/commander.js/pull/2180 +[#2191]: https://github.com/tj/commander.js/pull/2191 [#1]: https://github.com/tj/commander.js/issues/1 @@ -1149,6 +1325,7 @@ program [#1248]: https://github.com/tj/commander.js/pull/1248 +[#933]: https://github.com/tj/commander.js/pull/933 [#1027]: https://github.com/tj/commander.js/pull/1027 [#1035]: https://github.com/tj/commander.js/pull/1035 [#1040]: https://github.com/tj/commander.js/pull/1040 @@ -1187,6 +1364,15 @@ program [#1028]: https://github.com/tj/commander.js/pull/1028 [Unreleased]: https://github.com/tj/commander.js/compare/master...develop +[12.0.0]: https://github.com/tj/commander.js/compare/v12.0.0...v12.1.0 +[12.0.0]: https://github.com/tj/commander.js/compare/v11.1.0...v12.0.0 +[12.0.0-1]: https://github.com/tj/commander.js/compare/v12.0.0-0...v12.0.0-1 +[12.0.0-0]: https://github.com/tj/commander.js/compare/v11.1.0...v12.0.0-0 +[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 +[9.5.0]: https://github.com/tj/commander.js/compare/v9.4.1...v9.5.0 [9.4.1]: https://github.com/tj/commander.js/compare/v9.4.0...v9.4.1 [9.4.0]: https://github.com/tj/commander.js/compare/v9.3.0...v9.4.0 [9.3.0]: https://github.com/tj/commander.js/compare/v9.2.0...v9.3.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f11d40397..e00e59ac1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,10 +14,10 @@ or after six months otherwise. Pull Requests will be considered. Please submit pull requests against the develop branch. -Follow the existing code style. Check the tests succeed, including lint. +Follow the existing code style. Check the tests succeed, including format and lint. - `npm run test` -- `npm run lint` +- `npm run check` Don't update the CHANGELOG or command version number. That gets done by maintainers when preparing the release. diff --git a/Readme.md b/Readme.md index 39c248b04..e4e365787 100644 --- a/Readme.md +++ b/Readme.md @@ -37,7 +37,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [.usage](#usage) - [.description and .summary](#description-and-summary) - [.helpOption(flags, description)](#helpoptionflags-description) - - [.addHelpCommand()](#addhelpcommand) + - [.helpCommand()](#helpcommand) - [More configuration](#more-configuration-2) - [Custom event listeners](#custom-event-listeners) - [Bits and pieces](#bits-and-pieces) @@ -48,6 +48,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [createCommand()](#createcommand) - [Node options such as `--harmony`](#node-options-such-as---harmony) - [Debugging stand-alone executable subcommands](#debugging-stand-alone-executable-subcommands) + - [npm run-script](#npm-run-script) - [Display error](#display-error) - [Override exit and output handling](#override-exit-and-output-handling) - [Additional documentation](#additional-documentation) @@ -190,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: @@ -233,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. @@ -326,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 @@ -378,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 @@ -543,6 +544,10 @@ Configuration options can be passed with the call to `.command()` and `.addComma remove the command from the generated help output. Specifying `isDefault: true` will run the subcommand if no other 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 @@ -673,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 @@ -728,6 +733,8 @@ The supported events are: | `preAction`, `postAction` | before/after action handler for this command and its nested subcommands | `(thisCommand, actionCommand)` | | `preSubcommand` | before parsing direct subcommand | `(thisCommand, subcommand)` | +For an overview of the life cycle events see [parsing life cycle and hooks](./docs/parsing-and-hooks.md). + ## Automated help The help information is auto-generated based on the information commander already knows about your program. The default @@ -759,6 +766,8 @@ 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.) + ### Custom help You can add extra text to be displayed along with the built-in help. @@ -845,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'); @@ -888,23 +897,27 @@ 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 .helpOption('-e, --HELP', 'read more information'); ``` -### .addHelpCommand() +(Or use `.addHelpOption()` to add an option you construct yourself.) + +### .helpCommand() -A help command is added by default if your command has subcommands. You can explicitly turn on or off the implicit help command with `.addHelpCommand()` and `.addHelpCommand(false)`. +A help command is added by default if your command has subcommands. You can explicitly turn on or off the implicit help command with `.helpCommand(true)` and `.helpCommand(false)`. You can both turn on and customise the help command by supplying the name and description: ```js -program.addHelpCommand('assist [command]', 'show assistance'); +program.helpCommand('assist [command]', 'show assistance'); ``` +(Or use `.addHelpCommand()` to add a command you construct yourself.) + ### More configuration The built-in help is formatted using the Help class. @@ -915,8 +928,9 @@ The data properties are: - `helpWidth`: specify the wrap width, useful for unit tests - `sortSubcommands`: sort the subcommands alphabetically - `sortOptions`: sort the options alphabetically +- `showGlobalOptions`: show a section with the global options from the parent command(s) -There are methods getting the visible lists of arguments, options, and subcommands. There are methods for formatting the items in the lists, with each item having a _term_ and _description_. Take a look at `.formatHelp()` to see how they are used. +You can override any method on the [Help](./lib/help.js) class. There are methods getting the visible lists of arguments, options, and subcommands. There are methods for formatting the items in the lists, with each item having a _term_ and _description_. Take a look at `.formatHelp()` to see how they are used. Example file: [configure-help.js](./examples/configure-help.js) @@ -941,27 +955,31 @@ program.on('option:verbose', function () { ### .parse() and .parseAsync() -The first argument to `.parse` is the array of strings to parse. You may omit the parameter to implicitly use `process.argv`. +Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode! -If the arguments follow different conventions than node you can pass a `from` option in the second parameter: +Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`: -- 'node': default, `argv[0]` is the application and `argv[1]` is the script being run, with user parameters after that -- 'electron': `argv[1]` varies depending on whether the electron application is packaged -- 'user': all of the arguments from the user +- `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that +- `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged +- `'user'`: just user arguments For example: ```js -program.parse(process.argv); // Explicit, node conventions -program.parse(); // Implicit, and auto-detect electron -program.parse(['-f', 'filename'], { from: 'user' }); +program.parse(); // parse process.argv and auto-detect electron and special node flags +program.parse(process.argv); // assume argv[0] is app and argv[1] is script +program.parse(['--port', '80'], { from: 'user' }); // just user supplied arguments, nothing special about argv[0] ``` +Use parseAsync instead of parse if any of your action handlers are async. + +If you want to parse multiple times, create a new program each time. Calling parse does not clear out any previous state. + ### Parsing Configuration 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) @@ -973,8 +991,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. @@ -987,15 +1005,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 @@ -1011,7 +1029,15 @@ program ### TypeScript -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. +extra-typings: There is an optional project to infer extra type information from the option and argument definitions. +This adds strong typing to the options returned by `.opts()` and the parameters to `.action()`. +See [commander-js/extra-typings](https://github.com/commander-js/extra-typings) for more. + +``` +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. ```sh node -r ts-node/register pm.ts @@ -1041,11 +1067,22 @@ 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 + `--` 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: + +```console +npm run-script [-- ] +``` + ### Display error This routine is available to invoke the Commander error handling for your own error conditions. (See also the next section about exit handling.) @@ -1060,11 +1097,12 @@ 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 -is not affected by the override which is called after the display. +The override callback is passed a `CommanderError` with properties `exitCode` number, `code` string, and `message`. +Commander expects the callback to terminate the normal program flow, and will call `process.exit` if the callback returns. +The normal display of error messages or version or help is not affected by the override which is called after the display. ```js program.exitOverride(); @@ -1076,7 +1114,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) @@ -1102,11 +1140,12 @@ 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 -The current version of Commander is fully supported on Long Term Support versions of Node.js, and requires at least v12.20.0. +The current version of Commander is fully supported on Long Term Support versions of Node.js, and requires at least v18. (For older versions of Node.js, use an older version of Commander.) The main forum for free and community support is the project [Issues](https://github.com/tj/commander.js/issues) on GitHub. diff --git a/Readme_zh-CN.md b/Readme_zh-CN.md index c50626f55..fc3bd0c41 100644 --- a/Readme_zh-CN.md +++ b/Readme_zh-CN.md @@ -1059,7 +1059,7 @@ program ## 支持 -当前版本的 Commander 在 LTS 版本的 Node.js 上完全支持。并且至少需要 v12.20.0。 +当前版本的 Commander 在 LTS 版本的 Node.js 上完全支持。并且至少需要 v18。 (使用更低版本 Node.js 的用户建议安装更低版本的 Commander) 社区支持请访问项目的 [Issues](https://github.com/tj/commander.js/issues)。 diff --git a/SECURITY.md b/SECURITY.md index 3d73b7d3e..602dd968a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,12 +2,7 @@ ## Supported Versions -Old versions receive security updates for six months. - -| Version | Supported | -| ------- | ------------------------------------------ | -| 8.x | :white_check_mark: support ends 2022-07-31 | -| < 8 | :x: | +Security updates are supported for the current version and the previous major version. Pull Requests for security issues will be considered for older versions back to 2.x. diff --git a/docs/deprecated.md b/docs/deprecated.md index 8f7fa5f67..7dc5bad23 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -4,18 +4,22 @@ These features are deprecated, which means they may go away in a future major ve They are currently still available for backwards compatibility, but should not be used in new code. - [Deprecated](#deprecated) - - [RegExp .option() parameter](#regexp-option-parameter) - - [noHelp](#nohelp) - - [Default import of global Command object](#default-import-of-global-command-object) - - [Callback to .help() and .outputHelp()](#callback-to-help-and-outputhelp) - - [.on('--help')](#on--help) - - [.on('command:*')](#oncommand) - - [.command('*')](#command) - - [cmd.description(cmdDescription, argDescriptions)](#cmddescriptioncmddescription-argdescriptions) - - [InvalidOptionArgumentError](#invalidoptionargumenterror) - - [Short option flag longer than a single character](#short-option-flag-longer-than-a-single-character) - -## RegExp .option() parameter + - [RegExp .option() parameter](#regexp-option-parameter) + - [noHelp](#nohelp) + - [Callback to .help() and .outputHelp()](#callback-to-help-and-outputhelp) + - [.on('--help')](#on--help) + - [.on('command:\*')](#oncommand) + - [.command('\*')](#command) + - [cmd.description(cmdDescription, argDescriptions)](#cmddescriptioncmddescription-argdescriptions) + - [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) + - [.addHelpCommand(string|boolean|undefined)](#addhelpcommandstringbooleanundefined) + - [Removed](#removed) + - [Default import of global Command object](#default-import-of-global-command-object) + +### RegExp .option() parameter The `.option()` method allowed a RegExp as the third parameter to restrict what values were accepted. @@ -27,38 +31,17 @@ Removed from README in Commander v3. Deprecated from Commander v7. The newer functionality is the Option `.choices()` method, or using a custom option processing function. -## noHelp +### noHelp 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. -## Default import of global Command object - -The default import was a global Command object. - -```js -const program = require('commander'); -``` - -The global Command object is exported as `program` from Commander v5, or import the Command object. - -```js -const { program } = require('commander'); -// or -const { Command } = require('commander'); -const program = new Command() -``` - -- Removed from README in Commander v5. -- Deprecated from Commander v7. -- Removed from TypeScript declarations in Commander v8. - -## Callback to .help() and .outputHelp() +### Callback to .help() and .outputHelp() These routines allowed a callback parameter to process the built-in help before display. @@ -76,7 +59,7 @@ console.error(colors.red(program.helpInformation())); Deprecated from Commander v7. -## .on('--help') +### .on('--help') This was the way to add custom help after the built-in help. From Commander v3.0.0 this used the custom long help option flags, if changed. @@ -101,7 +84,7 @@ Examples: Deprecated from Commander v7. -## .on('command:*') +### .on('command:*') This was emitted when the command argument did not match a known subcommand (as part of the implementation of `.command('*')`). @@ -112,7 +95,7 @@ or for custom behaviour catch the `commander.unknownCommand` error. Deprecated from Commander v8.3. -## .command('*') +### .command('*') This was used to add a default command to the program. @@ -132,7 +115,7 @@ program Removed from README in Commander v5. Deprecated from Commander v8.3. -## cmd.description(cmdDescription, argDescriptions) +### cmd.description(cmdDescription, argDescriptions) This was used to add command argument descriptions for the help. @@ -155,7 +138,7 @@ program Deprecated from Commander v8. -## InvalidOptionArgumentError +### InvalidOptionArgumentError This was used for throwing an error from custom option processing, for a nice error message. @@ -185,8 +168,85 @@ function myParseInt(value, dummyPrevious) { Deprecated from Commander v8. -## Short option flag longer than a single character +### Short option flag longer than a single character Short option flags like `-ws` were never supported, but the old README did not make this clear. The README now states that short options are a single character. README updated in Commander v3. Deprecated from Commander v9. + +### Import from `commander/esm.mjs` + +The first support for named imports required an explicit entry file. + +```js +import { Command } from 'commander/esm.mjs'; +``` + +This is no longer required, just import directly from the module. + +```js +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. + +### .addHelpCommand(string|boolean|undefined) + +This was originally used with a variety of parameters, but not by passing a Command object despite the "add" name. + +```js +program.addHelpCommand('assist [command]'); +program.addHelpCommand('assist', 'show assistance'); +program.addHelpCommand(false); + +``` + +In new code you configure the help command with `.helpCommand()`. Or use `.addHelpCommand()` which now takes a Command object, like `.addCommand()`. + +```js +program.helpCommand('assist [command]'); +program.helpCommand('assist', 'show assistance'); +program.helpCommand(false); + +program.addHelpCommand(new Command('assist').argument('[command]').description('show assistance')); + +``` +## Removed + +### Default import of global Command object + +The default import was a global Command object. + +```js +const program = require('commander'); +``` + +The global Command object is exported as `program` from Commander v5, or import the Command object. + +```js +const { program } = require('commander'); +// or +const { Command } = require('commander'); +const program = new Command() +``` + +- Removed from README in Commander v5. +- Deprecated from Commander v7. +- Removed from TypeScript declarations in Commander v8. +- Removed from CommonJS in Commander v12. Deprecated and gone! diff --git a/docs/options-taking-varying-arguments.md b/docs/options-in-depth.md similarity index 86% rename from docs/options-taking-varying-arguments.md rename to docs/options-in-depth.md index 5f661bb33..1ea3fce21 100644 --- a/docs/options-taking-varying-arguments.md +++ b/docs/options-in-depth.md @@ -1,15 +1,18 @@ -# Options taking varying numbers of option-arguments + +# 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: diff --git a/docs/parsing-and-hooks.md b/docs/parsing-and-hooks.md new file mode 100644 index 000000000..122cad6f7 --- /dev/null +++ b/docs/parsing-and-hooks.md @@ -0,0 +1,23 @@ +# Parsing life cycle and hooks + +The processing starts with an array of args. Each command processes and removes the options it understands, and passes the remaining args to the next subcommand. +The final command calls the action handler. + +Starting with top-level command (program): + +- parse options: parse recognised options (for this command) and remove from args +- parse env: look for environment variables (for this command) +- process implied: set any implied option values (for this command) +- if the first arg is a subcommand + - call `preSubcommand` hooks + - pass remaining arguments to subcommand, and process same way + +Once reach final (leaf) command: + +- check for missing mandatory options +- check for conflicting options +- check for unknown options +- process remaining args as command-arguments +- call `preAction` hooks +- call action handler +- call `postAction` hooks diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 000000000..4105ecb8d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,79 @@ +const globals = require('globals'); +const esLintjs = require('@eslint/js'); +const jest = require('eslint-plugin-jest'); +const tseslint = require('typescript-eslint'); +const prettier = require('eslint-config-prettier'); +// const jsdoc = require('eslint-plugin-jsdoc'); + +// Only run tseslint on the files that we have included for TypeScript. +const tsconfigTsFiles = ['**/*.{ts,mts}']; // match "include" in tsconfig.ts.json; +const tsconfigJsFiles = ['*.{js,mjs}', 'lib/**/*.{js,mjs}']; // match "include" in tsconfig.js.json + +// Using tseslint.config adds some type safety and `extends` to simplify customising config array. +module.exports = tseslint.config( + // Add recommended rules. + esLintjs.configs.recommended, + // jsdoc.configs['flat/recommended'], + jest.configs['flat/recommended'], + // tseslint with different setup for js/ts + { + files: tsconfigJsFiles, + extends: [...tseslint.configs.recommended], + languageOptions: { + parserOptions: { project: './tsconfig.js.json' }, + }, + rules: { + '@typescript-eslint/no-var-requires': 'off', // tseslint does not autodetect commonjs context + }, + }, + { + files: tsconfigTsFiles, + extends: [...tseslint.configs.recommended], + languageOptions: { + parserOptions: { project: './tsconfig.ts.json' }, + }, + }, + prettier, // Do Prettier last so it can override previous configs. + + // Customise rules. + { + files: ['**/*.{js,mjs,cjs}', '**/*.{ts,mts,cts}'], + rules: { + 'no-else-return': ['error', { allowElseIf: false }], + + // 'jsdoc/tag-lines': 'off', + // 'jsdoc/require-jsdoc': 'off', + // 'jsdoc/require-param-description': 'off', + // 'jsdoc/require-returns-description': 'off', + // 'jsdoc/require-param': ['warn', { exemptedBy: ['private'] }], + // // Currently can not configure checking to allow return/returns (and don't want wide change mixed with more interesting fixes), + // // and can not set options.jsdoc.mode yet to allow @remarks in typescript. + // 'jsdoc/check-tag-names': 0, + }, + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + { + files: ['**/*.test.{js,mjs,cjs}'], + rules: { + 'no-unused-vars': 'off', // lots in tests, minimise churn for now + }, + }, + { + files: [...tsconfigTsFiles, ...tsconfigJsFiles], + rules: { + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-expect-error': 'allow-with-description', + 'ts-ignore': 'allow-with-description', + 'ts-nocheck': true, + 'ts-check': true, + }, + ], + }, + }, +); diff --git a/esm.mjs b/esm.mjs index e7190a1b8..796ec3a58 100644 --- a/esm.mjs +++ b/esm.mjs @@ -12,5 +12,5 @@ export const { Command, Argument, Option, - Help + Help, } = commander; diff --git a/examples/action-this.js b/examples/action-this.js index 1ff286c4e..d6ac722ee 100644 --- a/examples/action-this.js +++ b/examples/action-this.js @@ -2,15 +2,14 @@ // This example is used as an example in the README for the action handler. -// const { Command } = require('commander'); // (normal include) -const { Command } = require('../'); // include commander in git clone of commander repo +const { Command } = require('commander'); const program = new Command(); program .command('serve') .argument('