commander 5.0.0 → 6.1.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/index.js CHANGED
@@ -20,13 +20,18 @@ class Option {
20
20
 
21
21
  constructor(flags, description) {
22
22
  this.flags = flags;
23
- this.required = flags.indexOf('<') >= 0; // A value must be supplied when the option is specified.
24
- this.optional = flags.indexOf('[') >= 0; // A value is optional when the option is specified.
23
+ this.required = flags.includes('<'); // A value must be supplied when the option is specified.
24
+ this.optional = flags.includes('['); // A value is optional when the option is specified.
25
+ // variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
26
+ this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
25
27
  this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
26
- this.negate = flags.indexOf('-no-') !== -1;
27
- const flagParts = flags.split(/[ ,|]+/);
28
- if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) this.short = flagParts.shift();
29
- this.long = flagParts.shift();
28
+ const optionFlags = _parseOptionFlags(flags);
29
+ this.short = optionFlags.shortFlag;
30
+ this.long = optionFlags.longFlag;
31
+ this.negate = false;
32
+ if (this.long) {
33
+ this.negate = this.long.startsWith('--no-');
34
+ }
30
35
  this.description = description || '';
31
36
  this.defaultValue = undefined;
32
37
  }
@@ -39,7 +44,10 @@ class Option {
39
44
  */
40
45
 
41
46
  name() {
42
- return this.long.replace(/^--/, '');
47
+ if (this.long) {
48
+ return this.long.replace(/^--/, '');
49
+ }
50
+ return this.short.replace(/^-/, '');
43
51
  };
44
52
 
45
53
  /**
@@ -110,6 +118,7 @@ class Command extends EventEmitter {
110
118
  this._name = name || '';
111
119
  this._optionValues = {};
112
120
  this._storeOptionsAsProperties = true; // backwards compatible by default
121
+ this._storeOptionsAsPropertiesCalled = false;
113
122
  this._passCommandToAction = true; // backwards compatible by default
114
123
  this._actionResults = [];
115
124
  this._actionHandler = null;
@@ -117,9 +126,11 @@ class Command extends EventEmitter {
117
126
  this._executableFile = null; // custom name for executable
118
127
  this._defaultCommandName = null;
119
128
  this._exitCallback = null;
120
- this._alias = null;
129
+ this._aliases = [];
130
+ this._combineFlagAndOptionalValue = true;
121
131
 
122
- this._noHelp = false;
132
+ this._hidden = false;
133
+ this._hasHelpOption = true;
123
134
  this._helpFlags = '-h, --help';
124
135
  this._helpDescription = 'display help for command';
125
136
  this._helpShortFlag = '-h';
@@ -153,7 +164,7 @@ class Command extends EventEmitter {
153
164
  * @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
154
165
  * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
155
166
  * @param {Object} [execOpts] - configuration options (for executable)
156
- * @return {Command} returns new command for action handler, or top-level command for executable command
167
+ * @return {Command} returns new command for action handler, or `this` for executable command
157
168
  * @api public
158
169
  */
159
170
 
@@ -174,7 +185,8 @@ class Command extends EventEmitter {
174
185
  }
175
186
  if (opts.isDefault) this._defaultCommandName = cmd._name;
176
187
 
177
- cmd._noHelp = !!opts.noHelp;
188
+ cmd._hidden = !!(opts.noHelp || opts.hidden);
189
+ cmd._hasHelpOption = this._hasHelpOption;
178
190
  cmd._helpFlags = this._helpFlags;
179
191
  cmd._helpDescription = this._helpDescription;
180
192
  cmd._helpShortFlag = this._helpShortFlag;
@@ -185,6 +197,7 @@ class Command extends EventEmitter {
185
197
  cmd._exitCallback = this._exitCallback;
186
198
  cmd._storeOptionsAsProperties = this._storeOptionsAsProperties;
187
199
  cmd._passCommandToAction = this._passCommandToAction;
200
+ cmd._combineFlagAndOptionalValue = this._combineFlagAndOptionalValue;
188
201
 
189
202
  cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
190
203
  this.commands.push(cmd);
@@ -216,11 +229,12 @@ class Command extends EventEmitter {
216
229
  * See .command() for creating an attached subcommand which inherits settings from its parent.
217
230
  *
218
231
  * @param {Command} cmd - new subcommand
219
- * @return {Command} parent command for chaining
232
+ * @param {Object} [opts] - configuration options
233
+ * @return {Command} `this` command for chaining
220
234
  * @api public
221
235
  */
222
236
 
223
- addCommand(cmd) {
237
+ addCommand(cmd, opts) {
224
238
  if (!cmd._name) throw new Error('Command passed to .addCommand() must have a name');
225
239
 
226
240
  // To keep things simple, block automatic name generation for deeply nested executables.
@@ -235,13 +249,17 @@ class Command extends EventEmitter {
235
249
  }
236
250
  checkExplicitNames(cmd.commands);
237
251
 
252
+ opts = opts || {};
253
+ if (opts.isDefault) this._defaultCommandName = cmd._name;
254
+ if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
255
+
238
256
  this.commands.push(cmd);
239
257
  cmd.parent = this;
240
258
  return this;
241
259
  };
242
260
 
243
261
  /**
244
- * Define argument syntax for the top-level command.
262
+ * Define argument syntax for the command.
245
263
  *
246
264
  * @api public
247
265
  */
@@ -255,9 +273,9 @@ class Command extends EventEmitter {
255
273
  *
256
274
  * addHelpCommand() // force on
257
275
  * addHelpCommand(false); // force off
258
- * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom detais
276
+ * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
259
277
  *
260
- * @return {Command} for chaining
278
+ * @return {Command} `this` command for chaining
261
279
  * @api public
262
280
  */
263
281
 
@@ -293,7 +311,7 @@ class Command extends EventEmitter {
293
311
  * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
294
312
  *
295
313
  * @param {Array} args
296
- * @return {Command} for chaining
314
+ * @return {Command} `this` command for chaining
297
315
  * @api private
298
316
  */
299
317
 
@@ -336,7 +354,7 @@ class Command extends EventEmitter {
336
354
  * Register callback to use as replacement for calling process.exit.
337
355
  *
338
356
  * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
339
- * @return {Command} for chaining
357
+ * @return {Command} `this` command for chaining
340
358
  * @api public
341
359
  */
342
360
 
@@ -386,7 +404,7 @@ class Command extends EventEmitter {
386
404
  * });
387
405
  *
388
406
  * @param {Function} fn
389
- * @return {Command} for chaining
407
+ * @return {Command} `this` command for chaining
390
408
  * @api public
391
409
  */
392
410
 
@@ -417,15 +435,58 @@ class Command extends EventEmitter {
417
435
  return this;
418
436
  };
419
437
 
438
+ /**
439
+ * Internal routine to check whether there is a clash storing option value with a Command property.
440
+ *
441
+ * @param {Option} option
442
+ * @api private
443
+ */
444
+
445
+ _checkForOptionNameClash(option) {
446
+ if (!this._storeOptionsAsProperties || this._storeOptionsAsPropertiesCalled) {
447
+ // Storing options safely, or user has been explicit and up to them.
448
+ return;
449
+ }
450
+ // User may override help, and hard to tell if worth warning.
451
+ if (option.name() === 'help') {
452
+ return;
453
+ }
454
+
455
+ const commandProperty = this._getOptionValue(option.attributeName());
456
+ if (commandProperty === undefined) {
457
+ // no clash
458
+ return;
459
+ }
460
+
461
+ let foundClash = true;
462
+ if (option.negate) {
463
+ // It is ok if define foo before --no-foo.
464
+ const positiveLongFlag = option.long.replace(/^--no-/, '--');
465
+ foundClash = !this._findOption(positiveLongFlag);
466
+ } else if (option.long) {
467
+ const negativeLongFlag = option.long.replace(/^--/, '--no-');
468
+ foundClash = !this._findOption(negativeLongFlag);
469
+ }
470
+
471
+ if (foundClash) {
472
+ throw new Error(`option '${option.name()}' clashes with existing property '${option.attributeName()}' on Command
473
+ - call storeOptionsAsProperties(false) to store option values safely,
474
+ - or call storeOptionsAsProperties(true) to suppress this check,
475
+ - or change option name
476
+
477
+ Read more on https://git.io/JJc0W`);
478
+ }
479
+ };
480
+
420
481
  /**
421
482
  * Internal implementation shared by .option() and .requiredOption()
422
483
  *
423
484
  * @param {Object} config
424
485
  * @param {string} flags
425
486
  * @param {string} description
426
- * @param {Function|*} [fn] - custom option processing function or default vaue
487
+ * @param {Function|*} [fn] - custom option processing function or default value
427
488
  * @param {*} [defaultValue]
428
- * @return {Command} for chaining
489
+ * @return {Command} `this` command for chaining
429
490
  * @api private
430
491
  */
431
492
 
@@ -435,6 +496,8 @@ class Command extends EventEmitter {
435
496
  const name = option.attributeName();
436
497
  option.mandatory = !!config.mandatory;
437
498
 
499
+ this._checkForOptionNameClash(option);
500
+
438
501
  // default as 3rd arg
439
502
  if (typeof fn !== 'function') {
440
503
  if (fn instanceof RegExp) {
@@ -471,13 +534,21 @@ class Command extends EventEmitter {
471
534
  // when it's passed assign the value
472
535
  // and conditionally invoke the callback
473
536
  this.on('option:' + oname, (val) => {
474
- // coercion
537
+ const oldValue = this._getOptionValue(name);
538
+
539
+ // custom processing
475
540
  if (val !== null && fn) {
476
- val = fn(val, this._getOptionValue(name) === undefined ? defaultValue : this._getOptionValue(name));
541
+ val = fn(val, oldValue === undefined ? defaultValue : oldValue);
542
+ } else if (val !== null && option.variadic) {
543
+ if (oldValue === defaultValue || !Array.isArray(oldValue)) {
544
+ val = [val];
545
+ } else {
546
+ val = oldValue.concat(val);
547
+ }
477
548
  }
478
549
 
479
550
  // unassigned or boolean value
480
- if (typeof this._getOptionValue(name) === 'boolean' || typeof this._getOptionValue(name) === 'undefined') {
551
+ if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') {
481
552
  // if no value, negate false, and we have a default, then use it!
482
553
  if (val == null) {
483
554
  this._setOptionValue(name, option.negate
@@ -541,9 +612,9 @@ class Command extends EventEmitter {
541
612
  *
542
613
  * @param {string} flags
543
614
  * @param {string} description
544
- * @param {Function|*} [fn] - custom option processing function or default vaue
615
+ * @param {Function|*} [fn] - custom option processing function or default value
545
616
  * @param {*} [defaultValue]
546
- * @return {Command} for chaining
617
+ * @return {Command} `this` command for chaining
547
618
  * @api public
548
619
  */
549
620
 
@@ -551,7 +622,7 @@ class Command extends EventEmitter {
551
622
  return this._optionEx({}, flags, description, fn, defaultValue);
552
623
  };
553
624
 
554
- /*
625
+ /**
555
626
  * Add a required option which must have a value after parsing. This usually means
556
627
  * the option must be specified on the command line. (Otherwise the same as .option().)
557
628
  *
@@ -559,9 +630,9 @@ class Command extends EventEmitter {
559
630
  *
560
631
  * @param {string} flags
561
632
  * @param {string} description
562
- * @param {Function|*} [fn] - custom option processing function or default vaue
633
+ * @param {Function|*} [fn] - custom option processing function or default value
563
634
  * @param {*} [defaultValue]
564
- * @return {Command} for chaining
635
+ * @return {Command} `this` command for chaining
565
636
  * @api public
566
637
  */
567
638
 
@@ -569,6 +640,23 @@ class Command extends EventEmitter {
569
640
  return this._optionEx({ mandatory: true }, flags, description, fn, defaultValue);
570
641
  };
571
642
 
643
+ /**
644
+ * Alter parsing of short flags with optional values.
645
+ *
646
+ * Examples:
647
+ *
648
+ * // for `.option('-f,--flag [value]'):
649
+ * .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour
650
+ * .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
651
+ *
652
+ * @param {Boolean} [arg] - if `true` or omitted, an optional value can be specified directly after the flag.
653
+ * @api public
654
+ */
655
+ combineFlagAndOptionalValue(arg) {
656
+ this._combineFlagAndOptionalValue = (arg === undefined) || arg;
657
+ return this;
658
+ };
659
+
572
660
  /**
573
661
  * Allow unknown options on the command line.
574
662
  *
@@ -577,7 +665,7 @@ class Command extends EventEmitter {
577
665
  * @api public
578
666
  */
579
667
  allowUnknownOption(arg) {
580
- this._allowUnknownOption = arguments.length === 0 || arg;
668
+ this._allowUnknownOption = (arg === undefined) || arg;
581
669
  return this;
582
670
  };
583
671
 
@@ -586,11 +674,12 @@ class Command extends EventEmitter {
586
674
  * or store separately (specify false). In both cases the option values can be accessed using .opts().
587
675
  *
588
676
  * @param {boolean} value
589
- * @return {Command} Command for chaining
677
+ * @return {Command} `this` command for chaining
590
678
  * @api public
591
679
  */
592
680
 
593
681
  storeOptionsAsProperties(value) {
682
+ this._storeOptionsAsPropertiesCalled = true;
594
683
  this._storeOptionsAsProperties = (value === undefined) || value;
595
684
  if (this.options.length) {
596
685
  throw new Error('call .storeOptionsAsProperties() before adding options');
@@ -603,7 +692,7 @@ class Command extends EventEmitter {
603
692
  * or just the options (specify false).
604
693
  *
605
694
  * @param {boolean} value
606
- * @return {Command} Command for chaining
695
+ * @return {Command} `this` command for chaining
607
696
  * @api public
608
697
  */
609
698
 
@@ -658,7 +747,7 @@ class Command extends EventEmitter {
658
747
  * @param {string[]} [argv] - optional, defaults to process.argv
659
748
  * @param {Object} [parseOptions] - optionally specify style of options with from: node/user/electron
660
749
  * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
661
- * @return {Command} for chaining
750
+ * @return {Command} `this` command for chaining
662
751
  * @api public
663
752
  */
664
753
 
@@ -755,7 +844,11 @@ class Command extends EventEmitter {
755
844
  this._checkForMissingMandatoryOptions();
756
845
 
757
846
  // Want the entry script as the reference for command name and directory for searching for other files.
758
- const scriptPath = this._scriptPath;
847
+ let scriptPath = this._scriptPath;
848
+ // Fallback in case not set, due to how Command created or called.
849
+ if (!scriptPath && process.mainModule) {
850
+ scriptPath = process.mainModule.filename;
851
+ }
759
852
 
760
853
  let baseDir;
761
854
  try {
@@ -886,7 +979,7 @@ class Command extends EventEmitter {
886
979
  this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
887
980
  } else {
888
981
  if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
889
- // probaby missing subcommand and no handler, user needs help
982
+ // probably missing subcommand and no handler, user needs help
890
983
  this._helpAndError();
891
984
  }
892
985
 
@@ -932,7 +1025,7 @@ class Command extends EventEmitter {
932
1025
  */
933
1026
  _findCommand(name) {
934
1027
  if (!name) return undefined;
935
- return this.commands.find(cmd => cmd._name === name || cmd._alias === name);
1028
+ return this.commands.find(cmd => cmd._name === name || cmd._aliases.includes(name));
936
1029
  };
937
1030
 
938
1031
  /**
@@ -993,6 +1086,7 @@ class Command extends EventEmitter {
993
1086
  }
994
1087
 
995
1088
  // parse options
1089
+ let activeVariadicOption = null;
996
1090
  while (args.length) {
997
1091
  const arg = args.shift();
998
1092
 
@@ -1003,6 +1097,12 @@ class Command extends EventEmitter {
1003
1097
  break;
1004
1098
  }
1005
1099
 
1100
+ if (activeVariadicOption && !maybeOption(arg)) {
1101
+ this.emit(`option:${activeVariadicOption.name()}`, arg);
1102
+ continue;
1103
+ }
1104
+ activeVariadicOption = null;
1105
+
1006
1106
  if (maybeOption(arg)) {
1007
1107
  const option = this._findOption(arg);
1008
1108
  // recognised option, call listener to assign value with possible custom processing
@@ -1021,6 +1121,7 @@ class Command extends EventEmitter {
1021
1121
  } else { // boolean flag
1022
1122
  this.emit(`option:${option.name()}`);
1023
1123
  }
1124
+ activeVariadicOption = option.variadic ? option : null;
1024
1125
  continue;
1025
1126
  }
1026
1127
  }
@@ -1029,7 +1130,7 @@ class Command extends EventEmitter {
1029
1130
  if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
1030
1131
  const option = this._findOption(`-${arg[1]}`);
1031
1132
  if (option) {
1032
- if (option.required || option.optional) {
1133
+ if (option.required || (option.optional && this._combineFlagAndOptionalValue)) {
1033
1134
  // option with value following in same argument
1034
1135
  this.emit(`option:${option.name()}`, arg.slice(2));
1035
1136
  } else {
@@ -1156,7 +1257,8 @@ class Command extends EventEmitter {
1156
1257
  partCommands.unshift(parentCmd.name());
1157
1258
  }
1158
1259
  const fullCommand = partCommands.join(' ');
1159
- const message = `error: unknown command '${this.args[0]}'. See '${fullCommand} ${this._helpLongFlag}'.`;
1260
+ const message = `error: unknown command '${this.args[0]}'.` +
1261
+ (this._hasHelpOption ? ` See '${fullCommand} ${this._helpLongFlag}'.` : '');
1160
1262
  console.error(message);
1161
1263
  this._exit(1, 'commander.unknownCommand', message);
1162
1264
  };
@@ -1172,19 +1274,19 @@ class Command extends EventEmitter {
1172
1274
  * @param {string} str
1173
1275
  * @param {string} [flags]
1174
1276
  * @param {string} [description]
1175
- * @return {Command | string} this for chaining
1277
+ * @return {this | string} `this` command for chaining, or version string if no arguments
1176
1278
  * @api public
1177
1279
  */
1178
1280
 
1179
1281
  version(str, flags, description) {
1180
- if (arguments.length === 0) return this._version;
1282
+ if (str === undefined) return this._version;
1181
1283
  this._version = str;
1182
1284
  flags = flags || '-V, --version';
1183
1285
  description = description || 'output the version number';
1184
1286
  const versionOption = new Option(flags, description);
1185
- this._versionOptionName = versionOption.long.substr(2) || 'version';
1287
+ this._versionOptionName = versionOption.attributeName();
1186
1288
  this.options.push(versionOption);
1187
- this.on('option:' + this._versionOptionName, () => {
1289
+ this.on('option:' + versionOption.name(), () => {
1188
1290
  process.stdout.write(str + '\n');
1189
1291
  this._exit(0, 'commander.version', str);
1190
1292
  });
@@ -1196,36 +1298,57 @@ class Command extends EventEmitter {
1196
1298
  *
1197
1299
  * @param {string} str
1198
1300
  * @param {Object} [argsDescription]
1199
- * @return {String|Command}
1301
+ * @return {string|Command}
1200
1302
  * @api public
1201
1303
  */
1202
1304
 
1203
1305
  description(str, argsDescription) {
1204
- if (arguments.length === 0) return this._description;
1306
+ if (str === undefined && argsDescription === undefined) return this._description;
1205
1307
  this._description = str;
1206
1308
  this._argsDescription = argsDescription;
1207
1309
  return this;
1208
1310
  };
1209
1311
 
1210
1312
  /**
1211
- * Set an alias for the command
1313
+ * Set an alias for the command.
1212
1314
  *
1213
- * @param {string} alias
1214
- * @return {String|Command}
1315
+ * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
1316
+ *
1317
+ * @param {string} [alias]
1318
+ * @return {string|Command}
1215
1319
  * @api public
1216
1320
  */
1217
1321
 
1218
1322
  alias(alias) {
1323
+ if (alias === undefined) return this._aliases[0]; // just return first, for backwards compatibility
1324
+
1219
1325
  let command = this;
1220
- if (this.commands.length !== 0) {
1326
+ if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
1327
+ // assume adding alias for last added executable subcommand, rather than this
1221
1328
  command = this.commands[this.commands.length - 1];
1222
1329
  }
1223
1330
 
1224
- if (arguments.length === 0) return command._alias;
1225
-
1226
1331
  if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
1227
1332
 
1228
- command._alias = alias;
1333
+ command._aliases.push(alias);
1334
+ return this;
1335
+ };
1336
+
1337
+ /**
1338
+ * Set aliases for the command.
1339
+ *
1340
+ * Only the first alias is shown in the auto-generated help.
1341
+ *
1342
+ * @param {string[]} [aliases]
1343
+ * @return {string[]|Command}
1344
+ * @api public
1345
+ */
1346
+
1347
+ aliases(aliases) {
1348
+ // Getter for the array of aliases is the main reason for having aliases() in addition to alias().
1349
+ if (aliases === undefined) return this._aliases;
1350
+
1351
+ aliases.forEach((alias) => this.alias(alias));
1229
1352
  return this;
1230
1353
  };
1231
1354
 
@@ -1238,17 +1361,20 @@ class Command extends EventEmitter {
1238
1361
  */
1239
1362
 
1240
1363
  usage(str) {
1241
- const args = this._args.map((arg) => {
1242
- return humanReadableArgName(arg);
1243
- });
1364
+ if (str === undefined) {
1365
+ if (this._usage) return this._usage;
1244
1366
 
1245
- const usage = '[options]' +
1246
- (this.commands.length ? ' [command]' : '') +
1247
- (this._args.length ? ' ' + args.join(' ') : '');
1367
+ const args = this._args.map((arg) => {
1368
+ return humanReadableArgName(arg);
1369
+ });
1370
+ return [].concat(
1371
+ (this.options.length || this._hasHelpOption ? '[options]' : []),
1372
+ (this.commands.length ? '[command]' : []),
1373
+ (this._args.length ? args : [])
1374
+ ).join(' ');
1375
+ }
1248
1376
 
1249
- if (arguments.length === 0) return this._usage || usage;
1250
1377
  this._usage = str;
1251
-
1252
1378
  return this;
1253
1379
  };
1254
1380
 
@@ -1261,7 +1387,7 @@ class Command extends EventEmitter {
1261
1387
  */
1262
1388
 
1263
1389
  name(str) {
1264
- if (arguments.length === 0) return this._name;
1390
+ if (str === undefined) return this._name;
1265
1391
  this._name = str;
1266
1392
  return this;
1267
1393
  };
@@ -1275,7 +1401,7 @@ class Command extends EventEmitter {
1275
1401
 
1276
1402
  prepareCommands() {
1277
1403
  const commandDetails = this.commands.filter((cmd) => {
1278
- return !cmd._noHelp;
1404
+ return !cmd._hidden;
1279
1405
  }).map((cmd) => {
1280
1406
  const args = cmd._args.map((arg) => {
1281
1407
  return humanReadableArgName(arg);
@@ -1283,7 +1409,7 @@ class Command extends EventEmitter {
1283
1409
 
1284
1410
  return [
1285
1411
  cmd._name +
1286
- (cmd._alias ? '|' + cmd._alias : '') +
1412
+ (cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
1287
1413
  (cmd.options.length ? ' [options]' : '') +
1288
1414
  (args ? ' ' + args : ''),
1289
1415
  cmd._description
@@ -1374,17 +1500,33 @@ class Command extends EventEmitter {
1374
1500
 
1375
1501
  optionHelp() {
1376
1502
  const width = this.padWidth();
1377
-
1378
1503
  const columns = process.stdout.columns || 80;
1379
1504
  const descriptionWidth = columns - width - 4;
1505
+ function padOptionDetails(flags, description) {
1506
+ return pad(flags, width) + ' ' + optionalWrap(description, descriptionWidth, width + 2);
1507
+ };
1380
1508
 
1381
- // Append the help information
1382
- return this.options.map((option) => {
1509
+ // Explicit options (including version)
1510
+ const help = this.options.map((option) => {
1383
1511
  const fullDesc = option.description +
1384
1512
  ((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : '');
1385
- return pad(option.flags, width) + ' ' + optionalWrap(fullDesc, descriptionWidth, width + 2);
1386
- }).concat([pad(this._helpFlags, width) + ' ' + optionalWrap(this._helpDescription, descriptionWidth, width + 2)])
1387
- .join('\n');
1513
+ return padOptionDetails(option.flags, fullDesc);
1514
+ });
1515
+
1516
+ // Implicit help
1517
+ const showShortHelpFlag = this._hasHelpOption && this._helpShortFlag && !this._findOption(this._helpShortFlag);
1518
+ const showLongHelpFlag = this._hasHelpOption && !this._findOption(this._helpLongFlag);
1519
+ if (showShortHelpFlag || showLongHelpFlag) {
1520
+ let helpFlags = this._helpFlags;
1521
+ if (!showShortHelpFlag) {
1522
+ helpFlags = this._helpLongFlag;
1523
+ } else if (!showLongHelpFlag) {
1524
+ helpFlags = this._helpShortFlag;
1525
+ }
1526
+ help.push(padOptionDetails(helpFlags, this._helpDescription));
1527
+ }
1528
+
1529
+ return help.join('\n');
1388
1530
  };
1389
1531
 
1390
1532
  /**
@@ -1436,15 +1578,15 @@ class Command extends EventEmitter {
1436
1578
  desc.push('Arguments:');
1437
1579
  desc.push('');
1438
1580
  this._args.forEach((arg) => {
1439
- desc.push(' ' + pad(arg.name, width) + ' ' + wrap(argsDescription[arg.name], descriptionWidth, width + 4));
1581
+ desc.push(' ' + pad(arg.name, width) + ' ' + wrap(argsDescription[arg.name] || '', descriptionWidth, width + 4));
1440
1582
  });
1441
1583
  desc.push('');
1442
1584
  }
1443
1585
  }
1444
1586
 
1445
1587
  let cmdName = this._name;
1446
- if (this._alias) {
1447
- cmdName = cmdName + '|' + this._alias;
1588
+ if (this._aliases[0]) {
1589
+ cmdName = cmdName + '|' + this._aliases[0];
1448
1590
  }
1449
1591
  let parentCmdNames = '';
1450
1592
  for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) {
@@ -1459,11 +1601,14 @@ class Command extends EventEmitter {
1459
1601
  const commandHelp = this.commandHelp();
1460
1602
  if (commandHelp) cmds = [commandHelp];
1461
1603
 
1462
- const options = [
1463
- 'Options:',
1464
- '' + this.optionHelp().replace(/^/gm, ' '),
1465
- ''
1466
- ];
1604
+ let options = [];
1605
+ if (this._hasHelpOption || this.options.length > 0) {
1606
+ options = [
1607
+ 'Options:',
1608
+ '' + this.optionHelp().replace(/^/gm, ' '),
1609
+ ''
1610
+ ];
1611
+ }
1467
1612
 
1468
1613
  return usage
1469
1614
  .concat(desc)
@@ -1497,23 +1642,26 @@ class Command extends EventEmitter {
1497
1642
 
1498
1643
  /**
1499
1644
  * You can pass in flags and a description to override the help
1500
- * flags and help description for your command.
1645
+ * flags and help description for your command. Pass in false to
1646
+ * disable the built-in help option.
1501
1647
  *
1502
- * @param {string} [flags]
1648
+ * @param {string | boolean} [flags]
1503
1649
  * @param {string} [description]
1504
- * @return {Command}
1650
+ * @return {Command} `this` command for chaining
1505
1651
  * @api public
1506
1652
  */
1507
1653
 
1508
1654
  helpOption(flags, description) {
1655
+ if (typeof flags === 'boolean') {
1656
+ this._hasHelpOption = flags;
1657
+ return this;
1658
+ }
1509
1659
  this._helpFlags = flags || this._helpFlags;
1510
1660
  this._helpDescription = description || this._helpDescription;
1511
1661
 
1512
- const splitFlags = this._helpFlags.split(/[ ,|]+/);
1513
-
1514
- if (splitFlags.length > 1) this._helpShortFlag = splitFlags.shift();
1515
-
1516
- this._helpLongFlag = splitFlags.shift();
1662
+ const helpFlags = _parseOptionFlags(this._helpFlags);
1663
+ this._helpShortFlag = helpFlags.shortFlag;
1664
+ this._helpLongFlag = helpFlags.longFlag;
1517
1665
 
1518
1666
  return this;
1519
1667
  };
@@ -1640,7 +1788,7 @@ function optionalWrap(str, width, indent) {
1640
1788
  */
1641
1789
 
1642
1790
  function outputHelpIfRequested(cmd, args) {
1643
- const helpOption = args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
1791
+ const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
1644
1792
  if (helpOption) {
1645
1793
  cmd.outputHelp();
1646
1794
  // (Do not have all displayed text available so only passing placeholder.)
@@ -1664,6 +1812,28 @@ function humanReadableArgName(arg) {
1664
1812
  : '[' + nameOutput + ']';
1665
1813
  }
1666
1814
 
1815
+ /**
1816
+ * Parse the short and long flag out of something like '-m,--mixed <value>'
1817
+ *
1818
+ * @api private
1819
+ */
1820
+
1821
+ function _parseOptionFlags(flags) {
1822
+ let shortFlag;
1823
+ let longFlag;
1824
+ // Use original very loose parsing to maintain backwards compatibility for now,
1825
+ // which allowed for example unintended `-sw, --short-word` [sic].
1826
+ const flagParts = flags.split(/[ |,]+/);
1827
+ if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) shortFlag = flagParts.shift();
1828
+ longFlag = flagParts.shift();
1829
+ // Add support for lone short flag without significantly changing parsing!
1830
+ if (!shortFlag && /^-[^-]$/.test(longFlag)) {
1831
+ shortFlag = longFlag;
1832
+ longFlag = undefined;
1833
+ }
1834
+ return { shortFlag, longFlag };
1835
+ }
1836
+
1667
1837
  /**
1668
1838
  * Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command).
1669
1839
  *
@@ -1678,35 +1848,35 @@ function incrementNodeInspectorPort(args) {
1678
1848
  // --inspect-brk[=[host:]port]
1679
1849
  // --inspect-port=[host:]port
1680
1850
  return args.map((arg) => {
1681
- let result = arg;
1682
- if (arg.indexOf('--inspect') === 0) {
1683
- let debugOption;
1684
- let debugHost = '127.0.0.1';
1685
- let debugPort = '9229';
1686
- let match;
1687
- if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
1688
- // e.g. --inspect
1689
- debugOption = match[1];
1690
- } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
1691
- debugOption = match[1];
1692
- if (/^\d+$/.test(match[3])) {
1693
- // e.g. --inspect=1234
1694
- debugPort = match[3];
1695
- } else {
1696
- // e.g. --inspect=localhost
1697
- debugHost = match[3];
1698
- }
1699
- } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
1700
- // e.g. --inspect=localhost:1234
1701
- debugOption = match[1];
1851
+ if (!arg.startsWith('--inspect')) {
1852
+ return arg;
1853
+ }
1854
+ let debugOption;
1855
+ let debugHost = '127.0.0.1';
1856
+ let debugPort = '9229';
1857
+ let match;
1858
+ if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
1859
+ // e.g. --inspect
1860
+ debugOption = match[1];
1861
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
1862
+ debugOption = match[1];
1863
+ if (/^\d+$/.test(match[3])) {
1864
+ // e.g. --inspect=1234
1865
+ debugPort = match[3];
1866
+ } else {
1867
+ // e.g. --inspect=localhost
1702
1868
  debugHost = match[3];
1703
- debugPort = match[4];
1704
1869
  }
1870
+ } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
1871
+ // e.g. --inspect=localhost:1234
1872
+ debugOption = match[1];
1873
+ debugHost = match[3];
1874
+ debugPort = match[4];
1875
+ }
1705
1876
 
1706
- if (debugOption && debugPort !== '0') {
1707
- result = `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
1708
- }
1877
+ if (debugOption && debugPort !== '0') {
1878
+ return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
1709
1879
  }
1710
- return result;
1880
+ return arg;
1711
1881
  });
1712
1882
  }