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: v6.2.1
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: v7.0.0
Choose a head ref

Commits on Sep 9, 2020

  1. Expand help customisation with .addHelpText (#1296)

    * Add new help events
    
    * Add context for new help events
    
    * Rename and use help context
    
    * Suppress help output
    
    * Slight tidy of legacy callback handling
    
    * Shift help contextOptions up to public API
    
    * Fix some spelling errors
    
    * Start adding tests. Fix groupHelp.
    
    * Test help event order
    
    * Add tests on context
    
    * Add missing semicolon
    
    * Add context.error tests
    
    * First cut at README adding events and removing callbacks
    
    * Add typings and parameter descriptions
    
    * Change from --help to postHelp event
    
    * Update and add custom help examples
    
    * Make help listener example more realistic
    
    * Update example
    
    * Call the old callback, deprecated
    
    * First cut at .addHelp()
    
    * Change name to addHelpText
    
    * First round of tests for addHelpText
    
    * Simplify help event context, remove log
    
    * Assign write directly
    
    * Add end-to-end and context checks for addHelpText
    
    * Put back write wrapper to fix unit test
    
    * Fix write mocks for help-as-error output
    
    * Ignore falsy values for AddHelpText
    
    * Remove the help override from addHelpText as not good fit
    
    * Convert example to addHelpText
    
    * Update README
    
    * Add info about new .help param
    
    * Remove excess space
    
    * Update more examples
    
    * Remove references to override
    
    * Update docs with .addHelpText
    shadowspawn authored Sep 9, 2020
    Copy the full SHA
    d26a26d View commit details
  2. Copy the full SHA
    8afc0ba View commit details

Commits on Sep 14, 2020

  1. Enhance Option class to allow hiding help, specifying choices, and ch…

    …ange how default value displayed in help (#1331)
    
    * Add .addOption and use as bottleneck. Make all Option properties private.
    
    * Desription for object is optional
    
    * Add support for hidden options
    
    * Try setFoo for booleans so less ambiguous what no parameter means
    
    * Renamed method
    
    * Restore Option property names to reduce churn
    
    * Try more fluent names for non-property methods
    
    * Avoid renaming existing members of Option
    
    * Add default description for help
    
    * Fix return JSDoc
    
    * First cut at choices
    
    * Throw CommanderError for easier detection
    
    * Add catch for tidy coercion failure handing
    
    * Add tests for Option.choices
    
    * Rename custom option processing property
    
    * Add run script for TypeScript checkJS
    
    * Add tests for chaining routines
    
    * More consistent name for custom option arg processing
    
    * Add choices to help
    
    * .default() now expects parameter
    
    * Fixed return type
    
    * Separate out argumentRejected for possible reuse
    
    * Add back support for RegExp which accidentally dropped.
    
    * Add test for obsolete regexp
    
    * Add TypeScript definitions for new Option properties
    
    * Switch from obsolete to deprecated, clearer meaning
    
    * Fix left-over edit
    
    * Add comment
    
    * Simplify the comments
    
    * Add README and example file
    
    * Remove example covered elsewhere
    
    * Restore example, leave change for a separate PR
    
    * Fix example output to match changed code
    
    * Add language to code blocks
    
    * Rename getFullDescription, not using get much
    
    * Add chaining test for addHelpText
    
    * Describe as legacy rather than deprecated in comments, add @deprecated for editor feedback to discourage use
    
    * Do not have to have both should and long flags these days
    
    * Eliminate duplicate code using internal knowledge
    
    * Rename parseArgWith to argParser
    
    * Improve JSDoc for help
    
    * Make code and declarations consistent to pass tsc checks
    
    * Match up write signature to fix linting error
    
    * Restore "deprecated", it is the right word
    shadowspawn authored Sep 14, 2020
    Copy the full SHA
    a05a8bc View commit details
  2. Copy the full SHA
    091224d View commit details
  3. Bump version number

    shadowspawn committed Sep 14, 2020
    Copy the full SHA
    9abb793 View commit details

Commits on Sep 26, 2020

  1. Deprecated functionality (#1349)

    * Add documentation on deprecated routines
    
    * Tidy up deprecated and link from README.
    shadowspawn authored Sep 26, 2020
    Copy the full SHA
    9f5aabc View commit details
  2. Copy the full SHA
    e5512a4 View commit details
  3. Copy the full SHA
    b4fbe67 View commit details

Commits on Oct 18, 2020

  1. Copy the full SHA
    b0c5884 View commit details

Commits on Oct 23, 2020

  1. Refactor help internals into separate interface/class (#1365)

    * Start filling out HelpUtils to try pattern
    
    * Shift the largestFoo routines into helper
    
    * Update generation of Commands section of help
    
    * Rework helpInformation in consistent new style
    
    * Remove unused routines
    
    * Make columns part of HelpUtils
    
    * Offer a light weight override to HelpUtils
    
    * Tweak comment
    
    * Add chain test for helpUtilOverrides
    
    * Add itemIndent as another proof of concept of allowing overrides
    
    * Avoid Utils contraction
    
    * Update comments
    
    * Switch columns from function to data property
    
    * Remove itemIndent(), not useful enough alone or as a pattern for now
    
    * Make _helpToolsOverrides inherited
    
    * Improve naming for termWidth
    
    * Move usage into HelpTools
    
    * Add term and description routines to HelpTools so symmetrical pattern
    
    * Name the magic numbers
    
    * More consistent naming
    
    * Remove reference to removed routine
    
    * Move help formatting into HelpTools
    
    * Fix typescript-checkJS errors
    
    * Simpler naming
    
    * Slightly simplify code
    
    * Add getter/setter to assist overrides
    
    * Add sort overrides
    
    * First cut at  TypeScript definitions for Help, no TSDoc yet
    
    * Replace pad and low level indents with modern calls
    
    * Rename and rework type for HelpConfiguration
    
    * Combine optionalWrap and wrap
    
    * Add createHelp to TypeScript definition
    
    * Add test-all script
    
    * More carefully make concrete help option for displaying
    
    * Fix test with valid parameters for custom help
    
    * Start adding Help tests
    
    * Add largestCommandTermLength tests
    
    * Add more Help tests
    
    * Add commandUsage tests
    
    * Add test for commandDescription
    
    * Add missing Command properties, and default description to empty string
    
    * Add tests for optionDescription
    
    * Add padWidth tests
    
    * Add sort tests
    
    * Add columns and wrap tests
    
    * Add test for legacy commandTerm behaviour
    
    * Add TypeScript usage tests for Help
    
    * Refactor Help tests into separate files
    
    * Add tests for createHelp and configureHelp
    
    * Add JSDoc for Help.
    Rename methods.
    Delete @api public as default.
    
    * Add TSDoc for Help
    
    * Test special caes of implicit help flags
    
    * Clarify method naming
    
    * Shift the Usage prefix into formatHelp
    
    * Add Help class mention and reorder help
    
    * Add simple configure-help example
    
    * Add example file to README
    
    * Rename to sortSubcommands to match other naming
    
    * Do not weaken configuration type, user can extend as required
    
    * Do not cache implicit help command calculation so safer (no need, not being thrashed)
    
    * Add JSDoc for configureHelp
    
    * Add TSDoc
    
    * Add missing TSDoc
    
    * Switch option sort to use attributeName, with negative after positive
    
    * No need for string template literal
    
    * No need for string template literal
    shadowspawn authored Oct 23, 2020
    Copy the full SHA
    19ae912 View commit details
  2. Copy the full SHA
    d90ef12 View commit details
  3. Fix lint issues

    shadowspawn committed Oct 23, 2020
    Copy the full SHA
    800955e View commit details

Commits on Oct 25, 2020

  1. Copy the full SHA
    5911197 View commit details
  2. Copy the full SHA
    0703a4d View commit details

Commits on Oct 26, 2020

  1. Copy the full SHA
    033e6d9 View commit details
  2. Feature createOption (#1380)

    * Add createOption (like createCommand and createHelp)
    
    * Add tests
    
    * Add TypeScript
    
    * option description is optional
    shadowspawn authored Oct 26, 2020
    Copy the full SHA
    b1d984b View commit details
  3. Increase test coverage (#1381)

    * Test passing parameter to allowUnknownOptions
    
    * Test negated option in help
    shadowspawn authored Oct 26, 2020
    Copy the full SHA
    7a0baab View commit details
  4. Copy the full SHA
    df5d679 View commit details
  5. Copy the full SHA
    5b98fc8 View commit details

Commits on Nov 10, 2020

  1. Copy the full SHA
    2f7aa33 View commit details

Commits on Nov 17, 2020

  1. configure output (#1387)

    * Add output configuration and use for version and errors
    
    * Tests passing using write/writeErr
    
    * Suppress test output on stderr using spyon
    
    * Accurately set help columns for stdout/stderr
    
    * Remove bogus file
    
    * Tidy comments
    
    * Only using single argument to write, simplify declaration to match
    
    * Add tests for configureOutput write and writeError
    
    * Add tests for configureOutput getColumns and getErrorColumns
    
    * Add error case too
    
    * Use configureOutput instead of jest.spyon for some tests
    
    * Add configureOutput to chain tests
    
    * Add set/get test for configureOutput
    
    * Rename routines with symmetrical out/err
    
    * Add outputError simple code
    
    * Add tests for outputError
    
    * Add JSDoc
    
    * Tweak wording
    
    * First cut at TypeScript
    
    * Add TypeScript sanity check for configureOutput
    
    * Add example for configureOutput
    
    * Add configureOutput to README
    
    * Make example in README a little clearer
    shadowspawn authored Nov 17, 2020
    Copy the full SHA
    ed7f13e View commit details

Commits on Nov 20, 2020

  1. Copy the full SHA
    4555881 View commit details

Commits on Nov 21, 2020

  1. Copy the full SHA
    eac4787 View commit details
  2. Copy the full SHA
    e3ad76d View commit details
  3. Copy the full SHA
    e1a6cf4 View commit details

Commits on Nov 25, 2020

  1. Support custom error messages for option argument coercion failures (#…

    …1392)
    
    * Make commander.optionArgumentRejected reusable
    
    * Add InvalidOptionArgumentError to simplify usage
    
    * Add TypeScript for InvalidOptionArgumentError
    
    * Add test for InvalidOptionArgumentError  from custom option processing
    
    * Add InvalidOptionArgumentError to example and README
    shadowspawn authored Nov 25, 2020
    Copy the full SHA
    689d7a0 View commit details
  2. WYSIWYG sort (#1400)

    * Use simpler more WYSIWYG sort of options
    
    * Do a WYSIWYG style sort
    shadowspawn authored Nov 25, 2020
    Copy the full SHA
    9b087c8 View commit details

Commits on Dec 1, 2020

  1. Improve backwards compatibility for command events (#1403)

    * Improve backwards compatibility for command events
    
    * Implement tests for legacy command event
    shadowspawn authored Dec 1, 2020
    Copy the full SHA
    bd538aa View commit details

Commits on Dec 4, 2020

  1. Add .allowExcessArguments() and error message (#1407)

    * Fix test file name
    
    * Add initial support for allowExcessArguments
    
    * Add TypeScript
    
    * Add tests
    
    * Refine error for program
    
    * Remove exception for legacy asterisk command, can disable if needed
    
    * Use defaulr parameters and simplify code
    shadowspawn authored Dec 4, 2020
    Copy the full SHA
    09f277a View commit details

Commits on Dec 5, 2020

  1. Copy the full SHA
    f2f21f1 View commit details

Commits on Dec 6, 2020

  1. Copy the full SHA
    1a3db74 View commit details
  2. Update dependencies in 7.x (#1412)

    * Simplicy RegExp usage per lint
    
    * Update dependencies
    
    * Revert node change, do that elsewhere
    shadowspawn authored Dec 6, 2020
    Copy the full SHA
    469acc9 View commit details

Commits on Dec 9, 2020

  1. Copy the full SHA
    1db83b6 View commit details

Commits on Dec 14, 2020

  1. Change to safe storage of options by default, and change action param…

    …eters (#1409)
    
    * First cut at storing options safely by default
    
    * Pass command as options for legacy behaviour
    
    * Update examples for new patterns
    
    * Rework README on using storeOptionsAsProperties
    
    * Tweak wording
    
    * Updat example for options-common
    
    * Update options-defaults
    
    * Update options-negatable example
    
    * Update options-boolean-or-value example
    
    * Update options-custom-processing example
    
    * Turn on error for excess arguments, and update tests accordingly
    
    * Add first cut at migration tips
    
    * Second cut at migration tips
    
    * Migration wording tweaks
    
    * Reworking README for new patterns
    
    * Goodbye to option properties on Command
    
    * Add migration tip for excess arguments
    
    * Minor improvements to migrations tips
    
    * Update examples for arguments
    
    * Update example for action handler
    
    * Add allowUnknownOption and allowExcessArguments to README
    
    * Update pizza example
    
    * Reuse pizza as single command example. Tidy up pizza and deploy.
    
    * Word change
    shadowspawn authored Dec 14, 2020
    Copy the full SHA
    f76bc71 View commit details
  2. Copy the full SHA
    5fd6e88 View commit details
  3. Prepare for release

    shadowspawn committed Dec 14, 2020
    Copy the full SHA
    4b43f66 View commit details
  4. Copy the full SHA
    5d95213 View commit details

Commits on Dec 20, 2020

  1. Update supported details for 7.x (#1419)

    * Update supported versions
    
    * Slight reword to clarify node 10 is (just) minimum
    shadowspawn authored Dec 20, 2020
    Copy the full SHA
    20cef03 View commit details

Commits on Dec 21, 2020

  1. Copy the full SHA
    75c2a63 View commit details

Commits on Dec 26, 2020

  1. Copy the full SHA
    c09159d View commit details

Commits on Dec 27, 2020

  1. Simplify eslint dependencies for TypeScript (#1425)

    * Simplify TypeScript eslint dependencies
    
    * Remove comment
    
    * Build fresh package-lock
    shadowspawn authored Dec 27, 2020
    Copy the full SHA
    5173665 View commit details

Commits on Jan 3, 2021

  1. Increase test coverage, including incrementNodeInspectorPort (#1428)

    * Add tests for incrementNodeInspectorPort
    
    * Fill out incrementNodeInspectorPort
    
    * Remove unused code from optionMissingArgument
    
    * Add tests for exit and custom throw
    
    * Error if call storeOptionsAsProperties with options already stored
    
    * Add test for bad params to parse
    
    * Error if parse "from" is unsupported
    
    * Can set alias after adding external command
    
    * Add test for invalid position to addHelpText
    
    * Clarify throw rather than display error
    
    * Add special case which displays help
    
    * Add test for incorrect return type on deprecated callback
    
    * Add test for implicit electron args
    
    * Add test for help requested on unknown subcommand
    
    * Add test for help suggestion including command path
    shadowspawn authored Jan 3, 2021
    Copy the full SHA
    d8faba2 View commit details

Commits on Jan 4, 2021

  1. Copy the full SHA
    1383870 View commit details
  2. Positional options (#1427)

    * First cut at optionsBeforeArguments
    
    * Different to mix global options and subcommands, and options and arguments.
    
    * Different to mix global options and subcommands, and options and arguments.
    
    * Add _parseOptionsFollowingArguments
    
    * Use allow wording
    
    * Another try at naming
    
    * Exclude options from special processing, which fixes help
    
    * Add help checks for new option configuration
    
    * Rename after discovering needed for any positional options
    
    * Rework logic to hopefully cope with default commands.
    
    * Expand basic tests. Positional options are tricky!
    
    * Add first default command tests
    
    * Fill out more tests
    
    * Add setters, and throw when passThrough without enabling positional
    
    * Rename test file
    
    * Add TypeScript
    
    * Add tests. Fix help handling by making explicit.
    
    * Reorder tests
    
    * Use usual indentation
    
    * Make _enablePositionalOptions inherited to simpify nested commands
    
    * Add examples
    
    * Add tests for some less common setups
    
    * Test the boring true/false parameters
    
    * Fix typo
    
    * Add new section to README with parsing configuration.
    
    * Tweak wording in README
    shadowspawn authored Jan 4, 2021
    Copy the full SHA
    8ac84ec View commit details

Commits on Jan 7, 2021

  1. Make new excess arguments error opt-in (#1429)

    * Make allowExcessArguments opt-in (again) and inherited configuration
    
    * Remove stale JSDoc
    
    * Update documentation for allowExcessArguments being opt-in
    
    * Make comment more specific about what is being checked for excess arguments
    
    * Rework example class override
    shadowspawn authored Jan 7, 2021
    Copy the full SHA
    e2670f4 View commit details

Commits on Jan 8, 2021

  1. Feature/update chinese readme (#1431)

    * Update Chinese README to match release/7.x (#1420)
    
    * Add Chinese versions of documents in the docs folder; update links in README (#1420)
    
    * Update Chinese README: excess args error opt-in (#1420) (#1429)
    y1hao authored Jan 8, 2021
    Copy the full SHA
    ff301fa View commit details

Commits on Jan 11, 2021

  1. Test coverage (#1433)

    * Test for just flags and just description
    
    * Edge case test for wrap with trivial input
    
    * Add obscure edge case for coverage
    
    * Add test for variadic argument usage/help
    
    * Suppress tests that uncredited coverage and not currently chasing
    
    * Revert "Suppress tests that uncredited coverage and not currently chasing"
    
    This reverts commit d3c9146.
    shadowspawn authored Jan 11, 2021
    Copy the full SHA
    891e23d View commit details
  2. Copy the full SHA
    d934573 View commit details
  3. ability to specify minColumnWidth for Help.wrap (#1430)

    * ability to specify `minColumnWidth` for `Help.wrap`
    
    * Added missing param in TypeScript for Help.wrap
    cravler authored Jan 11, 2021
    Copy the full SHA
    fcc8988 View commit details
  4. Copy the full SHA
    182ee06 View commit details
Showing with 7,052 additions and 3,503 deletions.
  1. +12 −4 .eslintrc.js
  2. +2 −2 .github/workflows/tests.yml
  3. +0 −11 .travis.yml
  4. +140 −105 CHANGELOG.md
  5. +318 −195 Readme.md
  6. +333 −207 Readme_zh-CN.md
  7. +6 −3 SECURITY.md
  8. +108 −0 changelogs/CHANGELOG-4.md
  9. +88 −0 docs/deprecated.md
  10. +87 −0 docs/zh-CN/不再推荐使用的功能.md
  11. +193 −0 docs/zh-CN/可变参数的选项.md
  12. +18 −0 docs/zh-CN/术语表.md
  13. +26 −0 examples/arguments.js
  14. +24 −0 examples/configure-help.js
  15. +31 −0 examples/configure-output.js
  16. +33 −19 examples/custom-command-class.js
  17. +0 −36 examples/custom-command-function.js
  18. +9 −7 examples/custom-help
  19. +1 −1 examples/custom-help-description
  20. +33 −0 examples/custom-help-text.js
  21. +2 −2 examples/defaultCommand.js
  22. +16 −19 examples/deploy
  23. +0 −29 examples/env
  24. +9 −12 examples/options-boolean-or-value.js
  25. +10 −20 examples/options-common.js
  26. +21 −21 examples/options-custom-processing.js
  27. +6 −10 examples/options-defaults.js
  28. +20 −0 examples/options-extra.js
  29. +10 −14 examples/options-negatable.js
  30. +1 −1 examples/options-required.js
  31. +23 −0 examples/pass-through-options.js
  32. +5 −10 examples/pizza
  33. +27 −0 examples/positional-options.js
  34. +0 −41 examples/storeOptionsAsProperties-action.js
  35. +0 −39 examples/storeOptionsAsProperties-opts.js
  36. +0 −36 examples/storeOptionsAsProperties-problem.js
  37. +26 −0 examples/thank.js
  38. +792 −473 index.js
  39. +1,258 −1,646 package-lock.json
  40. +15 −12 package.json
  41. +3 −2 tests/args.literal.test.js
  42. +13 −19 tests/args.variadic.test.js
  43. +8 −22 tests/command.action.test.js
  44. +245 −0 tests/command.addHelpText.test.js
  45. +9 −0 tests/command.alias.test.js
  46. +145 −0 tests/command.allowExcessArguments.test.js
  47. +30 −5 tests/{command.allowUnknownOptions.test.js → command.allowUnknownOption.test.js}
  48. +4 −11 tests/command.asterisk.test.js
  49. +156 −0 tests/command.chain.test.js
  50. +5 −5 tests/command.commandHelp.test.js
  51. +31 −0 tests/command.configureHelp.test.js
  52. +275 −0 tests/command.configureOutput.test.js
  53. +18 −0 tests/command.createHelp.test.js
  54. +42 −0 tests/command.createOption.test.js
  55. +2 −0 tests/command.default.test.js
  56. +2 −2 tests/command.executableSubcommand.lookup.test.js
  57. +2 −2 tests/command.executableSubcommand.test.js
  58. +109 −15 tests/command.exitOverride.test.js
  59. +98 −3 tests/command.help.test.js
  60. +32 −6 tests/command.helpCommand.test.js
  61. +20 −4 tests/command.helpOption.test.js
  62. +38 −0 tests/command.onCommand.test.js
  63. +26 −2 tests/command.parse.test.js
  64. +515 −0 tests/command.positionalOptions.test.js
  65. +22 −5 tests/command.unknownCommand.test.js
  66. +4 −4 tests/command.unknownOption.test.js
  67. +2 −2 tests/command.usage.test.js
  68. +19 −20 tests/commander.configureCommand.test.js
  69. +31 −0 tests/deprecated.test.js
  70. +20 −0 tests/help.commandDescription.test.js
  71. +43 −0 tests/help.commandTerm.test.js
  72. +49 −0 tests/help.commandUsage.test.js
  73. +32 −0 tests/help.longestArgumentTermLength.test.js
  74. +52 −0 tests/help.longestCommandTermLength.test.js
  75. +30 −0 tests/help.longestOptionTermLength.test.js
  76. +42 −0 tests/help.optionDescription.test.js
  77. +34 −0 tests/help.optionTerm.test.js
  78. +45 −0 tests/help.padWidth.test.js
  79. +29 −0 tests/help.sortCommands.test.js
  80. +78 −0 tests/help.sortOptions.test.js
  81. +27 −0 tests/help.visibleArguments.test.js
  82. +33 −0 tests/help.visibleCommands.test.js
  83. +70 −0 tests/help.visibleOptions.test.js
  84. +171 −0 tests/help.wrap.test.js
  85. +0 −125 tests/helpwrap.test.js
  86. +135 −0 tests/incrementNodeInspectorPort.test.js
  87. +33 −0 tests/option.chain.test.js
  88. +14 −14 tests/options.bool.combo.test.js
  89. +2 −2 tests/options.bool.small.combined.test.js
  90. +30 −20 tests/options.bool.test.js
  91. +6 −6 tests/options.camelcase.test.js
  92. +18 −8 tests/options.custom-processing.test.js
  93. +0 −52 tests/options.detectClash.test.js
  94. +10 −10 tests/options.flags.test.js
  95. +22 −22 tests/options.mandatory.test.js
  96. +7 −7 tests/options.optional.test.js
  97. +2 −1 tests/options.opts.test.js
  98. +0 −41 tests/options.regex.test.js
  99. +4 −4 tests/options.required.test.js
  100. +9 −9 tests/options.values.test.js
  101. +1 −5 tests/options.variadic.test.js
  102. +21 −18 tests/options.version.test.js
  103. +1 −1 tests/program.test.js
  104. +132 −24 typings/commander-tests.ts
  105. +241 −30 typings/index.d.ts
16 changes: 12 additions & 4 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -15,17 +15,22 @@ const javascriptSettings = {

const typescriptSettings = {
files: ['*.ts'],
extends: ['standard-with-typescript'],
parserOptions: {
project: './tsconfig.json'
},
plugins: [
'@typescript-eslint'
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended'
],
rules: {
'no-else-return': ['error', { allowElseIf: false }],
'no-var': 'warn',
'one-var': 'off',
'space-before-function-paren': ['error', 'never'],
// Using method rather than property for method-signature-style, to document method overloads separately.
'@typescript-eslint/method-signature-style': ['warn', 'method'],
semi: 'off',
'@typescript-eslint/semi': ['error', 'always'],
'@typescript-eslint/member-delimiter-style': [
@@ -40,7 +45,10 @@ const typescriptSettings = {
requireLast: false
}
}
]
],
// Add some "standard" rules by hand, as eslint-config-standard-with-typescript painful to keep up to date
quotes: ['error', 'single'],
'no-trailing-spaces': 'error'
}
};

4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Node CI
name: build

on: [push, pull_request]

@@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [10.x, 12.x, 14.x]
node-version: [10.x, 12.x, 14.x, 15.x]
os: [ubuntu-latest, windows-latest, macos-latest]

steps:
11 changes: 0 additions & 11 deletions .travis.yml

This file was deleted.

245 changes: 140 additions & 105 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -8,6 +8,132 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
<!-- markdownlint-disable MD024 -->
<!-- markdownlint-disable MD004 -->

## [7.0.0] (2021-01-15)

### Added

- `.enablePositionalOptions()` to let program and subcommand reuse same option ([#1427])
- `.passThroughOptions()` to pass options through to other programs without needing `--` ([#1427])
- `.allowExcessArguments(false)` to show an error message if there are too many command-arguments on command line for the action handler ([#1409])
- `.configureOutput()` to modify use of stdout and stderr or customise display of errors ([#1387])
- use `.addHelpText()` to add text before or after the built-in help, for just current command or also for all subcommands ([#1296])
- enhance Option class ([#1331])
- allow hiding options from help
- allow restricting option arguments to a list of choices
- allow setting how default value is shown in help
- `.createOption()` to support subclassing of automatically created options (like `.createCommand()`) ([#1380])
- refactor the code generating the help into a separate public Help class ([#1365])
- support sorting subcommands and options in help
- support specifying wrap width (columns)
- allow subclassing Help class
- allow configuring Help class without subclassing

### 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()`
- *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()`
- deprecate `.on('--help')` (removed from README) ([#1296])
- initialise the command description to empty string (previously undefined) ([#1365])
- document and annotate deprecated routines ([#1349])

### Fixed

- wrapping bugs in help ([#1365])
- first line of command description was wrapping two characters early
- pad width calculation was not including help option and help command
- pad width calculation was including hidden options and commands
- improve backwards compatibility for custom command event listeners ([#1403])

### Deleted

- *Breaking:* `.passCommandToAction()` ([#1409])
- 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 displayed by default

### Migration Tips

The biggest change is the parsed option values. Previously the options were stored by default as properties on the command object, and now the options are stored separately.

If you wish to restore the old behaviour and get running quickly you can call `.storeOptionsAsProperties()`.
To allow you to move to the new code patterns incrementally, the action handler will be passed the command _twice_,
to match the new "options" and "command" parameters (see below).

**program options**

Use the `.opts()` method to access the options. This is available on any command but is used most with the program.

```js
program.option('-d, --debug');
program.parse();
// Old code before Commander 7
if (program.debug) console.log(`Program name is ${program.name()}`);
```

```js
// New code
const options = program.opts();
if (options.debug) console.log(`Program name is ${program.name()}`);
```

**action handler**

The action handler gets passed a parameter for each command-argument you declared. Previously by default the next parameter was the command object with the options as properties. Now the next two parameters are instead the options and the command. If you
only accessed the options there may be no code changes required.

```js
program
.command('compress <filename>')
.option('-t, --trace')
// Old code before Commander 7
.action((filename, cmd)) => {
if (cmd.trace) console.log(`Command name is ${cmd.name()}`);
});
```

```js
// New code
.action((filename, options, command)) => {
if (options.trace) console.log(`Command name is ${command.name()}`);
});
```

If you already set `.storeOptionsAsProperties(false)` you may still need to adjust your code.

```js
program
.command('compress <filename>')
.storeOptionsAsProperties(false)
.option('-t, --trace')
// Old code before Commander 7
.action((filename, command)) => {
if (command.opts().trace) console.log(`Command name is ${command.name()}`);
});
```

```js
// New code
.action((filename, options, command)) => {
if (command.opts().trace) console.log(`Command name is ${command.name()}`);
});
```

## [7.0.0-2] (2020-12-14)

(Released in 7.0.0)

## [7.0.0-1] (2020-11-21)

(Released in 7.0.0)

## [7.0.0-0] (2020-10-25)

(Released in 7.0.0)

## [6.2.1] (2020-12-13)

### Fixed
@@ -178,90 +304,9 @@ to expand `-fb` to `-f -b` rather than `-f b`.

(Released in 5.0.0)

## [4.1.1] (2020-02-02)

### Fixed

* TypeScript definition for `.action()` should include Promise for async ([#1157])

## [4.1.0] (2020-01-06)

### Added

* two routines to change how option values are handled, and eliminate name clashes with command properties ([#933] [#1102])
* see storeOptionsAsProperties and passCommandToAction in README
* `.parseAsync` to use instead of `.parse` if supply async action handlers ([#806] [#1118])

### Fixed

* Remove trailing blanks from wrapped help text ([#1096])

### Changed

* update dependencies
* extend security coverage for Commander 2.x to 2020-02-03
* improvements to README
* improvements to TypeScript definition documentation
* move old versions out of main CHANGELOG
* removed explicit use of `ts-node` in tests

## [4.0.1] (2019-11-12)

### Fixed

* display help when requested, even if there are missing required options ([#1091])

## [4.0.0] (2019-11-02)

### Added

* automatically wrap and indent help descriptions for options and commands ([#1051])
* `.exitOverride()` allows override of calls to `process.exit` for additional error handling and to keep program running ([#1040])
* support for declaring required options with `.requiredOptions()` ([#1071])
* GitHub Actions support ([#1027])
* translation links in README

### Changed

* dev: switch tests from Sinon+Should to Jest with major rewrite of tests ([#1035])
* call default subcommand even when there are unknown options ([#1047])
* *Breaking* Commander is only officially supported on Node 8 and above, and requires Node 6 ([#1053])

### Fixed

* *Breaking* keep command object out of program.args when action handler called ([#1048])
* also, action handler now passed array of unknown arguments
* complain about unknown options when program argument supplied and action handler ([#1049])
* this changes parameters to `command:*` event to include unknown arguments
* removed deprecated `customFds` option from call to `child_process.spawn` ([#1052])
* rework TypeScript declarations to bring all types into imported namespace ([#1081])

### Migration Tips

#### Testing for no arguments

If you were previously using code like:

```js
if (!program.args.length) ...
```

a partial replacement is:

```js
if (program.rawArgs.length < 3) ...
```

## [4.0.0-1] Prerelease (2019-10-08)

(Released in 4.0.0)

## [4.0.0-0] Prerelease (2019-10-01)

(Released in 4.0.0)

## Older versions

* [4.x](./changelogs/CHANGELOG-4.md)
* [3.x](./changelogs/CHANGELOG-3.md)
* [2.x](./changelogs/CHANGELOG-2.md)
* [1.x](./changelogs/CHANGELOG-1.md)
@@ -276,37 +321,20 @@ if (program.rawArgs.length < 3) ...
[#742]: https://github.com/tj/commander.js/issues/742
[#764]: https://github.com/tj/commander.js/issues/764
[#802]: https://github.com/tj/commander.js/issues/802
[#806]: https://github.com/tj/commander.js/issues/806
[#809]: https://github.com/tj/commander.js/issues/809
[#948]: https://github.com/tj/commander.js/issues/948
[#962]: https://github.com/tj/commander.js/issues/962
[#995]: https://github.com/tj/commander.js/issues/995
[#1027]: https://github.com/tj/commander.js/pull/1027
[#1032]: https://github.com/tj/commander.js/issues/1032
[#1035]: https://github.com/tj/commander.js/pull/1035
[#1040]: https://github.com/tj/commander.js/pull/1040
[#1047]: https://github.com/tj/commander.js/pull/1047
[#1048]: https://github.com/tj/commander.js/pull/1048
[#1049]: https://github.com/tj/commander.js/pull/1049
[#1051]: https://github.com/tj/commander.js/pull/1051
[#1052]: https://github.com/tj/commander.js/pull/1052
[#1053]: https://github.com/tj/commander.js/pull/1053
[#1062]: https://github.com/tj/commander.js/pull/1062
[#1071]: https://github.com/tj/commander.js/pull/1071
[#1081]: https://github.com/tj/commander.js/pull/1081
[#1088]: https://github.com/tj/commander.js/issues/1088
[#1091]: https://github.com/tj/commander.js/pull/1091
[#1096]: https://github.com/tj/commander.js/pull/1096
[#1102]: https://github.com/tj/commander.js/pull/1102
[#1118]: https://github.com/tj/commander.js/pull/1118
[#1119]: https://github.com/tj/commander.js/pull/1119
[#1133]: https://github.com/tj/commander.js/pull/1133
[#1138]: https://github.com/tj/commander.js/pull/1138
[#1145]: https://github.com/tj/commander.js/pull/1145
[#1146]: https://github.com/tj/commander.js/pull/1146
[#1149]: https://github.com/tj/commander.js/pull/1149
[#1153]: https://github.com/tj/commander.js/issues/1153
[#1157]: https://github.com/tj/commander.js/pull/1157
[#1159]: https://github.com/tj/commander.js/pull/1159
[#1165]: https://github.com/tj/commander.js/pull/1165
[#1169]: https://github.com/tj/commander.js/pull/1169
@@ -325,22 +353,35 @@ if (program.rawArgs.length < 3) ...
[#1250]: https://github.com/tj/commander.js/pull/1250
[#1256]: https://github.com/tj/commander.js/pull/1256
[#1275]: https://github.com/tj/commander.js/pull/1275
[#1296]: https://github.com/tj/commander.js/pull/1296
[#1301]: https://github.com/tj/commander.js/issues/1301
[#1306]: https://github.com/tj/commander.js/pull/1306
[#1312]: https://github.com/tj/commander.js/pull/1312
[#1322]: https://github.com/tj/commander.js/pull/1322
[#1323]: https://github.com/tj/commander.js/pull/1323
[#1325]: https://github.com/tj/commander.js/pull/1325
[#1326]: https://github.com/tj/commander.js/pull/1326
[#1331]: https://github.com/tj/commander.js/pull/1331
[#1332]: https://github.com/tj/commander.js/pull/1332
[#1349]: https://github.com/tj/commander.js/pull/1349
[#1353]: https://github.com/tj/commander.js/pull/1353
[#1360]: https://github.com/tj/commander.js/pull/1360
[#1361]: https://github.com/tj/commander.js/pull/1361
[#1365]: https://github.com/tj/commander.js/pull/1365
[#1368]: https://github.com/tj/commander.js/pull/1368
[#1375]: https://github.com/tj/commander.js/pull/1375
[#1380]: https://github.com/tj/commander.js/pull/1380
[#1387]: https://github.com/tj/commander.js/pull/1387
[#1390]: https://github.com/tj/commander.js/pull/1390
[#1403]: https://github.com/tj/commander.js/pull/1403
[#1409]: https://github.com/tj/commander.js/pull/1409
[#1427]: https://github.com/tj/commander.js/pull/1427

[Unreleased]: https://github.com/tj/commander.js/compare/master...develop
[7.0.0]: https://github.com/tj/commander.js/compare/v6.2.1...v7.0.0
[7.0.0-2]: https://github.com/tj/commander.js/compare/v7.0.0-1...v7.0.0-2
[7.0.0-1]: https://github.com/tj/commander.js/compare/v7.0.0-0...v7.0.0-1
[7.0.0-0]: https://github.com/tj/commander.js/compare/v6.2.0...v7.0.0-0
[6.2.1]: https://github.com/tj/commander.js/compare/v6.2.0..v6.2.1
[6.2.0]: https://github.com/tj/commander.js/compare/v6.1.0..v6.2.0
[6.1.0]: https://github.com/tj/commander.js/compare/v6.0.0..v6.1.0
@@ -353,9 +394,3 @@ if (program.rawArgs.length < 3) ...
[5.0.0-2]: https://github.com/tj/commander.js/compare/v5.0.0-1..v5.0.0-2
[5.0.0-1]: https://github.com/tj/commander.js/compare/v5.0.0-0..v5.0.0-1
[5.0.0-0]: https://github.com/tj/commander.js/compare/v4.1.1..v5.0.0-0
[4.1.1]: https://github.com/tj/commander.js/compare/v4.1.0..v4.1.1
[4.1.0]: https://github.com/tj/commander.js/compare/v4.0.1..v4.1.0
[4.0.1]: https://github.com/tj/commander.js/compare/v4.0.0..v4.0.1
[4.0.0]: https://github.com/tj/commander.js/compare/v3.0.2..v4.0.0
[4.0.0-1]: https://github.com/tj/commander.js/compare/v4.0.0-0..v4.0.0-1
[4.0.0-0]: https://github.com/tj/commander.js/compare/v3.0.2...v4.0.0-0
513 changes: 318 additions & 195 deletions Readme.md

Large diffs are not rendered by default.

540 changes: 333 additions & 207 deletions Readme_zh-CN.md

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -6,9 +6,12 @@ Old versions receive security updates for six months.

| Version | Supported |
| ------- | ------------------------------------------ |
| 5.x | :white_check_mark: |
| 4.x | :white_check_mark: support ends 2020-09-14 |
| < 4 | :x: |
| 7.x | :white_check_mark: |
| 6.x | :white_check_mark: support ends 2021-30-06 |
| 5.x | :white_check_mark: support ends 2021-01-20 |
| < 5 | :x: |

Pull Requests for security issues will be considered for older versions back to 2.x.

## Reporting a Vulnerability

108 changes: 108 additions & 0 deletions changelogs/CHANGELOG-4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Changelog for 4.x

## [4.1.1] (2020-02-02)

### Fixed

* TypeScript definition for `.action()` should include Promise for async ([#1157])

## [4.1.0] (2020-01-06)

### Added

* two routines to change how option values are handled, and eliminate name clashes with command properties ([#933] [#1102])
* see storeOptionsAsProperties and passCommandToAction in README
* `.parseAsync` to use instead of `.parse` if supply async action handlers ([#806] [#1118])

### Fixed

* Remove trailing blanks from wrapped help text ([#1096])

### Changed

* update dependencies
* extend security coverage for Commander 2.x to 2020-02-03
* improvements to README
* improvements to TypeScript definition documentation
* move old versions out of main CHANGELOG
* removed explicit use of `ts-node` in tests

## [4.0.1] (2019-11-12)

### Fixed

* display help when requested, even if there are missing required options ([#1091])

## [4.0.0] (2019-11-02)

### Added

* automatically wrap and indent help descriptions for options and commands ([#1051])
* `.exitOverride()` allows override of calls to `process.exit` for additional error handling and to keep program running ([#1040])
* support for declaring required options with `.requiredOptions()` ([#1071])
* GitHub Actions support ([#1027])
* translation links in README

### Changed

* dev: switch tests from Sinon+Should to Jest with major rewrite of tests ([#1035])
* call default subcommand even when there are unknown options ([#1047])
* *Breaking* Commander is only officially supported on Node 8 and above, and requires Node 6 ([#1053])

### Fixed

* *Breaking* keep command object out of program.args when action handler called ([#1048])
* also, action handler now passed array of unknown arguments
* complain about unknown options when program argument supplied and action handler ([#1049])
* this changes parameters to `command:*` event to include unknown arguments
* removed deprecated `customFds` option from call to `child_process.spawn` ([#1052])
* rework TypeScript declarations to bring all types into imported namespace ([#1081])

### Migration Tips

#### Testing for no arguments

If you were previously using code like:

```js
if (!program.args.length) ...
```

a partial replacement is:

```js
if (program.rawArgs.length < 3) ...
```

## [4.0.0-1] Prerelease (2019-10-08)

(Released in 4.0.0)

## [4.0.0-0] Prerelease (2019-10-01)

(Released in 4.0.0)

[#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
[#1047]: https://github.com/tj/commander.js/pull/1047
[#1048]: https://github.com/tj/commander.js/pull/1048
[#1049]: https://github.com/tj/commander.js/pull/1049
[#1051]: https://github.com/tj/commander.js/pull/1051
[#1052]: https://github.com/tj/commander.js/pull/1052
[#1053]: https://github.com/tj/commander.js/pull/1053
[#1071]: https://github.com/tj/commander.js/pull/1071
[#1081]: https://github.com/tj/commander.js/pull/1081
[#1091]: https://github.com/tj/commander.js/pull/1091
[#1096]: https://github.com/tj/commander.js/pull/1096
[#1102]: https://github.com/tj/commander.js/pull/1102
[#1118]: https://github.com/tj/commander.js/pull/1118
[#1157]: https://github.com/tj/commander.js/pull/1157
[#806]: https://github.com/tj/commander.js/issues/806

[4.1.1]: https://github.com/tj/commander.js/compare/v4.1.0..v4.1.1
[4.1.0]: https://github.com/tj/commander.js/compare/v4.0.1..v4.1.0
[4.0.1]: https://github.com/tj/commander.js/compare/v4.0.0..v4.0.1
[4.0.0]: https://github.com/tj/commander.js/compare/v3.0.2..v4.0.0
[4.0.0-1]: https://github.com/tj/commander.js/compare/v4.0.0-0..v4.0.0-1
[4.0.0-0]: https://github.com/tj/commander.js/compare/v3.0.2...v4.0.0-0
88 changes: 88 additions & 0 deletions docs/deprecated.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Deprecated

These features are deprecated, which means they may go away in a future major version of Commander.
They are currently still available for backwards compatibility, but should not be used in new code.

## RegExp .option() parameter

The `.option()` method allowed a RegExp as the third parameter to restrict what values were accepted.

```js
program.option('-c,--coffee <type>', 'coffee', /short-white|long-black/);
```

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

This was an option passed to `.command()` to hide the command from the built-in help:

```js
program.command('example', 'examnple 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');
comnst program = new Command()
```

Removed from README in Commander v5. Deprecated from Commander v7.

## Callback to .help() and .outputHelp()

These routines allowed a callback parameter to process the built-in help before display.

```js
program.outputHelp((text) => {
return colors.red(text);
});
```

The newer approach is to directly access the built-in help text using `.helpInformation()`.

```js
console.error(colors.red(program.helpInformation()));
```

Deprecated from Commander v7.

## .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.

```js
program.on('--help', function() {
console.log('')
console.log('Examples:');
console.log(' $ custom-help --help');
console.log(' $ custom-help -h');
});
```

The replacement is `.addHelpText()`:

```js
program.addHelpText('after', `
Examples:
$ custom-help --help
$ custom-help -h`
);
```

Deprecated from Commander v7.
87 changes: 87 additions & 0 deletions docs/zh-CN/不再推荐使用的功能.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# 不再推荐使用的功能

本文中列出的功能不再推荐使用。它们在未来版本的Commander 中可能被移除。为保证后向兼容,这些功能目前还可以使用,但它们不应被用在新代码中。

## .option() 方法的正则表达式参数

`.option()`方法可以接受一个正则表达式作为第三个参数,以规定可以接受的选项参数值。

```js
program.option('-c,--coffee <type>', 'coffee', /short-white|long-black/);
```

该功能从 Commander v3 的文档中被移除。自 Commander v7 起,该功能不再推荐使用。

新的写法是使用 Option 对象的`.choices()`方法,或者使用自定义选项处理函数。

## noHelp

此选项曾被用作传入`.command()`方法,以在内建的帮助信息中隐藏该命令:

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

在 Commander v5.1 中,该选项被更名为`hidden`。自 Commander v7 起`noHelp`不再推荐使用。

## 默认导入的全局 Command 对象

Commander 的默认导入曾是一个全局的 Command 对象。

```js
const program = require('commander');
```

自 Commander v5 后,全局 Command 对象作为 `program`被导出。或者也可以导入 `Command` 对象。

```js
const { program } = require('commander');
// 或者
const { Command } = require('commander');
comnst program = new Command()
```

此功能在 Commander v5 的文档中被移除。自 Commander v7 起不再推荐使用。

## .help() 与 .outputHelp() 的回调函数参数

这两个方法曾允许传入一个回调函数,以在展示前处理帮助信息。

```js
program.outputHelp((text) => {
return colors.red(text);
});
```

新的写法是直接通过调用`.helpInformation()`来获取帮助文本。

```js
console.error(colors.red(program.helpInformation()));
```

原功能自 Commander v7 起不再推荐使用。

## .on('--help')

此功能曾被用作在内建的帮助信息后输出自定义的帮助信息。自 Commander v3.0.0 起,如果有自定义的帮助选项,则此方法使用该自定义的帮助选项。

```js
program.on('--help', function() {
console.log('')
console.log('Examples:');
console.log(' $ custom-help --help');
console.log(' $ custom-help -h');
});
```

替代的方法是使用`.addHelpText()`

```js
program.addHelpText('after', `
Examples:
$ custom-help --help
$ custom-help -h`
);
```

原功能自 Commander v7 起不再推荐使用。
193 changes: 193 additions & 0 deletions docs/zh-CN/可变参数的选项.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# 可变参数的选项

README 文档介绍了选项的声明与使用。大多数情况下,选项的解析行为,与您和您程序用户的期望相符。本文将详述一些特殊用例和细节问题。

- [可变参数的选项](#%E5%8F%AF%E5%8F%98%E5%8F%82%E6%95%B0%E7%9A%84%E9%80%89%E9%A1%B9)
- [解析中的歧义](#%E8%A7%A3%E6%9E%90%E4%B8%AD%E7%9A%84%E6%AD%A7%E4%B9%89)
- [方案一:让`--`成为语法的一部分](#%E6%96%B9%E6%A1%88%E4%B8%80%EF%BC%9A%E8%AE%A9%60--%60%E6%88%90%E4%B8%BA%E8%AF%AD%E6%B3%95%E7%9A%84%E4%B8%80%E9%83%A8%E5%88%86)
- [方案二:把选项放在最后](#%E6%96%B9%E6%A1%88%E4%BA%8C%EF%BC%9A%E6%8A%8A%E9%80%89%E9%A1%B9%E6%94%BE%E5%9C%A8%E6%9C%80%E5%90%8E)
- [方案三:使用选项替代命令参数](#%E6%96%B9%E6%A1%88%E4%B8%89%EF%BC%9A%E4%BD%BF%E7%94%A8%E9%80%89%E9%A1%B9%E6%9B%BF%E4%BB%A3%E5%91%BD%E4%BB%A4%E5%8F%82%E6%95%B0)
- [合并短选项,和带有参数的选项](#%E5%90%88%E5%B9%B6%E7%9F%AD%E9%80%89%E9%A1%B9%E5%92%8C%E5%B8%A6%E6%9C%89%E5%8F%82%E6%95%B0%E7%9A%84%E9%80%89%E9%A1%B9)
- [将短选项视作boolean类型选项合并](#%E5%B0%86%E7%9F%AD%E9%80%89%E9%A1%B9%E8%A7%86%E4%BD%9Cboolean%E7%B1%BB%E5%9E%8B%E9%80%89%E9%A1%B9%E5%90%88%E5%B9%B6)

有些选项可以接受可变数量的参数:

```js
program
.option('-c, --compress [percentage]') // 0 或 1 个参数
.option('--preprocess <file...>') // 至少 1 个参数
.option('--test [name...]') // 0 或多个参数
```

本文中使用的示例代码接受0或1个参数。但相关的讨论对接受更多参数的选项同样适用。

关于本文档中使用的术语,参见[术语表](./%E6%9C%AF%E8%AF%AD%E8%A1%A8.md)

## 解析中的歧义

解析选项时,有一些潜在的问题值得注意。当一个命令既有命令参数,又有选项的时候,在解析过程中有可能出现歧义。这可能会影响用户使用您的程序。
Commander 首先解析选项的参数,而用户有可能想将选项后面跟的参数作为命令,或是作为命令参数进行解析。

```js
program
.name('cook')
.arguments('[technique]')
.option('-i, --ingredient [ingredient]', 'add cheese or given ingredient')
.action((technique, options) => {
console.log(`technique: ${technique}`);
const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient;
console.log(`ingredient: ${ingredient}`);
});

program.parse();
```

```sh
$ cook scrambled
technique: scrambled
ingredient: undefined

$ cook -i
technique: undefined
ingredient: cheese

$ cook -i egg
technique: undefined
ingredient: egg

$ cook -i scrambled # oops
technique: undefined
ingredient: scrambled
```

可以通过使用`--`来表示选项和选项参数部分的结束,以此来显式地解决这一冲突:

```sh
$ node cook.js -i -- egg
technique: egg
ingredient: cheese
```

如果不希望强制用户掌握这种使用`--`的写法,您可以尝试以下几种方案。

### 方案一:让`--`成为语法的一部分

与其让用户理解`--`的用法,您也可以直接把它作为程序语法的一部分。

```js
program.usage('[options] -- [technique]');
```

```sh
$ cook --help
Usage: cook [options] -- [technique]

Options:
-i, --ingredient [ingredient] add cheese or given ingredient
-h, --help display help for command

$ cook -- scrambled
technique: scrambled
ingredient: undefined

$ cook -i -- scrambled
technique: scrambled
ingredient: cheese
```

### 方案二:把选项放在最后

Commander 遵循GNU解析命令的惯例,允许选项出现在命令参数之前或者之后,也可以将选项和命令参数相互穿插。
因此,通过要求把选项放在最后,命令参数就不会和选项参数相混淆。

```js
program.usage('[technique] [options]');
```

```sh
$ cook --help
Usage: cook [technique] [options]

Options:
-i, --ingredient [ingredient] add cheese or given ingredient
-h, --help display help for command

$ node cook.js scrambled -i
technique: scrambled
ingredient: cheese
```

### 方案三:使用选项替代命令参数

这个方案虽然有点激进,但是可以完美避免解析中的歧义。

```js
program
.name('cook')
.option('-t, --technique <technique>', 'cooking technique')
.option('-i, --ingredient [ingredient]', 'add cheese or given ingredient')
.action((options) => {
console.log(`technique: ${options.technique}`);
const ingredient = (options.ingredient === true) ? 'cheese' : options.ingredient;
console.log(`ingredient: ${ingredient}`);
});
```

```sh
$ cook -i -t scrambled
technique: scrambled
ingredient: cheese
```

## 合并短选项和带有参数的选项

多个boolean型的短选项可以合并起来写在同一个`-`号后面,比如`ls -al`。同时,你也可以在其中包括一个接受参数的短选项,此时,任何跟在其后的字符都将被视作这个选项的值。

这就是说,默认情况下并不可以把多个带参数的选项合并起来。

```js
program
.name('collect')
.option("-o, --other [count]", "other serving(s)")
.option("-v, --vegan [count]", "vegan serving(s)")
.option("-l, --halal [count]", "halal serving(s)");
program.parse(process.argv);

const opts = program.opts();
if (opts.other) console.log(`other servings: ${opts.other}`);
if (opts.vegan) console.log(`vegan servings: ${opts.vegan}`);
if (opts.halal) console.log(`halal servings: ${opts.halal}`);
```

```sh
$ collect -o 3
other servings: 3
$ collect -o3
other servings: 3
$ collect -l -v
vegan servings: true
halal servings: true
$ collect -lv # oops
halal servings: v
```

如果需要使用多个既可用作boolean,又可以接受参数的选项,只能把它们分别声明。

```
$ collect -a -v -l
any servings: true
vegan servings: true
halal servings: true
```

### 将短选项视作boolean类型选项合并

在 Commander v5 之前,并不支持合并短选项和值。合并的boolean类型选项会被展开。
因此,`-avl`将被展开为 `-a -v -l`

如果您需要后向兼容,或是倾向于在短选项合并的时候把它们视作boolean类型,可以改变此行为:

```js
.combineFlagAndOptionalValue(true) // `-v45` 被视为 `--vegan=45`,这是默认的行为
.combineFlagAndOptionalValue(false) // `-vl` 被视为 `-v -l`
```
18 changes: 18 additions & 0 deletions docs/zh-CN/术语表.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# 术语表

命令行参数由选项(options)、选项参数(option-arguments)、命令(commands),以及命令参数(command-arguments)组成。

|术语|解释|
|---|---|
|选项(options)|`-`后跟单个字母,或`--`后跟单词(或以`-`连接的多个单词),例如`-s``--short`|
|选项参数(option-argument)|有的选项可以接受一个参数|
|命令(command)|一个程序或命令可以包含子命令|
|命令参数(command-argument)|传给命令的参数(不包含选项或选项参数)|

例如:

```sh
my-utility command -o --option option-argument command-argument-1 command-argument-2
```

在一些其他资料中,选项有时也称为标志(flags),而命令参数也被称为位置参数(positional arguments)或者操作数(operands)。
26 changes: 26 additions & 0 deletions examples/arguments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env node

// This example shows specifying the arguments for the program to pass to the action handler.

// const { Command } = require('commander'); // (normal include)
const { Command } = require('../'); // include commander in git clone of commander repo
const program = new Command();

program
.version('0.1.0')
.arguments('<username> [password]')
.description('test command', {
username: 'user to login',
password: 'password for user, if required'
})
.action((username, password) => {
console.log('username:', username);
console.log('environment:', password || 'no password given');
});

program.parse();

// Try the following:
// node arguments.js --help
// node arguments.js user
// node arguments.js user secret
24 changes: 24 additions & 0 deletions examples/configure-help.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo

const program = new commander.Command();

// This example shows a simple use of configureHelp.
// This is used as an example in the README.

program.configureHelp({
sortSubcommands: true,
subcommandTerm: (cmd) => cmd.name() // Just show the name, instead of short usage.
});

program.command('zebra <herd-size>', 'African equines with distinctive black-and-white striped coats');
program.command('aardvark [colour]', 'medium-sized, burrowing, nocturnal mammal');
program
.command('beaver', 'large, semiaquatic rodent')
.option('--pond')
.option('--river');

program.parse();

// Try the following:
// node configure-help.js --help
31 changes: 31 additions & 0 deletions examples/configure-output.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo

const program = new commander.Command();

function errorColor(str) {
// Add ANSI escape codes to display text in red.
return `\x1b[31m${str}\x1b[0m`;
}

program
.configureOutput({
// Visibly override write routines as example!
writeOut: (str) => process.stdout.write(`[OUT] ${str}`),
writeErr: (str) => process.stdout.write(`[ERR] ${str}`),
// Output errors in red.
outputError: (str, write) => write(errorColor(str))
});

program
.version('1.2.3')
.option('-c, --compress')
.command('sub-command');

program.parse();

// Try the following:
// node configure-output.js --version
// node configure-output.js --unknown
// node configure-output.js --help
// node configure-output.js
52 changes: 33 additions & 19 deletions examples/custom-command-class.js
Original file line number Diff line number Diff line change
@@ -3,34 +3,48 @@
// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo

// Use a class override of createCommand to customise subcommands,
// in this example by adding --debug option.
// Use a class override to customise the command and its subcommands.

class MyCommand extends commander.Command {
class CommandWithTrace extends commander.Command {
createCommand(name) {
const cmd = new MyCommand(name);
cmd.option('-d,--debug', 'output options');
const cmd = new CommandWithTrace(name);
// Add an option to subcommands created using `.command()`
cmd.option('-t, --trace', 'display extra information when run command');
return cmd;
}
};

const program = new MyCommand();
function inpectCommand(command) {
// The option value is stored as property on command because we called .storeOptionsAsProperties()
console.log(`Called '${command.name()}'`);
console.log(`args: ${command.args}`);
console.log('opts: %o', command.opts());
};

const program = new CommandWithTrace('program')
.option('-v, ---verbose')
.action((options, command) => {
inpectCommand(command);
});

program
.command('serve [params...]')
.option('-p, --port <number>', 'port number')
.action((params, options, command) => {
inpectCommand(command);
});

program
.command('serve')
.option('--port <port-number>', 'specify port number', 80)
.action((cmd) => {
if (cmd.debug) {
console.log('Options:');
console.log(cmd.opts());
console.log();
}

console.log(`Start serve on port ${cmd.port}`);
.command('build <target>')
.action((buildTarget, options, command) => {
inpectCommand(command);
});

program.parse();

// Try the following:
// node custom-command-class.js help serve
// node custom-command-class.js serve --debug
// node custom-command-class.js serve --debug --port 8080
// node custom-command-class.js --help
// node custom-command-class.js serve --help
// node custom-command-class.js serve -t -p 80 a b c
// node custom-command-class.js build --help
// node custom-command-class.js build --trace foo
36 changes: 0 additions & 36 deletions examples/custom-command-function.js

This file was deleted.

16 changes: 9 additions & 7 deletions examples/custom-help
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

// This example displays some custom help text after the built-in help.
// This example shows a simple use of addHelpText.
// This is used as an example in the README.

// const { Command } = require('commander'); // (normal include)
const { Command } = require('../'); // include commander in git clone of commander repo
@@ -9,11 +10,12 @@ const program = new Command();
program
.option('-f, --foo', 'enable some foo');

// must be before .parse()
program.on('--help', () => {
console.log('');
console.log('Example call:');
console.log(' $ custom-help --help');
});
program.addHelpText('after', `
Example call:
$ custom-help --help`);

program.parse(process.argv);

// Try the following:
// node custom-help --help
2 changes: 1 addition & 1 deletion examples/custom-help-description
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ program
program
.command('child')
.option('--gender', 'specific gender of child')
.action((cmd) => {
.action((options) => {
console.log('Childsubcommand...');
});

33 changes: 33 additions & 0 deletions examples/custom-help-text.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env node

// This example shows using addHelpText.

// const { Command } = require('commander'); // (normal include)
const { Command } = require('../'); // include commander in git clone of commander repo
const program = new Command();

program.name('awesome');

program
.addHelpText('beforeAll', 'A W E S O M E\n')
.addHelpText('afterAll', (context) => {
if (context.error) {
return '\nHelp being displayed for an error';
}
return '\nSee web site for further help';
});

program
.command('extra')
.addHelpText('before', 'Note: the extra command does not do anything')
.addHelpText('after', `
Examples:
awesome extra --help
awesome help extra`);

program.parse();

// Try the following:
// node custom-help-text.js --help
// node custom-help-text.js extra --help
// node custom-help-text.js
4 changes: 2 additions & 2 deletions examples/defaultCommand.js
Original file line number Diff line number Diff line change
@@ -31,8 +31,8 @@ program
.command('serve', { isDefault: true })
.description('launch web server')
.option('-p,--port <port_number>', 'web port')
.action((opts) => {
console.log(`server on port ${opts.port}`);
.action((options) => {
console.log(`server on port ${options.port}`);
});

program.parse(process.argv);
35 changes: 16 additions & 19 deletions examples/deploy
Original file line number Diff line number Diff line change
@@ -6,33 +6,30 @@ const program = new Command();

program
.version('0.0.1')
.option('-C, --chdir <path>', 'change the working directory')
.option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
.option('-T, --no-tests', 'ignore test hook');
.option('-c, --config <path>', 'set config path', './deploy.conf');

program
.command('setup [env]')
.description('run setup commands for all envs')
.option('-s, --setup_mode [mode]', 'Which setup mode to use')
.action(function(env, options) {
const mode = options.setup_mode || 'normal';
.option('-s, --setup_mode <mode>', 'Which setup mode to use', 'normal')
.action((env, options) => {
env = env || 'all';
console.log('setup for %s env(s) with %s mode', env, mode);
console.log('read config from %s', program.opts().config);
console.log('setup for %s env(s) with %s mode', env, options.setup_mode);
});

program
.command('exec <cmd>')
.command('exec <script>')
.alias('ex')
.description('execute the given remote cmd')
.option('-e, --exec_mode <mode>', 'Which exec mode to use')
.action(function(cmd, options) {
console.log('exec "%s" using %s mode', cmd, options.exec_mode);
}).on('--help', function() {
console.log(' Examples:');
console.log();
console.log(' $ deploy exec sequential');
console.log(' $ deploy exec async');
console.log();
});

.option('-e, --exec_mode <mode>', 'Which exec mode to use', 'fast')
.action((script, options) => {
console.log('read config from %s', program.opts().config);
console.log('exec "%s" using %s mode and config %s', script, options.exec_mode, program.opts().config);
}).addHelpText('after', `
Examples:
$ deploy exec sequential
$ deploy exec async`
);

program.parse(process.argv);
29 changes: 0 additions & 29 deletions examples/env

This file was deleted.

21 changes: 9 additions & 12 deletions examples/options-boolean-or-value.js
Original file line number Diff line number Diff line change
@@ -3,15 +3,6 @@
// This is used as an example in the README for:
// Other option types, flag|value
// You can specify an option which functions as a flag but may also take a value (declared using square brackets).
//
// Example output pretending command called pizza-options (or try directly with `node options-flag-or-value.js`)
//
// $ pizza-options
// no cheese
// $ pizza-options --cheese
// add cheese
// $ pizza-options --cheese mozzarella
// add cheese type mozzarella

// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
@@ -22,6 +13,12 @@ program

program.parse(process.argv);

if (program.cheese === undefined) console.log('no cheese');
else if (program.cheese === true) console.log('add cheese');
else console.log(`add cheese type ${program.cheese}`);
const options = program.opts();
if (options.cheese === undefined) console.log('no cheese');
else if (options.cheese === true) console.log('add cheese');
else console.log(`add cheese type ${options.cheese}`);

// Try the following:
// node options-boolean-or-value
// node options-boolean-or-value --cheese
// node options-boolean-or-value --cheese mozzarella
30 changes: 10 additions & 20 deletions examples/options-common.js
Original file line number Diff line number Diff line change
@@ -2,23 +2,6 @@

// This is used as an example in the README for:
// Common option types, boolean and value
// The two most used option types are a boolean flag, and an option which takes a value (declared using angle brackets).
//
// Example output pretending command called pizza-options (or try directly with `node options-common.js`)
//
// $ pizza-options -d
// { debug: true, small: undefined, pizzaType: undefined }
// pizza details:
// $ pizza-options -p
// error: option '-p, --pizza-type <type>' argument missing
// $ pizza-options -ds -p vegetarian
// { debug: true, small: true, pizzaType: 'vegetarian' }
// pizza details:
// - small pizza size
// - vegetarian
// $ pizza-options --pizza-type=cheese
// pizza details:
// - cheese

// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
@@ -31,7 +14,14 @@ program

program.parse(process.argv);

if (program.debug) console.log(program.opts());
const options = program.opts();
if (options.debug) console.log(options);
console.log('pizza details:');
if (program.small) console.log('- small pizza size');
if (program.pizzaType) console.log(`- ${program.pizzaType}`);
if (options.small) console.log('- small pizza size');
if (options.pizzaType) console.log(`- ${options.pizzaType}`);

// Try the following:
// node options-common.js -d
// node options-common.js -p
// node options-common.js -ds -p vegetarian
// node options-common.js --pizza-type=cheese
42 changes: 21 additions & 21 deletions examples/options-custom-processing.js
Original file line number Diff line number Diff line change
@@ -2,28 +2,19 @@

// This is used as an example in the README for:
// Custom option processing
// You may specify a function to do custom processing of option values. ...
//
// Example output pretending command called custom (or try directly with `node options-custom-processing.js`)
//
// $ custom -f 1e2
// float: 100
// $ custom --integer 2
// integer: 2
// $ custom -v -v -v
// verbose: 3
// $ custom -c a -c b -c c
// [ 'a', 'b', 'c' ]
// $ custom --list x,y,z
// [ 'x', 'y', 'z' ]
// You may specify a function to do custom processing of option values.

// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
const program = new commander.Command();

function myParseInt(value, dummyPrevious) {
// parseInt takes a string and a radix
return parseInt(value, 10);
const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) {
throw new commander.InvalidOptionArgumentError('Not a number.');
}
return parsedValue;
}

function increaseVerbosity(dummyValue, previous) {
@@ -46,9 +37,18 @@ program
.option('-l, --list <items>', 'comma separated list', commaSeparatedList)
;

program.parse(process.argv);
if (program.float !== undefined) console.log(`float: ${program.float}`);
if (program.integer !== undefined) console.log(`integer: ${program.integer}`);
if (program.verbose > 0) console.log(`verbosity: ${program.verbose}`);
if (program.collect.length > 0) console.log(program.collect);
if (program.list !== undefined) console.log(program.list);
program.parse();

const options = program.opts();
if (options.float !== undefined) console.log(`float: ${options.float}`);
if (options.integer !== undefined) console.log(`integer: ${options.integer}`);
if (options.verbose > 0) console.log(`verbosity: ${options.verbose}`);
if (options.collect.length > 0) console.log(options.collect);
if (options.list !== undefined) console.log(options.list);

// Try the following:
// node options-custom-processing -f 1e2
// node options-custom-processing --integer 2
// node options-custom-processing -v -v -v
// node options-custom-processing -c a -c b -c c
// node options-custom-processing --list x,y,z
16 changes: 6 additions & 10 deletions examples/options-defaults.js
Original file line number Diff line number Diff line change
@@ -2,14 +2,6 @@

// This is used as an example in the README for:
// Default option value
// You can specify a default value for an option which takes a value.
//
// Example output pretending command called pizza-options (or try directly with `node options-defaults.js`)
//
// $ pizza-options
// cheese: blue
// $ pizza-options --cheese stilton
// cheese: stilton

// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
@@ -18,6 +10,10 @@ const program = new commander.Command();
program
.option('-c, --cheese <type>', 'Add the specified type of cheese', 'blue');

program.parse(process.argv);
program.parse();

console.log(`cheese: ${program.cheese}`);
console.log(`cheese: ${program.opts().cheese}`);

// Try the following:
// node options-defaults.js
// node options-defaults.js --cheese stilton
20 changes: 20 additions & 0 deletions examples/options-extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/usr/bin/env node

// This is used as an example in the README for extra option features.

// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
const program = new commander.Command();

program
.addOption(new commander.Option('-s, --secret').hideHelp())
.addOption(new commander.Option('-t, --timeout <delay>', 'timeout in seconds').default(60, 'one minute'))
.addOption(new commander.Option('-d, --drink <size>', 'drink cup size').choices(['small', 'medium', 'large']));

program.parse();

console.log('Options: ', program.opts());

// Try the following:
// node options-extra.js --help
// node options-extra.js --drink huge
24 changes: 10 additions & 14 deletions examples/options-negatable.js
Original file line number Diff line number Diff line change
@@ -3,17 +3,6 @@
// This is used as an example in the README for:
// Other option types, negatable boolean
// You can specify a boolean option long name with a leading `no-` to make it true by default and able to be negated.
//
// Example output pretending command called pizza-options (or try directly with `node options-negatable.js`)
//
// $ pizza-options
// You ordered a pizza with sauce and mozzarella cheese
// $ pizza-options --sauce
// error: unknown option '--sauce'
// $ pizza-options --cheese=blue
// You ordered a pizza with sauce and blue cheese
// $ pizza-options --no-sauce --no-cheese
// You ordered a pizza with no sauce and no cheese

// const commander = require('commander'); // (normal include)
const commander = require('../'); // include commander in git clone of commander repo
@@ -24,8 +13,15 @@ program
.option('--cheese <flavour>', 'cheese flavour', 'mozzarella')
.option('--no-cheese', 'plain with no cheese');

program.parse(process.argv);
program.parse();

const sauceStr = program.sauce ? 'sauce' : 'no sauce';
const cheeseStr = (program.cheese === false) ? 'no cheese' : `${program.cheese} cheese`;
const options = program.opts();
const sauceStr = options.sauce ? 'sauce' : 'no sauce';
const cheeseStr = (options.cheese === false) ? 'no cheese' : `${options.cheese} cheese`;
console.log(`You ordered a pizza with ${sauceStr} and ${cheeseStr}`);

// Try the following:
// node options-negatable.js
// node options-negatable.js --sauce
// node options-negatable.js --cheese=blue
// node options-negatable.js --no-sauce --no-cheese
2 changes: 1 addition & 1 deletion examples/options-required.js
Original file line number Diff line number Diff line change
@@ -17,4 +17,4 @@ const program = new commander.Command();
program
.requiredOption('-c, --cheese <type>', 'pizza must have cheese');

program.parse(process.argv);
program.parse();
23 changes: 23 additions & 0 deletions examples/pass-through-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env node

// const { Command } = require('commander'); // (normal include)
const { Command } = require('../'); // include commander in git clone of commander repo
const program = new Command();

program
.arguments('<utility> [args...]')
.passThroughOptions()
.option('-d, --dry-run')
.action((utility, args, options) => {
const action = options.dryRun ? 'Would run' : 'Running';
console.log(`${action}: ${utility} ${args.join(' ')}`);
});

program.parse();

// Try the following:
//
// node pass-through-options.js git status
// node pass-through-options.js git --version
// node pass-through-options.js --dry-run git checkout -b new-branch
// node pass-through-options.js git push --dry-run
15 changes: 5 additions & 10 deletions examples/pizza
Original file line number Diff line number Diff line change
@@ -5,20 +5,15 @@ const { Command } = require('../'); // include commander in git clone of command
const program = new Command();

program
.version('0.0.1')
.description('An application for pizzas ordering')
.description('An application for pizza ordering')
.option('-p, --peppers', 'Add peppers')
.option('-c, --cheese <type>', 'Add the specified type of cheese', 'marble')
.option('-C, --no-cheese', 'You do not want any cheese');

program.parse(process.argv);
program.parse();

const options = program.opts();
console.log('you ordered a pizza with:');
if (program.peppers) console.log(' - peppers');
if (program.pineapple) console.log(' - pineapple');
if (program.bbq) console.log(' - bbq');

const cheese = !program.cheese ? 'no' : program.cheese;

if (options.peppers) console.log(' - peppers');
const cheese = !options.cheese ? 'no' : options.cheese;
console.log(' - %s cheese', cheese);
console.log(program.args);
27 changes: 27 additions & 0 deletions examples/positional-options.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env node

// const { Command } = require('commander'); // (normal include)
const { Command } = require('../'); // include commander in git clone of commander repo
const program = new Command();

program
.enablePositionalOptions()
.option('-p, --progress');

program
.command('upload <file>')
.option('-p, --port <number>', 'port number', 80)
.action((file, options) => {
if (program.opts().progress) console.log('Starting upload...');
console.log(`Uploading ${file} to port ${options.port}`);
if (program.opts().progress) console.log('Finished upload');
});

program.parse();

// Try the following:
//
// node positional-options.js upload test.js
// node positional-options.js -p upload test.js
// node positional-options.js upload -p 8080 test.js
// node positional-options.js -p upload -p 8080 test.js
41 changes: 0 additions & 41 deletions examples/storeOptionsAsProperties-action.js

This file was deleted.

39 changes: 0 additions & 39 deletions examples/storeOptionsAsProperties-opts.js

This file was deleted.

36 changes: 0 additions & 36 deletions examples/storeOptionsAsProperties-problem.js

This file was deleted.

26 changes: 26 additions & 0 deletions examples/thank.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env node

// 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 program = new Command();

program
.arguments('<name>')
.option('-t, --title <honorific>', 'title to use before name')
.option('-d, --debug', 'display some debugging')
.action((name, options, command) => {
if (options.debug) {
console.error('Called %s with options %o', command.name(), options);
}
const title = options.title ? `${options.title} ` : '';
console.log(`Thank-you ${title}${name}`);
});

program.parse();

// Try the following:
// node thank.js John
// node thank.js Doe --title Mr
// node thank.js --debug Doe --title Mr
1,265 changes: 792 additions & 473 deletions index.js

Large diffs are not rendered by default.

2,904 changes: 1,258 additions & 1,646 deletions package-lock.json

Large diffs are not rendered by default.

27 changes: 15 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "commander",
"version": "6.2.1",
"version": "7.0.0",
"description": "the complete solution for node.js command-line programs",
"keywords": [
"commander",
@@ -22,7 +22,9 @@
"lint": "eslint index.js \"tests/**/*.js\"",
"typescript-lint": "eslint typings/*.ts",
"test": "jest && npm run test-typings",
"test-typings": "tsc -p tsconfig.json"
"test-typings": "tsc -p tsconfig.json",
"typescript-checkJS": "tsc --allowJS --checkJS index.js --noEmit",
"test-all": "npm run test && npm run lint && npm run typescript-lint && npm run typescript-checkJS"
},
"main": "index",
"files": [
@@ -31,21 +33,22 @@
],
"dependencies": {},
"devDependencies": {
"@types/jest": "^26.0.15",
"@types/node": "^14.14.2",
"@typescript-eslint/eslint-plugin": "^4.5.0",
"eslint": "^7.11.0",
"eslint-config-standard-with-typescript": "^19.0.1",
"eslint-plugin-jest": "^24.1.0",
"jest": "^26.6.0",
"standard": "^15.0.0",
"typescript": "^4.0.3"
"@types/jest": "^26.0.20",
"@types/node": "^14.14.20",
"@typescript-eslint/eslint-plugin": "^4.12.0",
"@typescript-eslint/parser": "^4.12.0",
"eslint": "^7.17.0",
"eslint-config-standard": "^16.0.2",
"eslint-plugin-jest": "^24.1.3",
"jest": "^26.6.3",
"standard": "^16.0.3",
"typescript": "^4.1.2"
},
"typings": "typings/index.d.ts",
"jest": {
"collectCoverage": true
},
"engines": {
"node": ">= 6"
"node": ">= 10"
}
}
5 changes: 3 additions & 2 deletions tests/args.literal.test.js
Original file line number Diff line number Diff line change
@@ -12,8 +12,9 @@ test('when arguments includes -- then stop processing options', () => {
.option('-b, --bar', 'add some bar');
program.parse(['node', 'test', '--foo', '--', '--bar', 'baz']);
// More than one assert, ported from legacy test
expect(program.foo).toBe(true);
expect(program.bar).toBeUndefined();
const opts = program.opts();
expect(opts.foo).toBe(true);
expect(opts.bar).toBeUndefined();
expect(program.args).toEqual(['--bar', 'baz']);
});

32 changes: 13 additions & 19 deletions tests/args.variadic.test.js
Original file line number Diff line number Diff line change
@@ -3,21 +3,6 @@ const commander = require('../');
// Testing variadic arguments. Testing all the action arguments, but could test just variadicArg.

describe('variadic argument', () => {
// Optional. Use internal knowledge to suppress output to keep test output clean.
let consoleErrorSpy;

beforeAll(() => {
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
});

afterEach(() => {
consoleErrorSpy.mockClear();
});

afterAll(() => {
consoleErrorSpy.mockRestore();
});

test('when no extra arguments specified for program then variadic arg is empty array', () => {
const actionMock = jest.fn();
const program = new commander.Command();
@@ -27,7 +12,7 @@ describe('variadic argument', () => {

program.parse(['node', 'test', 'id']);

expect(actionMock).toHaveBeenCalledWith('id', [], program);
expect(actionMock).toHaveBeenCalledWith('id', [], program.opts(), program);
});

test('when extra arguments specified for program then variadic arg is array of values', () => {
@@ -40,7 +25,7 @@ describe('variadic argument', () => {

program.parse(['node', 'test', 'id', ...extraArguments]);

expect(actionMock).toHaveBeenCalledWith('id', extraArguments, program);
expect(actionMock).toHaveBeenCalledWith('id', extraArguments, program.opts(), program);
});

test('when no extra arguments specified for command then variadic arg is empty array', () => {
@@ -52,7 +37,7 @@ describe('variadic argument', () => {

program.parse(['node', 'test', 'sub']);

expect(actionMock).toHaveBeenCalledWith([], cmd);
expect(actionMock).toHaveBeenCalledWith([], cmd.opts(), cmd);
});

test('when extra arguments specified for command then variadic arg is array of values', () => {
@@ -65,7 +50,7 @@ describe('variadic argument', () => {

program.parse(['node', 'test', 'sub', ...extraArguments]);

expect(actionMock).toHaveBeenCalledWith(extraArguments, cmd);
expect(actionMock).toHaveBeenCalledWith(extraArguments, cmd.opts(), cmd);
});

test('when program variadic argument not last then error', () => {
@@ -83,4 +68,13 @@ describe('variadic argument', () => {
program.command('sub <variadicArg...> [optionalArg]');
}).toThrow("only the last argument can be variadic 'variadicArg'");
});

test('when variadic argument then usage shows variadic', () => {
const program = new commander.Command();
program
.name('foo')
.arguments('[args...]');

expect(program.usage()).toBe('[options] [args...]');
});
});
30 changes: 8 additions & 22 deletions tests/command.action.test.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ test('when .action called then command passed to action', () => {
.command('info')
.action(actionMock);
program.parse(['node', 'test', 'info']);
expect(actionMock).toHaveBeenCalledWith(cmd);
expect(actionMock).toHaveBeenCalledWith(cmd.opts(), cmd);
});

test('when .action called then program.args only contains args', () => {
@@ -23,42 +23,28 @@ test('when .action called then program.args only contains args', () => {
expect(program.args).toEqual(['info', 'my-file']);
});

test('when .action called with extra arguments then extras also passed to action', () => {
// This is a new and undocumented behaviour for now.
// Might make this an error by default in future.
const actionMock = jest.fn();
const program = new commander.Command();
const cmd = program
.command('info <file>')
.action(actionMock);
program.parse(['node', 'test', 'info', 'my-file', 'a']);
expect(actionMock).toHaveBeenCalledWith('my-file', cmd, ['a']);
});

test('when .action on program with required argument and argument supplied then action called', () => {
const actionMock = jest.fn();
const program = new commander.Command();
program
.arguments('<file>')
.action(actionMock);
program.parse(['node', 'test', 'my-file']);
expect(actionMock).toHaveBeenCalledWith('my-file', program);
expect(actionMock).toHaveBeenCalledWith('my-file', program.opts(), program);
});

test('when .action on program with required argument and argument not supplied then action not called', () => {
// Optional. Use internal knowledge to suppress output to keep test output clean.
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
const actionMock = jest.fn();
const program = new commander.Command();
program
.exitOverride()
.configureOutput({ writeErr: () => {} })
.arguments('<file>')
.action(actionMock);
expect(() => {
program.parse(['node', 'test']);
}).toThrow();
expect(actionMock).not.toHaveBeenCalled();
consoleErrorSpy.mockRestore();
});

// Changes made in #729 to call program action handler
@@ -68,7 +54,7 @@ test('when .action on program and no arguments then action called', () => {
program
.action(actionMock);
program.parse(['node', 'test']);
expect(actionMock).toHaveBeenCalledWith(program);
expect(actionMock).toHaveBeenCalledWith(program.opts(), program);
});

test('when .action on program with optional argument supplied then action called', () => {
@@ -78,7 +64,7 @@ test('when .action on program with optional argument supplied then action called
.arguments('[file]')
.action(actionMock);
program.parse(['node', 'test', 'my-file']);
expect(actionMock).toHaveBeenCalledWith('my-file', program);
expect(actionMock).toHaveBeenCalledWith('my-file', program.opts(), program);
});

test('when .action on program without optional argument supplied then action called', () => {
@@ -88,7 +74,7 @@ test('when .action on program without optional argument supplied then action cal
.arguments('[file]')
.action(actionMock);
program.parse(['node', 'test']);
expect(actionMock).toHaveBeenCalledWith(undefined, program);
expect(actionMock).toHaveBeenCalledWith(undefined, program.opts(), program);
});

test('when .action on program with optional argument and subcommand and program argument then program action called', () => {
@@ -102,7 +88,7 @@ test('when .action on program with optional argument and subcommand and program

program.parse(['node', 'test', 'a']);

expect(actionMock).toHaveBeenCalledWith('a', program);
expect(actionMock).toHaveBeenCalledWith('a', program.opts(), program);
});

// Changes made in #1062 to allow this case
@@ -117,7 +103,7 @@ test('when .action on program with optional argument and subcommand and no progr

program.parse(['node', 'test']);

expect(actionMock).toHaveBeenCalledWith(undefined, program);
expect(actionMock).toHaveBeenCalledWith(undefined, program.opts(), program);
});

test('when action is async then can await parseAsync', async() => {
245 changes: 245 additions & 0 deletions tests/command.addHelpText.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
const commander = require('../');

// Using outputHelp to simplify testing.

describe('program calls to addHelpText', () => {
let writeSpy;

beforeAll(() => {
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
});

afterEach(() => {
writeSpy.mockClear();
});

afterAll(() => {
writeSpy.mockRestore();
});

test('when "before" string then string before built-in help', () => {
const program = new commander.Command();
program.addHelpText('before', 'text');
program.outputHelp();
expect(writeSpy).toHaveBeenNthCalledWith(1, 'text\n');
expect(writeSpy).toHaveBeenNthCalledWith(2, program.helpInformation());
});

test('when "before" function then function result before built-in help', () => {
const program = new commander.Command();
program.addHelpText('before', () => 'text');
program.outputHelp();
expect(writeSpy).toHaveBeenNthCalledWith(1, 'text\n');
expect(writeSpy).toHaveBeenNthCalledWith(2, program.helpInformation());
});

test('when "before" function returns nothing then no effect', () => {
const program = new commander.Command();
program.addHelpText('before', () => { });
program.outputHelp();
expect(writeSpy).toHaveBeenNthCalledWith(1, program.helpInformation());
});

test('when "beforeAll" string then string before built-in help', () => {
const program = new commander.Command();
program.addHelpText('beforeAll', 'text');
program.outputHelp();
expect(writeSpy).toHaveBeenNthCalledWith(1, 'text\n');
expect(writeSpy).toHaveBeenNthCalledWith(2, program.helpInformation());
});

test('when "after" string then string after built-in help', () => {
const program = new commander.Command();
program.addHelpText('after', 'text');
program.outputHelp();
expect(writeSpy).toHaveBeenNthCalledWith(1, program.helpInformation());
expect(writeSpy).toHaveBeenNthCalledWith(2, 'text\n');
});

test('when "afterAll" string then string after built-in help', () => {
const program = new commander.Command();
program.addHelpText('afterAll', 'text');
program.outputHelp();
expect(writeSpy).toHaveBeenNthCalledWith(1, program.helpInformation());
expect(writeSpy).toHaveBeenNthCalledWith(2, 'text\n');
});

test('when all the simple positions then strings in order', () => {
const program = new commander.Command();
program.addHelpText('before', 'before');
program.addHelpText('after', 'after');
program.addHelpText('beforeAll', 'beforeAll');
program.addHelpText('afterAll', 'afterAll');
program.outputHelp();
expect(writeSpy).toHaveBeenNthCalledWith(1, 'beforeAll\n');
expect(writeSpy).toHaveBeenNthCalledWith(2, 'before\n');
expect(writeSpy).toHaveBeenNthCalledWith(3, program.helpInformation());
expect(writeSpy).toHaveBeenNthCalledWith(4, 'after\n');
expect(writeSpy).toHaveBeenNthCalledWith(5, 'afterAll\n');
});

test('when "silly" position then throw', () => {
const program = new commander.Command();
expect(() => {
program.addHelpText('silly', 'text');
}).toThrow();
});
});

describe('program and subcommand calls to addHelpText', () => {
let writeSpy;

beforeAll(() => {
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
});

afterEach(() => {
writeSpy.mockClear();
});

afterAll(() => {
writeSpy.mockRestore();
});

test('when "before" on program then not called on subcommand', () => {
const program = new commander.Command();
const sub = program.command('sub');
const testMock = jest.fn();
program.addHelpText('before', testMock);
sub.outputHelp();
expect(testMock).not.toHaveBeenCalled();
});

test('when "beforeAll" on program then is called on subcommand', () => {
const program = new commander.Command();
const sub = program.command('sub');
const testMock = jest.fn();
program.addHelpText('beforeAll', testMock);
sub.outputHelp();
expect(testMock).toHaveBeenCalled();
});

test('when "after" on program then not called on subcommand', () => {
const program = new commander.Command();
const sub = program.command('sub');
const testMock = jest.fn();
program.addHelpText('after', testMock);
sub.outputHelp();
expect(testMock).not.toHaveBeenCalled();
});

test('when "afterAll" on program then is called on subcommand', () => {
const program = new commander.Command();
const sub = program.command('sub');
const testMock = jest.fn();
program.addHelpText('afterAll', testMock);
sub.outputHelp();
expect(testMock).toHaveBeenCalled();
});
});

describe('context checks with full parse', () => {
let stdoutSpy;
let stderrSpy;

beforeAll(() => {
stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
});

afterEach(() => {
stdoutSpy.mockClear();
stderrSpy.mockClear();
});

afterAll(() => {
stdoutSpy.mockRestore();
stderrSpy.mockRestore();
});

test('when help requested then text is on stdout', () => {
const program = new commander.Command();
program
.exitOverride()
.addHelpText('before', 'text');
expect(() => {
program.parse(['--help'], { from: 'user' });
}).toThrow();
expect(stdoutSpy).toHaveBeenCalledWith('text\n');
});

test('when help for error then text is on stderr', () => {
const program = new commander.Command();
program
.exitOverride()
.addHelpText('before', 'text')
.command('sub');
expect(() => {
program.parse([], { from: 'user' });
}).toThrow();
expect(stderrSpy).toHaveBeenCalledWith('text\n');
});

test('when help requested then context.error is false', () => {
let context = {};
const program = new commander.Command();
program
.exitOverride()
.addHelpText('before', (contextParam) => { context = contextParam; });
expect(() => {
program.parse(['--help'], { from: 'user' });
}).toThrow();
expect(context.error).toBe(false);
});

test('when help for error then context.error is true', () => {
let context = {};
const program = new commander.Command();
program
.exitOverride()
.addHelpText('before', (contextParam) => { context = contextParam; })
.command('sub');
expect(() => {
program.parse([], { from: 'user' });
}).toThrow();
expect(context.error).toBe(true);
});

test('when help on program then context.command is program', () => {
let context = {};
const program = new commander.Command();
program
.exitOverride()
.addHelpText('before', (contextParam) => { context = contextParam; });
expect(() => {
program.parse(['--help'], { from: 'user' });
}).toThrow();
expect(context.command).toBe(program);
});

test('when help on subcommand and "before" subcommand then context.command is subcommand', () => {
let context = {};
const program = new commander.Command();
program
.exitOverride();
const sub = program.command('sub')
.addHelpText('before', (contextParam) => { context = contextParam; });
expect(() => {
program.parse(['sub', '--help'], { from: 'user' });
}).toThrow();
expect(context.command).toBe(sub);
});

test('when help on subcommand and "beforeAll" on program then context.command is subcommand', () => {
let context = {};
const program = new commander.Command();
program
.exitOverride()
.addHelpText('beforeAll', (contextParam) => { context = contextParam; });
const sub = program.command('sub');
expect(() => {
program.parse(['sub', '--help'], { from: 'user' });
}).toThrow();
expect(context.command).toBe(sub);
});
});
9 changes: 9 additions & 0 deletions tests/command.alias.test.js
Original file line number Diff line number Diff line change
@@ -87,3 +87,12 @@ test('when set aliases then can get aliases', () => {
program.aliases(aliases);
expect(program.aliases()).toEqual(aliases);
});

test('when set alias on executable then can get alias', () => {
const program = new commander.Command();
const alias = 'abcde';
program
.command('external', 'external command')
.alias(alias);
expect(program.commands[0].alias()).toEqual(alias);
});
145 changes: 145 additions & 0 deletions tests/command.allowExcessArguments.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
const commander = require('../');

// Not testing output, just testing whether an error is detected.

describe('allowUnknownOption', () => {
// Optional. Use internal knowledge to suppress output to keep test output clean.
let writeErrorSpy;

beforeAll(() => {
writeErrorSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
});

afterEach(() => {
writeErrorSpy.mockClear();
});

afterAll(() => {
writeErrorSpy.mockRestore();
});

test('when specify excess program argument then no error by default', () => {
const program = new commander.Command();
program
.exitOverride()
.action(() => {});

expect(() => {
program.parse(['excess'], { from: 'user' });
}).not.toThrow();
});

test('when specify excess program argument and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
program
.exitOverride()
.allowExcessArguments(false)
.action(() => {});

expect(() => {
program.parse(['excess'], { from: 'user' });
}).toThrow();
});

test('when specify excess program argument and allowExcessArguments() then no error', () => {
const program = new commander.Command();
program
.exitOverride()
.allowExcessArguments()
.action(() => {});

expect(() => {
program.parse(['excess'], { from: 'user' });
}).not.toThrow();
});

test('when specify excess program argument and allowExcessArguments(true) then no error', () => {
const program = new commander.Command();
program
.exitOverride()
.allowExcessArguments(true)
.action(() => {});

expect(() => {
program.parse(['excess'], { from: 'user' });
}).not.toThrow();
});

test('when specify excess command argument then no error (by default)', () => {
const program = new commander.Command();
program
.exitOverride()
.command('sub')
.action(() => { });

expect(() => {
program.parse(['sub', 'excess'], { from: 'user' });
}).not.toThrow();
});

test('when specify excess command argument and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
program
.exitOverride()
.command('sub')
.allowUnknownOption()
.allowExcessArguments(false)
.action(() => { });

expect(() => {
program.parse(['sub', 'excess'], { from: 'user' });
}).toThrow();
});

test('when specify expected arg and allowExcessArguments(false) then no error', () => {
const program = new commander.Command();
program
.arguments('<file>')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});

expect(() => {
program.parse(['file'], { from: 'user' });
}).not.toThrow();
});

test('when specify excess after <arg> and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
program
.arguments('<file>')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});

expect(() => {
program.parse(['file', 'excess'], { from: 'user' });
}).toThrow();
});

test('when specify excess after [arg] and allowExcessArguments(false) then error', () => {
const program = new commander.Command();
program
.arguments('[file]')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});

expect(() => {
program.parse(['file', 'excess'], { from: 'user' });
}).toThrow();
});

test('when specify args for [args...] and allowExcessArguments(false) then no error', () => {
const program = new commander.Command();
program
.arguments('[files...]')
.exitOverride()
.allowExcessArguments(false)
.action(() => {});

expect(() => {
program.parse(['file1', 'file2', 'file3'], { from: 'user' });
}).not.toThrow();
});
});
Original file line number Diff line number Diff line change
@@ -4,18 +4,18 @@ const commander = require('../');

describe('allowUnknownOption', () => {
// Optional. Use internal knowledge to suppress output to keep test output clean.
let consoleErrorSpy;
let writeErrorSpy;

beforeAll(() => {
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
writeErrorSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
});

afterEach(() => {
consoleErrorSpy.mockClear();
writeErrorSpy.mockClear();
});

afterAll(() => {
consoleErrorSpy.mockRestore();
writeErrorSpy.mockRestore();
});

test('when specify unknown program option then error', () => {
@@ -29,7 +29,19 @@ describe('allowUnknownOption', () => {
}).toThrow();
});

test('when specify unknown program option and allowUnknownOption then no error', () => {
test('when specify unknown program option and allowUnknownOption(false) then error', () => {
const program = new commander.Command();
program
.exitOverride()
.allowUnknownOption(false)
.option('-p, --pepper', 'add pepper');

expect(() => {
program.parse(['node', 'test', '-m']);
}).toThrow();
});

test('when specify unknown program option and allowUnknownOption() then no error', () => {
const program = new commander.Command();
program
.exitOverride()
@@ -41,6 +53,18 @@ describe('allowUnknownOption', () => {
}).not.toThrow();
});

test('when specify unknown program option and allowUnknownOption(true) then no error', () => {
const program = new commander.Command();
program
.exitOverride()
.allowUnknownOption(true)
.option('-p, --pepper', 'add pepper');

expect(() => {
program.parse(['node', 'test', '-m']);
}).not.toThrow();
});

test('when specify unknown command option then error', () => {
const program = new commander.Command();
program
@@ -59,6 +83,7 @@ describe('allowUnknownOption', () => {
program
.exitOverride()
.command('sub')
.arguments('[args...]') // unknown option will be passed as an argument
.allowUnknownOption()
.option('-p, --pepper', 'add pepper')
.action(() => { });
15 changes: 4 additions & 11 deletions tests/command.asterisk.test.js
Original file line number Diff line number Diff line change
@@ -11,18 +11,8 @@ const commander = require('../');
// Historical: the event 'command:*' used to also be shared by the action handler on the program.

describe(".command('*')", () => {
// Use internal knowledge to suppress output to keep test output clean.
let writeMock;

beforeAll(() => {
writeMock = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
});

afterAll(() => {
writeMock.mockRestore();
});

test('when no arguments then asterisk action not called', () => {
const writeMock = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
const mockAction = jest.fn();
const program = new commander.Command();
program
@@ -35,13 +25,15 @@ describe(".command('*')", () => {
;
}
expect(mockAction).not.toHaveBeenCalled();
writeMock.mockRestore();
});

test('when unrecognised argument then asterisk action called', () => {
const mockAction = jest.fn();
const program = new commander.Command();
program
.command('*')
.arguments('[args...]')
.action(mockAction);
program.parse(['node', 'test', 'unrecognised-command']);
expect(mockAction).toHaveBeenCalled();
@@ -67,6 +59,7 @@ describe(".command('*')", () => {
.command('install');
program
.command('*')
.arguments('[args...]')
.action(mockAction);
program.parse(['node', 'test', 'unrecognised-command']);
expect(mockAction).toHaveBeenCalled();
156 changes: 156 additions & 0 deletions tests/command.chain.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
const { Command, Option } = require('../');

// Testing the functions which should chain.
// parse and parseAsync are tested in command.parse.test.js

describe('Command methods that should return this for chaining', () => {
test('when call .command() with description for stand-alone executable then returns this', () => {
const program = new Command();
const result = program.command('foo', 'foo description');
expect(result).toBe(program);
});

test('when call .addCommand() then returns this', () => {
const program = new Command();
const result = program.addCommand(new Command('name'));
expect(result).toBe(program);
});

test('when set .arguments() then returns this', () => {
const program = new Command();
const result = program.arguments('<file>');
expect(result).toBe(program);
});

test('when call .addHelpCommand() then returns this', () => {
const program = new Command();
const result = program.addHelpCommand(false);
expect(result).toBe(program);
});

test('when call .exitOverride() then returns this', () => {
const program = new Command();
const result = program.exitOverride(() => { });
expect(result).toBe(program);
});

test('when call .action() then returns this', () => {
const program = new Command();
const result = program.action(() => { });
expect(result).toBe(program);
});

test('when call .addOption() then returns this', () => {
const program = new Command();
const result = program.addOption(new Option('-e'));
expect(result).toBe(program);
});

test('when call .option() then returns this', () => {
const program = new Command();
const result = program.option('-e');
expect(result).toBe(program);
});

test('when call .requiredOption() then returns this', () => {
const program = new Command();
const result = program.requiredOption('-r');
expect(result).toBe(program);
});

test('when call .combineFlagAndOptionalValue() then returns this', () => {
const program = new Command();
const result = program.combineFlagAndOptionalValue();
expect(result).toBe(program);
});

test('when call .allowUnknownOption() then returns this', () => {
const program = new Command();
const result = program.allowUnknownOption();
expect(result).toBe(program);
});

test('when call .allowExcessArguments() then returns this', () => {
const program = new Command();
const result = program.allowExcessArguments();
expect(result).toBe(program);
});

test('when call .storeOptionsAsProperties() then returns this', () => {
const program = new Command();
const result = program.storeOptionsAsProperties();
expect(result).toBe(program);
});

test('when call .version() then returns this', () => {
const program = new Command();
const result = program.version('1.2.3');
expect(result).toBe(program);
});

test('when set .description() then returns this', () => {
const program = new Command();
const result = program.description('description');
expect(result).toBe(program);
});

test('when set .alias() then returns this', () => {
const program = new Command();
const result = program.alias('alias');
expect(result).toBe(program);
});

test('when set .aliases() then returns this', () => {
const program = new Command();
const result = program.aliases(['foo', 'bar']);
expect(result).toBe(program);
});

test('when set .usage() then returns this', () => {
const program = new Command();
const result = program.usage('[options]');
expect(result).toBe(program);
});

test('when set .name() then returns this', () => {
const program = new Command();
const result = program.name('easy');
expect(result).toBe(program);
});

test('when call .helpOption() then returns this', () => {
const program = new Command();
const result = program.helpOption(false);
expect(result).toBe(program);
});

test('when call .addHelpText() then returns this', () => {
const program = new Command();
const result = program.addHelpText('before', 'example');
expect(result).toBe(program);
});

test('when call .configureHelp() then returns this', () => {
const program = new Command();
const result = program.configureHelp({ });
expect(result).toBe(program);
});

test('when call .configureOutput() then returns this', () => {
const program = new Command();
const result = program.configureOutput({ });
expect(result).toBe(program);
});

test('when call .passThroughOptions() then returns this', () => {
const program = new Command();
const result = program.passThroughOptions();
expect(result).toBe(program);
});

test('when call .enablePositionalOptions() then returns this', () => {
const program = new Command();
const result = program.enablePositionalOptions();
expect(result).toBe(program);
});
});
10 changes: 5 additions & 5 deletions tests/command.commandHelp.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
const commander = require('../');

// Note: .commandHelp is not currently documented in the README. This is a ported legacy test.
// This is a ported legacy test.

test('when program has command then appears in commandHelp', () => {
test('when program has command then appears in help', () => {
const program = new commander.Command();
program
.command('bare');
const commandHelp = program.commandHelp();
const commandHelp = program.helpInformation();
expect(commandHelp).toMatch(/Commands:\n +bare\n/);
});

test('when program has command with optional arg then appears in commandHelp', () => {
test('when program has command with optional arg then appears in help', () => {
const program = new commander.Command();
program
.command('bare [bare-arg]');
const commandHelp = program.commandHelp();
const commandHelp = program.helpInformation();
expect(commandHelp).toMatch(/Commands:\n +bare \[bare-arg\]\n/);
});
31 changes: 31 additions & 0 deletions tests/command.configureHelp.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const commander = require('../');

test('when configure program then affects program helpInformation', () => {
const program = new commander.Command();
program.configureHelp({ formatHelp: () => { return 'custom'; } });
expect(program.helpInformation()).toEqual('custom');
});

test('when configure program then affects subcommand helpInformation', () => {
const program = new commander.Command();
program.configureHelp({ formatHelp: () => { return 'custom'; } });
const sub = program.command('sub');
expect(sub.helpInformation()).toEqual('custom');
});

test('when configure with unknown property then createHelp has unknown property', () => {
const program = new commander.Command();
program.configureHelp({ mySecretValue: 'secret' });
expect(program.createHelp().mySecretValue).toEqual('secret');
});

test('when configure with unknown property then helper passed to formatHelp has unknown property', () => {
const program = new commander.Command();
program.configureHelp({
mySecretValue: 'secret',
formatHelp: (cmd, helper) => {
return helper.mySecretValue;
}
});
expect(program.helpInformation()).toEqual('secret');
});
275 changes: 275 additions & 0 deletions tests/command.configureOutput.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
const commander = require('../');

test('when default writeErr() then error on stderr', () => {
const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
const program = new commander.Command();
program.exitOverride();

try {
program.parse(['--unknown'], { from: 'user' });
} catch (err) {
}

expect(writeSpy).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when custom writeErr() then error on custom output', () => {
const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
const customWrite = jest.fn();
const program = new commander.Command();
program
.exitOverride()
.configureOutput({ writeErr: customWrite });

try {
program.parse(['--unknown'], { from: 'user' });
} catch (err) {
}

expect(writeSpy).toHaveBeenCalledTimes(0);
expect(customWrite).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when default write() then version on stdout', () => {
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const program = new commander.Command();
program
.exitOverride()
.version('1.2.3');

expect(() => {
program.parse(['--version'], { from: 'user' });
}).toThrow();

expect(writeSpy).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when custom write() then version on custom output', () => {
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const customWrite = jest.fn();
const program = new commander.Command();
program
.exitOverride()
.version('1.2.3')
.configureOutput({ writeOut: customWrite });

expect(() => {
program.parse(['--version'], { from: 'user' });
}).toThrow();

expect(writeSpy).toHaveBeenCalledTimes(0);
expect(customWrite).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when default write() then help on stdout', () => {
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const program = new commander.Command();
program.outputHelp();

expect(writeSpy).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when custom write() then help error on custom output', () => {
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const customWrite = jest.fn();
const program = new commander.Command();
program.configureOutput({ writeOut: customWrite });
program.outputHelp();

expect(writeSpy).toHaveBeenCalledTimes(0);
expect(customWrite).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when default writeErr then help error on stderr', () => {
const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
const program = new commander.Command();
program.outputHelp({ error: true });

expect(writeSpy).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when custom writeErr then help error on custom output', () => {
const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
const customWrite = jest.fn();
const program = new commander.Command();
program.configureOutput({ writeErr: customWrite });
program.outputHelp({ error: true });

expect(writeSpy).toHaveBeenCalledTimes(0);
expect(customWrite).toHaveBeenCalledTimes(1);
writeSpy.mockRestore();
});

test('when default getOutHelpWidth then help helpWidth from stdout', () => {
const expectedColumns = 123;
const holdIsTTY = process.stdout.isTTY;
const holdColumns = process.stdout.columns;
let helpWidth;

process.stderr.isTTY = true;
process.stdout.columns = expectedColumns;
process.stdout.isTTY = true;
const program = new commander.Command();
program
.configureHelp({
formatHelp: (cmd, helper) => {
helpWidth = helper.helpWidth;
return '';
}
});
program.outputHelp();

expect(helpWidth).toBe(expectedColumns);
process.stdout.columns = holdColumns;
process.stdout.isTTY = holdIsTTY;
});

test('when custom getOutHelpWidth then help helpWidth custom', () => {
const expectedColumns = 123;
let helpWidth;

const program = new commander.Command();
program
.configureHelp({
formatHelp: (cmd, helper) => {
helpWidth = helper.helpWidth;
return '';
}
}).configureOutput({
getOutHelpWidth: () => expectedColumns
});
program.outputHelp();

expect(helpWidth).toBe(expectedColumns);
});

test('when default getErrHelpWidth then help error helpWidth from stderr', () => {
const expectedColumns = 123;
const holdIsTTY = process.stderr.isTTY;
const holdColumns = process.stderr.columns;
let helpWidth;

process.stderr.isTTY = true;
process.stderr.columns = expectedColumns;
const program = new commander.Command();
program
.configureHelp({
formatHelp: (cmd, helper) => {
helpWidth = helper.helpWidth;
return '';
}
});
program.outputHelp({ error: true });

expect(helpWidth).toBe(expectedColumns);
process.stderr.isTTY = holdIsTTY;
process.stderr.columns = holdColumns;
});

test('when custom getErrHelpWidth then help error helpWidth custom', () => {
const expectedColumns = 123;
let helpWidth;

const program = new commander.Command();
program
.configureHelp({
formatHelp: (cmd, helper) => {
helpWidth = helper.helpWidth;
return '';
}
}).configureOutput({
getErrHelpWidth: () => expectedColumns
});
program.outputHelp({ error: true });

expect(helpWidth).toBe(expectedColumns);
});

test('when custom getOutHelpWidth and configureHelp:helpWidth then help helpWidth from configureHelp', () => {
const expectedColumns = 123;
let helpWidth;

const program = new commander.Command();
program
.configureHelp({
formatHelp: (cmd, helper) => {
helpWidth = helper.helpWidth;
return '';
},
helpWidth: expectedColumns
}).configureOutput({
getOutHelpWidth: () => 999
});
program.outputHelp();

expect(helpWidth).toBe(expectedColumns);
});

test('when custom getErrHelpWidth and configureHelp:helpWidth then help error helpWidth from configureHelp', () => {
const expectedColumns = 123;
let helpWidth;

const program = new commander.Command();
program
.configureHelp({
formatHelp: (cmd, helper) => {
helpWidth = helper.helpWidth;
return '';
},
helpWidth: expectedColumns
}).configureOutput({
getErrHelpWidth: () => 999
});
program.outputHelp({ error: true });

expect(helpWidth).toBe(expectedColumns);
});

test('when set configureOutput then get configureOutput', () => {
const outputOptions = {
writeOut: jest.fn(),
writeErr: jest.fn(),
getOutHelpWidth: jest.fn(),
getErrHelpWidth: jest.fn(),
outputError: jest.fn()
};
const program = new commander.Command();
program.configureOutput(outputOptions);
expect(program.configureOutput()).toEqual(outputOptions);
});

test('when custom outputErr and error then outputErr called', () => {
const outputError = jest.fn();
const program = new commander.Command();
program
.exitOverride()
.configureOutput({
outputError
});

expect(() => {
program.parse(['--unknownOption'], { from: 'user' });
}).toThrow();
expect(outputError).toHaveBeenCalledWith("error: unknown option '--unknownOption'\n", program._outputConfiguration.writeErr);
});

test('when custom outputErr and writeErr and error then outputErr passed writeErr', () => {
const writeErr = () => jest.fn();
const outputError = jest.fn();
const program = new commander.Command();
program
.exitOverride()
.configureOutput({ writeErr, outputError });

expect(() => {
program.parse(['--unknownOption'], { from: 'user' });
}).toThrow();
expect(outputError).toHaveBeenCalledWith("error: unknown option '--unknownOption'\n", writeErr);
});
18 changes: 18 additions & 0 deletions tests/command.createHelp.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const commander = require('../');

test('when override createCommand then affects help', () => {
class MyHelp extends commander.Help {
formatHelp(cmd, helper) {
return 'custom';
}
}

class MyCommand extends commander.Command {
createHelp() {
return Object.assign(new MyHelp(), this.configureHelp());
};
}

const program = new MyCommand();
expect(program.helpInformation()).toEqual('custom');
});
42 changes: 42 additions & 0 deletions tests/command.createOption.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const commander = require('../');

class MyOption extends commander.Option {
constructor(flags, description) {
super(flags, description);
this.myProperty = 'MyOption';
};
}

class MyCommand extends commander.Command {
createOption(flags, description) {
return new MyOption(flags, description);
};
}

test('when override createOption then used for option()', () => {
const program = new MyCommand();
program.option('-a, --alpha');
expect(program.options.length).toEqual(1);
expect(program.options[0].myProperty).toEqual('MyOption');
});

test('when override createOption then used for requiredOption()', () => {
const program = new MyCommand();
program.requiredOption('-a, --alpha');
expect(program.options.length).toEqual(1);
expect(program.options[0].myProperty).toEqual('MyOption');
});

test('when override createOption then used for version()', () => {
const program = new MyCommand();
program.version('1.2.3');
expect(program.options.length).toEqual(1);
expect(program.options[0].myProperty).toEqual('MyOption');
});

test('when override createOption then used for help option in visibleOptions', () => {
const program = new MyCommand();
const visibleOptions = program.createHelp().visibleOptions(program);
expect(visibleOptions.length).toEqual(1);
expect(visibleOptions[0].myProperty).toEqual('MyOption');
});
2 changes: 2 additions & 0 deletions tests/command.default.test.js
Original file line number Diff line number Diff line change
@@ -34,6 +34,7 @@ describe('default action command', () => {
program
.command('default', { isDefault: true })
.allowUnknownOption()
.allowExcessArguments()
.action(actionMock);
return { program, actionMock };
}
@@ -62,6 +63,7 @@ describe('default added command', () => {
const actionMock = jest.fn();
const defaultCmd = new commander.Command('default')
.allowUnknownOption()
.allowExcessArguments()
.action(actionMock);

const program = new commander.Command();
4 changes: 2 additions & 2 deletions tests/command.executableSubcommand.lookup.test.js
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@ test('when subcommand file missing then error', () => {
expect(err.stderr).toBeDefined();
} else {
// eslint-disable-next-line jest/no-conditional-expect
expect(err.stderr).toMatch(new RegExp(/Error: 'pm-list' does not exist/));
expect(err.stderr).toMatch(/Error: 'pm-list' does not exist/);
}
});
});
@@ -34,7 +34,7 @@ test('when alias subcommand file missing then error', () => {
expect(err.stderr).toBeDefined();
} else {
// eslint-disable-next-line jest/no-conditional-expect
expect(err.stderr).toMatch(new RegExp(/Error: 'pm-list' does not exist/));
expect(err.stderr).toMatch(/Error: 'pm-list' does not exist/);
}
});
});
4 changes: 2 additions & 2 deletions tests/command.executableSubcommand.test.js
Original file line number Diff line number Diff line change
@@ -3,9 +3,9 @@ const commander = require('../');
// Executable subcommand tests that didn't fit in elsewhere.

// This is the default behaviour when no default command and no action handlers
test('when no command missing then display help', () => {
test('when no command specified and executable then display help', () => {
// Optional. Suppress normal output to keep test output clean.
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
const program = new commander.Command();
program
.exitOverride((err) => { throw err; })
124 changes: 109 additions & 15 deletions tests/command.exitOverride.test.js
Original file line number Diff line number Diff line change
@@ -17,22 +17,18 @@ function expectCommanderError(err, exitCode, code, message) {

describe('.exitOverride and error details', () => {
// Use internal knowledge to suppress output to keep test output clean.
let consoleErrorSpy;
let writeSpy; // used for help [sic] and version
let stderrSpy;

beforeAll(() => {
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
stderrSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
});

afterEach(() => {
consoleErrorSpy.mockClear();
writeSpy.mockClear();
stderrSpy.mockClear();
});

afterAll(() => {
consoleErrorSpy.mockRestore();
writeSpy.mockRestore();
stderrSpy.mockRestore();
});

test('when specify unknown program option then throw CommanderError', () => {
@@ -47,7 +43,7 @@ describe('.exitOverride and error details', () => {
caughtErr = err;
}

expect(consoleErrorSpy).toHaveBeenCalled();
expect(stderrSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.unknownOption', "error: unknown option '-m'");
});

@@ -65,7 +61,7 @@ describe('.exitOverride and error details', () => {
caughtErr = err;
}

expect(consoleErrorSpy).toHaveBeenCalled();
expect(stderrSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.unknownCommand', "error: unknown command 'oops'. See 'prog --help'.");
});

@@ -102,7 +98,7 @@ describe('.exitOverride and error details', () => {
caughtErr = err;
}

expect(consoleErrorSpy).toHaveBeenCalled();
expect(stderrSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.optionMissingArgument', `error: option '${optionFlags}' argument missing`);
});

@@ -120,11 +116,49 @@ describe('.exitOverride and error details', () => {
caughtErr = err;
}

expect(consoleErrorSpy).toHaveBeenCalled();
expect(stderrSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.missingArgument', "error: missing required argument 'arg-name'");
});

test('when specify excess argument then throw CommanderError', () => {
const program = new commander.Command();
program
.exitOverride()
.allowExcessArguments(false)
.action(() => { });

let caughtErr;
try {
program.parse(['node', 'test', 'excess']);
} catch (err) {
caughtErr = err;
}

expect(stderrSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.excessArguments', 'error: too many arguments. Expected 0 arguments but got 1.');
});

test('when specify command with excess argument then throw CommanderError', () => {
const program = new commander.Command();
program
.exitOverride()
.command('speak')
.allowExcessArguments(false)
.action(() => { });

let caughtErr;
try {
program.parse(['node', 'test', 'speak', 'excess']);
} catch (err) {
caughtErr = err;
}

expect(stderrSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.excessArguments', "error: too many arguments for 'speak'. Expected 0 arguments but got 1.");
});

test('when specify --help then throw CommanderError', () => {
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const program = new commander.Command();
program
.exitOverride();
@@ -136,8 +170,8 @@ describe('.exitOverride and error details', () => {
caughtErr = err;
}

expect(writeSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 0, 'commander.helpDisplayed', '(outputHelp)');
writeSpy.mockRestore();
});

test('when executable subcommand and no command specified then throw CommanderError', () => {
@@ -153,11 +187,11 @@ describe('.exitOverride and error details', () => {
caughtErr = err;
}

expect(writeSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 1, 'commander.help', '(outputHelp)');
});

test('when specify --version then throw CommanderError', () => {
const stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const myVersion = '1.2.3';
const program = new commander.Command();
program
@@ -171,8 +205,8 @@ describe('.exitOverride and error details', () => {
caughtErr = err;
}

expect(writeSpy).toHaveBeenCalled();
expectCommanderError(caughtErr, 0, 'commander.version', myVersion);
stdoutSpy.mockRestore();
});

test('when executableSubcommand succeeds then call exitOverride', async() => {
@@ -206,4 +240,64 @@ describe('.exitOverride and error details', () => {

expectCommanderError(caughtErr, 1, 'commander.missingMandatoryOptionValue', `error: required option '${optionFlags}' not specified`);
});

test('when option argument not in choices then throw CommanderError', () => {
const optionFlags = '--colour <shade>';
const program = new commander.Command();
program
.exitOverride()
.addOption(new commander.Option(optionFlags).choices(['red', 'blue']));

let caughtErr;
try {
program.parse(['--colour', 'green'], { from: 'user' });
} catch (err) {
caughtErr = err;
}

expectCommanderError(caughtErr, 1, 'commander.invalidOptionArgument', "error: option '--colour <shade>' argument 'green' is invalid. Allowed choices are red, blue.");
});

test('when custom processing throws InvalidOptionArgumentError then throw CommanderError', () => {
function justSayNo(value) {
throw new commander.InvalidOptionArgumentError('NO');
}
const optionFlags = '--colour <shade>';
const program = new commander.Command();
program
.exitOverride()
.option(optionFlags, 'specify shade', justSayNo);

let caughtErr;
try {
program.parse(['--colour', 'green'], { from: 'user' });
} catch (err) {
caughtErr = err;
}

expectCommanderError(caughtErr, 1, 'commander.invalidOptionArgument', "error: option '--colour <shade>' argument 'green' is invalid. NO");
});
});

test('when no override and error then exit(1)', () => {
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => { });
const program = new commander.Command();
program.configureOutput({ outputError: () => {} });
program.parse(['--unknownOption'], { from: 'user' });
expect(exitSpy).toHaveBeenCalledWith(1);
exitSpy.mockRestore();
});

test('when custom processing throws custom error then throw custom error', () => {
function justSayNo(value) {
throw new Error('custom');
}
const program = new commander.Command();
program
.exitOverride()
.option('-s, --shade <value>', 'specify shade', justSayNo);

expect(() => {
program.parse(['--shade', 'green'], { from: 'user' });
}).toThrow('custom');
});
101 changes: 98 additions & 3 deletions tests/command.help.test.js
Original file line number Diff line number Diff line change
@@ -88,8 +88,14 @@ test('when call outputHelp(cb) then display cb output', () => {
writeSpy.mockClear();
});

// noHelp is now named hidden, not officially deprecated yet
test('when command sets noHelp then not displayed in helpInformation', () => {
test('when call deprecated outputHelp(cb) with wrong callback return type then throw', () => {
const program = new commander.Command();
expect(() => {
program.outputHelp((helpInformation) => 3);
}).toThrow();
});

test('when command sets deprecated noHelp then not displayed in helpInformation', () => {
const program = new commander.Command();
program
.command('secret', 'secret description', { noHelp: true });
@@ -131,7 +137,31 @@ test('when both help flags masked then not displayed in helpInformation', () =>
expect(helpInformation).not.toMatch('display help');
});

test('when no options then Options not includes in helpInformation', () => {
test('when call .help then output on stdout', () => {
const writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
const program = new commander.Command();
program
.exitOverride();
expect(() => {
program.help();
}).toThrow('(outputHelp)');
expect(writeSpy).toHaveBeenCalledWith(program.helpInformation());
writeSpy.mockClear();
});

test('when call .help with { error: true } then output on stderr', () => {
const writeSpy = jest.spyOn(process.stderr, 'write').mockImplementation(() => { });
const program = new commander.Command();
program
.exitOverride();
expect(() => {
program.help({ error: true });
}).toThrow('(outputHelp)');
expect(writeSpy).toHaveBeenCalledWith(program.helpInformation());
writeSpy.mockClear();
});

test('when no options then Options not included in helpInformation', () => {
const program = new commander.Command();
// No custom options, no version option, no help option
program
@@ -140,6 +170,71 @@ test('when no options then Options not includes in helpInformation', () => {
expect(helpInformation).not.toMatch('Options');
});

test('when negated option then option included in helpInformation', () => {
const program = new commander.Command();
program
.option('-C, --no-colour', 'colourless');
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch('--no-colour');
expect(helpInformation).toMatch('colourless');
});

test('when option.hideHelp() then option not included in helpInformation', () => {
const program = new commander.Command();
program
.addOption(new commander.Option('-s,--secret', 'secret option').hideHelp());
const helpInformation = program.helpInformation();
expect(helpInformation).not.toMatch('secret');
});

test('when option.hideHelp(true) then option not included in helpInformation', () => {
const program = new commander.Command();
program
.addOption(new commander.Option('-s,--secret', 'secret option').hideHelp(true));
const helpInformation = program.helpInformation();
expect(helpInformation).not.toMatch('secret');
});

test('when option.hideHelp(false) then option included in helpInformation', () => {
const program = new commander.Command();
program
.addOption(new commander.Option('-s,--secret', 'secret option').hideHelp(false));
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch('secret');
});

test('when option has default value then default included in helpInformation', () => {
const program = new commander.Command();
program
.option('-p, --port <portNumber>', 'port number', 80);
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch('(default: 80)');
});

test('when option has default value description then default description included in helpInformation', () => {
const program = new commander.Command();
program
.addOption(new commander.Option('-a, --address <dotted>', 'ip address').default('127.0.0.1', 'home'));
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch('(default: home)');
});

test('when option has choices then choices included in helpInformation', () => {
const program = new commander.Command();
program
.addOption(new commander.Option('-c, --colour <colour>').choices(['red', 'blue']));
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch('(choices: "red", "blue")');
});

test('when option has choices and default then both included in helpInformation', () => {
const program = new commander.Command();
program
.addOption(new commander.Option('-c, --colour <colour>').choices(['red', 'blue']).default('red'));
const helpInformation = program.helpInformation();
expect(helpInformation).toMatch('(choices: "red", "blue", default: "red")');
});

test('when arguments then included in helpInformation', () => {
const program = new commander.Command();
program
Loading