commander 11.1.0 → 12.0.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Readme.md CHANGED
@@ -37,7 +37,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
37
37
  - [.usage](#usage)
38
38
  - [.description and .summary](#description-and-summary)
39
39
  - [.helpOption(flags, description)](#helpoptionflags-description)
40
- - [.addHelpCommand()](#addhelpcommand)
40
+ - [.helpCommand()](#helpcommand)
41
41
  - [More configuration](#more-configuration-2)
42
42
  - [Custom event listeners](#custom-event-listeners)
43
43
  - [Bits and pieces](#bits-and-pieces)
@@ -904,16 +904,20 @@ program
904
904
  .helpOption('-e, --HELP', 'read more information');
905
905
  ```
906
906
 
907
- ### .addHelpCommand()
907
+ (Or use `.addHelpOption()` to add an option you construct yourself.)
908
908
 
909
- A help command is added by default if your command has subcommands. You can explicitly turn on or off the implicit help command with `.addHelpCommand()` and `.addHelpCommand(false)`.
909
+ ### .helpCommand()
910
+
911
+ A help command is added by default if your command has subcommands. You can explicitly turn on or off the implicit help command with `.helpCommand(true)` and `.helpCommand(false)`.
910
912
 
911
913
  You can both turn on and customise the help command by supplying the name and description:
912
914
 
913
915
  ```js
914
- program.addHelpCommand('assist [command]', 'show assistance');
916
+ program.helpCommand('assist [command]', 'show assistance');
915
917
  ```
916
918
 
919
+ (Or use `.addHelpCommand()` to add a command you construct yourself.)
920
+
917
921
  ### More configuration
918
922
 
919
923
  The built-in help is formatted using the Help class.
@@ -967,6 +971,8 @@ program.parse(); // Implicit, and auto-detect electron
967
971
  program.parse(['-f', 'filename'], { from: 'user' });
968
972
  ```
969
973
 
974
+ If you want to parse multiple times, create a new program each time. Calling parse does not clear out any previous state.
975
+
970
976
  ### Parsing Configuration
971
977
 
972
978
  If the default parsing does not suit your needs, there are some behaviours to support other usage patterns.
@@ -1136,7 +1142,7 @@ There is more information available about:
1136
1142
 
1137
1143
  ## Support
1138
1144
 
1139
- The current version of Commander is fully supported on Long Term Support versions of Node.js, and requires at least v16.
1145
+ The current version of Commander is fully supported on Long Term Support versions of Node.js, and requires at least v18.
1140
1146
  (For older versions of Node.js, use an older version of Commander.)
1141
1147
 
1142
1148
  The main forum for free and community support is the project [Issues](https://github.com/tj/commander.js/issues) on GitHub.
package/index.js CHANGED
@@ -4,13 +4,11 @@ const { CommanderError, InvalidArgumentError } = require('./lib/error.js');
4
4
  const { Help } = require('./lib/help.js');
5
5
  const { Option } = require('./lib/option.js');
6
6
 
7
- /**
8
- * Expose the root command.
9
- */
7
+ exports.program = new Command();
10
8
 
11
- exports = module.exports = new Command();
12
- exports.program = exports; // More explicit access to global command.
13
- // createArgument, createCommand, and createOption are implicitly available as they are methods on program.
9
+ exports.createCommand = (name) => new Command(name);
10
+ exports.createOption = (flags, description) => new Option(flags, description);
11
+ exports.createArgument = (name, description) => new Argument(name, description);
14
12
 
15
13
  /**
16
14
  * Expose classes
package/lib/argument.js CHANGED
@@ -50,7 +50,7 @@ class Argument {
50
50
  }
51
51
 
52
52
  /**
53
- * @api private
53
+ * @package internal use only
54
54
  */
55
55
 
56
56
  _concatValue(value, previous) {
@@ -130,7 +130,7 @@ class Argument {
130
130
  *
131
131
  * @param {Argument} arg
132
132
  * @return {string}
133
- * @api private
133
+ * @private
134
134
  */
135
135
 
136
136
  function humanReadableArgName(arg) {
package/lib/command.js CHANGED
@@ -7,7 +7,7 @@ const process = require('process');
7
7
  const { Argument, humanReadableArgName } = require('./argument.js');
8
8
  const { CommanderError } = require('./error.js');
9
9
  const { Help } = require('./help.js');
10
- const { Option, splitOptionFlags, DualOptions } = require('./option.js');
10
+ const { Option, DualOptions } = require('./option.js');
11
11
  const { suggestSimilar } = require('./suggestSimilar');
12
12
 
13
13
  class Command extends EventEmitter {
@@ -52,7 +52,7 @@ class Command extends EventEmitter {
52
52
  this._enablePositionalOptions = false;
53
53
  this._passThroughOptions = false;
54
54
  this._lifeCycleHooks = {}; // a hash of arrays
55
- /** @type {boolean | string} */
55
+ /** @type {(boolean | string)} */
56
56
  this._showHelpAfterError = false;
57
57
  this._showSuggestionAfterError = true;
58
58
 
@@ -66,15 +66,11 @@ class Command extends EventEmitter {
66
66
  };
67
67
 
68
68
  this._hidden = false;
69
- this._hasHelpOption = true;
70
- this._helpFlags = '-h, --help';
71
- this._helpDescription = 'display help for command';
72
- this._helpShortFlag = '-h';
73
- this._helpLongFlag = '--help';
74
- this._addImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false
75
- this._helpCommandName = 'help';
76
- this._helpCommandnameAndArgs = 'help [command]';
77
- this._helpCommandDescription = 'display help for command';
69
+ /** @type {(Option | null | undefined)} */
70
+ this._helpOption = undefined; // Lazy created on demand. May be null if help option is disabled.
71
+ this._addImplicitHelpCommand = undefined; // undecided whether true or false yet, not inherited
72
+ /** @type {Command} */
73
+ this._helpCommand = undefined; // lazy initialised, inherited
78
74
  this._helpConfiguration = {};
79
75
  }
80
76
 
@@ -88,14 +84,8 @@ class Command extends EventEmitter {
88
84
  */
89
85
  copyInheritedSettings(sourceCommand) {
90
86
  this._outputConfiguration = sourceCommand._outputConfiguration;
91
- this._hasHelpOption = sourceCommand._hasHelpOption;
92
- this._helpFlags = sourceCommand._helpFlags;
93
- this._helpDescription = sourceCommand._helpDescription;
94
- this._helpShortFlag = sourceCommand._helpShortFlag;
95
- this._helpLongFlag = sourceCommand._helpLongFlag;
96
- this._helpCommandName = sourceCommand._helpCommandName;
97
- this._helpCommandnameAndArgs = sourceCommand._helpCommandnameAndArgs;
98
- this._helpCommandDescription = sourceCommand._helpCommandDescription;
87
+ this._helpOption = sourceCommand._helpOption;
88
+ this._helpCommand = sourceCommand._helpCommand;
99
89
  this._helpConfiguration = sourceCommand._helpConfiguration;
100
90
  this._exitCallback = sourceCommand._exitCallback;
101
91
  this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;
@@ -110,7 +100,7 @@ class Command extends EventEmitter {
110
100
 
111
101
  /**
112
102
  * @returns {Command[]}
113
- * @api private
103
+ * @private
114
104
  */
115
105
 
116
106
  _getCommandAndAncestors() {
@@ -141,7 +131,7 @@ class Command extends EventEmitter {
141
131
  * .command('stop [service]', 'stop named service, or all if no name supplied');
142
132
  *
143
133
  * @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
144
- * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
134
+ * @param {(Object|string)} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
145
135
  * @param {Object} [execOpts] - configuration options (for executable)
146
136
  * @return {Command} returns new command for action handler, or `this` for executable command
147
137
  */
@@ -165,7 +155,7 @@ class Command extends EventEmitter {
165
155
  cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden
166
156
  cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
167
157
  if (args) cmd.arguments(args);
168
- this.commands.push(cmd);
158
+ this._registerCommand(cmd);
169
159
  cmd.parent = this;
170
160
  cmd.copyInheritedSettings(this);
171
161
 
@@ -203,7 +193,7 @@ class Command extends EventEmitter {
203
193
  * or with a subclass of Help by overriding createHelp().
204
194
  *
205
195
  * @param {Object} [configuration] - configuration options
206
- * @return {Command|Object} `this` command for chaining, or stored configuration
196
+ * @return {(Command|Object)} `this` command for chaining, or stored configuration
207
197
  */
208
198
 
209
199
  configureHelp(configuration) {
@@ -229,7 +219,7 @@ class Command extends EventEmitter {
229
219
  * outputError(str, write) // used for displaying errors, and not used for displaying help
230
220
  *
231
221
  * @param {Object} [configuration] - configuration options
232
- * @return {Command|Object} `this` command for chaining, or stored configuration
222
+ * @return {(Command|Object)} `this` command for chaining, or stored configuration
233
223
  */
234
224
 
235
225
  configureOutput(configuration) {
@@ -242,7 +232,7 @@ class Command extends EventEmitter {
242
232
  /**
243
233
  * Display the help or a custom message after an error occurs.
244
234
  *
245
- * @param {boolean|string} [displayHelp]
235
+ * @param {(boolean|string)} [displayHelp]
246
236
  * @return {Command} `this` command for chaining
247
237
  */
248
238
  showHelpAfterError(displayHelp = true) {
@@ -282,8 +272,10 @@ class Command extends EventEmitter {
282
272
  if (opts.isDefault) this._defaultCommandName = cmd._name;
283
273
  if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
284
274
 
285
- this.commands.push(cmd);
275
+ this._registerCommand(cmd);
286
276
  cmd.parent = this;
277
+ cmd._checkForBrokenPassThrough();
278
+
287
279
  return this;
288
280
  }
289
281
 
@@ -314,7 +306,7 @@ class Command extends EventEmitter {
314
306
  *
315
307
  * @param {string} name
316
308
  * @param {string} [description]
317
- * @param {Function|*} [fn] - custom argument processing function
309
+ * @param {(Function|*)} [fn] - custom argument processing function
318
310
  * @param {*} [defaultValue]
319
311
  * @return {Command} `this` command for chaining
320
312
  */
@@ -367,39 +359,76 @@ class Command extends EventEmitter {
367
359
  }
368
360
 
369
361
  /**
370
- * Override default decision whether to add implicit help command.
362
+ * Customise or override default help command. By default a help command is automatically added if your command has subcommands.
371
363
  *
372
- * addHelpCommand() // force on
373
- * addHelpCommand(false); // force off
374
- * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
364
+ * program.helpCommand('help [cmd]');
365
+ * program.helpCommand('help [cmd]', 'show help');
366
+ * program.helpCommand(false); // suppress default help command
367
+ * program.helpCommand(true); // add help command even if no subcommands
375
368
  *
369
+ * @param {string|boolean} enableOrNameAndArgs - enable with custom name and/or arguments, or boolean to override whether added
370
+ * @param {string} [description] - custom description
376
371
  * @return {Command} `this` command for chaining
377
372
  */
378
373
 
379
- addHelpCommand(enableOrNameAndArgs, description) {
380
- if (enableOrNameAndArgs === false) {
381
- this._addImplicitHelpCommand = false;
382
- } else {
383
- this._addImplicitHelpCommand = true;
384
- if (typeof enableOrNameAndArgs === 'string') {
385
- this._helpCommandName = enableOrNameAndArgs.split(' ')[0];
386
- this._helpCommandnameAndArgs = enableOrNameAndArgs;
387
- }
388
- this._helpCommandDescription = description || this._helpCommandDescription;
374
+ helpCommand(enableOrNameAndArgs, description) {
375
+ if (typeof enableOrNameAndArgs === 'boolean') {
376
+ this._addImplicitHelpCommand = enableOrNameAndArgs;
377
+ return this;
389
378
  }
379
+
380
+ enableOrNameAndArgs = enableOrNameAndArgs ?? 'help [command]';
381
+ const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);
382
+ const helpDescription = description ?? 'display help for command';
383
+
384
+ const helpCommand = this.createCommand(helpName);
385
+ helpCommand.helpOption(false);
386
+ if (helpArgs) helpCommand.arguments(helpArgs);
387
+ if (helpDescription) helpCommand.description(helpDescription);
388
+
389
+ this._addImplicitHelpCommand = true;
390
+ this._helpCommand = helpCommand;
391
+
390
392
  return this;
391
393
  }
392
394
 
393
395
  /**
394
- * @return {boolean}
395
- * @api private
396
+ * Add prepared custom help command.
397
+ *
398
+ * @param {(Command|string|boolean)} helpCommand - custom help command, or deprecated enableOrNameAndArgs as for `.helpCommand()`
399
+ * @param {string} [deprecatedDescription] - deprecated custom description used with custom name only
400
+ * @return {Command} `this` command for chaining
401
+ */
402
+ addHelpCommand(helpCommand, deprecatedDescription) {
403
+ // If not passed an object, call through to helpCommand for backwards compatibility,
404
+ // as addHelpCommand was originally used like helpCommand is now.
405
+ if (typeof helpCommand !== 'object') {
406
+ this.helpCommand(helpCommand, deprecatedDescription);
407
+ return this;
408
+ }
409
+
410
+ this._addImplicitHelpCommand = true;
411
+ this._helpCommand = helpCommand;
412
+ return this;
413
+ }
414
+
415
+ /**
416
+ * Lazy create help command.
417
+ *
418
+ * @return {(Command|null)}
419
+ * @package
396
420
  */
421
+ _getHelpCommand() {
422
+ const hasImplicitHelpCommand = this._addImplicitHelpCommand ??
423
+ (this.commands.length && !this._actionHandler && !this._findCommand('help'));
397
424
 
398
- _hasImplicitHelpCommand() {
399
- if (this._addImplicitHelpCommand === undefined) {
400
- return this.commands.length && !this._actionHandler && !this._findCommand('help');
425
+ if (hasImplicitHelpCommand) {
426
+ if (this._helpCommand === undefined) {
427
+ this.helpCommand(undefined, undefined); // use default name and description
428
+ }
429
+ return this._helpCommand;
401
430
  }
402
- return this._addImplicitHelpCommand;
431
+ return null;
403
432
  }
404
433
 
405
434
  /**
@@ -453,7 +482,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
453
482
  * @param {string} code an id string representing the error
454
483
  * @param {string} message human-readable description of the error
455
484
  * @return never
456
- * @api private
485
+ * @private
457
486
  */
458
487
 
459
488
  _exit(exitCode, code, message) {
@@ -515,11 +544,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
515
544
  /**
516
545
  * Wrap parseArgs to catch 'commander.invalidArgument'.
517
546
  *
518
- * @param {Option | Argument} target
547
+ * @param {(Option | Argument)} target
519
548
  * @param {string} value
520
549
  * @param {*} previous
521
550
  * @param {string} invalidArgumentMessage
522
- * @api private
551
+ * @private
523
552
  */
524
553
 
525
554
  _callParseArg(target, value, previous, invalidArgumentMessage) {
@@ -534,6 +563,49 @@ Expecting one of '${allowedValues.join("', '")}'`);
534
563
  }
535
564
  }
536
565
 
566
+ /**
567
+ * Check for option flag conflicts.
568
+ * Register option if no conflicts found, or throw on conflict.
569
+ *
570
+ * @param {Option} option
571
+ * @api private
572
+ */
573
+
574
+ _registerOption(option) {
575
+ const matchingOption = (option.short && this._findOption(option.short)) ||
576
+ (option.long && this._findOption(option.long));
577
+ if (matchingOption) {
578
+ const matchingFlag = (option.long && this._findOption(option.long)) ? option.long : option.short;
579
+ throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
580
+ - already used by option '${matchingOption.flags}'`);
581
+ }
582
+
583
+ this.options.push(option);
584
+ }
585
+
586
+ /**
587
+ * Check for command name and alias conflicts with existing commands.
588
+ * Register command if no conflicts found, or throw on conflict.
589
+ *
590
+ * @param {Command} command
591
+ * @api private
592
+ */
593
+
594
+ _registerCommand(command) {
595
+ const knownBy = (cmd) => {
596
+ return [cmd.name()].concat(cmd.aliases());
597
+ };
598
+
599
+ const alreadyUsed = knownBy(command).find((name) => this._findCommand(name));
600
+ if (alreadyUsed) {
601
+ const existingCmd = knownBy(this._findCommand(alreadyUsed)).join('|');
602
+ const newCmd = knownBy(command).join('|');
603
+ throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
604
+ }
605
+
606
+ this.commands.push(command);
607
+ }
608
+
537
609
  /**
538
610
  * Add an option.
539
611
  *
@@ -541,6 +613,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
541
613
  * @return {Command} `this` command for chaining
542
614
  */
543
615
  addOption(option) {
616
+ this._registerOption(option);
617
+
544
618
  const oname = option.name();
545
619
  const name = option.attributeName();
546
620
 
@@ -555,9 +629,6 @@ Expecting one of '${allowedValues.join("', '")}'`);
555
629
  this.setOptionValueWithSource(name, option.defaultValue, 'default');
556
630
  }
557
631
 
558
- // register the option
559
- this.options.push(option);
560
-
561
632
  // handler for cli and env supplied values
562
633
  const handleOptionValue = (val, invalidValueMessage, valueSource) => {
563
634
  // val is null for optional option used without an optional-argument.
@@ -605,7 +676,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
605
676
  /**
606
677
  * Internal implementation shared by .option() and .requiredOption()
607
678
  *
608
- * @api private
679
+ * @private
609
680
  */
610
681
  _optionEx(config, flags, description, fn, defaultValue) {
611
682
  if (typeof flags === 'object' && flags instanceof Option) {
@@ -647,7 +718,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
647
718
  *
648
719
  * @param {string} flags
649
720
  * @param {string} [description]
650
- * @param {Function|*} [parseArg] - custom option processing function or default value
721
+ * @param {(Function|*)} [parseArg] - custom option processing function or default value
651
722
  * @param {*} [defaultValue]
652
723
  * @return {Command} `this` command for chaining
653
724
  */
@@ -664,7 +735,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
664
735
  *
665
736
  * @param {string} flags
666
737
  * @param {string} [description]
667
- * @param {Function|*} [parseArg] - custom option processing function or default value
738
+ * @param {(Function|*)} [parseArg] - custom option processing function or default value
668
739
  * @param {*} [defaultValue]
669
740
  * @return {Command} `this` command for chaining
670
741
  */
@@ -681,7 +752,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
681
752
  * program.combineFlagAndOptionalValue(true); // `-f80` is treated like `--flag=80`, this is the default behaviour
682
753
  * program.combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
683
754
  *
684
- * @param {Boolean} [combine=true] - if `true` or omitted, an optional value can be specified directly after the flag.
755
+ * @param {boolean} [combine=true] - if `true` or omitted, an optional value can be specified directly after the flag.
685
756
  */
686
757
  combineFlagAndOptionalValue(combine = true) {
687
758
  this._combineFlagAndOptionalValue = !!combine;
@@ -691,7 +762,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
691
762
  /**
692
763
  * Allow unknown options on the command line.
693
764
  *
694
- * @param {Boolean} [allowUnknown=true] - if `true` or omitted, no error will be thrown
765
+ * @param {boolean} [allowUnknown=true] - if `true` or omitted, no error will be thrown
695
766
  * for unknown options.
696
767
  */
697
768
  allowUnknownOption(allowUnknown = true) {
@@ -702,7 +773,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
702
773
  /**
703
774
  * Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
704
775
  *
705
- * @param {Boolean} [allowExcess=true] - if `true` or omitted, no error will be thrown
776
+ * @param {boolean} [allowExcess=true] - if `true` or omitted, no error will be thrown
706
777
  * for excess arguments.
707
778
  */
708
779
  allowExcessArguments(allowExcess = true) {
@@ -715,7 +786,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
715
786
  * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
716
787
  * The default behaviour is non-positional and global options may appear anywhere on the command line.
717
788
  *
718
- * @param {Boolean} [positional=true]
789
+ * @param {boolean} [positional=true]
719
790
  */
720
791
  enablePositionalOptions(positional = true) {
721
792
  this._enablePositionalOptions = !!positional;
@@ -728,17 +799,25 @@ Expecting one of '${allowedValues.join("', '")}'`);
728
799
  * positional options to have been enabled on the program (parent commands).
729
800
  * The default behaviour is non-positional and options may appear before or after command-arguments.
730
801
  *
731
- * @param {Boolean} [passThrough=true]
802
+ * @param {boolean} [passThrough=true]
732
803
  * for unknown options.
733
804
  */
734
805
  passThroughOptions(passThrough = true) {
735
806
  this._passThroughOptions = !!passThrough;
736
- if (!!this.parent && passThrough && !this.parent._enablePositionalOptions) {
737
- throw new Error('passThroughOptions can not be used without turning on enablePositionalOptions for parent command(s)');
738
- }
807
+ this._checkForBrokenPassThrough();
739
808
  return this;
740
809
  }
741
810
 
811
+ /**
812
+ * @private
813
+ */
814
+
815
+ _checkForBrokenPassThrough() {
816
+ if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) {
817
+ throw new Error(`passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`);
818
+ }
819
+ }
820
+
742
821
  /**
743
822
  * Whether to store option values as properties on command object,
744
823
  * or store separately (specify false). In both cases the option values can be accessed using .opts().
@@ -751,9 +830,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
751
830
  if (this.options.length) {
752
831
  throw new Error('call .storeOptionsAsProperties() before adding options');
753
832
  }
754
- // if (Object.keys(this._optionValues).length) {
755
- // throw new Error('call .storeOptionsAsProperties() before setting option values');
756
- // }
833
+ if (Object.keys(this._optionValues).length) {
834
+ throw new Error('call .storeOptionsAsProperties() before setting option values');
835
+ }
757
836
  this._storeOptionsAsProperties = !!storeAsProperties;
758
837
  return this;
759
838
  }
@@ -838,7 +917,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
838
917
  * Get user arguments from implied or explicit arguments.
839
918
  * Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches.
840
919
  *
841
- * @api private
920
+ * @private
842
921
  */
843
922
 
844
923
  _prepareUserArgs(argv, parseOptions) {
@@ -941,7 +1020,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
941
1020
  /**
942
1021
  * Execute a sub-command executable.
943
1022
  *
944
- * @api private
1023
+ * @private
945
1024
  */
946
1025
 
947
1026
  _executeSubCommand(subcommand, args) {
@@ -1028,15 +1107,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
1028
1107
  }
1029
1108
 
1030
1109
  // By default terminate process when spawned process terminates.
1031
- // Suppressing the exit if exitCallback defined is a bit messy and of limited use, but does allow process to stay running!
1032
1110
  const exitCallback = this._exitCallback;
1033
- if (!exitCallback) {
1034
- proc.on('close', process.exit.bind(process));
1035
- } else {
1036
- proc.on('close', () => {
1037
- exitCallback(new CommanderError(process.exitCode || 0, 'commander.executeSubCommandAsync', '(close)'));
1038
- });
1039
- }
1111
+ proc.on('close', (code, _signal) => {
1112
+ code = code ?? 1; // code is null if spawned process terminated due to a signal
1113
+ if (!exitCallback) {
1114
+ process.exit(code);
1115
+ } else {
1116
+ exitCallback(new CommanderError(code, 'commander.executeSubCommandAsync', '(close)'));
1117
+ }
1118
+ });
1040
1119
  proc.on('error', (err) => {
1041
1120
  // @ts-ignore
1042
1121
  if (err.code === 'ENOENT') {
@@ -1066,7 +1145,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1066
1145
  }
1067
1146
 
1068
1147
  /**
1069
- * @api private
1148
+ * @private
1070
1149
  */
1071
1150
 
1072
1151
  _dispatchSubcommand(commandName, operands, unknown) {
@@ -1089,7 +1168,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1089
1168
  * Invoke help directly if possible, or dispatch if necessary.
1090
1169
  * e.g. help foo
1091
1170
  *
1092
- * @api private
1171
+ * @private
1093
1172
  */
1094
1173
 
1095
1174
  _dispatchHelpCommand(subcommandName) {
@@ -1103,14 +1182,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
1103
1182
 
1104
1183
  // Fallback to parsing the help flag to invoke the help.
1105
1184
  return this._dispatchSubcommand(subcommandName, [], [
1106
- this._helpLongFlag || this._helpShortFlag
1185
+ this._getHelpOption()?.long ?? this._getHelpOption()?.short ?? '--help'
1107
1186
  ]);
1108
1187
  }
1109
1188
 
1110
1189
  /**
1111
1190
  * Check this.args against expected this.registeredArguments.
1112
1191
  *
1113
- * @api private
1192
+ * @private
1114
1193
  */
1115
1194
 
1116
1195
  _checkNumberOfArguments() {
@@ -1132,7 +1211,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1132
1211
  /**
1133
1212
  * Process this.args using this.registeredArguments and save as this.processedArgs!
1134
1213
  *
1135
- * @api private
1214
+ * @private
1136
1215
  */
1137
1216
 
1138
1217
  _processArguments() {
@@ -1177,10 +1256,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1177
1256
  /**
1178
1257
  * Once we have a promise we chain, but call synchronously until then.
1179
1258
  *
1180
- * @param {Promise|undefined} promise
1259
+ * @param {(Promise|undefined)} promise
1181
1260
  * @param {Function} fn
1182
- * @return {Promise|undefined}
1183
- * @api private
1261
+ * @return {(Promise|undefined)}
1262
+ * @private
1184
1263
  */
1185
1264
 
1186
1265
  _chainOrCall(promise, fn) {
@@ -1195,10 +1274,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1195
1274
 
1196
1275
  /**
1197
1276
  *
1198
- * @param {Promise|undefined} promise
1277
+ * @param {(Promise|undefined)} promise
1199
1278
  * @param {string} event
1200
- * @return {Promise|undefined}
1201
- * @api private
1279
+ * @return {(Promise|undefined)}
1280
+ * @private
1202
1281
  */
1203
1282
 
1204
1283
  _chainOrCallHooks(promise, event) {
@@ -1226,11 +1305,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1226
1305
 
1227
1306
  /**
1228
1307
  *
1229
- * @param {Promise|undefined} promise
1308
+ * @param {(Promise|undefined)} promise
1230
1309
  * @param {Command} subCommand
1231
1310
  * @param {string} event
1232
- * @return {Promise|undefined}
1233
- * @api private
1311
+ * @return {(Promise|undefined)}
1312
+ * @private
1234
1313
  */
1235
1314
 
1236
1315
  _chainOrCallSubCommandHook(promise, subCommand, event) {
@@ -1249,7 +1328,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1249
1328
  * Process arguments in context of this command.
1250
1329
  * Returns action result, in case it is a promise.
1251
1330
  *
1252
- * @api private
1331
+ * @private
1253
1332
  */
1254
1333
 
1255
1334
  _parseCommand(operands, unknown) {
@@ -1263,11 +1342,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1263
1342
  if (operands && this._findCommand(operands[0])) {
1264
1343
  return this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
1265
1344
  }
1266
- if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
1345
+ if (this._getHelpCommand() && operands[0] === this._getHelpCommand().name()) {
1267
1346
  return this._dispatchHelpCommand(operands[1]);
1268
1347
  }
1269
1348
  if (this._defaultCommandName) {
1270
- outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
1349
+ this._outputHelpIfRequested(unknown); // Run the help for default command from parent rather than passing to default command
1271
1350
  return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
1272
1351
  }
1273
1352
  if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
@@ -1275,7 +1354,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1275
1354
  this.help({ error: true });
1276
1355
  }
1277
1356
 
1278
- outputHelpIfRequested(this, parsed.unknown);
1357
+ this._outputHelpIfRequested(parsed.unknown);
1279
1358
  this._checkForMissingMandatoryOptions();
1280
1359
  this._checkForConflictingOptions();
1281
1360
 
@@ -1333,7 +1412,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1333
1412
  /**
1334
1413
  * Find matching command.
1335
1414
  *
1336
- * @api private
1415
+ * @private
1337
1416
  */
1338
1417
  _findCommand(name) {
1339
1418
  if (!name) return undefined;
@@ -1345,7 +1424,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1345
1424
  *
1346
1425
  * @param {string} arg
1347
1426
  * @return {Option}
1348
- * @api private
1427
+ * @package internal use only
1349
1428
  */
1350
1429
 
1351
1430
  _findOption(arg) {
@@ -1356,7 +1435,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1356
1435
  * Display an error message if a mandatory option does not have a value.
1357
1436
  * Called after checking for help flags in leaf subcommand.
1358
1437
  *
1359
- * @api private
1438
+ * @private
1360
1439
  */
1361
1440
 
1362
1441
  _checkForMissingMandatoryOptions() {
@@ -1373,7 +1452,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1373
1452
  /**
1374
1453
  * Display an error message if conflicting options are used together in this.
1375
1454
  *
1376
- * @api private
1455
+ * @private
1377
1456
  */
1378
1457
  _checkForConflictingLocalOptions() {
1379
1458
  const definedNonDefaultOptions = this.options.filter(
@@ -1404,7 +1483,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1404
1483
  * Display an error message if conflicting options are used together.
1405
1484
  * Called after checking for help flags in leaf subcommand.
1406
1485
  *
1407
- * @api private
1486
+ * @private
1408
1487
  */
1409
1488
  _checkForConflictingOptions() {
1410
1489
  // Walk up hierarchy so can call in subcommand after checking for displaying help.
@@ -1425,8 +1504,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1425
1504
  * sub --unknown uuu op => [sub], [--unknown uuu op]
1426
1505
  * sub -- --unknown uuu op => [sub --unknown uuu op], []
1427
1506
  *
1428
- * @param {String[]} argv
1429
- * @return {{operands: String[], unknown: String[]}}
1507
+ * @param {string[]} argv
1508
+ * @return {{operands: string[], unknown: string[]}}
1430
1509
  */
1431
1510
 
1432
1511
  parseOptions(argv) {
@@ -1520,7 +1599,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1520
1599
  operands.push(arg);
1521
1600
  if (args.length > 0) unknown.push(...args);
1522
1601
  break;
1523
- } else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
1602
+ } else if (this._getHelpCommand() && arg === this._getHelpCommand().name()) {
1524
1603
  operands.push(arg);
1525
1604
  if (args.length > 0) operands.push(...args);
1526
1605
  break;
@@ -1608,7 +1687,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1608
1687
  * Apply any option related environment variables, if option does
1609
1688
  * not have a value from cli or client code.
1610
1689
  *
1611
- * @api private
1690
+ * @private
1612
1691
  */
1613
1692
  _parseOptionsEnv() {
1614
1693
  this.options.forEach((option) => {
@@ -1631,7 +1710,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1631
1710
  /**
1632
1711
  * Apply any implied option values, if option is undefined or default value.
1633
1712
  *
1634
- * @api private
1713
+ * @private
1635
1714
  */
1636
1715
  _parseOptionsImplied() {
1637
1716
  const dualHelper = new DualOptions(this.options);
@@ -1655,7 +1734,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1655
1734
  * Argument `name` is missing.
1656
1735
  *
1657
1736
  * @param {string} name
1658
- * @api private
1737
+ * @private
1659
1738
  */
1660
1739
 
1661
1740
  missingArgument(name) {
@@ -1667,7 +1746,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1667
1746
  * `Option` is missing an argument.
1668
1747
  *
1669
1748
  * @param {Option} option
1670
- * @api private
1749
+ * @private
1671
1750
  */
1672
1751
 
1673
1752
  optionMissingArgument(option) {
@@ -1679,7 +1758,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1679
1758
  * `Option` does not have a value, and is a mandatory option.
1680
1759
  *
1681
1760
  * @param {Option} option
1682
- * @api private
1761
+ * @private
1683
1762
  */
1684
1763
 
1685
1764
  missingMandatoryOptionValue(option) {
@@ -1692,7 +1771,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1692
1771
  *
1693
1772
  * @param {Option} option
1694
1773
  * @param {Option} conflictingOption
1695
- * @api private
1774
+ * @private
1696
1775
  */
1697
1776
  _conflictingOption(option, conflictingOption) {
1698
1777
  // The calling code does not know whether a negated option is the source of the
@@ -1729,7 +1808,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1729
1808
  * Unknown option `flag`.
1730
1809
  *
1731
1810
  * @param {string} flag
1732
- * @api private
1811
+ * @private
1733
1812
  */
1734
1813
 
1735
1814
  unknownOption(flag) {
@@ -1758,7 +1837,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1758
1837
  * Excess arguments, more than expected.
1759
1838
  *
1760
1839
  * @param {string[]} receivedArgs
1761
- * @api private
1840
+ * @private
1762
1841
  */
1763
1842
 
1764
1843
  _excessArguments(receivedArgs) {
@@ -1774,7 +1853,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1774
1853
  /**
1775
1854
  * Unknown command.
1776
1855
  *
1777
- * @api private
1856
+ * @private
1778
1857
  */
1779
1858
 
1780
1859
  unknownCommand() {
@@ -1805,7 +1884,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1805
1884
  * @param {string} [str]
1806
1885
  * @param {string} [flags]
1807
1886
  * @param {string} [description]
1808
- * @return {this | string | undefined} `this` command for chaining, or version string if no arguments
1887
+ * @return {(this | string | undefined)} `this` command for chaining, or version string if no arguments
1809
1888
  */
1810
1889
 
1811
1890
  version(str, flags, description) {
@@ -1814,8 +1893,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
1814
1893
  flags = flags || '-V, --version';
1815
1894
  description = description || 'output the version number';
1816
1895
  const versionOption = this.createOption(flags, description);
1817
- this._versionOptionName = versionOption.attributeName(); // [sic] not defined in constructor, partly legacy, partly only needed at root
1818
- this.options.push(versionOption);
1896
+ this._versionOptionName = versionOption.attributeName();
1897
+ this._registerOption(versionOption);
1898
+
1819
1899
  this.on('option:' + versionOption.name(), () => {
1820
1900
  this._outputConfiguration.writeOut(`${str}\n`);
1821
1901
  this._exit(0, 'commander.version', str);
@@ -1828,7 +1908,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1828
1908
  *
1829
1909
  * @param {string} [str]
1830
1910
  * @param {Object} [argsDescription]
1831
- * @return {string|Command}
1911
+ * @return {(string|Command)}
1832
1912
  */
1833
1913
  description(str, argsDescription) {
1834
1914
  if (str === undefined && argsDescription === undefined) return this._description;
@@ -1843,7 +1923,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1843
1923
  * Set the summary. Used when listed as subcommand of parent.
1844
1924
  *
1845
1925
  * @param {string} [str]
1846
- * @return {string|Command}
1926
+ * @return {(string|Command)}
1847
1927
  */
1848
1928
  summary(str) {
1849
1929
  if (str === undefined) return this._summary;
@@ -1857,7 +1937,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1857
1937
  * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
1858
1938
  *
1859
1939
  * @param {string} [alias]
1860
- * @return {string|Command}
1940
+ * @return {(string|Command)}
1861
1941
  */
1862
1942
 
1863
1943
  alias(alias) {
@@ -1871,6 +1951,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
1871
1951
  }
1872
1952
 
1873
1953
  if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
1954
+ const matchingCommand = this.parent?._findCommand(alias);
1955
+ if (matchingCommand) {
1956
+ // c.f. _registerCommand
1957
+ const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join('|');
1958
+ throw new Error(`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`);
1959
+ }
1874
1960
 
1875
1961
  command._aliases.push(alias);
1876
1962
  return this;
@@ -1882,7 +1968,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1882
1968
  * Only the first alias is shown in the auto-generated help.
1883
1969
  *
1884
1970
  * @param {string[]} [aliases]
1885
- * @return {string[]|Command}
1971
+ * @return {(string[]|Command)}
1886
1972
  */
1887
1973
 
1888
1974
  aliases(aliases) {
@@ -1897,7 +1983,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1897
1983
  * Set / get the command usage `str`.
1898
1984
  *
1899
1985
  * @param {string} [str]
1900
- * @return {String|Command}
1986
+ * @return {(string|Command)}
1901
1987
  */
1902
1988
 
1903
1989
  usage(str) {
@@ -1908,7 +1994,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1908
1994
  return humanReadableArgName(arg);
1909
1995
  });
1910
1996
  return [].concat(
1911
- (this.options.length || this._hasHelpOption ? '[options]' : []),
1997
+ (this.options.length || (this._helpOption !== null) ? '[options]' : []),
1912
1998
  (this.commands.length ? '[command]' : []),
1913
1999
  (this.registeredArguments.length ? args : [])
1914
2000
  ).join(' ');
@@ -1922,7 +2008,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1922
2008
  * Get or set the name of the command.
1923
2009
  *
1924
2010
  * @param {string} [str]
1925
- * @return {string|Command}
2011
+ * @return {(string|Command)}
1926
2012
  */
1927
2013
 
1928
2014
  name(str) {
@@ -1959,7 +2045,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1959
2045
  * program.executableDir('subcommands');
1960
2046
  *
1961
2047
  * @param {string} [path]
1962
- * @return {string|null|Command}
2048
+ * @return {(string|null|Command)}
1963
2049
  */
1964
2050
 
1965
2051
  executableDir(path) {
@@ -1984,7 +2070,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1984
2070
  }
1985
2071
 
1986
2072
  /**
1987
- * @api private
2073
+ * @private
1988
2074
  */
1989
2075
 
1990
2076
  _getHelpContext(contextOptions) {
@@ -2029,38 +2115,72 @@ Expecting one of '${allowedValues.join("', '")}'`);
2029
2115
  }
2030
2116
  context.write(helpInformation);
2031
2117
 
2032
- if (this._helpLongFlag) {
2033
- this.emit(this._helpLongFlag); // deprecated
2118
+ if (this._getHelpOption()?.long) {
2119
+ this.emit(this._getHelpOption().long); // deprecated
2034
2120
  }
2035
2121
  this.emit('afterHelp', context);
2036
2122
  this._getCommandAndAncestors().forEach(command => command.emit('afterAllHelp', context));
2037
2123
  }
2038
2124
 
2039
2125
  /**
2040
- * You can pass in flags and a description to override the help
2041
- * flags and help description for your command. Pass in false to
2042
- * disable the built-in help option.
2126
+ * You can pass in flags and a description to customise the built-in help option.
2127
+ * Pass in false to disable the built-in help option.
2128
+ *
2129
+ * @example
2130
+ * program.helpOption('-?, --help' 'show help'); // customise
2131
+ * program.helpOption(false); // disable
2043
2132
  *
2044
- * @param {string | boolean} [flags]
2133
+ * @param {(string | boolean)} flags
2045
2134
  * @param {string} [description]
2046
2135
  * @return {Command} `this` command for chaining
2047
2136
  */
2048
2137
 
2049
2138
  helpOption(flags, description) {
2139
+ // Support disabling built-in help option.
2050
2140
  if (typeof flags === 'boolean') {
2051
- this._hasHelpOption = flags;
2141
+ if (flags) {
2142
+ this._helpOption = this._helpOption ?? undefined; // preserve existing option
2143
+ } else {
2144
+ this._helpOption = null; // disable
2145
+ }
2052
2146
  return this;
2053
2147
  }
2054
- this._helpFlags = flags || this._helpFlags;
2055
- this._helpDescription = description || this._helpDescription;
2056
2148
 
2057
- const helpFlags = splitOptionFlags(this._helpFlags);
2058
- this._helpShortFlag = helpFlags.shortFlag;
2059
- this._helpLongFlag = helpFlags.longFlag;
2149
+ // Customise flags and description.
2150
+ flags = flags ?? '-h, --help';
2151
+ description = description ?? 'display help for command';
2152
+ this._helpOption = this.createOption(flags, description);
2060
2153
 
2061
2154
  return this;
2062
2155
  }
2063
2156
 
2157
+ /**
2158
+ * Lazy create help option.
2159
+ * Returns null if has been disabled with .helpOption(false).
2160
+ *
2161
+ * @returns {(Option | null)} the help option
2162
+ * @package internal use only
2163
+ */
2164
+ _getHelpOption() {
2165
+ // Lazy create help option on demand.
2166
+ if (this._helpOption === undefined) {
2167
+ this.helpOption(undefined, undefined);
2168
+ }
2169
+ return this._helpOption;
2170
+ }
2171
+
2172
+ /**
2173
+ * Supply your own option to use for the built-in help option.
2174
+ * This is an alternative to using helpOption() to customise the flags and description etc.
2175
+ *
2176
+ * @param {Option} option
2177
+ * @return {Command} `this` command for chaining
2178
+ */
2179
+ addHelpOption(option) {
2180
+ this._helpOption = option;
2181
+ return this;
2182
+ }
2183
+
2064
2184
  /**
2065
2185
  * Output help information and exit.
2066
2186
  *
@@ -2086,7 +2206,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2086
2206
  * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
2087
2207
  *
2088
2208
  * @param {string} position - before or after built-in help
2089
- * @param {string | Function} text - string to add, or a function returning a string
2209
+ * @param {(string | Function)} text - string to add, or a function returning a string
2090
2210
  * @return {Command} `this` command for chaining
2091
2211
  */
2092
2212
  addHelpText(position, text) {
@@ -2110,22 +2230,22 @@ Expecting one of '${allowedValues.join("', '")}'`);
2110
2230
  });
2111
2231
  return this;
2112
2232
  }
2113
- }
2114
2233
 
2115
- /**
2116
- * Output help information if help flags specified
2117
- *
2118
- * @param {Command} cmd - command to output help for
2119
- * @param {Array} args - array of options to search for help flags
2120
- * @api private
2121
- */
2234
+ /**
2235
+ * Output help information if help flags specified
2236
+ *
2237
+ * @param {Array} args - array of options to search for help flags
2238
+ * @private
2239
+ */
2122
2240
 
2123
- function outputHelpIfRequested(cmd, args) {
2124
- const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
2125
- if (helpOption) {
2126
- cmd.outputHelp();
2127
- // (Do not have all displayed text available so only passing placeholder.)
2128
- cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)');
2241
+ _outputHelpIfRequested(args) {
2242
+ const helpOption = this._getHelpOption();
2243
+ const helpRequested = helpOption && args.find(arg => helpOption.is(arg));
2244
+ if (helpRequested) {
2245
+ this.outputHelp();
2246
+ // (Do not have all displayed text available so only passing placeholder.)
2247
+ this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
2248
+ }
2129
2249
  }
2130
2250
  }
2131
2251
 
@@ -2134,7 +2254,7 @@ function outputHelpIfRequested(cmd, args) {
2134
2254
  *
2135
2255
  * @param {string[]} args - array of arguments from node.execArgv
2136
2256
  * @returns {string[]}
2137
- * @api private
2257
+ * @private
2138
2258
  */
2139
2259
 
2140
2260
  function incrementNodeInspectorPort(args) {
package/lib/help.js CHANGED
@@ -26,13 +26,8 @@ class Help {
26
26
 
27
27
  visibleCommands(cmd) {
28
28
  const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
29
- if (cmd._hasImplicitHelpCommand()) {
30
- // Create a command matching the implicit help command.
31
- const [, helpName, helpArgs] = cmd._helpCommandnameAndArgs.match(/([^ ]+) *(.*)/);
32
- const helpCommand = cmd.createCommand(helpName)
33
- .helpOption(false);
34
- helpCommand.description(cmd._helpCommandDescription);
35
- if (helpArgs) helpCommand.arguments(helpArgs);
29
+ const helpCommand = cmd._getHelpCommand();
30
+ if (helpCommand && !helpCommand._hidden) {
36
31
  visibleCommands.push(helpCommand);
37
32
  }
38
33
  if (this.sortSubcommands) {
@@ -68,19 +63,19 @@ class Help {
68
63
 
69
64
  visibleOptions(cmd) {
70
65
  const visibleOptions = cmd.options.filter((option) => !option.hidden);
71
- // Implicit help
72
- const showShortHelpFlag = cmd._hasHelpOption && cmd._helpShortFlag && !cmd._findOption(cmd._helpShortFlag);
73
- const showLongHelpFlag = cmd._hasHelpOption && !cmd._findOption(cmd._helpLongFlag);
74
- if (showShortHelpFlag || showLongHelpFlag) {
75
- let helpOption;
76
- if (!showShortHelpFlag) {
77
- helpOption = cmd.createOption(cmd._helpLongFlag, cmd._helpDescription);
78
- } else if (!showLongHelpFlag) {
79
- helpOption = cmd.createOption(cmd._helpShortFlag, cmd._helpDescription);
80
- } else {
81
- helpOption = cmd.createOption(cmd._helpFlags, cmd._helpDescription);
66
+ // Built-in help option.
67
+ const helpOption = cmd._getHelpOption();
68
+ if (helpOption && !helpOption.hidden) {
69
+ // Automatically hide conflicting flags. Bit dubious but a historical behaviour that is convenient for single-command programs.
70
+ const removeShort = helpOption.short && cmd._findOption(helpOption.short);
71
+ const removeLong = helpOption.long && cmd._findOption(helpOption.long);
72
+ if (!removeShort && !removeLong) {
73
+ visibleOptions.push(helpOption); // no changes needed
74
+ } else if (helpOption.long && !removeLong) {
75
+ visibleOptions.push(cmd.createOption(helpOption.long, helpOption.description));
76
+ } else if (helpOption.short && !removeShort) {
77
+ visibleOptions.push(cmd.createOption(helpOption.short, helpOption.description));
82
78
  }
83
- visibleOptions.push(helpOption);
84
79
  }
85
80
  if (this.sortOptions) {
86
81
  visibleOptions.sort(this.compareOptions);
package/lib/option.js CHANGED
@@ -74,7 +74,7 @@ class Option {
74
74
  * new Option('--rgb').conflicts('cmyk');
75
75
  * new Option('--js').conflicts(['ts', 'jsx']);
76
76
  *
77
- * @param {string | string[]} names
77
+ * @param {(string | string[])} names
78
78
  * @return {Option}
79
79
  */
80
80
 
@@ -158,7 +158,7 @@ class Option {
158
158
  }
159
159
 
160
160
  /**
161
- * @api private
161
+ * @package internal use only
162
162
  */
163
163
 
164
164
  _concatValue(value, previous) {
@@ -208,7 +208,6 @@ class Option {
208
208
  * as a object attribute key.
209
209
  *
210
210
  * @return {string}
211
- * @api private
212
211
  */
213
212
 
214
213
  attributeName() {
@@ -220,7 +219,7 @@ class Option {
220
219
  *
221
220
  * @param {string} arg
222
221
  * @return {boolean}
223
- * @api private
222
+ * @package internal use only
224
223
  */
225
224
 
226
225
  is(arg) {
@@ -233,7 +232,7 @@ class Option {
233
232
  * Options are one of boolean, negated, required argument, or optional argument.
234
233
  *
235
234
  * @return {boolean}
236
- * @api private
235
+ * @package internal use only
237
236
  */
238
237
 
239
238
  isBoolean() {
@@ -293,7 +292,7 @@ class DualOptions {
293
292
  *
294
293
  * @param {string} str
295
294
  * @return {string}
296
- * @api private
295
+ * @private
297
296
  */
298
297
 
299
298
  function camelcase(str) {
@@ -305,7 +304,7 @@ function camelcase(str) {
305
304
  /**
306
305
  * Split the short and long flag out of something like '-m,--mixed <value>'
307
306
  *
308
- * @api private
307
+ * @private
309
308
  */
310
309
 
311
310
  function splitOptionFlags(flags) {
@@ -325,5 +324,4 @@ function splitOptionFlags(flags) {
325
324
  }
326
325
 
327
326
  exports.Option = Option;
328
- exports.splitOptionFlags = splitOptionFlags;
329
327
  exports.DualOptions = DualOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commander",
3
- "version": "11.1.0",
3
+ "version": "12.0.0-1",
4
4
  "description": "the complete solution for node.js command-line programs",
5
5
  "keywords": [
6
6
  "commander",
@@ -16,7 +16,7 @@
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "https://github.com/tj/commander.js.git"
19
+ "url": "git+https://github.com/tj/commander.js.git"
20
20
  },
21
21
  "scripts": {
22
22
  "lint": "npm run lint:javascript && npm run lint:typescript",
@@ -58,23 +58,23 @@
58
58
  "devDependencies": {
59
59
  "@types/jest": "^29.2.4",
60
60
  "@types/node": "^20.2.5",
61
- "@typescript-eslint/eslint-plugin": "^5.47.1",
62
- "@typescript-eslint/parser": "^5.47.1",
61
+ "@typescript-eslint/eslint-plugin": "^6.7.5",
62
+ "@typescript-eslint/parser": "^6.7.5",
63
63
  "eslint": "^8.30.0",
64
64
  "eslint-config-standard": "^17.0.0",
65
- "eslint-config-standard-with-typescript": "^33.0.0",
65
+ "eslint-config-standard-with-typescript": "^40.0.0",
66
66
  "eslint-plugin-import": "^2.26.0",
67
67
  "eslint-plugin-jest": "^27.1.7",
68
- "eslint-plugin-n": "^15.6.0",
68
+ "eslint-plugin-n": "^16.2.0",
69
69
  "eslint-plugin-promise": "^6.1.1",
70
70
  "jest": "^29.3.1",
71
71
  "ts-jest": "^29.0.3",
72
- "tsd": "^0.28.1",
72
+ "tsd": "^0.29.0",
73
73
  "typescript": "^5.0.4"
74
74
  },
75
75
  "types": "typings/index.d.ts",
76
76
  "engines": {
77
- "node": ">=16"
77
+ "node": ">=18"
78
78
  },
79
79
  "support": true
80
80
  }
@@ -419,18 +419,27 @@ export class Command {
419
419
  arguments(names: string): this;
420
420
 
421
421
  /**
422
- * Override default decision whether to add implicit help command.
422
+ * Customise or override default help command. By default a help command is automatically added if your command has subcommands.
423
423
  *
424
424
  * @example
425
+ * ```ts
426
+ * program.helpCommand('help [cmd]');
427
+ * program.helpCommand('help [cmd]', 'show help');
428
+ * program.helpCommand(false); // suppress default help command
429
+ * program.helpCommand(true); // add help command even if no subcommands
425
430
  * ```
426
- * addHelpCommand() // force on
427
- * addHelpCommand(false); // force off
428
- * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
429
- * ```
430
- *
431
- * @returns `this` command for chaining
432
431
  */
433
- addHelpCommand(enableOrNameAndArgs?: string | boolean, description?: string): this;
432
+ helpCommand(nameAndArgs: string, description?: string): this;
433
+ helpCommand(enable: boolean): this;
434
+
435
+ /**
436
+ * Add prepared custom help command.
437
+ */
438
+ addHelpCommand(cmd: Command): this;
439
+ /** @deprecated since v12, instead use helpCommand */
440
+ addHelpCommand(nameAndArgs: string, description?: string): this;
441
+ /** @deprecated since v12, instead use helpCommand */
442
+ addHelpCommand(enable?: boolean): this;
434
443
 
435
444
  /**
436
445
  * Add hook for life cycle event.
@@ -838,6 +847,12 @@ export class Command {
838
847
  */
839
848
  helpOption(flags?: string | boolean, description?: string): this;
840
849
 
850
+ /**
851
+ * Supply your own option to use for the built-in help option.
852
+ * This is an alternative to using helpOption() to customise the flags and description etc.
853
+ */
854
+ addHelpOption(option: Option): this;
855
+
841
856
  /**
842
857
  * Output help information and exit.
843
858
  *