commander 11.0.0 → 12.0.0-0

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/lib/command.js CHANGED
@@ -10,8 +10,6 @@ const { Help } = require('./help.js');
10
10
  const { Option, splitOptionFlags, DualOptions } = require('./option.js');
11
11
  const { suggestSimilar } = require('./suggestSimilar');
12
12
 
13
- // @ts-check
14
-
15
13
  class Command extends EventEmitter {
16
14
  /**
17
15
  * Initialize a new `Command`.
@@ -29,7 +27,8 @@ class Command extends EventEmitter {
29
27
  this._allowUnknownOption = false;
30
28
  this._allowExcessArguments = true;
31
29
  /** @type {Argument[]} */
32
- this._args = [];
30
+ this.registeredArguments = [];
31
+ this._args = this.registeredArguments; // deprecated old name
33
32
  /** @type {string[]} */
34
33
  this.args = []; // cli args with options removed
35
34
  this.rawArgs = [];
@@ -109,6 +108,19 @@ class Command extends EventEmitter {
109
108
  return this;
110
109
  }
111
110
 
111
+ /**
112
+ * @returns {Command[]}
113
+ * @private
114
+ */
115
+
116
+ _getCommandAndAncestors() {
117
+ const result = [];
118
+ for (let command = this; command; command = command.parent) {
119
+ result.push(command);
120
+ }
121
+ return result;
122
+ }
123
+
112
124
  /**
113
125
  * Define a command.
114
126
  *
@@ -153,7 +165,7 @@ class Command extends EventEmitter {
153
165
  cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden
154
166
  cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
155
167
  if (args) cmd.arguments(args);
156
- this.commands.push(cmd);
168
+ this._registerCommand(cmd);
157
169
  cmd.parent = this;
158
170
  cmd.copyInheritedSettings(this);
159
171
 
@@ -270,8 +282,10 @@ class Command extends EventEmitter {
270
282
  if (opts.isDefault) this._defaultCommandName = cmd._name;
271
283
  if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
272
284
 
273
- this.commands.push(cmd);
285
+ this._registerCommand(cmd);
274
286
  cmd.parent = this;
287
+ cmd._checkForBrokenPassThrough();
288
+
275
289
  return this;
276
290
  }
277
291
 
@@ -343,14 +357,14 @@ class Command extends EventEmitter {
343
357
  * @return {Command} `this` command for chaining
344
358
  */
345
359
  addArgument(argument) {
346
- const previousArgument = this._args.slice(-1)[0];
360
+ const previousArgument = this.registeredArguments.slice(-1)[0];
347
361
  if (previousArgument && previousArgument.variadic) {
348
362
  throw new Error(`only the last argument can be variadic '${previousArgument.name()}'`);
349
363
  }
350
364
  if (argument.required && argument.defaultValue !== undefined && argument.parseArg === undefined) {
351
365
  throw new Error(`a default value for a required argument is never used: '${argument.name()}'`);
352
366
  }
353
- this._args.push(argument);
367
+ this.registeredArguments.push(argument);
354
368
  return this;
355
369
  }
356
370
 
@@ -380,7 +394,7 @@ class Command extends EventEmitter {
380
394
 
381
395
  /**
382
396
  * @return {boolean}
383
- * @api private
397
+ * @package internal use only
384
398
  */
385
399
 
386
400
  _hasImplicitHelpCommand() {
@@ -441,7 +455,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
441
455
  * @param {string} code an id string representing the error
442
456
  * @param {string} message human-readable description of the error
443
457
  * @return never
444
- * @api private
458
+ * @private
445
459
  */
446
460
 
447
461
  _exit(exitCode, code, message) {
@@ -470,7 +484,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
470
484
  action(fn) {
471
485
  const listener = (args) => {
472
486
  // The .action callback takes an extra parameter which is the command or options.
473
- const expectedArgsCount = this._args.length;
487
+ const expectedArgsCount = this.registeredArguments.length;
474
488
  const actionArgs = args.slice(0, expectedArgsCount);
475
489
  if (this._storeOptionsAsProperties) {
476
490
  actionArgs[expectedArgsCount] = this; // backwards compatible "options"
@@ -500,6 +514,71 @@ Expecting one of '${allowedValues.join("', '")}'`);
500
514
  return new Option(flags, description);
501
515
  }
502
516
 
517
+ /**
518
+ * Wrap parseArgs to catch 'commander.invalidArgument'.
519
+ *
520
+ * @param {Option | Argument} target
521
+ * @param {string} value
522
+ * @param {*} previous
523
+ * @param {string} invalidArgumentMessage
524
+ * @private
525
+ */
526
+
527
+ _callParseArg(target, value, previous, invalidArgumentMessage) {
528
+ try {
529
+ return target.parseArg(value, previous);
530
+ } catch (err) {
531
+ if (err.code === 'commander.invalidArgument') {
532
+ const message = `${invalidArgumentMessage} ${err.message}`;
533
+ this.error(message, { exitCode: err.exitCode, code: err.code });
534
+ }
535
+ throw err;
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Check for option flag conflicts.
541
+ * Register option if no conflicts found, or throw on conflict.
542
+ *
543
+ * @param {Option} option
544
+ * @api private
545
+ */
546
+
547
+ _registerOption(option) {
548
+ const matchingOption = (option.short && this._findOption(option.short)) ||
549
+ (option.long && this._findOption(option.long));
550
+ if (matchingOption) {
551
+ const matchingFlag = (option.long && this._findOption(option.long)) ? option.long : option.short;
552
+ throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'
553
+ - already used by option '${matchingOption.flags}'`);
554
+ }
555
+
556
+ this.options.push(option);
557
+ }
558
+
559
+ /**
560
+ * Check for command name and alias conflicts with existing commands.
561
+ * Register command if no conflicts found, or throw on conflict.
562
+ *
563
+ * @param {Command} command
564
+ * @api private
565
+ */
566
+
567
+ _registerCommand(command) {
568
+ const knownBy = (cmd) => {
569
+ return [cmd.name()].concat(cmd.aliases());
570
+ };
571
+
572
+ const alreadyUsed = knownBy(command).find((name) => this._findCommand(name));
573
+ if (alreadyUsed) {
574
+ const existingCmd = knownBy(this._findCommand(alreadyUsed)).join('|');
575
+ const newCmd = knownBy(command).join('|');
576
+ throw new Error(`cannot add command '${newCmd}' as already have command '${existingCmd}'`);
577
+ }
578
+
579
+ this.commands.push(command);
580
+ }
581
+
503
582
  /**
504
583
  * Add an option.
505
584
  *
@@ -507,6 +586,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
507
586
  * @return {Command} `this` command for chaining
508
587
  */
509
588
  addOption(option) {
589
+ this._registerOption(option);
590
+
510
591
  const oname = option.name();
511
592
  const name = option.attributeName();
512
593
 
@@ -521,9 +602,6 @@ Expecting one of '${allowedValues.join("', '")}'`);
521
602
  this.setOptionValueWithSource(name, option.defaultValue, 'default');
522
603
  }
523
604
 
524
- // register the option
525
- this.options.push(option);
526
-
527
605
  // handler for cli and env supplied values
528
606
  const handleOptionValue = (val, invalidValueMessage, valueSource) => {
529
607
  // val is null for optional option used without an optional-argument.
@@ -535,15 +613,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
535
613
  // custom processing
536
614
  const oldValue = this.getOptionValue(name);
537
615
  if (val !== null && option.parseArg) {
538
- try {
539
- val = option.parseArg(val, oldValue);
540
- } catch (err) {
541
- if (err.code === 'commander.invalidArgument') {
542
- const message = `${invalidValueMessage} ${err.message}`;
543
- this.error(message, { exitCode: err.exitCode, code: err.code });
544
- }
545
- throw err;
546
- }
616
+ val = this._callParseArg(option, val, oldValue, invalidValueMessage);
547
617
  } else if (val !== null && option.variadic) {
548
618
  val = option._concatValue(val, oldValue);
549
619
  }
@@ -579,7 +649,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
579
649
  /**
580
650
  * Internal implementation shared by .option() and .requiredOption()
581
651
  *
582
- * @api private
652
+ * @private
583
653
  */
584
654
  _optionEx(config, flags, description, fn, defaultValue) {
585
655
  if (typeof flags === 'object' && flags instanceof Option) {
@@ -605,57 +675,29 @@ Expecting one of '${allowedValues.join("', '")}'`);
605
675
  }
606
676
 
607
677
  /**
608
- * Define option with `flags`, `description` and optional
609
- * coercion `fn`.
678
+ * Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both.
610
679
  *
611
- * The `flags` string contains the short and/or long flags,
612
- * separated by comma, a pipe or space. The following are all valid
613
- * all will output this way when `--help` is used.
680
+ * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. A required
681
+ * option-argument is indicated by `<>` and an optional option-argument by `[]`.
614
682
  *
615
- * "-p, --pepper"
616
- * "-p|--pepper"
617
- * "-p --pepper"
683
+ * See the README for more details, and see also addOption() and requiredOption().
618
684
  *
619
685
  * @example
620
- * // simple boolean defaulting to undefined
621
- * program.option('-p, --pepper', 'add pepper');
622
- *
623
- * program.pepper
624
- * // => undefined
625
- *
626
- * --pepper
627
- * program.pepper
628
- * // => true
629
- *
630
- * // simple boolean defaulting to true (unless non-negated option is also defined)
631
- * program.option('-C, --no-cheese', 'remove cheese');
632
- *
633
- * program.cheese
634
- * // => true
635
- *
636
- * --no-cheese
637
- * program.cheese
638
- * // => false
639
- *
640
- * // required argument
641
- * program.option('-C, --chdir <path>', 'change the working directory');
642
- *
643
- * --chdir /tmp
644
- * program.chdir
645
- * // => "/tmp"
646
- *
647
- * // optional argument
648
- * program.option('-c, --cheese [type]', 'add cheese [marble]');
686
+ * program
687
+ * .option('-p, --pepper', 'add pepper')
688
+ * .option('-p, --pizza-type <TYPE>', 'type of pizza') // required option-argument
689
+ * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
690
+ * .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
649
691
  *
650
692
  * @param {string} flags
651
693
  * @param {string} [description]
652
- * @param {Function|*} [fn] - custom option processing function or default value
694
+ * @param {Function|*} [parseArg] - custom option processing function or default value
653
695
  * @param {*} [defaultValue]
654
696
  * @return {Command} `this` command for chaining
655
697
  */
656
698
 
657
- option(flags, description, fn, defaultValue) {
658
- return this._optionEx({}, flags, description, fn, defaultValue);
699
+ option(flags, description, parseArg, defaultValue) {
700
+ return this._optionEx({}, flags, description, parseArg, defaultValue);
659
701
  }
660
702
 
661
703
  /**
@@ -666,13 +708,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
666
708
  *
667
709
  * @param {string} flags
668
710
  * @param {string} [description]
669
- * @param {Function|*} [fn] - custom option processing function or default value
711
+ * @param {Function|*} [parseArg] - custom option processing function or default value
670
712
  * @param {*} [defaultValue]
671
713
  * @return {Command} `this` command for chaining
672
714
  */
673
715
 
674
- requiredOption(flags, description, fn, defaultValue) {
675
- return this._optionEx({ mandatory: true }, flags, description, fn, defaultValue);
716
+ requiredOption(flags, description, parseArg, defaultValue) {
717
+ return this._optionEx({ mandatory: true }, flags, description, parseArg, defaultValue);
676
718
  }
677
719
 
678
720
  /**
@@ -735,12 +777,20 @@ Expecting one of '${allowedValues.join("', '")}'`);
735
777
  */
736
778
  passThroughOptions(passThrough = true) {
737
779
  this._passThroughOptions = !!passThrough;
738
- if (!!this.parent && passThrough && !this.parent._enablePositionalOptions) {
739
- throw new Error('passThroughOptions can not be used without turning on enablePositionalOptions for parent command(s)');
740
- }
780
+ this._checkForBrokenPassThrough();
741
781
  return this;
742
782
  }
743
783
 
784
+ /**
785
+ * @private
786
+ */
787
+
788
+ _checkForBrokenPassThrough() {
789
+ if (this.parent && this._passThroughOptions && !this.parent._enablePositionalOptions) {
790
+ throw new Error(`passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`);
791
+ }
792
+ }
793
+
744
794
  /**
745
795
  * Whether to store option values as properties on command object,
746
796
  * or store separately (specify false). In both cases the option values can be accessed using .opts().
@@ -750,10 +800,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
750
800
  */
751
801
 
752
802
  storeOptionsAsProperties(storeAsProperties = true) {
753
- this._storeOptionsAsProperties = !!storeAsProperties;
754
803
  if (this.options.length) {
755
804
  throw new Error('call .storeOptionsAsProperties() before adding options');
756
805
  }
806
+ if (Object.keys(this._optionValues).length) {
807
+ throw new Error('call .storeOptionsAsProperties() before setting option values');
808
+ }
809
+ this._storeOptionsAsProperties = !!storeAsProperties;
757
810
  return this;
758
811
  }
759
812
 
@@ -825,7 +878,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
825
878
  getOptionValueSourceWithGlobals(key) {
826
879
  // global overwrites local, like optsWithGlobals
827
880
  let source;
828
- getCommandAndParents(this).forEach((cmd) => {
881
+ this._getCommandAndAncestors().forEach((cmd) => {
829
882
  if (cmd.getOptionValueSource(key) !== undefined) {
830
883
  source = cmd.getOptionValueSource(key);
831
884
  }
@@ -837,7 +890,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
837
890
  * Get user arguments from implied or explicit arguments.
838
891
  * Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches.
839
892
  *
840
- * @api private
893
+ * @private
841
894
  */
842
895
 
843
896
  _prepareUserArgs(argv, parseOptions) {
@@ -940,7 +993,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
940
993
  /**
941
994
  * Execute a sub-command executable.
942
995
  *
943
- * @api private
996
+ * @private
944
997
  */
945
998
 
946
999
  _executeSubCommand(subcommand, args) {
@@ -1027,15 +1080,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
1027
1080
  }
1028
1081
 
1029
1082
  // By default terminate process when spawned process terminates.
1030
- // Suppressing the exit if exitCallback defined is a bit messy and of limited use, but does allow process to stay running!
1031
1083
  const exitCallback = this._exitCallback;
1032
- if (!exitCallback) {
1033
- proc.on('close', process.exit.bind(process));
1034
- } else {
1035
- proc.on('close', () => {
1036
- exitCallback(new CommanderError(process.exitCode || 0, 'commander.executeSubCommandAsync', '(close)'));
1037
- });
1038
- }
1084
+ proc.on('close', (code, _signal) => {
1085
+ code = code ?? 1; // code is null if spawned process terminated due to a signal
1086
+ if (!exitCallback) {
1087
+ process.exit(code);
1088
+ } else {
1089
+ exitCallback(new CommanderError(code, 'commander.executeSubCommandAsync', '(close)'));
1090
+ }
1091
+ });
1039
1092
  proc.on('error', (err) => {
1040
1093
  // @ts-ignore
1041
1094
  if (err.code === 'ENOENT') {
@@ -1065,30 +1118,30 @@ Expecting one of '${allowedValues.join("', '")}'`);
1065
1118
  }
1066
1119
 
1067
1120
  /**
1068
- * @api private
1121
+ * @private
1069
1122
  */
1070
1123
 
1071
1124
  _dispatchSubcommand(commandName, operands, unknown) {
1072
1125
  const subCommand = this._findCommand(commandName);
1073
1126
  if (!subCommand) this.help({ error: true });
1074
1127
 
1075
- let hookResult;
1076
- hookResult = this._chainOrCallSubCommandHook(hookResult, subCommand, 'preSubcommand');
1077
- hookResult = this._chainOrCall(hookResult, () => {
1128
+ let promiseChain;
1129
+ promiseChain = this._chainOrCallSubCommandHook(promiseChain, subCommand, 'preSubcommand');
1130
+ promiseChain = this._chainOrCall(promiseChain, () => {
1078
1131
  if (subCommand._executableHandler) {
1079
1132
  this._executeSubCommand(subCommand, operands.concat(unknown));
1080
1133
  } else {
1081
1134
  return subCommand._parseCommand(operands, unknown);
1082
1135
  }
1083
1136
  });
1084
- return hookResult;
1137
+ return promiseChain;
1085
1138
  }
1086
1139
 
1087
1140
  /**
1088
1141
  * Invoke help directly if possible, or dispatch if necessary.
1089
1142
  * e.g. help foo
1090
1143
  *
1091
- * @api private
1144
+ * @private
1092
1145
  */
1093
1146
 
1094
1147
  _dispatchHelpCommand(subcommandName) {
@@ -1101,35 +1154,37 @@ Expecting one of '${allowedValues.join("', '")}'`);
1101
1154
  }
1102
1155
 
1103
1156
  // Fallback to parsing the help flag to invoke the help.
1104
- return this._dispatchSubcommand(subcommandName, [], [this._helpLongFlag]);
1157
+ return this._dispatchSubcommand(subcommandName, [], [
1158
+ this._helpLongFlag || this._helpShortFlag
1159
+ ]);
1105
1160
  }
1106
1161
 
1107
1162
  /**
1108
- * Check this.args against expected this._args.
1163
+ * Check this.args against expected this.registeredArguments.
1109
1164
  *
1110
- * @api private
1165
+ * @private
1111
1166
  */
1112
1167
 
1113
1168
  _checkNumberOfArguments() {
1114
1169
  // too few
1115
- this._args.forEach((arg, i) => {
1170
+ this.registeredArguments.forEach((arg, i) => {
1116
1171
  if (arg.required && this.args[i] == null) {
1117
1172
  this.missingArgument(arg.name());
1118
1173
  }
1119
1174
  });
1120
1175
  // too many
1121
- if (this._args.length > 0 && this._args[this._args.length - 1].variadic) {
1176
+ if (this.registeredArguments.length > 0 && this.registeredArguments[this.registeredArguments.length - 1].variadic) {
1122
1177
  return;
1123
1178
  }
1124
- if (this.args.length > this._args.length) {
1179
+ if (this.args.length > this.registeredArguments.length) {
1125
1180
  this._excessArguments(this.args);
1126
1181
  }
1127
1182
  }
1128
1183
 
1129
1184
  /**
1130
- * Process this.args using this._args and save as this.processedArgs!
1185
+ * Process this.args using this.registeredArguments and save as this.processedArgs!
1131
1186
  *
1132
- * @api private
1187
+ * @private
1133
1188
  */
1134
1189
 
1135
1190
  _processArguments() {
@@ -1137,15 +1192,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1137
1192
  // Extra processing for nice error message on parsing failure.
1138
1193
  let parsedValue = value;
1139
1194
  if (value !== null && argument.parseArg) {
1140
- try {
1141
- parsedValue = argument.parseArg(value, previous);
1142
- } catch (err) {
1143
- if (err.code === 'commander.invalidArgument') {
1144
- const message = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'. ${err.message}`;
1145
- this.error(message, { exitCode: err.exitCode, code: err.code });
1146
- }
1147
- throw err;
1148
- }
1195
+ const invalidValueMessage = `error: command-argument value '${value}' is invalid for argument '${argument.name()}'.`;
1196
+ parsedValue = this._callParseArg(argument, value, previous, invalidValueMessage);
1149
1197
  }
1150
1198
  return parsedValue;
1151
1199
  };
@@ -1153,7 +1201,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1153
1201
  this._checkNumberOfArguments();
1154
1202
 
1155
1203
  const processedArgs = [];
1156
- this._args.forEach((declaredArg, index) => {
1204
+ this.registeredArguments.forEach((declaredArg, index) => {
1157
1205
  let value = declaredArg.defaultValue;
1158
1206
  if (declaredArg.variadic) {
1159
1207
  // Collect together remaining arguments for passing together as an array.
@@ -1184,7 +1232,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1184
1232
  * @param {Promise|undefined} promise
1185
1233
  * @param {Function} fn
1186
1234
  * @return {Promise|undefined}
1187
- * @api private
1235
+ * @private
1188
1236
  */
1189
1237
 
1190
1238
  _chainOrCall(promise, fn) {
@@ -1202,13 +1250,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1202
1250
  * @param {Promise|undefined} promise
1203
1251
  * @param {string} event
1204
1252
  * @return {Promise|undefined}
1205
- * @api private
1253
+ * @private
1206
1254
  */
1207
1255
 
1208
1256
  _chainOrCallHooks(promise, event) {
1209
1257
  let result = promise;
1210
1258
  const hooks = [];
1211
- getCommandAndParents(this)
1259
+ this._getCommandAndAncestors()
1212
1260
  .reverse()
1213
1261
  .filter(cmd => cmd._lifeCycleHooks[event] !== undefined)
1214
1262
  .forEach(hookedCommand => {
@@ -1234,7 +1282,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1234
1282
  * @param {Command} subCommand
1235
1283
  * @param {string} event
1236
1284
  * @return {Promise|undefined}
1237
- * @api private
1285
+ * @private
1238
1286
  */
1239
1287
 
1240
1288
  _chainOrCallSubCommandHook(promise, subCommand, event) {
@@ -1253,7 +1301,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1253
1301
  * Process arguments in context of this command.
1254
1302
  * Returns action result, in case it is a promise.
1255
1303
  *
1256
- * @api private
1304
+ * @private
1257
1305
  */
1258
1306
 
1259
1307
  _parseCommand(operands, unknown) {
@@ -1271,7 +1319,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1271
1319
  return this._dispatchHelpCommand(operands[1]);
1272
1320
  }
1273
1321
  if (this._defaultCommandName) {
1274
- outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
1322
+ this._outputHelpIfRequested(unknown); // Run the help for default command from parent rather than passing to default command
1275
1323
  return this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
1276
1324
  }
1277
1325
  if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
@@ -1279,7 +1327,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1279
1327
  this.help({ error: true });
1280
1328
  }
1281
1329
 
1282
- outputHelpIfRequested(this, parsed.unknown);
1330
+ this._outputHelpIfRequested(parsed.unknown);
1283
1331
  this._checkForMissingMandatoryOptions();
1284
1332
  this._checkForConflictingOptions();
1285
1333
 
@@ -1295,16 +1343,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
1295
1343
  checkForUnknownOptions();
1296
1344
  this._processArguments();
1297
1345
 
1298
- let actionResult;
1299
- actionResult = this._chainOrCallHooks(actionResult, 'preAction');
1300
- actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this.processedArgs));
1346
+ let promiseChain;
1347
+ promiseChain = this._chainOrCallHooks(promiseChain, 'preAction');
1348
+ promiseChain = this._chainOrCall(promiseChain, () => this._actionHandler(this.processedArgs));
1301
1349
  if (this.parent) {
1302
- actionResult = this._chainOrCall(actionResult, () => {
1350
+ promiseChain = this._chainOrCall(promiseChain, () => {
1303
1351
  this.parent.emit(commandEvent, operands, unknown); // legacy
1304
1352
  });
1305
1353
  }
1306
- actionResult = this._chainOrCallHooks(actionResult, 'postAction');
1307
- return actionResult;
1354
+ promiseChain = this._chainOrCallHooks(promiseChain, 'postAction');
1355
+ return promiseChain;
1308
1356
  }
1309
1357
  if (this.parent && this.parent.listenerCount(commandEvent)) {
1310
1358
  checkForUnknownOptions();
@@ -1337,7 +1385,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1337
1385
  /**
1338
1386
  * Find matching command.
1339
1387
  *
1340
- * @api private
1388
+ * @private
1341
1389
  */
1342
1390
  _findCommand(name) {
1343
1391
  if (!name) return undefined;
@@ -1349,7 +1397,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1349
1397
  *
1350
1398
  * @param {string} arg
1351
1399
  * @return {Option}
1352
- * @api private
1400
+ * @package internal use only
1353
1401
  */
1354
1402
 
1355
1403
  _findOption(arg) {
@@ -1360,24 +1408,24 @@ Expecting one of '${allowedValues.join("', '")}'`);
1360
1408
  * Display an error message if a mandatory option does not have a value.
1361
1409
  * Called after checking for help flags in leaf subcommand.
1362
1410
  *
1363
- * @api private
1411
+ * @private
1364
1412
  */
1365
1413
 
1366
1414
  _checkForMissingMandatoryOptions() {
1367
1415
  // Walk up hierarchy so can call in subcommand after checking for displaying help.
1368
- for (let cmd = this; cmd; cmd = cmd.parent) {
1416
+ this._getCommandAndAncestors().forEach((cmd) => {
1369
1417
  cmd.options.forEach((anOption) => {
1370
1418
  if (anOption.mandatory && (cmd.getOptionValue(anOption.attributeName()) === undefined)) {
1371
1419
  cmd.missingMandatoryOptionValue(anOption);
1372
1420
  }
1373
1421
  });
1374
- }
1422
+ });
1375
1423
  }
1376
1424
 
1377
1425
  /**
1378
1426
  * Display an error message if conflicting options are used together in this.
1379
1427
  *
1380
- * @api private
1428
+ * @private
1381
1429
  */
1382
1430
  _checkForConflictingLocalOptions() {
1383
1431
  const definedNonDefaultOptions = this.options.filter(
@@ -1408,13 +1456,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1408
1456
  * Display an error message if conflicting options are used together.
1409
1457
  * Called after checking for help flags in leaf subcommand.
1410
1458
  *
1411
- * @api private
1459
+ * @private
1412
1460
  */
1413
1461
  _checkForConflictingOptions() {
1414
1462
  // Walk up hierarchy so can call in subcommand after checking for displaying help.
1415
- for (let cmd = this; cmd; cmd = cmd.parent) {
1463
+ this._getCommandAndAncestors().forEach((cmd) => {
1416
1464
  cmd._checkForConflictingLocalOptions();
1417
- }
1465
+ });
1418
1466
  }
1419
1467
 
1420
1468
  /**
@@ -1577,7 +1625,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1577
1625
  */
1578
1626
  optsWithGlobals() {
1579
1627
  // globals overwrite locals
1580
- return getCommandAndParents(this).reduce(
1628
+ return this._getCommandAndAncestors().reduce(
1581
1629
  (combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()),
1582
1630
  {}
1583
1631
  );
@@ -1612,7 +1660,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1612
1660
  * Apply any option related environment variables, if option does
1613
1661
  * not have a value from cli or client code.
1614
1662
  *
1615
- * @api private
1663
+ * @private
1616
1664
  */
1617
1665
  _parseOptionsEnv() {
1618
1666
  this.options.forEach((option) => {
@@ -1635,7 +1683,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1635
1683
  /**
1636
1684
  * Apply any implied option values, if option is undefined or default value.
1637
1685
  *
1638
- * @api private
1686
+ * @private
1639
1687
  */
1640
1688
  _parseOptionsImplied() {
1641
1689
  const dualHelper = new DualOptions(this.options);
@@ -1659,7 +1707,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1659
1707
  * Argument `name` is missing.
1660
1708
  *
1661
1709
  * @param {string} name
1662
- * @api private
1710
+ * @private
1663
1711
  */
1664
1712
 
1665
1713
  missingArgument(name) {
@@ -1671,7 +1719,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1671
1719
  * `Option` is missing an argument.
1672
1720
  *
1673
1721
  * @param {Option} option
1674
- * @api private
1722
+ * @private
1675
1723
  */
1676
1724
 
1677
1725
  optionMissingArgument(option) {
@@ -1683,7 +1731,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1683
1731
  * `Option` does not have a value, and is a mandatory option.
1684
1732
  *
1685
1733
  * @param {Option} option
1686
- * @api private
1734
+ * @private
1687
1735
  */
1688
1736
 
1689
1737
  missingMandatoryOptionValue(option) {
@@ -1696,7 +1744,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1696
1744
  *
1697
1745
  * @param {Option} option
1698
1746
  * @param {Option} conflictingOption
1699
- * @api private
1747
+ * @private
1700
1748
  */
1701
1749
  _conflictingOption(option, conflictingOption) {
1702
1750
  // The calling code does not know whether a negated option is the source of the
@@ -1733,7 +1781,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1733
1781
  * Unknown option `flag`.
1734
1782
  *
1735
1783
  * @param {string} flag
1736
- * @api private
1784
+ * @private
1737
1785
  */
1738
1786
 
1739
1787
  unknownOption(flag) {
@@ -1762,13 +1810,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1762
1810
  * Excess arguments, more than expected.
1763
1811
  *
1764
1812
  * @param {string[]} receivedArgs
1765
- * @api private
1813
+ * @private
1766
1814
  */
1767
1815
 
1768
1816
  _excessArguments(receivedArgs) {
1769
1817
  if (this._allowExcessArguments) return;
1770
1818
 
1771
- const expected = this._args.length;
1819
+ const expected = this.registeredArguments.length;
1772
1820
  const s = (expected === 1) ? '' : 's';
1773
1821
  const forSubcommand = this.parent ? ` for '${this.name()}'` : '';
1774
1822
  const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
@@ -1778,7 +1826,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1778
1826
  /**
1779
1827
  * Unknown command.
1780
1828
  *
1781
- * @api private
1829
+ * @private
1782
1830
  */
1783
1831
 
1784
1832
  unknownCommand() {
@@ -1800,17 +1848,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
1800
1848
  }
1801
1849
 
1802
1850
  /**
1803
- * Set the program version to `str`.
1851
+ * Get or set the program version.
1804
1852
  *
1805
- * This method auto-registers the "-V, --version" flag
1806
- * which will print the version number when passed.
1853
+ * This method auto-registers the "-V, --version" option which will print the version number.
1807
1854
  *
1808
- * You can optionally supply the flags and description to override the defaults.
1855
+ * You can optionally supply the flags and description to override the defaults.
1809
1856
  *
1810
- * @param {string} str
1857
+ * @param {string} [str]
1811
1858
  * @param {string} [flags]
1812
1859
  * @param {string} [description]
1813
- * @return {this | string} `this` command for chaining, or version string if no arguments
1860
+ * @return {this | string | undefined} `this` command for chaining, or version string if no arguments
1814
1861
  */
1815
1862
 
1816
1863
  version(str, flags, description) {
@@ -1820,7 +1867,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
1820
1867
  description = description || 'output the version number';
1821
1868
  const versionOption = this.createOption(flags, description);
1822
1869
  this._versionOptionName = versionOption.attributeName();
1823
- this.options.push(versionOption);
1870
+ this._registerOption(versionOption);
1871
+
1824
1872
  this.on('option:' + versionOption.name(), () => {
1825
1873
  this._outputConfiguration.writeOut(`${str}\n`);
1826
1874
  this._exit(0, 'commander.version', str);
@@ -1876,6 +1924,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
1876
1924
  }
1877
1925
 
1878
1926
  if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
1927
+ const matchingCommand = this.parent?._findCommand(alias);
1928
+ if (matchingCommand) {
1929
+ // c.f. _registerCommand
1930
+ const existingCmd = [matchingCommand.name()].concat(matchingCommand.aliases()).join('|');
1931
+ throw new Error(`cannot add alias '${alias}' to command '${this.name()}' as already have command '${existingCmd}'`);
1932
+ }
1879
1933
 
1880
1934
  command._aliases.push(alias);
1881
1935
  return this;
@@ -1909,13 +1963,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1909
1963
  if (str === undefined) {
1910
1964
  if (this._usage) return this._usage;
1911
1965
 
1912
- const args = this._args.map((arg) => {
1966
+ const args = this.registeredArguments.map((arg) => {
1913
1967
  return humanReadableArgName(arg);
1914
1968
  });
1915
1969
  return [].concat(
1916
1970
  (this.options.length || this._hasHelpOption ? '[options]' : []),
1917
1971
  (this.commands.length ? '[command]' : []),
1918
- (this._args.length ? args : [])
1972
+ (this.registeredArguments.length ? args : [])
1919
1973
  ).join(' ');
1920
1974
  }
1921
1975
 
@@ -1964,7 +2018,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1964
2018
  * program.executableDir('subcommands');
1965
2019
  *
1966
2020
  * @param {string} [path]
1967
- * @return {string|Command}
2021
+ * @return {string|null|Command}
1968
2022
  */
1969
2023
 
1970
2024
  executableDir(path) {
@@ -1989,7 +2043,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1989
2043
  }
1990
2044
 
1991
2045
  /**
1992
- * @api private
2046
+ * @private
1993
2047
  */
1994
2048
 
1995
2049
  _getHelpContext(contextOptions) {
@@ -2022,7 +2076,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2022
2076
  }
2023
2077
  const context = this._getHelpContext(contextOptions);
2024
2078
 
2025
- getCommandAndParents(this).reverse().forEach(command => command.emit('beforeAllHelp', context));
2079
+ this._getCommandAndAncestors().reverse().forEach(command => command.emit('beforeAllHelp', context));
2026
2080
  this.emit('beforeHelp', context);
2027
2081
 
2028
2082
  let helpInformation = this.helpInformation(context);
@@ -2034,9 +2088,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2034
2088
  }
2035
2089
  context.write(helpInformation);
2036
2090
 
2037
- this.emit(this._helpLongFlag); // deprecated
2091
+ if (this._helpLongFlag) {
2092
+ this.emit(this._helpLongFlag); // deprecated
2093
+ }
2038
2094
  this.emit('afterHelp', context);
2039
- getCommandAndParents(this).forEach(command => command.emit('afterAllHelp', context));
2095
+ this._getCommandAndAncestors().forEach(command => command.emit('afterAllHelp', context));
2040
2096
  }
2041
2097
 
2042
2098
  /**
@@ -2113,22 +2169,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2113
2169
  });
2114
2170
  return this;
2115
2171
  }
2116
- }
2117
2172
 
2118
- /**
2119
- * Output help information if help flags specified
2120
- *
2121
- * @param {Command} cmd - command to output help for
2122
- * @param {Array} args - array of options to search for help flags
2123
- * @api private
2124
- */
2173
+ /**
2174
+ * Output help information if help flags specified
2175
+ *
2176
+ * @param {Array} args - array of options to search for help flags
2177
+ * @private
2178
+ */
2125
2179
 
2126
- function outputHelpIfRequested(cmd, args) {
2127
- const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
2128
- if (helpOption) {
2129
- cmd.outputHelp();
2130
- // (Do not have all displayed text available so only passing placeholder.)
2131
- cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)');
2180
+ _outputHelpIfRequested(args) {
2181
+ const helpOption = this._hasHelpOption && args.find(arg => arg === this._helpLongFlag || arg === this._helpShortFlag);
2182
+ if (helpOption) {
2183
+ this.outputHelp();
2184
+ // (Do not have all displayed text available so only passing placeholder.)
2185
+ this._exit(0, 'commander.helpDisplayed', '(outputHelp)');
2186
+ }
2132
2187
  }
2133
2188
  }
2134
2189
 
@@ -2137,7 +2192,7 @@ function outputHelpIfRequested(cmd, args) {
2137
2192
  *
2138
2193
  * @param {string[]} args - array of arguments from node.execArgv
2139
2194
  * @returns {string[]}
2140
- * @api private
2195
+ * @private
2141
2196
  */
2142
2197
 
2143
2198
  function incrementNodeInspectorPort(args) {
@@ -2179,18 +2234,4 @@ function incrementNodeInspectorPort(args) {
2179
2234
  });
2180
2235
  }
2181
2236
 
2182
- /**
2183
- * @param {Command} startCommand
2184
- * @returns {Command[]}
2185
- * @api private
2186
- */
2187
-
2188
- function getCommandAndParents(startCommand) {
2189
- const result = [];
2190
- for (let command = startCommand; command; command = command.parent) {
2191
- result.push(command);
2192
- }
2193
- return result;
2194
- }
2195
-
2196
2237
  exports.Command = Command;