commander 13.0.0 → 14.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/Readme.md +24 -6
- package/lib/command.js +135 -19
- package/lib/help.js +77 -39
- package/lib/option.js +46 -15
- package/package.json +4 -4
- package/typings/index.d.ts +70 -2
package/Readme.md
CHANGED
|
@@ -38,6 +38,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md)
|
|
|
38
38
|
- [.description and .summary](#description-and-summary)
|
|
39
39
|
- [.helpOption(flags, description)](#helpoptionflags-description)
|
|
40
40
|
- [.helpCommand()](#helpcommand)
|
|
41
|
+
- [Help Groups](#help-groups)
|
|
41
42
|
- [More configuration](#more-configuration-2)
|
|
42
43
|
- [Custom event listeners](#custom-event-listeners)
|
|
43
44
|
- [Bits and pieces](#bits-and-pieces)
|
|
@@ -175,7 +176,15 @@ const program = new Command();
|
|
|
175
176
|
|
|
176
177
|
## Options
|
|
177
178
|
|
|
178
|
-
Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|').
|
|
179
|
+
Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|'). To allow a wider range of short-ish flags than just
|
|
180
|
+
single characters, you may also have two long options. Examples:
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
program
|
|
184
|
+
.option('-p, --port <number>', 'server port number')
|
|
185
|
+
.option('--trace', 'add extra debugging output')
|
|
186
|
+
.option('--ws, --workspace <name>', 'use a custom workspace')
|
|
187
|
+
```
|
|
179
188
|
|
|
180
189
|
The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler.
|
|
181
190
|
|
|
@@ -326,7 +335,8 @@ add cheese type mozzarella
|
|
|
326
335
|
```
|
|
327
336
|
|
|
328
337
|
Options with an optional option-argument are not greedy and will ignore arguments starting with a dash.
|
|
329
|
-
So `id` behaves as a boolean option for `--id -
|
|
338
|
+
So `id` behaves as a boolean option for `--id -ABCD`, but you can use a combined form if needed like `--id=-ABCD`.
|
|
339
|
+
Negative numbers are special and are accepted as an option-argument.
|
|
330
340
|
|
|
331
341
|
For information about possible ambiguous cases, see [options taking varying arguments](./docs/options-in-depth.md).
|
|
332
342
|
|
|
@@ -918,6 +928,14 @@ program.helpCommand('assist [command]', 'show assistance');
|
|
|
918
928
|
|
|
919
929
|
(Or use `.addHelpCommand()` to add a command you construct yourself.)
|
|
920
930
|
|
|
931
|
+
### Help Groups
|
|
932
|
+
|
|
933
|
+
The help by default lists options under the the heading `Options:` and commands under `Commands:`. You can create your own groups
|
|
934
|
+
with different headings. The high-level way is to set the desired group heading while adding the options and commands,
|
|
935
|
+
using `.optionsGroup()` and `.commandsGroup()`. The low-level way is using `.helpGroup()` on an individual `Option` or `Command`
|
|
936
|
+
|
|
937
|
+
Example file: [help-groups.js](./examples/help-groups.js)
|
|
938
|
+
|
|
921
939
|
### More configuration
|
|
922
940
|
|
|
923
941
|
The built-in help is formatted using the Help class.
|
|
@@ -925,7 +943,7 @@ You can configure the help by modifying data properties and methods using `.conf
|
|
|
925
943
|
|
|
926
944
|
Simple properties include `sortSubcommands`, `sortOptions`, and `showGlobalOptions`. You can add color using the style methods like `styleTitle()`.
|
|
927
945
|
|
|
928
|
-
For more detail and examples of changing the displayed text, color, and layout see (./docs/help-in-depth.md)
|
|
946
|
+
For more detail and examples of changing the displayed text, color, and layout see (./docs/help-in-depth.md).
|
|
929
947
|
|
|
930
948
|
## Custom event listeners
|
|
931
949
|
|
|
@@ -991,8 +1009,8 @@ program arg --port=80
|
|
|
991
1009
|
|
|
992
1010
|
By default, the option processing shows an error for an unknown option. To have an unknown option treated as an ordinary command-argument and continue looking for options, use `.allowUnknownOption()`. This lets you mix known and unknown options.
|
|
993
1011
|
|
|
994
|
-
By default, the argument processing
|
|
995
|
-
To
|
|
1012
|
+
By default, the argument processing displays an error for more command-arguments than expected.
|
|
1013
|
+
To suppress the error for excess arguments, use`.allowExcessArguments()`.
|
|
996
1014
|
|
|
997
1015
|
### Legacy options as properties
|
|
998
1016
|
|
|
@@ -1129,7 +1147,7 @@ There is more information available about:
|
|
|
1129
1147
|
|
|
1130
1148
|
## Support
|
|
1131
1149
|
|
|
1132
|
-
The current version of Commander is fully supported on Long Term Support versions of Node.js, and requires at least
|
|
1150
|
+
The current version of Commander is fully supported on Long Term Support versions of Node.js, and requires at least v20.
|
|
1133
1151
|
(For older versions of Node.js, use an older version of Commander.)
|
|
1134
1152
|
|
|
1135
1153
|
The main forum for free and community support is the project [Issues](https://github.com/tj/commander.js/issues) on GitHub.
|
package/lib/command.js
CHANGED
|
@@ -80,6 +80,12 @@ class Command extends EventEmitter {
|
|
|
80
80
|
/** @type {Command} */
|
|
81
81
|
this._helpCommand = undefined; // lazy initialised, inherited
|
|
82
82
|
this._helpConfiguration = {};
|
|
83
|
+
/** @type {string | undefined} */
|
|
84
|
+
this._helpGroupHeading = undefined; // soft initialised when added to parent
|
|
85
|
+
/** @type {string | undefined} */
|
|
86
|
+
this._defaultCommandGroup = undefined;
|
|
87
|
+
/** @type {string | undefined} */
|
|
88
|
+
this._defaultOptionGroup = undefined;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
/**
|
|
@@ -239,7 +245,11 @@ class Command extends EventEmitter {
|
|
|
239
245
|
configureOutput(configuration) {
|
|
240
246
|
if (configuration === undefined) return this._outputConfiguration;
|
|
241
247
|
|
|
242
|
-
Object.assign(
|
|
248
|
+
this._outputConfiguration = Object.assign(
|
|
249
|
+
{},
|
|
250
|
+
this._outputConfiguration,
|
|
251
|
+
configuration,
|
|
252
|
+
);
|
|
243
253
|
return this;
|
|
244
254
|
}
|
|
245
255
|
|
|
@@ -320,16 +330,16 @@ class Command extends EventEmitter {
|
|
|
320
330
|
*
|
|
321
331
|
* @param {string} name
|
|
322
332
|
* @param {string} [description]
|
|
323
|
-
* @param {(Function|*)} [
|
|
333
|
+
* @param {(Function|*)} [parseArg] - custom argument processing function or default value
|
|
324
334
|
* @param {*} [defaultValue]
|
|
325
335
|
* @return {Command} `this` command for chaining
|
|
326
336
|
*/
|
|
327
|
-
argument(name, description,
|
|
337
|
+
argument(name, description, parseArg, defaultValue) {
|
|
328
338
|
const argument = this.createArgument(name, description);
|
|
329
|
-
if (typeof
|
|
330
|
-
argument.default(defaultValue).argParser(
|
|
339
|
+
if (typeof parseArg === 'function') {
|
|
340
|
+
argument.default(defaultValue).argParser(parseArg);
|
|
331
341
|
} else {
|
|
332
|
-
argument.default(
|
|
342
|
+
argument.default(parseArg);
|
|
333
343
|
}
|
|
334
344
|
this.addArgument(argument);
|
|
335
345
|
return this;
|
|
@@ -400,11 +410,15 @@ class Command extends EventEmitter {
|
|
|
400
410
|
helpCommand(enableOrNameAndArgs, description) {
|
|
401
411
|
if (typeof enableOrNameAndArgs === 'boolean') {
|
|
402
412
|
this._addImplicitHelpCommand = enableOrNameAndArgs;
|
|
413
|
+
if (enableOrNameAndArgs && this._defaultCommandGroup) {
|
|
414
|
+
// make the command to store the group
|
|
415
|
+
this._initCommandGroup(this._getHelpCommand());
|
|
416
|
+
}
|
|
403
417
|
return this;
|
|
404
418
|
}
|
|
405
419
|
|
|
406
|
-
|
|
407
|
-
const [, helpName, helpArgs] =
|
|
420
|
+
const nameAndArgs = enableOrNameAndArgs ?? 'help [command]';
|
|
421
|
+
const [, helpName, helpArgs] = nameAndArgs.match(/([^ ]+) *(.*)/);
|
|
408
422
|
const helpDescription = description ?? 'display help for command';
|
|
409
423
|
|
|
410
424
|
const helpCommand = this.createCommand(helpName);
|
|
@@ -414,6 +428,8 @@ class Command extends EventEmitter {
|
|
|
414
428
|
|
|
415
429
|
this._addImplicitHelpCommand = true;
|
|
416
430
|
this._helpCommand = helpCommand;
|
|
431
|
+
// init group unless lazy create
|
|
432
|
+
if (enableOrNameAndArgs || description) this._initCommandGroup(helpCommand);
|
|
417
433
|
|
|
418
434
|
return this;
|
|
419
435
|
}
|
|
@@ -435,6 +451,7 @@ class Command extends EventEmitter {
|
|
|
435
451
|
|
|
436
452
|
this._addImplicitHelpCommand = true;
|
|
437
453
|
this._helpCommand = helpCommand;
|
|
454
|
+
this._initCommandGroup(helpCommand);
|
|
438
455
|
return this;
|
|
439
456
|
}
|
|
440
457
|
|
|
@@ -613,6 +630,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
613
630
|
- already used by option '${matchingOption.flags}'`);
|
|
614
631
|
}
|
|
615
632
|
|
|
633
|
+
this._initOptionGroup(option);
|
|
616
634
|
this.options.push(option);
|
|
617
635
|
}
|
|
618
636
|
|
|
@@ -640,6 +658,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
640
658
|
);
|
|
641
659
|
}
|
|
642
660
|
|
|
661
|
+
this._initCommandGroup(command);
|
|
643
662
|
this.commands.push(command);
|
|
644
663
|
}
|
|
645
664
|
|
|
@@ -756,7 +775,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
756
775
|
* @example
|
|
757
776
|
* program
|
|
758
777
|
* .option('-p, --pepper', 'add pepper')
|
|
759
|
-
* .option('
|
|
778
|
+
* .option('--pt, --pizza-type <TYPE>', 'type of pizza') // required option-argument
|
|
760
779
|
* .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
|
|
761
780
|
* .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
|
|
762
781
|
*
|
|
@@ -1737,6 +1756,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1737
1756
|
return arg.length > 1 && arg[0] === '-';
|
|
1738
1757
|
}
|
|
1739
1758
|
|
|
1759
|
+
const negativeNumberArg = (arg) => {
|
|
1760
|
+
// return false if not a negative number
|
|
1761
|
+
if (!/^-\d*\.?\d+(e[+-]?\d+)?$/.test(arg)) return false;
|
|
1762
|
+
// negative number is ok unless digit used as an option in command hierarchy
|
|
1763
|
+
return !this._getCommandAndAncestors().some((cmd) =>
|
|
1764
|
+
cmd.options
|
|
1765
|
+
.map((opt) => opt.short)
|
|
1766
|
+
.some((short) => /^-\d$/.test(short)),
|
|
1767
|
+
);
|
|
1768
|
+
};
|
|
1769
|
+
|
|
1740
1770
|
// parse options
|
|
1741
1771
|
let activeVariadicOption = null;
|
|
1742
1772
|
while (args.length) {
|
|
@@ -1749,7 +1779,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1749
1779
|
break;
|
|
1750
1780
|
}
|
|
1751
1781
|
|
|
1752
|
-
if (
|
|
1782
|
+
if (
|
|
1783
|
+
activeVariadicOption &&
|
|
1784
|
+
(!maybeOption(arg) || negativeNumberArg(arg))
|
|
1785
|
+
) {
|
|
1753
1786
|
this.emit(`option:${activeVariadicOption.name()}`, arg);
|
|
1754
1787
|
continue;
|
|
1755
1788
|
}
|
|
@@ -1766,7 +1799,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1766
1799
|
} else if (option.optional) {
|
|
1767
1800
|
let value = null;
|
|
1768
1801
|
// historical behaviour is optional value is following arg unless an option
|
|
1769
|
-
if (
|
|
1802
|
+
if (
|
|
1803
|
+
args.length > 0 &&
|
|
1804
|
+
(!maybeOption(args[0]) || negativeNumberArg(args[0]))
|
|
1805
|
+
) {
|
|
1770
1806
|
value = args.shift();
|
|
1771
1807
|
}
|
|
1772
1808
|
this.emit(`option:${option.name()}`, value);
|
|
@@ -1812,7 +1848,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1812
1848
|
// Might be a command-argument, or subcommand option, or unknown option, or help command or option.
|
|
1813
1849
|
|
|
1814
1850
|
// An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
|
|
1815
|
-
|
|
1851
|
+
// A negative number in a leaf command is not an unknown option.
|
|
1852
|
+
if (
|
|
1853
|
+
dest === operands &&
|
|
1854
|
+
maybeOption(arg) &&
|
|
1855
|
+
!(this.commands.length === 0 && negativeNumberArg(arg))
|
|
1856
|
+
) {
|
|
1816
1857
|
dest = unknown;
|
|
1817
1858
|
}
|
|
1818
1859
|
|
|
@@ -2294,6 +2335,75 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2294
2335
|
return this;
|
|
2295
2336
|
}
|
|
2296
2337
|
|
|
2338
|
+
/**
|
|
2339
|
+
* Set/get the help group heading for this subcommand in parent command's help.
|
|
2340
|
+
*
|
|
2341
|
+
* @param {string} [heading]
|
|
2342
|
+
* @return {Command | string}
|
|
2343
|
+
*/
|
|
2344
|
+
|
|
2345
|
+
helpGroup(heading) {
|
|
2346
|
+
if (heading === undefined) return this._helpGroupHeading ?? '';
|
|
2347
|
+
this._helpGroupHeading = heading;
|
|
2348
|
+
return this;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
/**
|
|
2352
|
+
* Set/get the default help group heading for subcommands added to this command.
|
|
2353
|
+
* (This does not override a group set directly on the subcommand using .helpGroup().)
|
|
2354
|
+
*
|
|
2355
|
+
* @example
|
|
2356
|
+
* program.commandsGroup('Development Commands:);
|
|
2357
|
+
* program.command('watch')...
|
|
2358
|
+
* program.command('lint')...
|
|
2359
|
+
* ...
|
|
2360
|
+
*
|
|
2361
|
+
* @param {string} [heading]
|
|
2362
|
+
* @returns {Command | string}
|
|
2363
|
+
*/
|
|
2364
|
+
commandsGroup(heading) {
|
|
2365
|
+
if (heading === undefined) return this._defaultCommandGroup ?? '';
|
|
2366
|
+
this._defaultCommandGroup = heading;
|
|
2367
|
+
return this;
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
/**
|
|
2371
|
+
* Set/get the default help group heading for options added to this command.
|
|
2372
|
+
* (This does not override a group set directly on the option using .helpGroup().)
|
|
2373
|
+
*
|
|
2374
|
+
* @example
|
|
2375
|
+
* program
|
|
2376
|
+
* .optionsGroup('Development Options:')
|
|
2377
|
+
* .option('-d, --debug', 'output extra debugging')
|
|
2378
|
+
* .option('-p, --profile', 'output profiling information')
|
|
2379
|
+
*
|
|
2380
|
+
* @param {string} [heading]
|
|
2381
|
+
* @returns {Command | string}
|
|
2382
|
+
*/
|
|
2383
|
+
optionsGroup(heading) {
|
|
2384
|
+
if (heading === undefined) return this._defaultOptionGroup ?? '';
|
|
2385
|
+
this._defaultOptionGroup = heading;
|
|
2386
|
+
return this;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
/**
|
|
2390
|
+
* @param {Option} option
|
|
2391
|
+
* @private
|
|
2392
|
+
*/
|
|
2393
|
+
_initOptionGroup(option) {
|
|
2394
|
+
if (this._defaultOptionGroup && !option.helpGroupHeading)
|
|
2395
|
+
option.helpGroup(this._defaultOptionGroup);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
/**
|
|
2399
|
+
* @param {Command} cmd
|
|
2400
|
+
* @private
|
|
2401
|
+
*/
|
|
2402
|
+
_initCommandGroup(cmd) {
|
|
2403
|
+
if (this._defaultCommandGroup && !cmd.helpGroup())
|
|
2404
|
+
cmd.helpGroup(this._defaultCommandGroup);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2297
2407
|
/**
|
|
2298
2408
|
* Set the name of the command from script filename, such as process.argv[1],
|
|
2299
2409
|
* or require.main.filename, or __filename.
|
|
@@ -2448,12 +2558,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2448
2558
|
*/
|
|
2449
2559
|
|
|
2450
2560
|
helpOption(flags, description) {
|
|
2451
|
-
// Support disabling built-in help option.
|
|
2561
|
+
// Support enabling/disabling built-in help option.
|
|
2452
2562
|
if (typeof flags === 'boolean') {
|
|
2453
|
-
// true is not an expected value. Do something sensible but no unit-test.
|
|
2454
|
-
// istanbul ignore if
|
|
2455
2563
|
if (flags) {
|
|
2456
|
-
this._helpOption
|
|
2564
|
+
if (this._helpOption === null) this._helpOption = undefined; // reenable
|
|
2565
|
+
if (this._defaultOptionGroup) {
|
|
2566
|
+
// make the option to store the group
|
|
2567
|
+
this._initOptionGroup(this._getHelpOption());
|
|
2568
|
+
}
|
|
2457
2569
|
} else {
|
|
2458
2570
|
this._helpOption = null; // disable
|
|
2459
2571
|
}
|
|
@@ -2461,9 +2573,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2461
2573
|
}
|
|
2462
2574
|
|
|
2463
2575
|
// Customise flags and description.
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2576
|
+
this._helpOption = this.createOption(
|
|
2577
|
+
flags ?? '-h, --help',
|
|
2578
|
+
description ?? 'display help for command',
|
|
2579
|
+
);
|
|
2580
|
+
// init group unless lazy create
|
|
2581
|
+
if (flags || description) this._initOptionGroup(this._helpOption);
|
|
2467
2582
|
|
|
2468
2583
|
return this;
|
|
2469
2584
|
}
|
|
@@ -2492,6 +2607,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2492
2607
|
*/
|
|
2493
2608
|
addHelpOption(option) {
|
|
2494
2609
|
this._helpOption = option;
|
|
2610
|
+
this._initOptionGroup(option);
|
|
2495
2611
|
return this;
|
|
2496
2612
|
}
|
|
2497
2613
|
|
package/lib/help.js
CHANGED
|
@@ -352,7 +352,11 @@ class Help {
|
|
|
352
352
|
extraInfo.push(`env: ${option.envVar}`);
|
|
353
353
|
}
|
|
354
354
|
if (extraInfo.length > 0) {
|
|
355
|
-
|
|
355
|
+
const extraDescription = `(${extraInfo.join(', ')})`;
|
|
356
|
+
if (option.description) {
|
|
357
|
+
return `${option.description} ${extraDescription}`;
|
|
358
|
+
}
|
|
359
|
+
return extraDescription;
|
|
356
360
|
}
|
|
357
361
|
|
|
358
362
|
return option.description;
|
|
@@ -388,6 +392,46 @@ class Help {
|
|
|
388
392
|
return argument.description;
|
|
389
393
|
}
|
|
390
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Format a list of items, given a heading and an array of formatted items.
|
|
397
|
+
*
|
|
398
|
+
* @param {string} heading
|
|
399
|
+
* @param {string[]} items
|
|
400
|
+
* @param {Help} helper
|
|
401
|
+
* @returns string[]
|
|
402
|
+
*/
|
|
403
|
+
formatItemList(heading, items, helper) {
|
|
404
|
+
if (items.length === 0) return [];
|
|
405
|
+
|
|
406
|
+
return [helper.styleTitle(heading), ...items, ''];
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Group items by their help group heading.
|
|
411
|
+
*
|
|
412
|
+
* @param {Command[] | Option[]} unsortedItems
|
|
413
|
+
* @param {Command[] | Option[]} visibleItems
|
|
414
|
+
* @param {Function} getGroup
|
|
415
|
+
* @returns {Map<string, Command[] | Option[]>}
|
|
416
|
+
*/
|
|
417
|
+
groupItems(unsortedItems, visibleItems, getGroup) {
|
|
418
|
+
const result = new Map();
|
|
419
|
+
// Add groups in order of appearance in unsortedItems.
|
|
420
|
+
unsortedItems.forEach((item) => {
|
|
421
|
+
const group = getGroup(item);
|
|
422
|
+
if (!result.has(group)) result.set(group, []);
|
|
423
|
+
});
|
|
424
|
+
// Add items in order of appearance in visibleItems.
|
|
425
|
+
visibleItems.forEach((item) => {
|
|
426
|
+
const group = getGroup(item);
|
|
427
|
+
if (!result.has(group)) {
|
|
428
|
+
result.set(group, []);
|
|
429
|
+
}
|
|
430
|
+
result.get(group).push(item);
|
|
431
|
+
});
|
|
432
|
+
return result;
|
|
433
|
+
}
|
|
434
|
+
|
|
391
435
|
/**
|
|
392
436
|
* Generate the built-in help text.
|
|
393
437
|
*
|
|
@@ -429,28 +473,25 @@ class Help {
|
|
|
429
473
|
helper.styleArgumentDescription(helper.argumentDescription(argument)),
|
|
430
474
|
);
|
|
431
475
|
});
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
...argumentList,
|
|
436
|
-
'',
|
|
437
|
-
]);
|
|
438
|
-
}
|
|
476
|
+
output = output.concat(
|
|
477
|
+
this.formatItemList('Arguments:', argumentList, helper),
|
|
478
|
+
);
|
|
439
479
|
|
|
440
480
|
// Options
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
481
|
+
const optionGroups = this.groupItems(
|
|
482
|
+
cmd.options,
|
|
483
|
+
helper.visibleOptions(cmd),
|
|
484
|
+
(option) => option.helpGroupHeading ?? 'Options:',
|
|
485
|
+
);
|
|
486
|
+
optionGroups.forEach((options, group) => {
|
|
487
|
+
const optionList = options.map((option) => {
|
|
488
|
+
return callFormatItem(
|
|
489
|
+
helper.styleOptionTerm(helper.optionTerm(option)),
|
|
490
|
+
helper.styleOptionDescription(helper.optionDescription(option)),
|
|
491
|
+
);
|
|
492
|
+
});
|
|
493
|
+
output = output.concat(this.formatItemList(group, optionList, helper));
|
|
446
494
|
});
|
|
447
|
-
if (optionList.length > 0) {
|
|
448
|
-
output = output.concat([
|
|
449
|
-
helper.styleTitle('Options:'),
|
|
450
|
-
...optionList,
|
|
451
|
-
'',
|
|
452
|
-
]);
|
|
453
|
-
}
|
|
454
495
|
|
|
455
496
|
if (helper.showGlobalOptions) {
|
|
456
497
|
const globalOptionList = helper
|
|
@@ -461,29 +502,26 @@ class Help {
|
|
|
461
502
|
helper.styleOptionDescription(helper.optionDescription(option)),
|
|
462
503
|
);
|
|
463
504
|
});
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
...globalOptionList,
|
|
468
|
-
'',
|
|
469
|
-
]);
|
|
470
|
-
}
|
|
505
|
+
output = output.concat(
|
|
506
|
+
this.formatItemList('Global Options:', globalOptionList, helper),
|
|
507
|
+
);
|
|
471
508
|
}
|
|
472
509
|
|
|
473
510
|
// Commands
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
511
|
+
const commandGroups = this.groupItems(
|
|
512
|
+
cmd.commands,
|
|
513
|
+
helper.visibleCommands(cmd),
|
|
514
|
+
(sub) => sub.helpGroup() || 'Commands:',
|
|
515
|
+
);
|
|
516
|
+
commandGroups.forEach((commands, group) => {
|
|
517
|
+
const commandList = commands.map((sub) => {
|
|
518
|
+
return callFormatItem(
|
|
519
|
+
helper.styleSubcommandTerm(helper.subcommandTerm(sub)),
|
|
520
|
+
helper.styleSubcommandDescription(helper.subcommandDescription(sub)),
|
|
521
|
+
);
|
|
522
|
+
});
|
|
523
|
+
output = output.concat(this.formatItemList(group, commandList, helper));
|
|
479
524
|
});
|
|
480
|
-
if (commandList.length > 0) {
|
|
481
|
-
output = output.concat([
|
|
482
|
-
helper.styleTitle('Commands:'),
|
|
483
|
-
...commandList,
|
|
484
|
-
'',
|
|
485
|
-
]);
|
|
486
|
-
}
|
|
487
525
|
|
|
488
526
|
return output.join('\n');
|
|
489
527
|
}
|
package/lib/option.js
CHANGED
|
@@ -18,7 +18,7 @@ class Option {
|
|
|
18
18
|
this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
|
|
19
19
|
this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
|
|
20
20
|
const optionFlags = splitOptionFlags(flags);
|
|
21
|
-
this.short = optionFlags.shortFlag;
|
|
21
|
+
this.short = optionFlags.shortFlag; // May be a short flag, undefined, or even a long flag (if option has two long flags).
|
|
22
22
|
this.long = optionFlags.longFlag;
|
|
23
23
|
this.negate = false;
|
|
24
24
|
if (this.long) {
|
|
@@ -33,6 +33,7 @@ class Option {
|
|
|
33
33
|
this.argChoices = undefined;
|
|
34
34
|
this.conflictsWith = [];
|
|
35
35
|
this.implied = undefined;
|
|
36
|
+
this.helpGroupHeading = undefined; // soft initialised when option added to command
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -219,6 +220,17 @@ class Option {
|
|
|
219
220
|
return camelcase(this.name());
|
|
220
221
|
}
|
|
221
222
|
|
|
223
|
+
/**
|
|
224
|
+
* Set the help group heading.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} heading
|
|
227
|
+
* @return {Option}
|
|
228
|
+
*/
|
|
229
|
+
helpGroup(heading) {
|
|
230
|
+
this.helpGroupHeading = heading;
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
222
234
|
/**
|
|
223
235
|
* Check if `arg` matches the short or long flag.
|
|
224
236
|
*
|
|
@@ -321,25 +333,44 @@ function splitOptionFlags(flags) {
|
|
|
321
333
|
const longFlagExp = /^--[^-]/;
|
|
322
334
|
|
|
323
335
|
const flagParts = flags.split(/[ |,]+/).concat('guard');
|
|
336
|
+
// Normal is short and/or long.
|
|
324
337
|
if (shortFlagExp.test(flagParts[0])) shortFlag = flagParts.shift();
|
|
325
338
|
if (longFlagExp.test(flagParts[0])) longFlag = flagParts.shift();
|
|
339
|
+
// Long then short. Rarely used but fine.
|
|
340
|
+
if (!shortFlag && shortFlagExp.test(flagParts[0]))
|
|
341
|
+
shortFlag = flagParts.shift();
|
|
342
|
+
// Allow two long flags, like '--ws, --workspace'
|
|
343
|
+
// This is the supported way to have a shortish option flag.
|
|
344
|
+
if (!shortFlag && longFlagExp.test(flagParts[0])) {
|
|
345
|
+
shortFlag = longFlag;
|
|
346
|
+
longFlag = flagParts.shift();
|
|
347
|
+
}
|
|
326
348
|
|
|
327
|
-
// Check for
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
)
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
349
|
+
// Check for unprocessed flag. Fail noisily rather than silently ignore.
|
|
350
|
+
if (flagParts[0].startsWith('-')) {
|
|
351
|
+
const unsupportedFlag = flagParts[0];
|
|
352
|
+
const baseError = `option creation failed due to '${unsupportedFlag}' in option flags '${flags}'`;
|
|
353
|
+
if (/^-[^-][^-]/.test(unsupportedFlag))
|
|
354
|
+
throw new Error(
|
|
355
|
+
`${baseError}
|
|
356
|
+
- a short flag is a single dash and a single character
|
|
357
|
+
- either use a single dash and a single character (for a short flag)
|
|
358
|
+
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`,
|
|
359
|
+
);
|
|
360
|
+
if (shortFlagExp.test(unsupportedFlag))
|
|
361
|
+
throw new Error(`${baseError}
|
|
362
|
+
- too many short flags`);
|
|
363
|
+
if (longFlagExp.test(unsupportedFlag))
|
|
364
|
+
throw new Error(`${baseError}
|
|
365
|
+
- too many long flags`);
|
|
366
|
+
|
|
367
|
+
throw new Error(`${baseError}
|
|
368
|
+
- unrecognised flag format`);
|
|
369
|
+
}
|
|
370
|
+
if (shortFlag === undefined && longFlag === undefined)
|
|
337
371
|
throw new Error(
|
|
338
|
-
`
|
|
372
|
+
`option creation failed due to no flags found in '${flags}'.`,
|
|
339
373
|
);
|
|
340
|
-
// Generic error if failed to find a flag or an unexpected flag left over.
|
|
341
|
-
if (!(shortFlag || longFlag) || flagParts[0].startsWith('-'))
|
|
342
|
-
throw new Error(`invalid Option flags: '${flags}'`);
|
|
343
374
|
|
|
344
375
|
return { shortFlag, longFlag };
|
|
345
376
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commander",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "the complete solution for node.js command-line programs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"commander",
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"@types/jest": "^29.2.4",
|
|
65
65
|
"@types/node": "^22.7.4",
|
|
66
66
|
"eslint": "^9.17.0",
|
|
67
|
-
"eslint-config-prettier": "^
|
|
67
|
+
"eslint-config-prettier": "^10.0.1",
|
|
68
68
|
"eslint-plugin-jest": "^28.3.0",
|
|
69
|
-
"globals": "^
|
|
69
|
+
"globals": "^16.0.0",
|
|
70
70
|
"jest": "^29.3.1",
|
|
71
71
|
"prettier": "^3.2.5",
|
|
72
72
|
"ts-jest": "^29.0.3",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"types": "typings/index.d.ts",
|
|
78
78
|
"engines": {
|
|
79
|
-
"node": ">=
|
|
79
|
+
"node": ">=20"
|
|
80
80
|
},
|
|
81
81
|
"support": true
|
|
82
82
|
}
|
package/typings/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export class Argument {
|
|
|
51
51
|
variadic: boolean;
|
|
52
52
|
defaultValue?: any;
|
|
53
53
|
defaultValueDescription?: string;
|
|
54
|
+
parseArg?: <T>(value: string, previous: T) => T;
|
|
54
55
|
argChoices?: string[];
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -109,6 +110,7 @@ export class Option {
|
|
|
109
110
|
parseArg?: <T>(value: string, previous: T) => T;
|
|
110
111
|
hidden: boolean;
|
|
111
112
|
argChoices?: string[];
|
|
113
|
+
helpGroupHeading?: string;
|
|
112
114
|
|
|
113
115
|
constructor(flags: string, description?: string);
|
|
114
116
|
|
|
@@ -192,6 +194,11 @@ export class Option {
|
|
|
192
194
|
*/
|
|
193
195
|
attributeName(): string;
|
|
194
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Set the help group heading.
|
|
199
|
+
*/
|
|
200
|
+
helpGroup(heading: string): this;
|
|
201
|
+
|
|
195
202
|
/**
|
|
196
203
|
* Return whether a boolean option.
|
|
197
204
|
*
|
|
@@ -313,6 +320,20 @@ export class Help {
|
|
|
313
320
|
helper: Help,
|
|
314
321
|
): string;
|
|
315
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Format a list of items, given a heading and an array of formatted items.
|
|
325
|
+
*/
|
|
326
|
+
formatItemList(heading: string, items: string[], helper: Help): string[];
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Group items by their help group heading.
|
|
330
|
+
*/
|
|
331
|
+
groupItems<T extends Command | Option>(
|
|
332
|
+
unsortedItems: T[],
|
|
333
|
+
visibleItems: T[],
|
|
334
|
+
getGroup: (item: T) => string,
|
|
335
|
+
): Map<string, T[]>;
|
|
336
|
+
|
|
316
337
|
/** Generate the built-in help text. */
|
|
317
338
|
formatHelp(cmd: Command, helper: Help): string;
|
|
318
339
|
}
|
|
@@ -466,7 +487,7 @@ export class Command {
|
|
|
466
487
|
argument<T>(
|
|
467
488
|
flags: string,
|
|
468
489
|
description: string,
|
|
469
|
-
|
|
490
|
+
parseArg: (value: string, previous: T) => T,
|
|
470
491
|
defaultValue?: T,
|
|
471
492
|
): this;
|
|
472
493
|
argument(name: string, description?: string, defaultValue?: unknown): this;
|
|
@@ -617,7 +638,7 @@ export class Command {
|
|
|
617
638
|
* ```js
|
|
618
639
|
* program
|
|
619
640
|
* .option('-p, --pepper', 'add pepper')
|
|
620
|
-
* .option('
|
|
641
|
+
* .option('--pt, --pizza-type <TYPE>', 'type of pizza') // required option-argument
|
|
621
642
|
* .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default
|
|
622
643
|
* .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function
|
|
623
644
|
* ```
|
|
@@ -968,6 +989,53 @@ export class Command {
|
|
|
968
989
|
*/
|
|
969
990
|
executableDir(): string | null;
|
|
970
991
|
|
|
992
|
+
/**
|
|
993
|
+
* Set the help group heading for this subcommand in parent command's help.
|
|
994
|
+
*
|
|
995
|
+
* @returns `this` command for chaining
|
|
996
|
+
*/
|
|
997
|
+
helpGroup(heading: string): this;
|
|
998
|
+
/**
|
|
999
|
+
* Get the help group heading for this subcommand in parent command's help.
|
|
1000
|
+
*/
|
|
1001
|
+
helpGroup(): string;
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* Set the default help group heading for subcommands added to this command.
|
|
1005
|
+
* (This does not override a group set directly on the subcommand using .helpGroup().)
|
|
1006
|
+
*
|
|
1007
|
+
* @example
|
|
1008
|
+
* program.commandsGroup('Development Commands:);
|
|
1009
|
+
* program.command('watch')...
|
|
1010
|
+
* program.command('lint')...
|
|
1011
|
+
* ...
|
|
1012
|
+
*
|
|
1013
|
+
* @returns `this` command for chaining
|
|
1014
|
+
*/
|
|
1015
|
+
commandsGroup(heading: string): this;
|
|
1016
|
+
/**
|
|
1017
|
+
* Get the default help group heading for subcommands added to this command.
|
|
1018
|
+
*/
|
|
1019
|
+
commandsGroup(): string;
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Set the default help group heading for options added to this command.
|
|
1023
|
+
* (This does not override a group set directly on the option using .helpGroup().)
|
|
1024
|
+
*
|
|
1025
|
+
* @example
|
|
1026
|
+
* program
|
|
1027
|
+
* .optionsGroup('Development Options:')
|
|
1028
|
+
* .option('-d, --debug', 'output extra debugging')
|
|
1029
|
+
* .option('-p, --profile', 'output profiling information')
|
|
1030
|
+
*
|
|
1031
|
+
* @returns `this` command for chaining
|
|
1032
|
+
*/
|
|
1033
|
+
optionsGroup(heading: string): this;
|
|
1034
|
+
/**
|
|
1035
|
+
* Get the default help group heading for options added to this command.
|
|
1036
|
+
*/
|
|
1037
|
+
optionsGroup(): string;
|
|
1038
|
+
|
|
971
1039
|
/**
|
|
972
1040
|
* Output help information for this command.
|
|
973
1041
|
*
|