commander 12.1.0 → 13.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 +7 -23
- package/lib/command.js +195 -42
- package/lib/help.js +268 -79
- package/lib/option.js +31 -13
- package/package.json +6 -8
- package/typings/index.d.ts +88 -12
package/Readme.md
CHANGED
|
@@ -79,7 +79,8 @@ const { program } = require('commander');
|
|
|
79
79
|
|
|
80
80
|
program
|
|
81
81
|
.option('--first')
|
|
82
|
-
.option('-s, --separator <char>')
|
|
82
|
+
.option('-s, --separator <char>')
|
|
83
|
+
.argument('<string>');
|
|
83
84
|
|
|
84
85
|
program.parse();
|
|
85
86
|
|
|
@@ -678,8 +679,7 @@ async function main() {
|
|
|
678
679
|
}
|
|
679
680
|
```
|
|
680
681
|
|
|
681
|
-
A command's options and arguments on the command line are validated when the command is used. Any unknown options or missing arguments will be reported as an error. You can suppress the unknown option
|
|
682
|
-
pass more arguments than declared, but you can make this an error with `.allowExcessArguments(false)`.
|
|
682
|
+
A command's options and arguments on the command line are validated when the command is used. Any unknown options or missing arguments or excess arguments will be reported as an error. You can suppress the unknown option check with `.allowUnknownOption()`. You can suppress the excess arguments check with `.allowExcessArguments()`.
|
|
683
683
|
|
|
684
684
|
### Stand-alone executable (sub)commands
|
|
685
685
|
|
|
@@ -696,7 +696,7 @@ Example file: [pm](./examples/pm)
|
|
|
696
696
|
program
|
|
697
697
|
.name('pm')
|
|
698
698
|
.version('0.1.0')
|
|
699
|
-
.command('install [
|
|
699
|
+
.command('install [package-names...]', 'install one or more packages')
|
|
700
700
|
.command('search [query]', 'search with optional query')
|
|
701
701
|
.command('update', 'update installed packages', { executableFile: 'myUpdateSubCommand' })
|
|
702
702
|
.command('list', 'list packages installed', { isDefault: true });
|
|
@@ -921,25 +921,11 @@ program.helpCommand('assist [command]', 'show assistance');
|
|
|
921
921
|
### More configuration
|
|
922
922
|
|
|
923
923
|
The built-in help is formatted using the Help class.
|
|
924
|
-
You can configure the
|
|
924
|
+
You can configure the help by modifying data properties and methods using `.configureHelp()`, or by subclassing Help using `.createHelp()` .
|
|
925
925
|
|
|
926
|
-
|
|
926
|
+
Simple properties include `sortSubcommands`, `sortOptions`, and `showGlobalOptions`. You can add color using the style methods like `styleTitle()`.
|
|
927
927
|
|
|
928
|
-
|
|
929
|
-
- `sortSubcommands`: sort the subcommands alphabetically
|
|
930
|
-
- `sortOptions`: sort the options alphabetically
|
|
931
|
-
- `showGlobalOptions`: show a section with the global options from the parent command(s)
|
|
932
|
-
|
|
933
|
-
You can override any method on the [Help](./lib/help.js) class. There are methods getting the visible lists of arguments, options, and subcommands. There are methods for formatting the items in the lists, with each item having a _term_ and _description_. Take a look at `.formatHelp()` to see how they are used.
|
|
934
|
-
|
|
935
|
-
Example file: [configure-help.js](./examples/configure-help.js)
|
|
936
|
-
|
|
937
|
-
```js
|
|
938
|
-
program.configureHelp({
|
|
939
|
-
sortSubcommands: true,
|
|
940
|
-
subcommandTerm: (cmd) => cmd.name() // Just show the name, instead of short usage.
|
|
941
|
-
});
|
|
942
|
-
```
|
|
928
|
+
For more detail and examples of changing the displayed text, color, and layout see (./docs/help-in-depth.md)
|
|
943
929
|
|
|
944
930
|
## Custom event listeners
|
|
945
931
|
|
|
@@ -973,8 +959,6 @@ program.parse(['--port', '80'], { from: 'user' }); // just user supplied argumen
|
|
|
973
959
|
|
|
974
960
|
Use parseAsync instead of parse if any of your action handlers are async.
|
|
975
961
|
|
|
976
|
-
If you want to parse multiple times, create a new program each time. Calling parse does not clear out any previous state.
|
|
977
|
-
|
|
978
962
|
### Parsing Configuration
|
|
979
963
|
|
|
980
964
|
If the default parsing does not suit your needs, there are some behaviours to support other usage patterns.
|
package/lib/command.js
CHANGED
|
@@ -6,7 +6,7 @@ const process = require('node:process');
|
|
|
6
6
|
|
|
7
7
|
const { Argument, humanReadableArgName } = require('./argument.js');
|
|
8
8
|
const { CommanderError } = require('./error.js');
|
|
9
|
-
const { Help } = require('./help.js');
|
|
9
|
+
const { Help, stripColor } = require('./help.js');
|
|
10
10
|
const { Option, DualOptions } = require('./option.js');
|
|
11
11
|
const { suggestSimilar } = require('./suggestSimilar');
|
|
12
12
|
|
|
@@ -25,7 +25,7 @@ class Command extends EventEmitter {
|
|
|
25
25
|
this.options = [];
|
|
26
26
|
this.parent = null;
|
|
27
27
|
this._allowUnknownOption = false;
|
|
28
|
-
this._allowExcessArguments =
|
|
28
|
+
this._allowExcessArguments = false;
|
|
29
29
|
/** @type {Argument[]} */
|
|
30
30
|
this.registeredArguments = [];
|
|
31
31
|
this._args = this.registeredArguments; // deprecated old name
|
|
@@ -55,16 +55,22 @@ class Command extends EventEmitter {
|
|
|
55
55
|
/** @type {(boolean | string)} */
|
|
56
56
|
this._showHelpAfterError = false;
|
|
57
57
|
this._showSuggestionAfterError = true;
|
|
58
|
+
this._savedState = null; // used in save/restoreStateBeforeParse
|
|
58
59
|
|
|
59
|
-
// see
|
|
60
|
+
// see configureOutput() for docs
|
|
60
61
|
this._outputConfiguration = {
|
|
61
62
|
writeOut: (str) => process.stdout.write(str),
|
|
62
63
|
writeErr: (str) => process.stderr.write(str),
|
|
64
|
+
outputError: (str, write) => write(str),
|
|
63
65
|
getOutHelpWidth: () =>
|
|
64
66
|
process.stdout.isTTY ? process.stdout.columns : undefined,
|
|
65
67
|
getErrHelpWidth: () =>
|
|
66
68
|
process.stderr.isTTY ? process.stderr.columns : undefined,
|
|
67
|
-
|
|
69
|
+
getOutHasColors: () =>
|
|
70
|
+
useColor() ?? (process.stdout.isTTY && process.stdout.hasColors?.()),
|
|
71
|
+
getErrHasColors: () =>
|
|
72
|
+
useColor() ?? (process.stderr.isTTY && process.stderr.hasColors?.()),
|
|
73
|
+
stripColor: (str) => stripColor(str),
|
|
68
74
|
};
|
|
69
75
|
|
|
70
76
|
this._hidden = false;
|
|
@@ -213,14 +219,18 @@ class Command extends EventEmitter {
|
|
|
213
219
|
*
|
|
214
220
|
* The configuration properties are all functions:
|
|
215
221
|
*
|
|
216
|
-
* //
|
|
222
|
+
* // change how output being written, defaults to stdout and stderr
|
|
217
223
|
* writeOut(str)
|
|
218
224
|
* writeErr(str)
|
|
219
|
-
* //
|
|
225
|
+
* // change how output being written for errors, defaults to writeErr
|
|
226
|
+
* outputError(str, write) // used for displaying errors and not used for displaying help
|
|
227
|
+
* // specify width for wrapping help
|
|
220
228
|
* getOutHelpWidth()
|
|
221
229
|
* getErrHelpWidth()
|
|
222
|
-
* //
|
|
223
|
-
*
|
|
230
|
+
* // color support, currently only used with Help
|
|
231
|
+
* getOutHasColors()
|
|
232
|
+
* getErrHasColors()
|
|
233
|
+
* stripColor() // used to remove ANSI escape codes if output does not have colors
|
|
224
234
|
*
|
|
225
235
|
* @param {object} [configuration] - configuration options
|
|
226
236
|
* @return {(Command | object)} `this` command for chaining, or stored configuration
|
|
@@ -1060,6 +1070,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1060
1070
|
*/
|
|
1061
1071
|
|
|
1062
1072
|
parse(argv, parseOptions) {
|
|
1073
|
+
this._prepareForParse();
|
|
1063
1074
|
const userArgs = this._prepareUserArgs(argv, parseOptions);
|
|
1064
1075
|
this._parseCommand([], userArgs);
|
|
1065
1076
|
|
|
@@ -1088,12 +1099,82 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1088
1099
|
*/
|
|
1089
1100
|
|
|
1090
1101
|
async parseAsync(argv, parseOptions) {
|
|
1102
|
+
this._prepareForParse();
|
|
1091
1103
|
const userArgs = this._prepareUserArgs(argv, parseOptions);
|
|
1092
1104
|
await this._parseCommand([], userArgs);
|
|
1093
1105
|
|
|
1094
1106
|
return this;
|
|
1095
1107
|
}
|
|
1096
1108
|
|
|
1109
|
+
_prepareForParse() {
|
|
1110
|
+
if (this._savedState === null) {
|
|
1111
|
+
this.saveStateBeforeParse();
|
|
1112
|
+
} else {
|
|
1113
|
+
this.restoreStateBeforeParse();
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Called the first time parse is called to save state and allow a restore before subsequent calls to parse.
|
|
1119
|
+
* Not usually called directly, but available for subclasses to save their custom state.
|
|
1120
|
+
*
|
|
1121
|
+
* This is called in a lazy way. Only commands used in parsing chain will have state saved.
|
|
1122
|
+
*/
|
|
1123
|
+
saveStateBeforeParse() {
|
|
1124
|
+
this._savedState = {
|
|
1125
|
+
// name is stable if supplied by author, but may be unspecified for root command and deduced during parsing
|
|
1126
|
+
_name: this._name,
|
|
1127
|
+
// option values before parse have default values (including false for negated options)
|
|
1128
|
+
// shallow clones
|
|
1129
|
+
_optionValues: { ...this._optionValues },
|
|
1130
|
+
_optionValueSources: { ...this._optionValueSources },
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Restore state before parse for calls after the first.
|
|
1136
|
+
* Not usually called directly, but available for subclasses to save their custom state.
|
|
1137
|
+
*
|
|
1138
|
+
* This is called in a lazy way. Only commands used in parsing chain will have state restored.
|
|
1139
|
+
*/
|
|
1140
|
+
restoreStateBeforeParse() {
|
|
1141
|
+
if (this._storeOptionsAsProperties)
|
|
1142
|
+
throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
|
|
1143
|
+
- either make a new Command for each call to parse, or stop storing options as properties`);
|
|
1144
|
+
|
|
1145
|
+
// clear state from _prepareUserArgs
|
|
1146
|
+
this._name = this._savedState._name;
|
|
1147
|
+
this._scriptPath = null;
|
|
1148
|
+
this.rawArgs = [];
|
|
1149
|
+
// clear state from setOptionValueWithSource
|
|
1150
|
+
this._optionValues = { ...this._savedState._optionValues };
|
|
1151
|
+
this._optionValueSources = { ...this._savedState._optionValueSources };
|
|
1152
|
+
// clear state from _parseCommand
|
|
1153
|
+
this.args = [];
|
|
1154
|
+
// clear state from _processArguments
|
|
1155
|
+
this.processedArgs = [];
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/**
|
|
1159
|
+
* Throw if expected executable is missing. Add lots of help for author.
|
|
1160
|
+
*
|
|
1161
|
+
* @param {string} executableFile
|
|
1162
|
+
* @param {string} executableDir
|
|
1163
|
+
* @param {string} subcommandName
|
|
1164
|
+
*/
|
|
1165
|
+
_checkForMissingExecutable(executableFile, executableDir, subcommandName) {
|
|
1166
|
+
if (fs.existsSync(executableFile)) return;
|
|
1167
|
+
|
|
1168
|
+
const executableDirMessage = executableDir
|
|
1169
|
+
? `searched for local subcommand relative to directory '${executableDir}'`
|
|
1170
|
+
: 'no directory for search for local subcommand, use .executableDir() to supply a custom directory';
|
|
1171
|
+
const executableMissing = `'${executableFile}' does not exist
|
|
1172
|
+
- if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
|
|
1173
|
+
- if the default executable name is not suitable, use the executableFile option to supply a custom name or path
|
|
1174
|
+
- ${executableDirMessage}`;
|
|
1175
|
+
throw new Error(executableMissing);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1097
1178
|
/**
|
|
1098
1179
|
* Execute a sub-command executable.
|
|
1099
1180
|
*
|
|
@@ -1134,7 +1215,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1134
1215
|
let resolvedScriptPath; // resolve possible symlink for installed npm binary
|
|
1135
1216
|
try {
|
|
1136
1217
|
resolvedScriptPath = fs.realpathSync(this._scriptPath);
|
|
1137
|
-
} catch
|
|
1218
|
+
} catch {
|
|
1138
1219
|
resolvedScriptPath = this._scriptPath;
|
|
1139
1220
|
}
|
|
1140
1221
|
executableDir = path.resolve(
|
|
@@ -1177,6 +1258,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1177
1258
|
proc = childProcess.spawn(executableFile, args, { stdio: 'inherit' });
|
|
1178
1259
|
}
|
|
1179
1260
|
} else {
|
|
1261
|
+
this._checkForMissingExecutable(
|
|
1262
|
+
executableFile,
|
|
1263
|
+
executableDir,
|
|
1264
|
+
subcommand._name,
|
|
1265
|
+
);
|
|
1180
1266
|
args.unshift(executableFile);
|
|
1181
1267
|
// add executable arguments to spawn
|
|
1182
1268
|
args = incrementNodeInspectorPort(process.execArgv).concat(args);
|
|
@@ -1215,14 +1301,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1215
1301
|
proc.on('error', (err) => {
|
|
1216
1302
|
// @ts-ignore: because err.code is an unknown property
|
|
1217
1303
|
if (err.code === 'ENOENT') {
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
- if the default executable name is not suitable, use the executableFile option to supply a custom name or path
|
|
1224
|
-
- ${executableDirMessage}`;
|
|
1225
|
-
throw new Error(executableMissing);
|
|
1304
|
+
this._checkForMissingExecutable(
|
|
1305
|
+
executableFile,
|
|
1306
|
+
executableDir,
|
|
1307
|
+
subcommand._name,
|
|
1308
|
+
);
|
|
1226
1309
|
// @ts-ignore: because err.code is an unknown property
|
|
1227
1310
|
} else if (err.code === 'EACCES') {
|
|
1228
1311
|
throw new Error(`'${executableFile}' not executable`);
|
|
@@ -1252,6 +1335,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1252
1335
|
const subCommand = this._findCommand(commandName);
|
|
1253
1336
|
if (!subCommand) this.help({ error: true });
|
|
1254
1337
|
|
|
1338
|
+
subCommand._prepareForParse();
|
|
1255
1339
|
let promiseChain;
|
|
1256
1340
|
promiseChain = this._chainOrCallSubCommandHook(
|
|
1257
1341
|
promiseChain,
|
|
@@ -1629,6 +1713,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1629
1713
|
* Parse options from `argv` removing known options,
|
|
1630
1714
|
* and return argv split into operands and unknown arguments.
|
|
1631
1715
|
*
|
|
1716
|
+
* Side effects: modifies command by storing options. Does not reset state if called again.
|
|
1717
|
+
*
|
|
1632
1718
|
* Examples:
|
|
1633
1719
|
*
|
|
1634
1720
|
* argv => operands, unknown
|
|
@@ -2254,31 +2340,49 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2254
2340
|
|
|
2255
2341
|
helpInformation(contextOptions) {
|
|
2256
2342
|
const helper = this.createHelp();
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
}
|
|
2263
|
-
|
|
2343
|
+
const context = this._getOutputContext(contextOptions);
|
|
2344
|
+
helper.prepareContext({
|
|
2345
|
+
error: context.error,
|
|
2346
|
+
helpWidth: context.helpWidth,
|
|
2347
|
+
outputHasColors: context.hasColors,
|
|
2348
|
+
});
|
|
2349
|
+
const text = helper.formatHelp(this, helper);
|
|
2350
|
+
if (context.hasColors) return text;
|
|
2351
|
+
return this._outputConfiguration.stripColor(text);
|
|
2264
2352
|
}
|
|
2265
2353
|
|
|
2266
2354
|
/**
|
|
2355
|
+
* @typedef HelpContext
|
|
2356
|
+
* @type {object}
|
|
2357
|
+
* @property {boolean} error
|
|
2358
|
+
* @property {number} helpWidth
|
|
2359
|
+
* @property {boolean} hasColors
|
|
2360
|
+
* @property {function} write - includes stripColor if needed
|
|
2361
|
+
*
|
|
2362
|
+
* @returns {HelpContext}
|
|
2267
2363
|
* @private
|
|
2268
2364
|
*/
|
|
2269
2365
|
|
|
2270
|
-
|
|
2366
|
+
_getOutputContext(contextOptions) {
|
|
2271
2367
|
contextOptions = contextOptions || {};
|
|
2272
|
-
const
|
|
2273
|
-
let
|
|
2274
|
-
|
|
2275
|
-
|
|
2368
|
+
const error = !!contextOptions.error;
|
|
2369
|
+
let baseWrite;
|
|
2370
|
+
let hasColors;
|
|
2371
|
+
let helpWidth;
|
|
2372
|
+
if (error) {
|
|
2373
|
+
baseWrite = (str) => this._outputConfiguration.writeErr(str);
|
|
2374
|
+
hasColors = this._outputConfiguration.getErrHasColors();
|
|
2375
|
+
helpWidth = this._outputConfiguration.getErrHelpWidth();
|
|
2276
2376
|
} else {
|
|
2277
|
-
|
|
2377
|
+
baseWrite = (str) => this._outputConfiguration.writeOut(str);
|
|
2378
|
+
hasColors = this._outputConfiguration.getOutHasColors();
|
|
2379
|
+
helpWidth = this._outputConfiguration.getOutHelpWidth();
|
|
2278
2380
|
}
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2381
|
+
const write = (str) => {
|
|
2382
|
+
if (!hasColors) str = this._outputConfiguration.stripColor(str);
|
|
2383
|
+
return baseWrite(str);
|
|
2384
|
+
};
|
|
2385
|
+
return { error, write, hasColors, helpWidth };
|
|
2282
2386
|
}
|
|
2283
2387
|
|
|
2284
2388
|
/**
|
|
@@ -2295,14 +2399,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2295
2399
|
deprecatedCallback = contextOptions;
|
|
2296
2400
|
contextOptions = undefined;
|
|
2297
2401
|
}
|
|
2298
|
-
|
|
2402
|
+
|
|
2403
|
+
const outputContext = this._getOutputContext(contextOptions);
|
|
2404
|
+
/** @type {HelpTextEventContext} */
|
|
2405
|
+
const eventContext = {
|
|
2406
|
+
error: outputContext.error,
|
|
2407
|
+
write: outputContext.write,
|
|
2408
|
+
command: this,
|
|
2409
|
+
};
|
|
2299
2410
|
|
|
2300
2411
|
this._getCommandAndAncestors()
|
|
2301
2412
|
.reverse()
|
|
2302
|
-
.forEach((command) => command.emit('beforeAllHelp',
|
|
2303
|
-
this.emit('beforeHelp',
|
|
2413
|
+
.forEach((command) => command.emit('beforeAllHelp', eventContext));
|
|
2414
|
+
this.emit('beforeHelp', eventContext);
|
|
2304
2415
|
|
|
2305
|
-
let helpInformation = this.helpInformation(
|
|
2416
|
+
let helpInformation = this.helpInformation({ error: outputContext.error });
|
|
2306
2417
|
if (deprecatedCallback) {
|
|
2307
2418
|
helpInformation = deprecatedCallback(helpInformation);
|
|
2308
2419
|
if (
|
|
@@ -2312,14 +2423,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2312
2423
|
throw new Error('outputHelp callback must return a string or a Buffer');
|
|
2313
2424
|
}
|
|
2314
2425
|
}
|
|
2315
|
-
|
|
2426
|
+
outputContext.write(helpInformation);
|
|
2316
2427
|
|
|
2317
2428
|
if (this._getHelpOption()?.long) {
|
|
2318
2429
|
this.emit(this._getHelpOption().long); // deprecated
|
|
2319
2430
|
}
|
|
2320
|
-
this.emit('afterHelp',
|
|
2431
|
+
this.emit('afterHelp', eventContext);
|
|
2321
2432
|
this._getCommandAndAncestors().forEach((command) =>
|
|
2322
|
-
command.emit('afterAllHelp',
|
|
2433
|
+
command.emit('afterAllHelp', eventContext),
|
|
2323
2434
|
);
|
|
2324
2435
|
}
|
|
2325
2436
|
|
|
@@ -2339,6 +2450,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2339
2450
|
helpOption(flags, description) {
|
|
2340
2451
|
// Support disabling built-in help option.
|
|
2341
2452
|
if (typeof flags === 'boolean') {
|
|
2453
|
+
// true is not an expected value. Do something sensible but no unit-test.
|
|
2454
|
+
// istanbul ignore if
|
|
2342
2455
|
if (flags) {
|
|
2343
2456
|
this._helpOption = this._helpOption ?? undefined; // preserve existing option
|
|
2344
2457
|
} else {
|
|
@@ -2392,7 +2505,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2392
2505
|
|
|
2393
2506
|
help(contextOptions) {
|
|
2394
2507
|
this.outputHelp(contextOptions);
|
|
2395
|
-
let exitCode = process.exitCode
|
|
2508
|
+
let exitCode = Number(process.exitCode ?? 0); // process.exitCode does allow a string or an integer, but we prefer just a number
|
|
2396
2509
|
if (
|
|
2397
2510
|
exitCode === 0 &&
|
|
2398
2511
|
contextOptions &&
|
|
@@ -2405,6 +2518,15 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2405
2518
|
this._exit(exitCode, 'commander.help', '(outputHelp)');
|
|
2406
2519
|
}
|
|
2407
2520
|
|
|
2521
|
+
/**
|
|
2522
|
+
* // Do a little typing to coordinate emit and listener for the help text events.
|
|
2523
|
+
* @typedef HelpTextEventContext
|
|
2524
|
+
* @type {object}
|
|
2525
|
+
* @property {boolean} error
|
|
2526
|
+
* @property {Command} command
|
|
2527
|
+
* @property {function} write
|
|
2528
|
+
*/
|
|
2529
|
+
|
|
2408
2530
|
/**
|
|
2409
2531
|
* Add additional text to be displayed with the built-in help.
|
|
2410
2532
|
*
|
|
@@ -2415,14 +2537,16 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2415
2537
|
* @param {(string | Function)} text - string to add, or a function returning a string
|
|
2416
2538
|
* @return {Command} `this` command for chaining
|
|
2417
2539
|
*/
|
|
2540
|
+
|
|
2418
2541
|
addHelpText(position, text) {
|
|
2419
2542
|
const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
|
|
2420
2543
|
if (!allowedValues.includes(position)) {
|
|
2421
2544
|
throw new Error(`Unexpected value for position to addHelpText.
|
|
2422
2545
|
Expecting one of '${allowedValues.join("', '")}'`);
|
|
2423
2546
|
}
|
|
2547
|
+
|
|
2424
2548
|
const helpEvent = `${position}Help`;
|
|
2425
|
-
this.on(helpEvent, (context) => {
|
|
2549
|
+
this.on(helpEvent, (/** @type {HelpTextEventContext} */ context) => {
|
|
2426
2550
|
let helpStr;
|
|
2427
2551
|
if (typeof text === 'function') {
|
|
2428
2552
|
helpStr = text({ error: context.error, command: context.command });
|
|
@@ -2506,4 +2630,33 @@ function incrementNodeInspectorPort(args) {
|
|
|
2506
2630
|
});
|
|
2507
2631
|
}
|
|
2508
2632
|
|
|
2633
|
+
/**
|
|
2634
|
+
* @returns {boolean | undefined}
|
|
2635
|
+
* @package
|
|
2636
|
+
*/
|
|
2637
|
+
function useColor() {
|
|
2638
|
+
// Test for common conventions.
|
|
2639
|
+
// NB: the observed behaviour is in combination with how author adds color! For example:
|
|
2640
|
+
// - we do not test NODE_DISABLE_COLORS, but util:styletext does
|
|
2641
|
+
// - we do test NO_COLOR, but Chalk does not
|
|
2642
|
+
//
|
|
2643
|
+
// References:
|
|
2644
|
+
// https://no-color.org
|
|
2645
|
+
// https://bixense.com/clicolors/
|
|
2646
|
+
// https://github.com/nodejs/node/blob/0a00217a5f67ef4a22384cfc80eb6dd9a917fdc1/lib/internal/tty.js#L109
|
|
2647
|
+
// https://github.com/chalk/supports-color/blob/c214314a14bcb174b12b3014b2b0a8de375029ae/index.js#L33
|
|
2648
|
+
// (https://force-color.org recent web page from 2023, does not match major javascript implementations)
|
|
2649
|
+
|
|
2650
|
+
if (
|
|
2651
|
+
process.env.NO_COLOR ||
|
|
2652
|
+
process.env.FORCE_COLOR === '0' ||
|
|
2653
|
+
process.env.FORCE_COLOR === 'false'
|
|
2654
|
+
)
|
|
2655
|
+
return false;
|
|
2656
|
+
if (process.env.FORCE_COLOR || process.env.CLICOLOR_FORCE !== undefined)
|
|
2657
|
+
return true;
|
|
2658
|
+
return undefined;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2509
2661
|
exports.Command = Command;
|
|
2662
|
+
exports.useColor = useColor; // exporting for tests
|
package/lib/help.js
CHANGED
|
@@ -12,11 +12,24 @@ const { humanReadableArgName } = require('./argument.js');
|
|
|
12
12
|
class Help {
|
|
13
13
|
constructor() {
|
|
14
14
|
this.helpWidth = undefined;
|
|
15
|
+
this.minWidthToWrap = 40;
|
|
15
16
|
this.sortSubcommands = false;
|
|
16
17
|
this.sortOptions = false;
|
|
17
18
|
this.showGlobalOptions = false;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* prepareContext is called by Commander after applying overrides from `Command.configureHelp()`
|
|
23
|
+
* and just before calling `formatHelp()`.
|
|
24
|
+
*
|
|
25
|
+
* Commander just uses the helpWidth and the rest is provided for optional use by more complex subclasses.
|
|
26
|
+
*
|
|
27
|
+
* @param {{ error?: boolean, helpWidth?: number, outputHasColors?: boolean }} contextOptions
|
|
28
|
+
*/
|
|
29
|
+
prepareContext(contextOptions) {
|
|
30
|
+
this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
/**
|
|
21
34
|
* Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
|
|
22
35
|
*
|
|
@@ -191,7 +204,12 @@ class Help {
|
|
|
191
204
|
|
|
192
205
|
longestSubcommandTermLength(cmd, helper) {
|
|
193
206
|
return helper.visibleCommands(cmd).reduce((max, command) => {
|
|
194
|
-
return Math.max(
|
|
207
|
+
return Math.max(
|
|
208
|
+
max,
|
|
209
|
+
this.displayWidth(
|
|
210
|
+
helper.styleSubcommandTerm(helper.subcommandTerm(command)),
|
|
211
|
+
),
|
|
212
|
+
);
|
|
195
213
|
}, 0);
|
|
196
214
|
}
|
|
197
215
|
|
|
@@ -205,7 +223,10 @@ class Help {
|
|
|
205
223
|
|
|
206
224
|
longestOptionTermLength(cmd, helper) {
|
|
207
225
|
return helper.visibleOptions(cmd).reduce((max, option) => {
|
|
208
|
-
return Math.max(
|
|
226
|
+
return Math.max(
|
|
227
|
+
max,
|
|
228
|
+
this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),
|
|
229
|
+
);
|
|
209
230
|
}, 0);
|
|
210
231
|
}
|
|
211
232
|
|
|
@@ -219,7 +240,10 @@ class Help {
|
|
|
219
240
|
|
|
220
241
|
longestGlobalOptionTermLength(cmd, helper) {
|
|
221
242
|
return helper.visibleGlobalOptions(cmd).reduce((max, option) => {
|
|
222
|
-
return Math.max(
|
|
243
|
+
return Math.max(
|
|
244
|
+
max,
|
|
245
|
+
this.displayWidth(helper.styleOptionTerm(helper.optionTerm(option))),
|
|
246
|
+
);
|
|
223
247
|
}, 0);
|
|
224
248
|
}
|
|
225
249
|
|
|
@@ -233,7 +257,12 @@ class Help {
|
|
|
233
257
|
|
|
234
258
|
longestArgumentTermLength(cmd, helper) {
|
|
235
259
|
return helper.visibleArguments(cmd).reduce((max, argument) => {
|
|
236
|
-
return Math.max(
|
|
260
|
+
return Math.max(
|
|
261
|
+
max,
|
|
262
|
+
this.displayWidth(
|
|
263
|
+
helper.styleArgumentTerm(helper.argumentTerm(argument)),
|
|
264
|
+
),
|
|
265
|
+
);
|
|
237
266
|
}, 0);
|
|
238
267
|
}
|
|
239
268
|
|
|
@@ -350,11 +379,11 @@ class Help {
|
|
|
350
379
|
);
|
|
351
380
|
}
|
|
352
381
|
if (extraInfo.length > 0) {
|
|
353
|
-
const
|
|
382
|
+
const extraDescription = `(${extraInfo.join(', ')})`;
|
|
354
383
|
if (argument.description) {
|
|
355
|
-
return `${argument.description} ${
|
|
384
|
+
return `${argument.description} ${extraDescription}`;
|
|
356
385
|
}
|
|
357
|
-
return
|
|
386
|
+
return extraDescription;
|
|
358
387
|
}
|
|
359
388
|
return argument.description;
|
|
360
389
|
}
|
|
@@ -369,71 +398,73 @@ class Help {
|
|
|
369
398
|
|
|
370
399
|
formatHelp(cmd, helper) {
|
|
371
400
|
const termWidth = helper.padWidth(cmd, helper);
|
|
372
|
-
const helpWidth = helper.helpWidth
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (description) {
|
|
377
|
-
const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
|
|
378
|
-
return helper.wrap(
|
|
379
|
-
fullText,
|
|
380
|
-
helpWidth - itemIndentWidth,
|
|
381
|
-
termWidth + itemSeparatorWidth,
|
|
382
|
-
);
|
|
383
|
-
}
|
|
384
|
-
return term;
|
|
385
|
-
}
|
|
386
|
-
function formatList(textArray) {
|
|
387
|
-
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
|
|
401
|
+
const helpWidth = helper.helpWidth ?? 80; // in case prepareContext() was not called
|
|
402
|
+
|
|
403
|
+
function callFormatItem(term, description) {
|
|
404
|
+
return helper.formatItem(term, termWidth, description, helper);
|
|
388
405
|
}
|
|
389
406
|
|
|
390
407
|
// Usage
|
|
391
|
-
let output = [
|
|
408
|
+
let output = [
|
|
409
|
+
`${helper.styleTitle('Usage:')} ${helper.styleUsage(helper.commandUsage(cmd))}`,
|
|
410
|
+
'',
|
|
411
|
+
];
|
|
392
412
|
|
|
393
413
|
// Description
|
|
394
414
|
const commandDescription = helper.commandDescription(cmd);
|
|
395
415
|
if (commandDescription.length > 0) {
|
|
396
416
|
output = output.concat([
|
|
397
|
-
helper.
|
|
417
|
+
helper.boxWrap(
|
|
418
|
+
helper.styleCommandDescription(commandDescription),
|
|
419
|
+
helpWidth,
|
|
420
|
+
),
|
|
398
421
|
'',
|
|
399
422
|
]);
|
|
400
423
|
}
|
|
401
424
|
|
|
402
425
|
// Arguments
|
|
403
426
|
const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
|
404
|
-
return
|
|
405
|
-
helper.argumentTerm(argument),
|
|
406
|
-
helper.argumentDescription(argument),
|
|
427
|
+
return callFormatItem(
|
|
428
|
+
helper.styleArgumentTerm(helper.argumentTerm(argument)),
|
|
429
|
+
helper.styleArgumentDescription(helper.argumentDescription(argument)),
|
|
407
430
|
);
|
|
408
431
|
});
|
|
409
432
|
if (argumentList.length > 0) {
|
|
410
|
-
output = output.concat([
|
|
433
|
+
output = output.concat([
|
|
434
|
+
helper.styleTitle('Arguments:'),
|
|
435
|
+
...argumentList,
|
|
436
|
+
'',
|
|
437
|
+
]);
|
|
411
438
|
}
|
|
412
439
|
|
|
413
440
|
// Options
|
|
414
441
|
const optionList = helper.visibleOptions(cmd).map((option) => {
|
|
415
|
-
return
|
|
416
|
-
helper.optionTerm(option),
|
|
417
|
-
helper.optionDescription(option),
|
|
442
|
+
return callFormatItem(
|
|
443
|
+
helper.styleOptionTerm(helper.optionTerm(option)),
|
|
444
|
+
helper.styleOptionDescription(helper.optionDescription(option)),
|
|
418
445
|
);
|
|
419
446
|
});
|
|
420
447
|
if (optionList.length > 0) {
|
|
421
|
-
output = output.concat([
|
|
448
|
+
output = output.concat([
|
|
449
|
+
helper.styleTitle('Options:'),
|
|
450
|
+
...optionList,
|
|
451
|
+
'',
|
|
452
|
+
]);
|
|
422
453
|
}
|
|
423
454
|
|
|
424
|
-
if (
|
|
455
|
+
if (helper.showGlobalOptions) {
|
|
425
456
|
const globalOptionList = helper
|
|
426
457
|
.visibleGlobalOptions(cmd)
|
|
427
458
|
.map((option) => {
|
|
428
|
-
return
|
|
429
|
-
helper.optionTerm(option),
|
|
430
|
-
helper.optionDescription(option),
|
|
459
|
+
return callFormatItem(
|
|
460
|
+
helper.styleOptionTerm(helper.optionTerm(option)),
|
|
461
|
+
helper.styleOptionDescription(helper.optionDescription(option)),
|
|
431
462
|
);
|
|
432
463
|
});
|
|
433
464
|
if (globalOptionList.length > 0) {
|
|
434
465
|
output = output.concat([
|
|
435
|
-
'Global Options:',
|
|
436
|
-
|
|
466
|
+
helper.styleTitle('Global Options:'),
|
|
467
|
+
...globalOptionList,
|
|
437
468
|
'',
|
|
438
469
|
]);
|
|
439
470
|
}
|
|
@@ -441,18 +472,103 @@ class Help {
|
|
|
441
472
|
|
|
442
473
|
// Commands
|
|
443
474
|
const commandList = helper.visibleCommands(cmd).map((cmd) => {
|
|
444
|
-
return
|
|
445
|
-
helper.subcommandTerm(cmd),
|
|
446
|
-
helper.subcommandDescription(cmd),
|
|
475
|
+
return callFormatItem(
|
|
476
|
+
helper.styleSubcommandTerm(helper.subcommandTerm(cmd)),
|
|
477
|
+
helper.styleSubcommandDescription(helper.subcommandDescription(cmd)),
|
|
447
478
|
);
|
|
448
479
|
});
|
|
449
480
|
if (commandList.length > 0) {
|
|
450
|
-
output = output.concat([
|
|
481
|
+
output = output.concat([
|
|
482
|
+
helper.styleTitle('Commands:'),
|
|
483
|
+
...commandList,
|
|
484
|
+
'',
|
|
485
|
+
]);
|
|
451
486
|
}
|
|
452
487
|
|
|
453
488
|
return output.join('\n');
|
|
454
489
|
}
|
|
455
490
|
|
|
491
|
+
/**
|
|
492
|
+
* Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations.
|
|
493
|
+
*
|
|
494
|
+
* @param {string} str
|
|
495
|
+
* @returns {number}
|
|
496
|
+
*/
|
|
497
|
+
displayWidth(str) {
|
|
498
|
+
return stripColor(str).length;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Style the title for displaying in the help. Called with 'Usage:', 'Options:', etc.
|
|
503
|
+
*
|
|
504
|
+
* @param {string} str
|
|
505
|
+
* @returns {string}
|
|
506
|
+
*/
|
|
507
|
+
styleTitle(str) {
|
|
508
|
+
return str;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
styleUsage(str) {
|
|
512
|
+
// Usage has lots of parts the user might like to color separately! Assume default usage string which is formed like:
|
|
513
|
+
// command subcommand [options] [command] <foo> [bar]
|
|
514
|
+
return str
|
|
515
|
+
.split(' ')
|
|
516
|
+
.map((word) => {
|
|
517
|
+
if (word === '[options]') return this.styleOptionText(word);
|
|
518
|
+
if (word === '[command]') return this.styleSubcommandText(word);
|
|
519
|
+
if (word[0] === '[' || word[0] === '<')
|
|
520
|
+
return this.styleArgumentText(word);
|
|
521
|
+
return this.styleCommandText(word); // Restrict to initial words?
|
|
522
|
+
})
|
|
523
|
+
.join(' ');
|
|
524
|
+
}
|
|
525
|
+
styleCommandDescription(str) {
|
|
526
|
+
return this.styleDescriptionText(str);
|
|
527
|
+
}
|
|
528
|
+
styleOptionDescription(str) {
|
|
529
|
+
return this.styleDescriptionText(str);
|
|
530
|
+
}
|
|
531
|
+
styleSubcommandDescription(str) {
|
|
532
|
+
return this.styleDescriptionText(str);
|
|
533
|
+
}
|
|
534
|
+
styleArgumentDescription(str) {
|
|
535
|
+
return this.styleDescriptionText(str);
|
|
536
|
+
}
|
|
537
|
+
styleDescriptionText(str) {
|
|
538
|
+
return str;
|
|
539
|
+
}
|
|
540
|
+
styleOptionTerm(str) {
|
|
541
|
+
return this.styleOptionText(str);
|
|
542
|
+
}
|
|
543
|
+
styleSubcommandTerm(str) {
|
|
544
|
+
// This is very like usage with lots of parts! Assume default string which is formed like:
|
|
545
|
+
// subcommand [options] <foo> [bar]
|
|
546
|
+
return str
|
|
547
|
+
.split(' ')
|
|
548
|
+
.map((word) => {
|
|
549
|
+
if (word === '[options]') return this.styleOptionText(word);
|
|
550
|
+
if (word[0] === '[' || word[0] === '<')
|
|
551
|
+
return this.styleArgumentText(word);
|
|
552
|
+
return this.styleSubcommandText(word); // Restrict to initial words?
|
|
553
|
+
})
|
|
554
|
+
.join(' ');
|
|
555
|
+
}
|
|
556
|
+
styleArgumentTerm(str) {
|
|
557
|
+
return this.styleArgumentText(str);
|
|
558
|
+
}
|
|
559
|
+
styleOptionText(str) {
|
|
560
|
+
return str;
|
|
561
|
+
}
|
|
562
|
+
styleArgumentText(str) {
|
|
563
|
+
return str;
|
|
564
|
+
}
|
|
565
|
+
styleSubcommandText(str) {
|
|
566
|
+
return str;
|
|
567
|
+
}
|
|
568
|
+
styleCommandText(str) {
|
|
569
|
+
return str;
|
|
570
|
+
}
|
|
571
|
+
|
|
456
572
|
/**
|
|
457
573
|
* Calculate the pad width from the maximum term length.
|
|
458
574
|
*
|
|
@@ -471,50 +587,123 @@ class Help {
|
|
|
471
587
|
}
|
|
472
588
|
|
|
473
589
|
/**
|
|
474
|
-
*
|
|
475
|
-
* Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
|
|
590
|
+
* Detect manually wrapped and indented strings by checking for line break followed by whitespace.
|
|
476
591
|
*
|
|
477
592
|
* @param {string} str
|
|
478
|
-
* @
|
|
479
|
-
* @param {number} indent
|
|
480
|
-
* @param {number} [minColumnWidth=40]
|
|
481
|
-
* @return {string}
|
|
482
|
-
*
|
|
593
|
+
* @returns {boolean}
|
|
483
594
|
*/
|
|
595
|
+
preformatted(str) {
|
|
596
|
+
return /\n[^\S\r\n]/.test(str);
|
|
597
|
+
}
|
|
484
598
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
|
|
601
|
+
*
|
|
602
|
+
* So "TTT", 5, "DDD DDDD DD DDD" might be formatted for this.helpWidth=17 like so:
|
|
603
|
+
* TTT DDD DDDD
|
|
604
|
+
* DD DDD
|
|
605
|
+
*
|
|
606
|
+
* @param {string} term
|
|
607
|
+
* @param {number} termWidth
|
|
608
|
+
* @param {string} description
|
|
609
|
+
* @param {Help} helper
|
|
610
|
+
* @returns {string}
|
|
611
|
+
*/
|
|
612
|
+
formatItem(term, termWidth, description, helper) {
|
|
613
|
+
const itemIndent = 2;
|
|
614
|
+
const itemIndentStr = ' '.repeat(itemIndent);
|
|
615
|
+
if (!description) return itemIndentStr + term;
|
|
616
|
+
|
|
617
|
+
// Pad the term out to a consistent width, so descriptions are aligned.
|
|
618
|
+
const paddedTerm = term.padEnd(
|
|
619
|
+
termWidth + term.length - helper.displayWidth(term),
|
|
506
620
|
);
|
|
507
|
-
|
|
621
|
+
|
|
622
|
+
// Format the description.
|
|
623
|
+
const spacerWidth = 2; // between term and description
|
|
624
|
+
const helpWidth = this.helpWidth ?? 80; // in case prepareContext() was not called
|
|
625
|
+
const remainingWidth = helpWidth - termWidth - spacerWidth - itemIndent;
|
|
626
|
+
let formattedDescription;
|
|
627
|
+
if (
|
|
628
|
+
remainingWidth < this.minWidthToWrap ||
|
|
629
|
+
helper.preformatted(description)
|
|
630
|
+
) {
|
|
631
|
+
formattedDescription = description;
|
|
632
|
+
} else {
|
|
633
|
+
const wrappedDescription = helper.boxWrap(description, remainingWidth);
|
|
634
|
+
formattedDescription = wrappedDescription.replace(
|
|
635
|
+
/\n/g,
|
|
636
|
+
'\n' + ' '.repeat(termWidth + spacerWidth),
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Construct and overall indent.
|
|
508
641
|
return (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
return (i > 0 ? indentString : '') + line.trimEnd();
|
|
514
|
-
})
|
|
515
|
-
.join('\n')
|
|
642
|
+
itemIndentStr +
|
|
643
|
+
paddedTerm +
|
|
644
|
+
' '.repeat(spacerWidth) +
|
|
645
|
+
formattedDescription.replace(/\n/g, `\n${itemIndentStr}`)
|
|
516
646
|
);
|
|
517
647
|
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Wrap a string at whitespace, preserving existing line breaks.
|
|
651
|
+
* Wrapping is skipped if the width is less than `minWidthToWrap`.
|
|
652
|
+
*
|
|
653
|
+
* @param {string} str
|
|
654
|
+
* @param {number} width
|
|
655
|
+
* @returns {string}
|
|
656
|
+
*/
|
|
657
|
+
boxWrap(str, width) {
|
|
658
|
+
if (width < this.minWidthToWrap) return str;
|
|
659
|
+
|
|
660
|
+
const rawLines = str.split(/\r\n|\n/);
|
|
661
|
+
// split up text by whitespace
|
|
662
|
+
const chunkPattern = /[\s]*[^\s]+/g;
|
|
663
|
+
const wrappedLines = [];
|
|
664
|
+
rawLines.forEach((line) => {
|
|
665
|
+
const chunks = line.match(chunkPattern);
|
|
666
|
+
if (chunks === null) {
|
|
667
|
+
wrappedLines.push('');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
let sumChunks = [chunks.shift()];
|
|
672
|
+
let sumWidth = this.displayWidth(sumChunks[0]);
|
|
673
|
+
chunks.forEach((chunk) => {
|
|
674
|
+
const visibleWidth = this.displayWidth(chunk);
|
|
675
|
+
// Accumulate chunks while they fit into width.
|
|
676
|
+
if (sumWidth + visibleWidth <= width) {
|
|
677
|
+
sumChunks.push(chunk);
|
|
678
|
+
sumWidth += visibleWidth;
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
wrappedLines.push(sumChunks.join(''));
|
|
682
|
+
|
|
683
|
+
const nextChunk = chunk.trimStart(); // trim space at line break
|
|
684
|
+
sumChunks = [nextChunk];
|
|
685
|
+
sumWidth = this.displayWidth(nextChunk);
|
|
686
|
+
});
|
|
687
|
+
wrappedLines.push(sumChunks.join(''));
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
return wrappedLines.join('\n');
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Strip style ANSI escape sequences from the string. In particular, SGR (Select Graphic Rendition) codes.
|
|
696
|
+
*
|
|
697
|
+
* @param {string} str
|
|
698
|
+
* @returns {string}
|
|
699
|
+
* @package
|
|
700
|
+
*/
|
|
701
|
+
|
|
702
|
+
function stripColor(str) {
|
|
703
|
+
// eslint-disable-next-line no-control-regex
|
|
704
|
+
const sgrPattern = /\x1b\[\d*(;\d*)*m/g;
|
|
705
|
+
return str.replace(sgrPattern, '');
|
|
518
706
|
}
|
|
519
707
|
|
|
520
708
|
exports.Help = Help;
|
|
709
|
+
exports.stripColor = stripColor;
|
package/lib/option.js
CHANGED
|
@@ -207,13 +207,16 @@ class Option {
|
|
|
207
207
|
|
|
208
208
|
/**
|
|
209
209
|
* Return option name, in a camelcase format that can be used
|
|
210
|
-
* as
|
|
210
|
+
* as an object attribute key.
|
|
211
211
|
*
|
|
212
212
|
* @return {string}
|
|
213
213
|
*/
|
|
214
214
|
|
|
215
215
|
attributeName() {
|
|
216
|
-
|
|
216
|
+
if (this.negate) {
|
|
217
|
+
return camelcase(this.name().replace(/^no-/, ''));
|
|
218
|
+
}
|
|
219
|
+
return camelcase(this.name());
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
/**
|
|
@@ -312,17 +315,32 @@ function camelcase(str) {
|
|
|
312
315
|
function splitOptionFlags(flags) {
|
|
313
316
|
let shortFlag;
|
|
314
317
|
let longFlag;
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
318
|
+
// short flag, single dash and single character
|
|
319
|
+
const shortFlagExp = /^-[^-]$/;
|
|
320
|
+
// long flag, double dash and at least one character
|
|
321
|
+
const longFlagExp = /^--[^-]/;
|
|
322
|
+
|
|
323
|
+
const flagParts = flags.split(/[ |,]+/).concat('guard');
|
|
324
|
+
if (shortFlagExp.test(flagParts[0])) shortFlag = flagParts.shift();
|
|
325
|
+
if (longFlagExp.test(flagParts[0])) longFlag = flagParts.shift();
|
|
326
|
+
|
|
327
|
+
// Check for some unsupported flags that people try.
|
|
328
|
+
if (/^-[^-][^-]/.test(flagParts[0]))
|
|
329
|
+
throw new Error(
|
|
330
|
+
`invalid Option flags, short option is dash and single character: '${flags}'`,
|
|
331
|
+
);
|
|
332
|
+
if (shortFlag && shortFlagExp.test(flagParts[0]))
|
|
333
|
+
throw new Error(
|
|
334
|
+
`invalid Option flags, more than one short flag: '${flags}'`,
|
|
335
|
+
);
|
|
336
|
+
if (longFlag && longFlagExp.test(flagParts[0]))
|
|
337
|
+
throw new Error(
|
|
338
|
+
`invalid Option flags, more than one long flag: '${flags}'`,
|
|
339
|
+
);
|
|
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
|
+
|
|
326
344
|
return { shortFlag, longFlag };
|
|
327
345
|
}
|
|
328
346
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commander",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "the complete solution for node.js command-line programs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"commander",
|
|
@@ -60,21 +60,19 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@eslint/js": "^
|
|
63
|
+
"@eslint/js": "^9.4.0",
|
|
64
64
|
"@types/jest": "^29.2.4",
|
|
65
|
-
"@types/node": "^
|
|
66
|
-
"eslint": "^
|
|
65
|
+
"@types/node": "^22.7.4",
|
|
66
|
+
"eslint": "^9.17.0",
|
|
67
67
|
"eslint-config-prettier": "^9.1.0",
|
|
68
68
|
"eslint-plugin-jest": "^28.3.0",
|
|
69
|
-
"
|
|
70
|
-
"globals": "^13.24.0",
|
|
69
|
+
"globals": "^15.7.0",
|
|
71
70
|
"jest": "^29.3.1",
|
|
72
71
|
"prettier": "^3.2.5",
|
|
73
|
-
"prettier-plugin-jsdoc": "^1.3.0",
|
|
74
72
|
"ts-jest": "^29.0.3",
|
|
75
73
|
"tsd": "^0.31.0",
|
|
76
74
|
"typescript": "^5.0.4",
|
|
77
|
-
"typescript-eslint": "^
|
|
75
|
+
"typescript-eslint": "^8.12.2"
|
|
78
76
|
},
|
|
79
77
|
"types": "typings/index.d.ts",
|
|
80
78
|
"engines": {
|
package/typings/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
// Type definitions for commander
|
|
2
2
|
// Original definitions by: Alan Agius <https://github.com/alan-agius4>, Marcelo Dezem <https://github.com/mdezem>, vvakame <https://github.com/vvakame>, Jules Randolph <https://github.com/sveinburne>
|
|
3
3
|
|
|
4
|
-
// Using method rather than property for method-signature-style, to document method overloads separately. Allow either.
|
|
5
|
-
/* eslint-disable @typescript-eslint/method-signature-style */
|
|
6
4
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
7
5
|
|
|
8
6
|
// This is a trick to encourage editor to suggest the known literals while still
|
|
@@ -190,7 +188,7 @@ export class Option {
|
|
|
190
188
|
|
|
191
189
|
/**
|
|
192
190
|
* Return option name, in a camelcase format that can be used
|
|
193
|
-
* as
|
|
191
|
+
* as an object attribute key.
|
|
194
192
|
*/
|
|
195
193
|
attributeName(): string;
|
|
196
194
|
|
|
@@ -205,12 +203,25 @@ export class Option {
|
|
|
205
203
|
export class Help {
|
|
206
204
|
/** output helpWidth, long lines are wrapped to fit */
|
|
207
205
|
helpWidth?: number;
|
|
206
|
+
minWidthToWrap: number;
|
|
208
207
|
sortSubcommands: boolean;
|
|
209
208
|
sortOptions: boolean;
|
|
210
209
|
showGlobalOptions: boolean;
|
|
211
210
|
|
|
212
211
|
constructor();
|
|
213
212
|
|
|
213
|
+
/*
|
|
214
|
+
* prepareContext is called by Commander after applying overrides from `Command.configureHelp()`
|
|
215
|
+
* and just before calling `formatHelp()`.
|
|
216
|
+
*
|
|
217
|
+
* Commander just uses the helpWidth and the others are provided for subclasses.
|
|
218
|
+
*/
|
|
219
|
+
prepareContext(contextOptions: {
|
|
220
|
+
error?: boolean;
|
|
221
|
+
helpWidth?: number;
|
|
222
|
+
outputHasColors?: boolean;
|
|
223
|
+
}): void;
|
|
224
|
+
|
|
214
225
|
/** Get the command term to show in the list of subcommands. */
|
|
215
226
|
subcommandTerm(cmd: Command): string;
|
|
216
227
|
/** Get the command summary to show in the list of subcommands. */
|
|
@@ -246,18 +257,60 @@ export class Help {
|
|
|
246
257
|
longestGlobalOptionTermLength(cmd: Command, helper: Help): number;
|
|
247
258
|
/** Get the longest argument term length. */
|
|
248
259
|
longestArgumentTermLength(cmd: Command, helper: Help): number;
|
|
260
|
+
|
|
261
|
+
/** Return display width of string, ignoring ANSI escape sequences. Used in padding and wrapping calculations. */
|
|
262
|
+
displayWidth(str: string): number;
|
|
263
|
+
|
|
264
|
+
/** Style the titles. Called with 'Usage:', 'Options:', etc. */
|
|
265
|
+
styleTitle(title: string): string;
|
|
266
|
+
|
|
267
|
+
/** Usage: <str> */
|
|
268
|
+
styleUsage(str: string): string;
|
|
269
|
+
/** Style for command name in usage string. */
|
|
270
|
+
styleCommandText(str: string): string;
|
|
271
|
+
|
|
272
|
+
styleCommandDescription(str: string): string;
|
|
273
|
+
styleOptionDescription(str: string): string;
|
|
274
|
+
styleSubcommandDescription(str: string): string;
|
|
275
|
+
styleArgumentDescription(str: string): string;
|
|
276
|
+
/** Base style used by descriptions. */
|
|
277
|
+
styleDescriptionText(str: string): string;
|
|
278
|
+
|
|
279
|
+
styleOptionTerm(str: string): string;
|
|
280
|
+
styleSubcommandTerm(str: string): string;
|
|
281
|
+
styleArgumentTerm(str: string): string;
|
|
282
|
+
|
|
283
|
+
/** Base style used in terms and usage for options. */
|
|
284
|
+
styleOptionText(str: string): string;
|
|
285
|
+
/** Base style used in terms and usage for subcommands. */
|
|
286
|
+
styleSubcommandText(str: string): string;
|
|
287
|
+
/** Base style used in terms and usage for arguments. */
|
|
288
|
+
styleArgumentText(str: string): string;
|
|
289
|
+
|
|
249
290
|
/** Calculate the pad width from the maximum term length. */
|
|
250
291
|
padWidth(cmd: Command, helper: Help): number;
|
|
251
292
|
|
|
252
293
|
/**
|
|
253
|
-
* Wrap
|
|
254
|
-
*
|
|
294
|
+
* Wrap a string at whitespace, preserving existing line breaks.
|
|
295
|
+
* Wrapping is skipped if the width is less than `minWidthToWrap`.
|
|
296
|
+
*/
|
|
297
|
+
boxWrap(str: string, width: number): string;
|
|
298
|
+
|
|
299
|
+
/** Detect manually wrapped and indented strings by checking for line break followed by whitespace. */
|
|
300
|
+
preformatted(str: string): boolean;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Format the "item", which consists of a term and description. Pad the term and wrap the description, indenting the following lines.
|
|
304
|
+
*
|
|
305
|
+
* So "TTT", 5, "DDD DDDD DD DDD" might be formatted for this.helpWidth=17 like so:
|
|
306
|
+
* TTT DDD DDDD
|
|
307
|
+
* DD DDD
|
|
255
308
|
*/
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
309
|
+
formatItem(
|
|
310
|
+
term: string,
|
|
311
|
+
termWidth: number,
|
|
312
|
+
description: string,
|
|
313
|
+
helper: Help,
|
|
261
314
|
): string;
|
|
262
315
|
|
|
263
316
|
/** Generate the built-in help text. */
|
|
@@ -280,9 +333,14 @@ export interface AddHelpTextContext {
|
|
|
280
333
|
export interface OutputConfiguration {
|
|
281
334
|
writeOut?(str: string): void;
|
|
282
335
|
writeErr?(str: string): void;
|
|
336
|
+
outputError?(str: string, write: (str: string) => void): void;
|
|
337
|
+
|
|
283
338
|
getOutHelpWidth?(): number;
|
|
284
339
|
getErrHelpWidth?(): number;
|
|
285
|
-
|
|
340
|
+
|
|
341
|
+
getOutHasColors?(): boolean;
|
|
342
|
+
getErrHasColors?(): boolean;
|
|
343
|
+
stripColor?(str: string): string;
|
|
286
344
|
}
|
|
287
345
|
|
|
288
346
|
export type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll';
|
|
@@ -544,7 +602,7 @@ export class Command {
|
|
|
544
602
|
*
|
|
545
603
|
* @returns `this` command for chaining
|
|
546
604
|
*/
|
|
547
|
-
action(fn: (...args: any[]) => void | Promise<void>): this;
|
|
605
|
+
action(fn: (this: this, ...args: any[]) => void | Promise<void>): this;
|
|
548
606
|
|
|
549
607
|
/**
|
|
550
608
|
* Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both.
|
|
@@ -763,10 +821,28 @@ export class Command {
|
|
|
763
821
|
parseOptions?: ParseOptions,
|
|
764
822
|
): Promise<this>;
|
|
765
823
|
|
|
824
|
+
/**
|
|
825
|
+
* Called the first time parse is called to save state and allow a restore before subsequent calls to parse.
|
|
826
|
+
* Not usually called directly, but available for subclasses to save their custom state.
|
|
827
|
+
*
|
|
828
|
+
* This is called in a lazy way. Only commands used in parsing chain will have state saved.
|
|
829
|
+
*/
|
|
830
|
+
saveStateBeforeParse(): void;
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Restore state before parse for calls after the first.
|
|
834
|
+
* Not usually called directly, but available for subclasses to save their custom state.
|
|
835
|
+
*
|
|
836
|
+
* This is called in a lazy way. Only commands used in parsing chain will have state restored.
|
|
837
|
+
*/
|
|
838
|
+
restoreStateBeforeParse(): void;
|
|
839
|
+
|
|
766
840
|
/**
|
|
767
841
|
* Parse options from `argv` removing known options,
|
|
768
842
|
* and return argv split into operands and unknown arguments.
|
|
769
843
|
*
|
|
844
|
+
* Side effects: modifies command by storing options. Does not reset state if called again.
|
|
845
|
+
*
|
|
770
846
|
* argv => operands, unknown
|
|
771
847
|
* --known kkk op => [op], []
|
|
772
848
|
* op --known kkk => [op], []
|