commander 6.2.1 → 7.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/CHANGELOG.md +140 -105
- package/Readme.md +318 -195
- package/index.js +792 -473
- package/package.json +15 -12
- package/typings/index.d.ts +241 -30
package/index.js
CHANGED
|
@@ -3,23 +3,359 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
const EventEmitter = require('events').EventEmitter;
|
|
6
|
-
const
|
|
6
|
+
const childProcess = require('child_process');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
|
|
10
10
|
// @ts-check
|
|
11
11
|
|
|
12
|
+
// Although this is a class, methods are static in style to allow override using subclass or just functions.
|
|
13
|
+
class Help {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.helpWidth = undefined;
|
|
16
|
+
this.sortSubcommands = false;
|
|
17
|
+
this.sortOptions = false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one.
|
|
22
|
+
*
|
|
23
|
+
* @param {Command} cmd
|
|
24
|
+
* @returns {Command[]}
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
visibleCommands(cmd) {
|
|
28
|
+
const visibleCommands = cmd.commands.filter(cmd => !cmd._hidden);
|
|
29
|
+
if (cmd._hasImplicitHelpCommand()) {
|
|
30
|
+
// Create a command matching the implicit help command.
|
|
31
|
+
const args = cmd._helpCommandnameAndArgs.split(/ +/);
|
|
32
|
+
const helpCommand = cmd.createCommand(args.shift())
|
|
33
|
+
.helpOption(false);
|
|
34
|
+
helpCommand.description(cmd._helpCommandDescription);
|
|
35
|
+
helpCommand._parseExpectedArgs(args);
|
|
36
|
+
visibleCommands.push(helpCommand);
|
|
37
|
+
}
|
|
38
|
+
if (this.sortSubcommands) {
|
|
39
|
+
visibleCommands.sort((a, b) => {
|
|
40
|
+
return a.name().localeCompare(b.name());
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return visibleCommands;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one.
|
|
48
|
+
*
|
|
49
|
+
* @param {Command} cmd
|
|
50
|
+
* @returns {Option[]}
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
visibleOptions(cmd) {
|
|
54
|
+
const visibleOptions = cmd.options.filter((option) => !option.hidden);
|
|
55
|
+
// Implicit help
|
|
56
|
+
const showShortHelpFlag = cmd._hasHelpOption && cmd._helpShortFlag && !cmd._findOption(cmd._helpShortFlag);
|
|
57
|
+
const showLongHelpFlag = cmd._hasHelpOption && !cmd._findOption(cmd._helpLongFlag);
|
|
58
|
+
if (showShortHelpFlag || showLongHelpFlag) {
|
|
59
|
+
let helpOption;
|
|
60
|
+
if (!showShortHelpFlag) {
|
|
61
|
+
helpOption = cmd.createOption(cmd._helpLongFlag, cmd._helpDescription);
|
|
62
|
+
} else if (!showLongHelpFlag) {
|
|
63
|
+
helpOption = cmd.createOption(cmd._helpShortFlag, cmd._helpDescription);
|
|
64
|
+
} else {
|
|
65
|
+
helpOption = cmd.createOption(cmd._helpFlags, cmd._helpDescription);
|
|
66
|
+
}
|
|
67
|
+
visibleOptions.push(helpOption);
|
|
68
|
+
}
|
|
69
|
+
if (this.sortOptions) {
|
|
70
|
+
const getSortKey = (option) => {
|
|
71
|
+
// WYSIWYG for order displayed in help with short before long, no special handling for negated.
|
|
72
|
+
return option.short ? option.short.replace(/^-/, '') : option.long.replace(/^--/, '');
|
|
73
|
+
};
|
|
74
|
+
visibleOptions.sort((a, b) => {
|
|
75
|
+
return getSortKey(a).localeCompare(getSortKey(b));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return visibleOptions;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get an array of the arguments which have descriptions.
|
|
83
|
+
*
|
|
84
|
+
* @param {Command} cmd
|
|
85
|
+
* @returns {{ term: string, description:string }[]}
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
visibleArguments(cmd) {
|
|
89
|
+
if (cmd._argsDescription && cmd._args.length) {
|
|
90
|
+
return cmd._args.map((argument) => {
|
|
91
|
+
return { term: argument.name, description: cmd._argsDescription[argument.name] || '' };
|
|
92
|
+
}, 0);
|
|
93
|
+
}
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the command term to show in the list of subcommands.
|
|
99
|
+
*
|
|
100
|
+
* @param {Command} cmd
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
subcommandTerm(cmd) {
|
|
105
|
+
// Legacy. Ignores custom usage string, and nested commands.
|
|
106
|
+
const args = cmd._args.map(arg => humanReadableArgName(arg)).join(' ');
|
|
107
|
+
return cmd._name +
|
|
108
|
+
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
|
|
109
|
+
(cmd.options.length ? ' [options]' : '') + // simplistic check for non-help option
|
|
110
|
+
(args ? ' ' + args : '');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get the option term to show in the list of options.
|
|
115
|
+
*
|
|
116
|
+
* @param {Option} option
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
|
|
120
|
+
optionTerm(option) {
|
|
121
|
+
return option.flags;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the longest command term length.
|
|
126
|
+
*
|
|
127
|
+
* @param {Command} cmd
|
|
128
|
+
* @param {Help} helper
|
|
129
|
+
* @returns {number}
|
|
130
|
+
*/
|
|
131
|
+
|
|
132
|
+
longestSubcommandTermLength(cmd, helper) {
|
|
133
|
+
return helper.visibleCommands(cmd).reduce((max, command) => {
|
|
134
|
+
return Math.max(max, helper.subcommandTerm(command).length);
|
|
135
|
+
}, 0);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get the longest option term length.
|
|
140
|
+
*
|
|
141
|
+
* @param {Command} cmd
|
|
142
|
+
* @param {Help} helper
|
|
143
|
+
* @returns {number}
|
|
144
|
+
*/
|
|
145
|
+
|
|
146
|
+
longestOptionTermLength(cmd, helper) {
|
|
147
|
+
return helper.visibleOptions(cmd).reduce((max, option) => {
|
|
148
|
+
return Math.max(max, helper.optionTerm(option).length);
|
|
149
|
+
}, 0);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get the longest argument term length.
|
|
154
|
+
*
|
|
155
|
+
* @param {Command} cmd
|
|
156
|
+
* @param {Help} helper
|
|
157
|
+
* @returns {number}
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
longestArgumentTermLength(cmd, helper) {
|
|
161
|
+
return helper.visibleArguments(cmd).reduce((max, argument) => {
|
|
162
|
+
return Math.max(max, argument.term.length);
|
|
163
|
+
}, 0);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Get the command usage to be displayed at the top of the built-in help.
|
|
168
|
+
*
|
|
169
|
+
* @param {Command} cmd
|
|
170
|
+
* @returns {string}
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
commandUsage(cmd) {
|
|
174
|
+
// Usage
|
|
175
|
+
let cmdName = cmd._name;
|
|
176
|
+
if (cmd._aliases[0]) {
|
|
177
|
+
cmdName = cmdName + '|' + cmd._aliases[0];
|
|
178
|
+
}
|
|
179
|
+
let parentCmdNames = '';
|
|
180
|
+
for (let parentCmd = cmd.parent; parentCmd; parentCmd = parentCmd.parent) {
|
|
181
|
+
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
|
|
182
|
+
}
|
|
183
|
+
return parentCmdNames + cmdName + ' ' + cmd.usage();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get the description for the command.
|
|
188
|
+
*
|
|
189
|
+
* @param {Command} cmd
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
commandDescription(cmd) {
|
|
194
|
+
// @ts-ignore: overloaded return type
|
|
195
|
+
return cmd.description();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get the command description to show in the list of subcommands.
|
|
200
|
+
*
|
|
201
|
+
* @param {Command} cmd
|
|
202
|
+
* @returns {string}
|
|
203
|
+
*/
|
|
204
|
+
|
|
205
|
+
subcommandDescription(cmd) {
|
|
206
|
+
// @ts-ignore: overloaded return type
|
|
207
|
+
return cmd.description();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get the option description to show in the list of options.
|
|
212
|
+
*
|
|
213
|
+
* @param {Option} option
|
|
214
|
+
* @return {string}
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
optionDescription(option) {
|
|
218
|
+
if (option.negate) {
|
|
219
|
+
return option.description;
|
|
220
|
+
}
|
|
221
|
+
const extraInfo = [];
|
|
222
|
+
if (option.argChoices) {
|
|
223
|
+
extraInfo.push(
|
|
224
|
+
// use stringify to match the display of the default value
|
|
225
|
+
`choices: ${option.argChoices.map((choice) => JSON.stringify(choice)).join(', ')}`);
|
|
226
|
+
}
|
|
227
|
+
if (option.defaultValue !== undefined) {
|
|
228
|
+
extraInfo.push(`default: ${option.defaultValueDescription || JSON.stringify(option.defaultValue)}`);
|
|
229
|
+
}
|
|
230
|
+
if (extraInfo.length > 0) {
|
|
231
|
+
return `${option.description} (${extraInfo.join(', ')})`;
|
|
232
|
+
}
|
|
233
|
+
return option.description;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Generate the built-in help text.
|
|
238
|
+
*
|
|
239
|
+
* @param {Command} cmd
|
|
240
|
+
* @param {Help} helper
|
|
241
|
+
* @returns {string}
|
|
242
|
+
*/
|
|
243
|
+
|
|
244
|
+
formatHelp(cmd, helper) {
|
|
245
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
246
|
+
const helpWidth = helper.helpWidth || 80;
|
|
247
|
+
const itemIndentWidth = 2;
|
|
248
|
+
const itemSeparatorWidth = 2; // between term and description
|
|
249
|
+
function formatItem(term, description) {
|
|
250
|
+
if (description) {
|
|
251
|
+
const fullText = `${term.padEnd(termWidth + itemSeparatorWidth)}${description}`;
|
|
252
|
+
return helper.wrap(fullText, helpWidth - itemIndentWidth, termWidth + itemSeparatorWidth);
|
|
253
|
+
}
|
|
254
|
+
return term;
|
|
255
|
+
};
|
|
256
|
+
function formatList(textArray) {
|
|
257
|
+
return textArray.join('\n').replace(/^/gm, ' '.repeat(itemIndentWidth));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Usage
|
|
261
|
+
let output = [`Usage: ${helper.commandUsage(cmd)}`, ''];
|
|
262
|
+
|
|
263
|
+
// Description
|
|
264
|
+
const commandDescription = helper.commandDescription(cmd);
|
|
265
|
+
if (commandDescription.length > 0) {
|
|
266
|
+
output = output.concat([commandDescription, '']);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Arguments
|
|
270
|
+
const argumentList = helper.visibleArguments(cmd).map((argument) => {
|
|
271
|
+
return formatItem(argument.term, argument.description);
|
|
272
|
+
});
|
|
273
|
+
if (argumentList.length > 0) {
|
|
274
|
+
output = output.concat(['Arguments:', formatList(argumentList), '']);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Options
|
|
278
|
+
const optionList = helper.visibleOptions(cmd).map((option) => {
|
|
279
|
+
return formatItem(helper.optionTerm(option), helper.optionDescription(option));
|
|
280
|
+
});
|
|
281
|
+
if (optionList.length > 0) {
|
|
282
|
+
output = output.concat(['Options:', formatList(optionList), '']);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Commands
|
|
286
|
+
const commandList = helper.visibleCommands(cmd).map((cmd) => {
|
|
287
|
+
return formatItem(helper.subcommandTerm(cmd), helper.subcommandDescription(cmd));
|
|
288
|
+
});
|
|
289
|
+
if (commandList.length > 0) {
|
|
290
|
+
output = output.concat(['Commands:', formatList(commandList), '']);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return output.join('\n');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Calculate the pad width from the maximum term length.
|
|
298
|
+
*
|
|
299
|
+
* @param {Command} cmd
|
|
300
|
+
* @param {Help} helper
|
|
301
|
+
* @returns {number}
|
|
302
|
+
*/
|
|
303
|
+
|
|
304
|
+
padWidth(cmd, helper) {
|
|
305
|
+
return Math.max(
|
|
306
|
+
helper.longestOptionTermLength(cmd, helper),
|
|
307
|
+
helper.longestSubcommandTermLength(cmd, helper),
|
|
308
|
+
helper.longestArgumentTermLength(cmd, helper)
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Wrap the given string to width characters per line, with lines after the first indented.
|
|
314
|
+
* Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted.
|
|
315
|
+
*
|
|
316
|
+
* @param {string} str
|
|
317
|
+
* @param {number} width
|
|
318
|
+
* @param {number} indent
|
|
319
|
+
* @param {number} [minColumnWidth=40]
|
|
320
|
+
* @return {string}
|
|
321
|
+
*
|
|
322
|
+
*/
|
|
323
|
+
|
|
324
|
+
wrap(str, width, indent, minColumnWidth = 40) {
|
|
325
|
+
// Detect manually wrapped and indented strings by searching for line breaks
|
|
326
|
+
// followed by multiple spaces/tabs.
|
|
327
|
+
if (str.match(/[\n]\s+/)) return str;
|
|
328
|
+
// Do not wrap if not enough room for a wrapped column of text (as could end up with a word per line).
|
|
329
|
+
const columnWidth = width - indent;
|
|
330
|
+
if (columnWidth < minColumnWidth) return str;
|
|
331
|
+
|
|
332
|
+
const leadingStr = str.substr(0, indent);
|
|
333
|
+
const columnText = str.substr(indent);
|
|
334
|
+
|
|
335
|
+
const indentString = ' '.repeat(indent);
|
|
336
|
+
const regex = new RegExp('.{1,' + (columnWidth - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
|
|
337
|
+
const lines = columnText.match(regex) || [];
|
|
338
|
+
return leadingStr + lines.map((line, i) => {
|
|
339
|
+
if (line.slice(-1) === '\n') {
|
|
340
|
+
line = line.slice(0, line.length - 1);
|
|
341
|
+
}
|
|
342
|
+
return ((i > 0) ? indentString : '') + line.trimRight();
|
|
343
|
+
}).join('\n');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
12
347
|
class Option {
|
|
13
348
|
/**
|
|
14
349
|
* Initialize a new `Option` with the given `flags` and `description`.
|
|
15
350
|
*
|
|
16
351
|
* @param {string} flags
|
|
17
|
-
* @param {string} description
|
|
18
|
-
* @api public
|
|
352
|
+
* @param {string} [description]
|
|
19
353
|
*/
|
|
20
354
|
|
|
21
355
|
constructor(flags, description) {
|
|
22
356
|
this.flags = flags;
|
|
357
|
+
this.description = description || '';
|
|
358
|
+
|
|
23
359
|
this.required = flags.includes('<'); // A value must be supplied when the option is specified.
|
|
24
360
|
this.optional = flags.includes('['); // A value is optional when the option is specified.
|
|
25
361
|
// variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
|
|
@@ -32,15 +368,85 @@ class Option {
|
|
|
32
368
|
if (this.long) {
|
|
33
369
|
this.negate = this.long.startsWith('--no-');
|
|
34
370
|
}
|
|
35
|
-
this.description = description || '';
|
|
36
371
|
this.defaultValue = undefined;
|
|
372
|
+
this.defaultValueDescription = undefined;
|
|
373
|
+
this.parseArg = undefined;
|
|
374
|
+
this.hidden = false;
|
|
375
|
+
this.argChoices = undefined;
|
|
37
376
|
}
|
|
38
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Set the default value, and optionally supply the description to be displayed in the help.
|
|
380
|
+
*
|
|
381
|
+
* @param {any} value
|
|
382
|
+
* @param {string} [description]
|
|
383
|
+
* @return {Option}
|
|
384
|
+
*/
|
|
385
|
+
|
|
386
|
+
default(value, description) {
|
|
387
|
+
this.defaultValue = value;
|
|
388
|
+
this.defaultValueDescription = description;
|
|
389
|
+
return this;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Set the custom handler for processing CLI option arguments into option values.
|
|
394
|
+
*
|
|
395
|
+
* @param {Function} [fn]
|
|
396
|
+
* @return {Option}
|
|
397
|
+
*/
|
|
398
|
+
|
|
399
|
+
argParser(fn) {
|
|
400
|
+
this.parseArg = fn;
|
|
401
|
+
return this;
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Whether the option is mandatory and must have a value after parsing.
|
|
406
|
+
*
|
|
407
|
+
* @param {boolean} [mandatory=true]
|
|
408
|
+
* @return {Option}
|
|
409
|
+
*/
|
|
410
|
+
|
|
411
|
+
makeOptionMandatory(mandatory = true) {
|
|
412
|
+
this.mandatory = !!mandatory;
|
|
413
|
+
return this;
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Hide option in help.
|
|
418
|
+
*
|
|
419
|
+
* @param {boolean} [hide=true]
|
|
420
|
+
* @return {Option}
|
|
421
|
+
*/
|
|
422
|
+
|
|
423
|
+
hideHelp(hide = true) {
|
|
424
|
+
this.hidden = !!hide;
|
|
425
|
+
return this;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Only allow option value to be one of choices.
|
|
430
|
+
*
|
|
431
|
+
* @param {string[]} values
|
|
432
|
+
* @return {Option}
|
|
433
|
+
*/
|
|
434
|
+
|
|
435
|
+
choices(values) {
|
|
436
|
+
this.argChoices = values;
|
|
437
|
+
this.parseArg = (arg) => {
|
|
438
|
+
if (!values.includes(arg)) {
|
|
439
|
+
throw new InvalidOptionArgumentError(`Allowed choices are ${values.join(', ')}.`);
|
|
440
|
+
}
|
|
441
|
+
return arg;
|
|
442
|
+
};
|
|
443
|
+
return this;
|
|
444
|
+
};
|
|
445
|
+
|
|
39
446
|
/**
|
|
40
447
|
* Return option name.
|
|
41
448
|
*
|
|
42
449
|
* @return {string}
|
|
43
|
-
* @api private
|
|
44
450
|
*/
|
|
45
451
|
|
|
46
452
|
name() {
|
|
@@ -98,12 +504,29 @@ class CommanderError extends Error {
|
|
|
98
504
|
}
|
|
99
505
|
}
|
|
100
506
|
|
|
507
|
+
/**
|
|
508
|
+
* InvalidOptionArgumentError class
|
|
509
|
+
* @class
|
|
510
|
+
*/
|
|
511
|
+
class InvalidOptionArgumentError extends CommanderError {
|
|
512
|
+
/**
|
|
513
|
+
* Constructs the InvalidOptionArgumentError class
|
|
514
|
+
* @param {string} [message] explanation of why argument is invalid
|
|
515
|
+
* @constructor
|
|
516
|
+
*/
|
|
517
|
+
constructor(message) {
|
|
518
|
+
super(1, 'commander.invalidOptionArgument', message);
|
|
519
|
+
// properly capture stack trace in Node.js
|
|
520
|
+
Error.captureStackTrace(this, this.constructor);
|
|
521
|
+
this.name = this.constructor.name;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
101
525
|
class Command extends EventEmitter {
|
|
102
526
|
/**
|
|
103
527
|
* Initialize a new `Command`.
|
|
104
528
|
*
|
|
105
529
|
* @param {string} [name]
|
|
106
|
-
* @api public
|
|
107
530
|
*/
|
|
108
531
|
|
|
109
532
|
constructor(name) {
|
|
@@ -112,14 +535,13 @@ class Command extends EventEmitter {
|
|
|
112
535
|
this.options = [];
|
|
113
536
|
this.parent = null;
|
|
114
537
|
this._allowUnknownOption = false;
|
|
538
|
+
this._allowExcessArguments = true;
|
|
115
539
|
this._args = [];
|
|
116
540
|
this.rawArgs = null;
|
|
117
541
|
this._scriptPath = null;
|
|
118
542
|
this._name = name || '';
|
|
119
543
|
this._optionValues = {};
|
|
120
|
-
this._storeOptionsAsProperties =
|
|
121
|
-
this._storeOptionsAsPropertiesCalled = false;
|
|
122
|
-
this._passCommandToAction = true; // backwards compatible by default
|
|
544
|
+
this._storeOptionsAsProperties = false;
|
|
123
545
|
this._actionResults = [];
|
|
124
546
|
this._actionHandler = null;
|
|
125
547
|
this._executableHandler = false;
|
|
@@ -128,6 +550,19 @@ class Command extends EventEmitter {
|
|
|
128
550
|
this._exitCallback = null;
|
|
129
551
|
this._aliases = [];
|
|
130
552
|
this._combineFlagAndOptionalValue = true;
|
|
553
|
+
this._description = '';
|
|
554
|
+
this._argsDescription = undefined;
|
|
555
|
+
this._enablePositionalOptions = false;
|
|
556
|
+
this._passThroughOptions = false;
|
|
557
|
+
|
|
558
|
+
// see .configureOutput() for docs
|
|
559
|
+
this._outputConfiguration = {
|
|
560
|
+
writeOut: (str) => process.stdout.write(str),
|
|
561
|
+
writeErr: (str) => process.stderr.write(str),
|
|
562
|
+
getOutHelpWidth: () => process.stdout.isTTY ? process.stdout.columns : undefined,
|
|
563
|
+
getErrHelpWidth: () => process.stderr.isTTY ? process.stderr.columns : undefined,
|
|
564
|
+
outputError: (str, write) => write(str)
|
|
565
|
+
};
|
|
131
566
|
|
|
132
567
|
this._hidden = false;
|
|
133
568
|
this._hasHelpOption = true;
|
|
@@ -135,10 +570,11 @@ class Command extends EventEmitter {
|
|
|
135
570
|
this._helpDescription = 'display help for command';
|
|
136
571
|
this._helpShortFlag = '-h';
|
|
137
572
|
this._helpLongFlag = '--help';
|
|
138
|
-
this.
|
|
573
|
+
this._addImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false
|
|
139
574
|
this._helpCommandName = 'help';
|
|
140
575
|
this._helpCommandnameAndArgs = 'help [command]';
|
|
141
576
|
this._helpCommandDescription = 'display help for command';
|
|
577
|
+
this._helpConfiguration = {};
|
|
142
578
|
}
|
|
143
579
|
|
|
144
580
|
/**
|
|
@@ -165,7 +601,6 @@ class Command extends EventEmitter {
|
|
|
165
601
|
* @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
|
|
166
602
|
* @param {Object} [execOpts] - configuration options (for executable)
|
|
167
603
|
* @return {Command} returns new command for action handler, or `this` for executable command
|
|
168
|
-
* @api public
|
|
169
604
|
*/
|
|
170
605
|
|
|
171
606
|
command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
|
|
@@ -185,7 +620,9 @@ class Command extends EventEmitter {
|
|
|
185
620
|
}
|
|
186
621
|
if (opts.isDefault) this._defaultCommandName = cmd._name;
|
|
187
622
|
|
|
188
|
-
cmd.
|
|
623
|
+
cmd._outputConfiguration = this._outputConfiguration;
|
|
624
|
+
|
|
625
|
+
cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden
|
|
189
626
|
cmd._hasHelpOption = this._hasHelpOption;
|
|
190
627
|
cmd._helpFlags = this._helpFlags;
|
|
191
628
|
cmd._helpDescription = this._helpDescription;
|
|
@@ -194,10 +631,12 @@ class Command extends EventEmitter {
|
|
|
194
631
|
cmd._helpCommandName = this._helpCommandName;
|
|
195
632
|
cmd._helpCommandnameAndArgs = this._helpCommandnameAndArgs;
|
|
196
633
|
cmd._helpCommandDescription = this._helpCommandDescription;
|
|
634
|
+
cmd._helpConfiguration = this._helpConfiguration;
|
|
197
635
|
cmd._exitCallback = this._exitCallback;
|
|
198
636
|
cmd._storeOptionsAsProperties = this._storeOptionsAsProperties;
|
|
199
|
-
cmd._passCommandToAction = this._passCommandToAction;
|
|
200
637
|
cmd._combineFlagAndOptionalValue = this._combineFlagAndOptionalValue;
|
|
638
|
+
cmd._allowExcessArguments = this._allowExcessArguments;
|
|
639
|
+
cmd._enablePositionalOptions = this._enablePositionalOptions;
|
|
201
640
|
|
|
202
641
|
cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
|
|
203
642
|
this.commands.push(cmd);
|
|
@@ -216,13 +655,64 @@ class Command extends EventEmitter {
|
|
|
216
655
|
*
|
|
217
656
|
* @param {string} [name]
|
|
218
657
|
* @return {Command} new command
|
|
219
|
-
* @api public
|
|
220
658
|
*/
|
|
221
659
|
|
|
222
660
|
createCommand(name) {
|
|
223
661
|
return new Command(name);
|
|
224
662
|
};
|
|
225
663
|
|
|
664
|
+
/**
|
|
665
|
+
* You can customise the help with a subclass of Help by overriding createHelp,
|
|
666
|
+
* or by overriding Help properties using configureHelp().
|
|
667
|
+
*
|
|
668
|
+
* @return {Help}
|
|
669
|
+
*/
|
|
670
|
+
|
|
671
|
+
createHelp() {
|
|
672
|
+
return Object.assign(new Help(), this.configureHelp());
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* You can customise the help by overriding Help properties using configureHelp(),
|
|
677
|
+
* or with a subclass of Help by overriding createHelp().
|
|
678
|
+
*
|
|
679
|
+
* @param {Object} [configuration] - configuration options
|
|
680
|
+
* @return {Command|Object} `this` command for chaining, or stored configuration
|
|
681
|
+
*/
|
|
682
|
+
|
|
683
|
+
configureHelp(configuration) {
|
|
684
|
+
if (configuration === undefined) return this._helpConfiguration;
|
|
685
|
+
|
|
686
|
+
this._helpConfiguration = configuration;
|
|
687
|
+
return this;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* The default output goes to stdout and stderr. You can customise this for special
|
|
692
|
+
* applications. You can also customise the display of errors by overriding outputError.
|
|
693
|
+
*
|
|
694
|
+
* The configuration properties are all functions:
|
|
695
|
+
*
|
|
696
|
+
* // functions to change where being written, stdout and stderr
|
|
697
|
+
* writeOut(str)
|
|
698
|
+
* writeErr(str)
|
|
699
|
+
* // matching functions to specify width for wrapping help
|
|
700
|
+
* getOutHelpWidth()
|
|
701
|
+
* getErrHelpWidth()
|
|
702
|
+
* // functions based on what is being written out
|
|
703
|
+
* outputError(str, write) // used for displaying errors, and not used for displaying help
|
|
704
|
+
*
|
|
705
|
+
* @param {Object} [configuration] - configuration options
|
|
706
|
+
* @return {Command|Object} `this` command for chaining, or stored configuration
|
|
707
|
+
*/
|
|
708
|
+
|
|
709
|
+
configureOutput(configuration) {
|
|
710
|
+
if (configuration === undefined) return this._outputConfiguration;
|
|
711
|
+
|
|
712
|
+
Object.assign(this._outputConfiguration, configuration);
|
|
713
|
+
return this;
|
|
714
|
+
}
|
|
715
|
+
|
|
226
716
|
/**
|
|
227
717
|
* Add a prepared subcommand.
|
|
228
718
|
*
|
|
@@ -231,7 +721,6 @@ class Command extends EventEmitter {
|
|
|
231
721
|
* @param {Command} cmd - new subcommand
|
|
232
722
|
* @param {Object} [opts] - configuration options
|
|
233
723
|
* @return {Command} `this` command for chaining
|
|
234
|
-
* @api public
|
|
235
724
|
*/
|
|
236
725
|
|
|
237
726
|
addCommand(cmd, opts) {
|
|
@@ -260,8 +749,6 @@ class Command extends EventEmitter {
|
|
|
260
749
|
|
|
261
750
|
/**
|
|
262
751
|
* Define argument syntax for the command.
|
|
263
|
-
*
|
|
264
|
-
* @api public
|
|
265
752
|
*/
|
|
266
753
|
|
|
267
754
|
arguments(desc) {
|
|
@@ -276,14 +763,13 @@ class Command extends EventEmitter {
|
|
|
276
763
|
* addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
|
|
277
764
|
*
|
|
278
765
|
* @return {Command} `this` command for chaining
|
|
279
|
-
* @api public
|
|
280
766
|
*/
|
|
281
767
|
|
|
282
768
|
addHelpCommand(enableOrNameAndArgs, description) {
|
|
283
769
|
if (enableOrNameAndArgs === false) {
|
|
284
|
-
this.
|
|
770
|
+
this._addImplicitHelpCommand = false;
|
|
285
771
|
} else {
|
|
286
|
-
this.
|
|
772
|
+
this._addImplicitHelpCommand = true;
|
|
287
773
|
if (typeof enableOrNameAndArgs === 'string') {
|
|
288
774
|
this._helpCommandName = enableOrNameAndArgs.split(' ')[0];
|
|
289
775
|
this._helpCommandnameAndArgs = enableOrNameAndArgs;
|
|
@@ -298,11 +784,11 @@ class Command extends EventEmitter {
|
|
|
298
784
|
* @api private
|
|
299
785
|
*/
|
|
300
786
|
|
|
301
|
-
|
|
302
|
-
if (this.
|
|
303
|
-
|
|
787
|
+
_hasImplicitHelpCommand() {
|
|
788
|
+
if (this._addImplicitHelpCommand === undefined) {
|
|
789
|
+
return this.commands.length && !this._actionHandler && !this._findCommand('help');
|
|
304
790
|
}
|
|
305
|
-
return this.
|
|
791
|
+
return this._addImplicitHelpCommand;
|
|
306
792
|
};
|
|
307
793
|
|
|
308
794
|
/**
|
|
@@ -355,7 +841,6 @@ class Command extends EventEmitter {
|
|
|
355
841
|
*
|
|
356
842
|
* @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
|
|
357
843
|
* @return {Command} `this` command for chaining
|
|
358
|
-
* @api public
|
|
359
844
|
*/
|
|
360
845
|
|
|
361
846
|
exitOverride(fn) {
|
|
@@ -405,7 +890,6 @@ class Command extends EventEmitter {
|
|
|
405
890
|
*
|
|
406
891
|
* @param {Function} fn
|
|
407
892
|
* @return {Command} `this` command for chaining
|
|
408
|
-
* @api public
|
|
409
893
|
*/
|
|
410
894
|
|
|
411
895
|
action(fn) {
|
|
@@ -413,15 +897,12 @@ class Command extends EventEmitter {
|
|
|
413
897
|
// The .action callback takes an extra parameter which is the command or options.
|
|
414
898
|
const expectedArgsCount = this._args.length;
|
|
415
899
|
const actionArgs = args.slice(0, expectedArgsCount);
|
|
416
|
-
if (this.
|
|
417
|
-
actionArgs[expectedArgsCount] = this;
|
|
900
|
+
if (this._storeOptionsAsProperties) {
|
|
901
|
+
actionArgs[expectedArgsCount] = this; // backwards compatible "options"
|
|
418
902
|
} else {
|
|
419
903
|
actionArgs[expectedArgsCount] = this.opts();
|
|
420
904
|
}
|
|
421
|
-
|
|
422
|
-
if (args.length > expectedArgsCount) {
|
|
423
|
-
actionArgs.push(args.slice(expectedArgsCount));
|
|
424
|
-
}
|
|
905
|
+
actionArgs.push(this);
|
|
425
906
|
|
|
426
907
|
const actionResult = fn.apply(this, actionArgs);
|
|
427
908
|
// Remember result in case it is async. Assume parseAsync getting called on root.
|
|
@@ -436,83 +917,31 @@ class Command extends EventEmitter {
|
|
|
436
917
|
};
|
|
437
918
|
|
|
438
919
|
/**
|
|
439
|
-
*
|
|
920
|
+
* Factory routine to create a new unattached option.
|
|
440
921
|
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
922
|
+
* See .option() for creating an attached option, which uses this routine to
|
|
923
|
+
* create the option. You can override createOption to return a custom option.
|
|
924
|
+
*
|
|
925
|
+
* @param {string} flags
|
|
926
|
+
* @param {string} [description]
|
|
927
|
+
* @return {Option} new option
|
|
443
928
|
*/
|
|
444
929
|
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
}
|
|
930
|
+
createOption(flags, description) {
|
|
931
|
+
return new Option(flags, description);
|
|
479
932
|
};
|
|
480
933
|
|
|
481
934
|
/**
|
|
482
|
-
*
|
|
935
|
+
* Add an option.
|
|
483
936
|
*
|
|
484
|
-
* @param {
|
|
485
|
-
* @param {string} flags
|
|
486
|
-
* @param {string} description
|
|
487
|
-
* @param {Function|*} [fn] - custom option processing function or default value
|
|
488
|
-
* @param {*} [defaultValue]
|
|
937
|
+
* @param {Option} option
|
|
489
938
|
* @return {Command} `this` command for chaining
|
|
490
|
-
* @api private
|
|
491
939
|
*/
|
|
492
|
-
|
|
493
|
-
_optionEx(config, flags, description, fn, defaultValue) {
|
|
494
|
-
const option = new Option(flags, description);
|
|
940
|
+
addOption(option) {
|
|
495
941
|
const oname = option.name();
|
|
496
942
|
const name = option.attributeName();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
this._checkForOptionNameClash(option);
|
|
500
|
-
|
|
501
|
-
// default as 3rd arg
|
|
502
|
-
if (typeof fn !== 'function') {
|
|
503
|
-
if (fn instanceof RegExp) {
|
|
504
|
-
// This is a bit simplistic (especially no error messages), and probably better handled by caller using custom option processing.
|
|
505
|
-
// No longer documented in README, but still present for backwards compatibility.
|
|
506
|
-
const regex = fn;
|
|
507
|
-
fn = (val, def) => {
|
|
508
|
-
const m = regex.exec(val);
|
|
509
|
-
return m ? m[0] : def;
|
|
510
|
-
};
|
|
511
|
-
} else {
|
|
512
|
-
defaultValue = fn;
|
|
513
|
-
fn = null;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
943
|
+
|
|
944
|
+
let defaultValue = option.defaultValue;
|
|
516
945
|
|
|
517
946
|
// preassign default value for --no-*, [optional], <required>, or plain flag if boolean value
|
|
518
947
|
if (option.negate || option.optional || option.required || typeof defaultValue === 'boolean') {
|
|
@@ -524,7 +953,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
524
953
|
// preassign only if we have a default
|
|
525
954
|
if (defaultValue !== undefined) {
|
|
526
955
|
this._setOptionValue(name, defaultValue);
|
|
527
|
-
option.defaultValue = defaultValue;
|
|
528
956
|
}
|
|
529
957
|
}
|
|
530
958
|
|
|
@@ -537,8 +965,16 @@ Read more on https://git.io/JJc0W`);
|
|
|
537
965
|
const oldValue = this._getOptionValue(name);
|
|
538
966
|
|
|
539
967
|
// custom processing
|
|
540
|
-
if (val !== null &&
|
|
541
|
-
|
|
968
|
+
if (val !== null && option.parseArg) {
|
|
969
|
+
try {
|
|
970
|
+
val = option.parseArg(val, oldValue === undefined ? defaultValue : oldValue);
|
|
971
|
+
} catch (err) {
|
|
972
|
+
if (err.code === 'commander.invalidOptionArgument') {
|
|
973
|
+
const message = `error: option '${option.flags}' argument '${val}' is invalid. ${err.message}`;
|
|
974
|
+
this._displayError(err.exitCode, err.code, message);
|
|
975
|
+
}
|
|
976
|
+
throw err;
|
|
977
|
+
}
|
|
542
978
|
} else if (val !== null && option.variadic) {
|
|
543
979
|
if (oldValue === defaultValue || !Array.isArray(oldValue)) {
|
|
544
980
|
val = [val];
|
|
@@ -564,13 +1000,38 @@ Read more on https://git.io/JJc0W`);
|
|
|
564
1000
|
});
|
|
565
1001
|
|
|
566
1002
|
return this;
|
|
567
|
-
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Internal implementation shared by .option() and .requiredOption()
|
|
1007
|
+
*
|
|
1008
|
+
* @api private
|
|
1009
|
+
*/
|
|
1010
|
+
_optionEx(config, flags, description, fn, defaultValue) {
|
|
1011
|
+
const option = this.createOption(flags, description);
|
|
1012
|
+
option.makeOptionMandatory(!!config.mandatory);
|
|
1013
|
+
if (typeof fn === 'function') {
|
|
1014
|
+
option.default(defaultValue).argParser(fn);
|
|
1015
|
+
} else if (fn instanceof RegExp) {
|
|
1016
|
+
// deprecated
|
|
1017
|
+
const regex = fn;
|
|
1018
|
+
fn = (val, def) => {
|
|
1019
|
+
const m = regex.exec(val);
|
|
1020
|
+
return m ? m[0] : def;
|
|
1021
|
+
};
|
|
1022
|
+
option.default(defaultValue).argParser(fn);
|
|
1023
|
+
} else {
|
|
1024
|
+
option.default(fn);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
return this.addOption(option);
|
|
1028
|
+
}
|
|
568
1029
|
|
|
569
1030
|
/**
|
|
570
1031
|
* Define option with `flags`, `description` and optional
|
|
571
1032
|
* coercion `fn`.
|
|
572
1033
|
*
|
|
573
|
-
* The `flags` string
|
|
1034
|
+
* The `flags` string contains the short and/or long flags,
|
|
574
1035
|
* separated by comma, a pipe or space. The following are all valid
|
|
575
1036
|
* all will output this way when `--help` is used.
|
|
576
1037
|
*
|
|
@@ -611,11 +1072,10 @@ Read more on https://git.io/JJc0W`);
|
|
|
611
1072
|
* program.option('-c, --cheese [type]', 'add cheese [marble]');
|
|
612
1073
|
*
|
|
613
1074
|
* @param {string} flags
|
|
614
|
-
* @param {string} description
|
|
1075
|
+
* @param {string} [description]
|
|
615
1076
|
* @param {Function|*} [fn] - custom option processing function or default value
|
|
616
1077
|
* @param {*} [defaultValue]
|
|
617
1078
|
* @return {Command} `this` command for chaining
|
|
618
|
-
* @api public
|
|
619
1079
|
*/
|
|
620
1080
|
|
|
621
1081
|
option(flags, description, fn, defaultValue) {
|
|
@@ -626,14 +1086,13 @@ Read more on https://git.io/JJc0W`);
|
|
|
626
1086
|
* Add a required option which must have a value after parsing. This usually means
|
|
627
1087
|
* the option must be specified on the command line. (Otherwise the same as .option().)
|
|
628
1088
|
*
|
|
629
|
-
* The `flags` string
|
|
1089
|
+
* The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
|
|
630
1090
|
*
|
|
631
1091
|
* @param {string} flags
|
|
632
|
-
* @param {string} description
|
|
1092
|
+
* @param {string} [description]
|
|
633
1093
|
* @param {Function|*} [fn] - custom option processing function or default value
|
|
634
1094
|
* @param {*} [defaultValue]
|
|
635
1095
|
* @return {Command} `this` command for chaining
|
|
636
|
-
* @api public
|
|
637
1096
|
*/
|
|
638
1097
|
|
|
639
1098
|
requiredOption(flags, description, fn, defaultValue) {
|
|
@@ -649,55 +1108,77 @@ Read more on https://git.io/JJc0W`);
|
|
|
649
1108
|
* .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour
|
|
650
1109
|
* .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
|
|
651
1110
|
*
|
|
652
|
-
* @param {Boolean} [
|
|
653
|
-
* @api public
|
|
1111
|
+
* @param {Boolean} [combine=true] - if `true` or omitted, an optional value can be specified directly after the flag.
|
|
654
1112
|
*/
|
|
655
|
-
combineFlagAndOptionalValue(
|
|
656
|
-
this._combineFlagAndOptionalValue =
|
|
1113
|
+
combineFlagAndOptionalValue(combine = true) {
|
|
1114
|
+
this._combineFlagAndOptionalValue = !!combine;
|
|
657
1115
|
return this;
|
|
658
1116
|
};
|
|
659
1117
|
|
|
660
1118
|
/**
|
|
661
1119
|
* Allow unknown options on the command line.
|
|
662
1120
|
*
|
|
663
|
-
* @param {Boolean} [
|
|
1121
|
+
* @param {Boolean} [allowUnknown=true] - if `true` or omitted, no error will be thrown
|
|
664
1122
|
* for unknown options.
|
|
665
|
-
* @api public
|
|
666
1123
|
*/
|
|
667
|
-
allowUnknownOption(
|
|
668
|
-
this._allowUnknownOption =
|
|
1124
|
+
allowUnknownOption(allowUnknown = true) {
|
|
1125
|
+
this._allowUnknownOption = !!allowUnknown;
|
|
669
1126
|
return this;
|
|
670
1127
|
};
|
|
671
1128
|
|
|
672
1129
|
/**
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1130
|
+
* Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
|
|
1131
|
+
*
|
|
1132
|
+
* @param {Boolean} [allowExcess=true] - if `true` or omitted, no error will be thrown
|
|
1133
|
+
* for excess arguments.
|
|
1134
|
+
*/
|
|
1135
|
+
allowExcessArguments(allowExcess = true) {
|
|
1136
|
+
this._allowExcessArguments = !!allowExcess;
|
|
1137
|
+
return this;
|
|
1138
|
+
};
|
|
680
1139
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1140
|
+
/**
|
|
1141
|
+
* Enable positional options. Positional means global options are specified before subcommands which lets
|
|
1142
|
+
* subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
|
|
1143
|
+
* The default behaviour is non-positional and global options may appear anywhere on the command line.
|
|
1144
|
+
*
|
|
1145
|
+
* @param {Boolean} [positional=true]
|
|
1146
|
+
*/
|
|
1147
|
+
enablePositionalOptions(positional = true) {
|
|
1148
|
+
this._enablePositionalOptions = !!positional;
|
|
1149
|
+
return this;
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
/**
|
|
1153
|
+
* Pass through options that come after command-arguments rather than treat them as command-options,
|
|
1154
|
+
* so actual command-options come before command-arguments. Turning this on for a subcommand requires
|
|
1155
|
+
* positional options to have been enabled on the program (parent commands).
|
|
1156
|
+
* The default behaviour is non-positional and options may appear before or after command-arguments.
|
|
1157
|
+
*
|
|
1158
|
+
* @param {Boolean} [passThrough=true]
|
|
1159
|
+
* for unknown options.
|
|
1160
|
+
*/
|
|
1161
|
+
passThroughOptions(passThrough = true) {
|
|
1162
|
+
this._passThroughOptions = !!passThrough;
|
|
1163
|
+
if (!!this.parent && passThrough && !this.parent._enablePositionalOptions) {
|
|
1164
|
+
throw new Error('passThroughOptions can not be used without turning on enablePositionOptions for parent command(s)');
|
|
686
1165
|
}
|
|
687
1166
|
return this;
|
|
688
1167
|
};
|
|
689
1168
|
|
|
690
1169
|
/**
|
|
691
|
-
* Whether to
|
|
692
|
-
* or
|
|
1170
|
+
* Whether to store option values as properties on command object,
|
|
1171
|
+
* or store separately (specify false). In both cases the option values can be accessed using .opts().
|
|
693
1172
|
*
|
|
694
|
-
* @param {boolean}
|
|
1173
|
+
* @param {boolean} [storeAsProperties=true]
|
|
695
1174
|
* @return {Command} `this` command for chaining
|
|
696
|
-
* @api public
|
|
697
1175
|
*/
|
|
698
1176
|
|
|
699
|
-
|
|
700
|
-
this.
|
|
1177
|
+
storeOptionsAsProperties(storeAsProperties = true) {
|
|
1178
|
+
this._storeOptionsAsProperties = !!storeAsProperties;
|
|
1179
|
+
if (this.options.length) {
|
|
1180
|
+
throw new Error('call .storeOptionsAsProperties() before adding options');
|
|
1181
|
+
}
|
|
701
1182
|
return this;
|
|
702
1183
|
};
|
|
703
1184
|
|
|
@@ -748,7 +1229,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
748
1229
|
* @param {Object} [parseOptions] - optionally specify style of options with from: node/user/electron
|
|
749
1230
|
* @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
|
|
750
1231
|
* @return {Command} `this` command for chaining
|
|
751
|
-
* @api public
|
|
752
1232
|
*/
|
|
753
1233
|
|
|
754
1234
|
parse(argv, parseOptions) {
|
|
@@ -760,7 +1240,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
760
1240
|
// Default to using process.argv
|
|
761
1241
|
if (argv === undefined) {
|
|
762
1242
|
argv = process.argv;
|
|
763
|
-
// @ts-ignore
|
|
1243
|
+
// @ts-ignore: unknown property
|
|
764
1244
|
if (process.versions && process.versions.electron) {
|
|
765
1245
|
parseOptions.from = 'electron';
|
|
766
1246
|
}
|
|
@@ -776,7 +1256,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
776
1256
|
userArgs = argv.slice(2);
|
|
777
1257
|
break;
|
|
778
1258
|
case 'electron':
|
|
779
|
-
// @ts-ignore
|
|
1259
|
+
// @ts-ignore: unknown property
|
|
780
1260
|
if (process.defaultApp) {
|
|
781
1261
|
this._scriptPath = argv[1];
|
|
782
1262
|
userArgs = argv.slice(2);
|
|
@@ -790,7 +1270,9 @@ Read more on https://git.io/JJc0W`);
|
|
|
790
1270
|
default:
|
|
791
1271
|
throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
|
|
792
1272
|
}
|
|
1273
|
+
// @ts-ignore: unknown property
|
|
793
1274
|
if (!this._scriptPath && process.mainModule) {
|
|
1275
|
+
// @ts-ignore: unknown property
|
|
794
1276
|
this._scriptPath = process.mainModule.filename;
|
|
795
1277
|
}
|
|
796
1278
|
|
|
@@ -821,7 +1303,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
821
1303
|
* @param {Object} [parseOptions]
|
|
822
1304
|
* @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
|
|
823
1305
|
* @return {Promise}
|
|
824
|
-
* @api public
|
|
825
1306
|
*/
|
|
826
1307
|
|
|
827
1308
|
parseAsync(argv, parseOptions) {
|
|
@@ -846,7 +1327,9 @@ Read more on https://git.io/JJc0W`);
|
|
|
846
1327
|
// Want the entry script as the reference for command name and directory for searching for other files.
|
|
847
1328
|
let scriptPath = this._scriptPath;
|
|
848
1329
|
// Fallback in case not set, due to how Command created or called.
|
|
1330
|
+
// @ts-ignore: unknown property
|
|
849
1331
|
if (!scriptPath && process.mainModule) {
|
|
1332
|
+
// @ts-ignore: unknown property
|
|
850
1333
|
scriptPath = process.mainModule.filename;
|
|
851
1334
|
}
|
|
852
1335
|
|
|
@@ -885,15 +1368,15 @@ Read more on https://git.io/JJc0W`);
|
|
|
885
1368
|
// add executable arguments to spawn
|
|
886
1369
|
args = incrementNodeInspectorPort(process.execArgv).concat(args);
|
|
887
1370
|
|
|
888
|
-
proc = spawn(process.argv[0], args, { stdio: 'inherit' });
|
|
1371
|
+
proc = childProcess.spawn(process.argv[0], args, { stdio: 'inherit' });
|
|
889
1372
|
} else {
|
|
890
|
-
proc = spawn(bin, args, { stdio: 'inherit' });
|
|
1373
|
+
proc = childProcess.spawn(bin, args, { stdio: 'inherit' });
|
|
891
1374
|
}
|
|
892
1375
|
} else {
|
|
893
1376
|
args.unshift(bin);
|
|
894
1377
|
// add executable arguments to spawn
|
|
895
1378
|
args = incrementNodeInspectorPort(process.execArgv).concat(args);
|
|
896
|
-
proc = spawn(process.execPath, args, { stdio: 'inherit' });
|
|
1379
|
+
proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });
|
|
897
1380
|
}
|
|
898
1381
|
|
|
899
1382
|
const signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
|
|
@@ -945,7 +1428,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
945
1428
|
*/
|
|
946
1429
|
_dispatchSubcommand(commandName, operands, unknown) {
|
|
947
1430
|
const subCommand = this._findCommand(commandName);
|
|
948
|
-
if (!subCommand) this.
|
|
1431
|
+
if (!subCommand) this.help({ error: true });
|
|
949
1432
|
|
|
950
1433
|
if (subCommand._executableHandler) {
|
|
951
1434
|
this._executeSubCommand(subCommand, operands.concat(unknown));
|
|
@@ -968,7 +1451,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
968
1451
|
|
|
969
1452
|
if (operands && this._findCommand(operands[0])) {
|
|
970
1453
|
this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
|
|
971
|
-
} else if (this.
|
|
1454
|
+
} else if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
|
|
972
1455
|
if (operands.length === 1) {
|
|
973
1456
|
this.help();
|
|
974
1457
|
} else {
|
|
@@ -980,7 +1463,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
980
1463
|
} else {
|
|
981
1464
|
if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
|
|
982
1465
|
// probably missing subcommand and no handler, user needs help
|
|
983
|
-
this.
|
|
1466
|
+
this.help({ error: true });
|
|
984
1467
|
}
|
|
985
1468
|
|
|
986
1469
|
outputHelpIfRequested(this, parsed.unknown);
|
|
@@ -989,20 +1472,28 @@ Read more on https://git.io/JJc0W`);
|
|
|
989
1472
|
this.unknownOption(parsed.unknown[0]);
|
|
990
1473
|
}
|
|
991
1474
|
|
|
1475
|
+
const commandEvent = `command:${this.name()}`;
|
|
992
1476
|
if (this._actionHandler) {
|
|
1477
|
+
// Check expected arguments and collect variadic together.
|
|
993
1478
|
const args = this.args.slice();
|
|
994
1479
|
this._args.forEach((arg, i) => {
|
|
995
1480
|
if (arg.required && args[i] == null) {
|
|
996
1481
|
this.missingArgument(arg.name);
|
|
997
1482
|
} else if (arg.variadic) {
|
|
998
1483
|
args[i] = args.splice(i);
|
|
1484
|
+
args.length = Math.min(i + 1, args.length);
|
|
999
1485
|
}
|
|
1000
1486
|
});
|
|
1487
|
+
if (args.length > this._args.length) {
|
|
1488
|
+
this._excessArguments(args);
|
|
1489
|
+
}
|
|
1001
1490
|
|
|
1002
1491
|
this._actionHandler(args);
|
|
1003
|
-
this.
|
|
1492
|
+
if (this.parent) this.parent.emit(commandEvent, operands, unknown); // legacy
|
|
1493
|
+
} else if (this.parent && this.parent.listenerCount(commandEvent)) {
|
|
1494
|
+
this.parent.emit(commandEvent, operands, unknown); // legacy
|
|
1004
1495
|
} else if (operands.length) {
|
|
1005
|
-
if (this._findCommand('*')) {
|
|
1496
|
+
if (this._findCommand('*')) { // legacy
|
|
1006
1497
|
this._dispatchSubcommand('*', operands, unknown);
|
|
1007
1498
|
} else if (this.listenerCount('command:*')) {
|
|
1008
1499
|
this.emit('command:*', operands, unknown);
|
|
@@ -1011,7 +1502,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
1011
1502
|
}
|
|
1012
1503
|
} else if (this.commands.length) {
|
|
1013
1504
|
// This command has subcommands and nothing hooked up at this level, so display help.
|
|
1014
|
-
this.
|
|
1505
|
+
this.help({ error: true });
|
|
1015
1506
|
} else {
|
|
1016
1507
|
// fall through for caller to handle after calling .parse()
|
|
1017
1508
|
}
|
|
@@ -1072,7 +1563,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
1072
1563
|
*
|
|
1073
1564
|
* @param {String[]} argv
|
|
1074
1565
|
* @return {{operands: String[], unknown: String[]}}
|
|
1075
|
-
* @api public
|
|
1076
1566
|
*/
|
|
1077
1567
|
|
|
1078
1568
|
parseOptions(argv) {
|
|
@@ -1152,11 +1642,38 @@ Read more on https://git.io/JJc0W`);
|
|
|
1152
1642
|
}
|
|
1153
1643
|
}
|
|
1154
1644
|
|
|
1155
|
-
//
|
|
1156
|
-
|
|
1645
|
+
// Not a recognised option by this command.
|
|
1646
|
+
// Might be a command-argument, or subcommand option, or unknown option, or help command or option.
|
|
1647
|
+
|
|
1648
|
+
// An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
|
|
1649
|
+
if (maybeOption(arg)) {
|
|
1157
1650
|
dest = unknown;
|
|
1158
1651
|
}
|
|
1159
1652
|
|
|
1653
|
+
// If using positionalOptions, stop processing our options at subcommand.
|
|
1654
|
+
if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
|
|
1655
|
+
if (this._findCommand(arg)) {
|
|
1656
|
+
operands.push(arg);
|
|
1657
|
+
if (args.length > 0) unknown.push(...args);
|
|
1658
|
+
break;
|
|
1659
|
+
} else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
|
|
1660
|
+
operands.push(arg);
|
|
1661
|
+
if (args.length > 0) operands.push(...args);
|
|
1662
|
+
break;
|
|
1663
|
+
} else if (this._defaultCommandName) {
|
|
1664
|
+
unknown.push(arg);
|
|
1665
|
+
if (args.length > 0) unknown.push(...args);
|
|
1666
|
+
break;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// If using passThroughOptions, stop processing options at first command-argument.
|
|
1671
|
+
if (this._passThroughOptions) {
|
|
1672
|
+
dest.push(arg);
|
|
1673
|
+
if (args.length > 0) dest.push(...args);
|
|
1674
|
+
break;
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1160
1677
|
// add arg
|
|
1161
1678
|
dest.push(arg);
|
|
1162
1679
|
}
|
|
@@ -1168,7 +1685,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
1168
1685
|
* Return an object containing options as key-value pairs
|
|
1169
1686
|
*
|
|
1170
1687
|
* @return {Object}
|
|
1171
|
-
* @api public
|
|
1172
1688
|
*/
|
|
1173
1689
|
opts() {
|
|
1174
1690
|
if (this._storeOptionsAsProperties) {
|
|
@@ -1186,6 +1702,16 @@ Read more on https://git.io/JJc0W`);
|
|
|
1186
1702
|
return this._optionValues;
|
|
1187
1703
|
};
|
|
1188
1704
|
|
|
1705
|
+
/**
|
|
1706
|
+
* Internal bottleneck for handling of parsing errors.
|
|
1707
|
+
*
|
|
1708
|
+
* @api private
|
|
1709
|
+
*/
|
|
1710
|
+
_displayError(exitCode, code, message) {
|
|
1711
|
+
this._outputConfiguration.outputError(`${message}\n`, this._outputConfiguration.writeErr);
|
|
1712
|
+
this._exit(exitCode, code, message);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1189
1715
|
/**
|
|
1190
1716
|
* Argument `name` is missing.
|
|
1191
1717
|
*
|
|
@@ -1195,27 +1721,19 @@ Read more on https://git.io/JJc0W`);
|
|
|
1195
1721
|
|
|
1196
1722
|
missingArgument(name) {
|
|
1197
1723
|
const message = `error: missing required argument '${name}'`;
|
|
1198
|
-
|
|
1199
|
-
this._exit(1, 'commander.missingArgument', message);
|
|
1724
|
+
this._displayError(1, 'commander.missingArgument', message);
|
|
1200
1725
|
};
|
|
1201
1726
|
|
|
1202
1727
|
/**
|
|
1203
|
-
* `Option` is missing an argument
|
|
1728
|
+
* `Option` is missing an argument.
|
|
1204
1729
|
*
|
|
1205
1730
|
* @param {Option} option
|
|
1206
|
-
* @param {string} [flag]
|
|
1207
1731
|
* @api private
|
|
1208
1732
|
*/
|
|
1209
1733
|
|
|
1210
|
-
optionMissingArgument(option
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
message = `error: option '${option.flags}' argument missing, got '${flag}'`;
|
|
1214
|
-
} else {
|
|
1215
|
-
message = `error: option '${option.flags}' argument missing`;
|
|
1216
|
-
}
|
|
1217
|
-
console.error(message);
|
|
1218
|
-
this._exit(1, 'commander.optionMissingArgument', message);
|
|
1734
|
+
optionMissingArgument(option) {
|
|
1735
|
+
const message = `error: option '${option.flags}' argument missing`;
|
|
1736
|
+
this._displayError(1, 'commander.optionMissingArgument', message);
|
|
1219
1737
|
};
|
|
1220
1738
|
|
|
1221
1739
|
/**
|
|
@@ -1227,8 +1745,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
1227
1745
|
|
|
1228
1746
|
missingMandatoryOptionValue(option) {
|
|
1229
1747
|
const message = `error: required option '${option.flags}' not specified`;
|
|
1230
|
-
|
|
1231
|
-
this._exit(1, 'commander.missingMandatoryOptionValue', message);
|
|
1748
|
+
this._displayError(1, 'commander.missingMandatoryOptionValue', message);
|
|
1232
1749
|
};
|
|
1233
1750
|
|
|
1234
1751
|
/**
|
|
@@ -1241,8 +1758,24 @@ Read more on https://git.io/JJc0W`);
|
|
|
1241
1758
|
unknownOption(flag) {
|
|
1242
1759
|
if (this._allowUnknownOption) return;
|
|
1243
1760
|
const message = `error: unknown option '${flag}'`;
|
|
1244
|
-
|
|
1245
|
-
|
|
1761
|
+
this._displayError(1, 'commander.unknownOption', message);
|
|
1762
|
+
};
|
|
1763
|
+
|
|
1764
|
+
/**
|
|
1765
|
+
* Excess arguments, more than expected.
|
|
1766
|
+
*
|
|
1767
|
+
* @param {string[]} receivedArgs
|
|
1768
|
+
* @api private
|
|
1769
|
+
*/
|
|
1770
|
+
|
|
1771
|
+
_excessArguments(receivedArgs) {
|
|
1772
|
+
if (this._allowExcessArguments) return;
|
|
1773
|
+
|
|
1774
|
+
const expected = this._args.length;
|
|
1775
|
+
const s = (expected === 1) ? '' : 's';
|
|
1776
|
+
const forSubcommand = this.parent ? ` for '${this.name()}'` : '';
|
|
1777
|
+
const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
|
|
1778
|
+
this._displayError(1, 'commander.excessArguments', message);
|
|
1246
1779
|
};
|
|
1247
1780
|
|
|
1248
1781
|
/**
|
|
@@ -1259,8 +1792,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
1259
1792
|
const fullCommand = partCommands.join(' ');
|
|
1260
1793
|
const message = `error: unknown command '${this.args[0]}'.` +
|
|
1261
1794
|
(this._hasHelpOption ? ` See '${fullCommand} ${this._helpLongFlag}'.` : '');
|
|
1262
|
-
|
|
1263
|
-
this._exit(1, 'commander.unknownCommand', message);
|
|
1795
|
+
this._displayError(1, 'commander.unknownCommand', message);
|
|
1264
1796
|
};
|
|
1265
1797
|
|
|
1266
1798
|
/**
|
|
@@ -1275,7 +1807,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
1275
1807
|
* @param {string} [flags]
|
|
1276
1808
|
* @param {string} [description]
|
|
1277
1809
|
* @return {this | string} `this` command for chaining, or version string if no arguments
|
|
1278
|
-
* @api public
|
|
1279
1810
|
*/
|
|
1280
1811
|
|
|
1281
1812
|
version(str, flags, description) {
|
|
@@ -1283,11 +1814,11 @@ Read more on https://git.io/JJc0W`);
|
|
|
1283
1814
|
this._version = str;
|
|
1284
1815
|
flags = flags || '-V, --version';
|
|
1285
1816
|
description = description || 'output the version number';
|
|
1286
|
-
const versionOption =
|
|
1817
|
+
const versionOption = this.createOption(flags, description);
|
|
1287
1818
|
this._versionOptionName = versionOption.attributeName();
|
|
1288
1819
|
this.options.push(versionOption);
|
|
1289
1820
|
this.on('option:' + versionOption.name(), () => {
|
|
1290
|
-
|
|
1821
|
+
this._outputConfiguration.writeOut(`${str}\n`);
|
|
1291
1822
|
this._exit(0, 'commander.version', str);
|
|
1292
1823
|
});
|
|
1293
1824
|
return this;
|
|
@@ -1296,12 +1827,10 @@ Read more on https://git.io/JJc0W`);
|
|
|
1296
1827
|
/**
|
|
1297
1828
|
* Set the description to `str`.
|
|
1298
1829
|
*
|
|
1299
|
-
* @param {string} str
|
|
1830
|
+
* @param {string} [str]
|
|
1300
1831
|
* @param {Object} [argsDescription]
|
|
1301
1832
|
* @return {string|Command}
|
|
1302
|
-
* @api public
|
|
1303
1833
|
*/
|
|
1304
|
-
|
|
1305
1834
|
description(str, argsDescription) {
|
|
1306
1835
|
if (str === undefined && argsDescription === undefined) return this._description;
|
|
1307
1836
|
this._description = str;
|
|
@@ -1316,7 +1845,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
1316
1845
|
*
|
|
1317
1846
|
* @param {string} [alias]
|
|
1318
1847
|
* @return {string|Command}
|
|
1319
|
-
* @api public
|
|
1320
1848
|
*/
|
|
1321
1849
|
|
|
1322
1850
|
alias(alias) {
|
|
@@ -1341,7 +1869,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
1341
1869
|
*
|
|
1342
1870
|
* @param {string[]} [aliases]
|
|
1343
1871
|
* @return {string[]|Command}
|
|
1344
|
-
* @api public
|
|
1345
1872
|
*/
|
|
1346
1873
|
|
|
1347
1874
|
aliases(aliases) {
|
|
@@ -1357,7 +1884,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
1357
1884
|
*
|
|
1358
1885
|
* @param {string} [str]
|
|
1359
1886
|
* @return {String|Command}
|
|
1360
|
-
* @api public
|
|
1361
1887
|
*/
|
|
1362
1888
|
|
|
1363
1889
|
usage(str) {
|
|
@@ -1382,8 +1908,7 @@ Read more on https://git.io/JJc0W`);
|
|
|
1382
1908
|
* Get or set the name of the command
|
|
1383
1909
|
*
|
|
1384
1910
|
* @param {string} [str]
|
|
1385
|
-
* @return {
|
|
1386
|
-
* @api public
|
|
1911
|
+
* @return {string|Command}
|
|
1387
1912
|
*/
|
|
1388
1913
|
|
|
1389
1914
|
name(str) {
|
|
@@ -1393,250 +1918,76 @@ Read more on https://git.io/JJc0W`);
|
|
|
1393
1918
|
};
|
|
1394
1919
|
|
|
1395
1920
|
/**
|
|
1396
|
-
* Return
|
|
1921
|
+
* Return program help documentation.
|
|
1397
1922
|
*
|
|
1398
|
-
* @
|
|
1399
|
-
* @
|
|
1923
|
+
* @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
|
|
1924
|
+
* @return {string}
|
|
1400
1925
|
*/
|
|
1401
1926
|
|
|
1402
|
-
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
const args = cmd._args.map((arg) => {
|
|
1407
|
-
return humanReadableArgName(arg);
|
|
1408
|
-
}).join(' ');
|
|
1409
|
-
|
|
1410
|
-
return [
|
|
1411
|
-
cmd._name +
|
|
1412
|
-
(cmd._aliases[0] ? '|' + cmd._aliases[0] : '') +
|
|
1413
|
-
(cmd.options.length ? ' [options]' : '') +
|
|
1414
|
-
(args ? ' ' + args : ''),
|
|
1415
|
-
cmd._description
|
|
1416
|
-
];
|
|
1417
|
-
});
|
|
1418
|
-
|
|
1419
|
-
if (this._lazyHasImplicitHelpCommand()) {
|
|
1420
|
-
commandDetails.push([this._helpCommandnameAndArgs, this._helpCommandDescription]);
|
|
1927
|
+
helpInformation(contextOptions) {
|
|
1928
|
+
const helper = this.createHelp();
|
|
1929
|
+
if (helper.helpWidth === undefined) {
|
|
1930
|
+
helper.helpWidth = (contextOptions && contextOptions.error) ? this._outputConfiguration.getErrHelpWidth() : this._outputConfiguration.getOutHelpWidth();
|
|
1421
1931
|
}
|
|
1422
|
-
return
|
|
1932
|
+
return helper.formatHelp(this, helper);
|
|
1423
1933
|
};
|
|
1424
1934
|
|
|
1425
1935
|
/**
|
|
1426
|
-
* Return the largest command length.
|
|
1427
|
-
*
|
|
1428
|
-
* @return {number}
|
|
1429
1936
|
* @api private
|
|
1430
1937
|
*/
|
|
1431
1938
|
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
largestOptionLength() {
|
|
1447
|
-
const options = [].slice.call(this.options);
|
|
1448
|
-
options.push({
|
|
1449
|
-
flags: this._helpFlags
|
|
1450
|
-
});
|
|
1451
|
-
|
|
1452
|
-
return options.reduce((max, option) => {
|
|
1453
|
-
return Math.max(max, option.flags.length);
|
|
1454
|
-
}, 0);
|
|
1455
|
-
};
|
|
1939
|
+
_getHelpContext(contextOptions) {
|
|
1940
|
+
contextOptions = contextOptions || {};
|
|
1941
|
+
const context = { error: !!contextOptions.error };
|
|
1942
|
+
let write;
|
|
1943
|
+
if (context.error) {
|
|
1944
|
+
write = (arg) => this._outputConfiguration.writeErr(arg);
|
|
1945
|
+
} else {
|
|
1946
|
+
write = (arg) => this._outputConfiguration.writeOut(arg);
|
|
1947
|
+
}
|
|
1948
|
+
context.write = contextOptions.write || write;
|
|
1949
|
+
context.command = this;
|
|
1950
|
+
return context;
|
|
1951
|
+
}
|
|
1456
1952
|
|
|
1457
1953
|
/**
|
|
1458
|
-
*
|
|
1954
|
+
* Output help information for this command.
|
|
1459
1955
|
*
|
|
1460
|
-
*
|
|
1461
|
-
* @api private
|
|
1462
|
-
*/
|
|
1463
|
-
|
|
1464
|
-
largestArgLength() {
|
|
1465
|
-
return this._args.reduce((max, arg) => {
|
|
1466
|
-
return Math.max(max, arg.name.length);
|
|
1467
|
-
}, 0);
|
|
1468
|
-
};
|
|
1469
|
-
|
|
1470
|
-
/**
|
|
1471
|
-
* Return the pad width.
|
|
1956
|
+
* Outputs built-in help, and custom text added using `.addHelpText()`.
|
|
1472
1957
|
*
|
|
1473
|
-
* @
|
|
1474
|
-
* @api private
|
|
1958
|
+
* @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
|
|
1475
1959
|
*/
|
|
1476
1960
|
|
|
1477
|
-
|
|
1478
|
-
let
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
if (this.commands && this.commands.length) {
|
|
1486
|
-
if (this.largestCommandLength() > width) {
|
|
1487
|
-
width = this.largestCommandLength();
|
|
1488
|
-
}
|
|
1961
|
+
outputHelp(contextOptions) {
|
|
1962
|
+
let deprecatedCallback;
|
|
1963
|
+
if (typeof contextOptions === 'function') {
|
|
1964
|
+
deprecatedCallback = contextOptions;
|
|
1965
|
+
contextOptions = undefined;
|
|
1489
1966
|
}
|
|
1967
|
+
const context = this._getHelpContext(contextOptions);
|
|
1490
1968
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
*
|
|
1497
|
-
* @return {string}
|
|
1498
|
-
* @api private
|
|
1499
|
-
*/
|
|
1500
|
-
|
|
1501
|
-
optionHelp() {
|
|
1502
|
-
const width = this.padWidth();
|
|
1503
|
-
const columns = process.stdout.columns || 80;
|
|
1504
|
-
const descriptionWidth = columns - width - 4;
|
|
1505
|
-
function padOptionDetails(flags, description) {
|
|
1506
|
-
return pad(flags, width) + ' ' + optionalWrap(description, descriptionWidth, width + 2);
|
|
1507
|
-
};
|
|
1508
|
-
|
|
1509
|
-
// Explicit options (including version)
|
|
1510
|
-
const help = this.options.map((option) => {
|
|
1511
|
-
const fullDesc = option.description +
|
|
1512
|
-
((!option.negate && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : '');
|
|
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));
|
|
1969
|
+
const groupListeners = [];
|
|
1970
|
+
let command = this;
|
|
1971
|
+
while (command) {
|
|
1972
|
+
groupListeners.push(command); // ordered from current command to root
|
|
1973
|
+
command = command.parent;
|
|
1527
1974
|
}
|
|
1528
1975
|
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
/**
|
|
1533
|
-
* Return command help documentation.
|
|
1534
|
-
*
|
|
1535
|
-
* @return {string}
|
|
1536
|
-
* @api private
|
|
1537
|
-
*/
|
|
1538
|
-
|
|
1539
|
-
commandHelp() {
|
|
1540
|
-
if (!this.commands.length && !this._lazyHasImplicitHelpCommand()) return '';
|
|
1541
|
-
|
|
1542
|
-
const commands = this.prepareCommands();
|
|
1543
|
-
const width = this.padWidth();
|
|
1976
|
+
groupListeners.slice().reverse().forEach(command => command.emit('beforeAllHelp', context));
|
|
1977
|
+
this.emit('beforeHelp', context);
|
|
1544
1978
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
commands.map((cmd) => {
|
|
1551
|
-
const desc = cmd[1] ? ' ' + cmd[1] : '';
|
|
1552
|
-
return (desc ? pad(cmd[0], width) : cmd[0]) + optionalWrap(desc, descriptionWidth, width + 2);
|
|
1553
|
-
}).join('\n').replace(/^/gm, ' '),
|
|
1554
|
-
''
|
|
1555
|
-
].join('\n');
|
|
1556
|
-
};
|
|
1557
|
-
|
|
1558
|
-
/**
|
|
1559
|
-
* Return program help documentation.
|
|
1560
|
-
*
|
|
1561
|
-
* @return {string}
|
|
1562
|
-
* @api public
|
|
1563
|
-
*/
|
|
1564
|
-
|
|
1565
|
-
helpInformation() {
|
|
1566
|
-
let desc = [];
|
|
1567
|
-
if (this._description) {
|
|
1568
|
-
desc = [
|
|
1569
|
-
this._description,
|
|
1570
|
-
''
|
|
1571
|
-
];
|
|
1572
|
-
|
|
1573
|
-
const argsDescription = this._argsDescription;
|
|
1574
|
-
if (argsDescription && this._args.length) {
|
|
1575
|
-
const width = this.padWidth();
|
|
1576
|
-
const columns = process.stdout.columns || 80;
|
|
1577
|
-
const descriptionWidth = columns - width - 5;
|
|
1578
|
-
desc.push('Arguments:');
|
|
1579
|
-
this._args.forEach((arg) => {
|
|
1580
|
-
desc.push(' ' + pad(arg.name, width) + ' ' + wrap(argsDescription[arg.name] || '', descriptionWidth, width + 4));
|
|
1581
|
-
});
|
|
1582
|
-
desc.push('');
|
|
1979
|
+
let helpInformation = this.helpInformation(context);
|
|
1980
|
+
if (deprecatedCallback) {
|
|
1981
|
+
helpInformation = deprecatedCallback(helpInformation);
|
|
1982
|
+
if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) {
|
|
1983
|
+
throw new Error('outputHelp callback must return a string or a Buffer');
|
|
1583
1984
|
}
|
|
1584
1985
|
}
|
|
1986
|
+
context.write(helpInformation);
|
|
1585
1987
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
}
|
|
1590
|
-
let parentCmdNames = '';
|
|
1591
|
-
for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) {
|
|
1592
|
-
parentCmdNames = parentCmd.name() + ' ' + parentCmdNames;
|
|
1593
|
-
}
|
|
1594
|
-
const usage = [
|
|
1595
|
-
'Usage: ' + parentCmdNames + cmdName + ' ' + this.usage(),
|
|
1596
|
-
''
|
|
1597
|
-
];
|
|
1598
|
-
|
|
1599
|
-
let cmds = [];
|
|
1600
|
-
const commandHelp = this.commandHelp();
|
|
1601
|
-
if (commandHelp) cmds = [commandHelp];
|
|
1602
|
-
|
|
1603
|
-
let options = [];
|
|
1604
|
-
if (this._hasHelpOption || this.options.length > 0) {
|
|
1605
|
-
options = [
|
|
1606
|
-
'Options:',
|
|
1607
|
-
'' + this.optionHelp().replace(/^/gm, ' '),
|
|
1608
|
-
''
|
|
1609
|
-
];
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
return usage
|
|
1613
|
-
.concat(desc)
|
|
1614
|
-
.concat(options)
|
|
1615
|
-
.concat(cmds)
|
|
1616
|
-
.join('\n');
|
|
1617
|
-
};
|
|
1618
|
-
|
|
1619
|
-
/**
|
|
1620
|
-
* Output help information for this command.
|
|
1621
|
-
*
|
|
1622
|
-
* When listener(s) are available for the helpLongFlag
|
|
1623
|
-
* those callbacks are invoked.
|
|
1624
|
-
*
|
|
1625
|
-
* @api public
|
|
1626
|
-
*/
|
|
1627
|
-
|
|
1628
|
-
outputHelp(cb) {
|
|
1629
|
-
if (!cb) {
|
|
1630
|
-
cb = (passthru) => {
|
|
1631
|
-
return passthru;
|
|
1632
|
-
};
|
|
1633
|
-
}
|
|
1634
|
-
const cbOutput = cb(this.helpInformation());
|
|
1635
|
-
if (typeof cbOutput !== 'string' && !Buffer.isBuffer(cbOutput)) {
|
|
1636
|
-
throw new Error('outputHelp callback must return a string or a Buffer');
|
|
1637
|
-
}
|
|
1638
|
-
process.stdout.write(cbOutput);
|
|
1639
|
-
this.emit(this._helpLongFlag);
|
|
1988
|
+
this.emit(this._helpLongFlag); // deprecated
|
|
1989
|
+
this.emit('afterHelp', context);
|
|
1990
|
+
groupListeners.forEach(command => command.emit('afterAllHelp', context));
|
|
1640
1991
|
};
|
|
1641
1992
|
|
|
1642
1993
|
/**
|
|
@@ -1647,7 +1998,6 @@ Read more on https://git.io/JJc0W`);
|
|
|
1647
1998
|
* @param {string | boolean} [flags]
|
|
1648
1999
|
* @param {string} [description]
|
|
1649
2000
|
* @return {Command} `this` command for chaining
|
|
1650
|
-
* @api public
|
|
1651
2001
|
*/
|
|
1652
2002
|
|
|
1653
2003
|
helpOption(flags, description) {
|
|
@@ -1668,28 +2018,52 @@ Read more on https://git.io/JJc0W`);
|
|
|
1668
2018
|
/**
|
|
1669
2019
|
* Output help information and exit.
|
|
1670
2020
|
*
|
|
1671
|
-
*
|
|
1672
|
-
*
|
|
2021
|
+
* Outputs built-in help, and custom text added using `.addHelpText()`.
|
|
2022
|
+
*
|
|
2023
|
+
* @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
|
|
1673
2024
|
*/
|
|
1674
2025
|
|
|
1675
|
-
help(
|
|
1676
|
-
this.outputHelp(
|
|
1677
|
-
|
|
2026
|
+
help(contextOptions) {
|
|
2027
|
+
this.outputHelp(contextOptions);
|
|
2028
|
+
let exitCode = process.exitCode || 0;
|
|
2029
|
+
if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error) {
|
|
2030
|
+
exitCode = 1;
|
|
2031
|
+
}
|
|
1678
2032
|
// message: do not have all displayed text available so only passing placeholder.
|
|
1679
|
-
this._exit(
|
|
2033
|
+
this._exit(exitCode, 'commander.help', '(outputHelp)');
|
|
1680
2034
|
};
|
|
1681
2035
|
|
|
1682
2036
|
/**
|
|
1683
|
-
*
|
|
2037
|
+
* Add additional text to be displayed with the built-in help.
|
|
1684
2038
|
*
|
|
1685
|
-
*
|
|
2039
|
+
* Position is 'before' or 'after' to affect just this command,
|
|
2040
|
+
* and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
|
|
2041
|
+
*
|
|
2042
|
+
* @param {string} position - before or after built-in help
|
|
2043
|
+
* @param {string | Function} text - string to add, or a function returning a string
|
|
2044
|
+
* @return {Command} `this` command for chaining
|
|
1686
2045
|
*/
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
2046
|
+
addHelpText(position, text) {
|
|
2047
|
+
const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
|
|
2048
|
+
if (!allowedValues.includes(position)) {
|
|
2049
|
+
throw new Error(`Unexpected value for position to addHelpText.
|
|
2050
|
+
Expecting one of '${allowedValues.join("', '")}'`);
|
|
2051
|
+
}
|
|
2052
|
+
const helpEvent = `${position}Help`;
|
|
2053
|
+
this.on(helpEvent, (context) => {
|
|
2054
|
+
let helpStr;
|
|
2055
|
+
if (typeof text === 'function') {
|
|
2056
|
+
helpStr = text({ error: context.error, command: context.command });
|
|
2057
|
+
} else {
|
|
2058
|
+
helpStr = text;
|
|
2059
|
+
}
|
|
2060
|
+
// Ignore falsy value when nothing to output.
|
|
2061
|
+
if (helpStr) {
|
|
2062
|
+
context.write(`${helpStr}\n`);
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
return this;
|
|
2066
|
+
}
|
|
1693
2067
|
};
|
|
1694
2068
|
|
|
1695
2069
|
/**
|
|
@@ -1706,6 +2080,8 @@ exports.program = exports; // More explicit access to global command.
|
|
|
1706
2080
|
exports.Command = Command;
|
|
1707
2081
|
exports.Option = Option;
|
|
1708
2082
|
exports.CommanderError = CommanderError;
|
|
2083
|
+
exports.InvalidOptionArgumentError = InvalidOptionArgumentError;
|
|
2084
|
+
exports.Help = Help;
|
|
1709
2085
|
|
|
1710
2086
|
/**
|
|
1711
2087
|
* Camel-case the given `flag`
|
|
@@ -1721,63 +2097,6 @@ function camelcase(flag) {
|
|
|
1721
2097
|
});
|
|
1722
2098
|
}
|
|
1723
2099
|
|
|
1724
|
-
/**
|
|
1725
|
-
* Pad `str` to `width`.
|
|
1726
|
-
*
|
|
1727
|
-
* @param {string} str
|
|
1728
|
-
* @param {number} width
|
|
1729
|
-
* @return {string}
|
|
1730
|
-
* @api private
|
|
1731
|
-
*/
|
|
1732
|
-
|
|
1733
|
-
function pad(str, width) {
|
|
1734
|
-
const len = Math.max(0, width - str.length);
|
|
1735
|
-
return str + Array(len + 1).join(' ');
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
/**
|
|
1739
|
-
* Wraps the given string with line breaks at the specified width while breaking
|
|
1740
|
-
* words and indenting every but the first line on the left.
|
|
1741
|
-
*
|
|
1742
|
-
* @param {string} str
|
|
1743
|
-
* @param {number} width
|
|
1744
|
-
* @param {number} indent
|
|
1745
|
-
* @return {string}
|
|
1746
|
-
* @api private
|
|
1747
|
-
*/
|
|
1748
|
-
function wrap(str, width, indent) {
|
|
1749
|
-
const regex = new RegExp('.{1,' + (width - 1) + '}([\\s\u200B]|$)|[^\\s\u200B]+?([\\s\u200B]|$)', 'g');
|
|
1750
|
-
const lines = str.match(regex) || [];
|
|
1751
|
-
return lines.map((line, i) => {
|
|
1752
|
-
if (line.slice(-1) === '\n') {
|
|
1753
|
-
line = line.slice(0, line.length - 1);
|
|
1754
|
-
}
|
|
1755
|
-
return ((i > 0 && indent) ? Array(indent + 1).join(' ') : '') + line.trimRight();
|
|
1756
|
-
}).join('\n');
|
|
1757
|
-
}
|
|
1758
|
-
|
|
1759
|
-
/**
|
|
1760
|
-
* Optionally wrap the given str to a max width of width characters per line
|
|
1761
|
-
* while indenting with indent spaces. Do not wrap if insufficient width or
|
|
1762
|
-
* string is manually formatted.
|
|
1763
|
-
*
|
|
1764
|
-
* @param {string} str
|
|
1765
|
-
* @param {number} width
|
|
1766
|
-
* @param {number} indent
|
|
1767
|
-
* @return {string}
|
|
1768
|
-
* @api private
|
|
1769
|
-
*/
|
|
1770
|
-
function optionalWrap(str, width, indent) {
|
|
1771
|
-
// Detect manually wrapped and indented strings by searching for line breaks
|
|
1772
|
-
// followed by multiple spaces/tabs.
|
|
1773
|
-
if (str.match(/[\n]\s+/)) return str;
|
|
1774
|
-
// Do not wrap to narrow columns (or can end up with a word per line).
|
|
1775
|
-
const minWidth = 40;
|
|
1776
|
-
if (width < minWidth) return str;
|
|
1777
|
-
|
|
1778
|
-
return wrap(str, width, indent);
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
2100
|
/**
|
|
1782
2101
|
* Output help information if help flags specified
|
|
1783
2102
|
*
|