UNPKG

68 kBJavaScriptView Raw
1/**
2 * Module dependencies.
3 */
4
5const EventEmitter = require('events').EventEmitter;
6const childProcess = require('child_process');
7const path = require('path');
8const fs = require('fs');
9
10// @ts-check
11
12// Although this is a class, methods are static in style to allow override using subclass or just functions.
13class 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
347class Option {
348 /**
349 * Initialize a new `Option` with the given `flags` and `description`.
350 *
351 * @param {string} flags
352 * @param {string} [description]
353 */
354
355 constructor(flags, description) {
356 this.flags = flags;
357 this.description = description || '';
358
359 this.required = flags.includes('<'); // A value must be supplied when the option is specified.
360 this.optional = flags.includes('['); // A value is optional when the option is specified.
361 // variadic test ignores <value,...> et al which might be used to describe custom splitting of single argument
362 this.variadic = /\w\.\.\.[>\]]$/.test(flags); // The option can take multiple values.
363 this.mandatory = false; // The option must have a value after parsing, which usually means it must be specified on command line.
364 const optionFlags = _parseOptionFlags(flags);
365 this.short = optionFlags.shortFlag;
366 this.long = optionFlags.longFlag;
367 this.negate = false;
368 if (this.long) {
369 this.negate = this.long.startsWith('--no-');
370 }
371 this.defaultValue = undefined;
372 this.defaultValueDescription = undefined;
373 this.parseArg = undefined;
374 this.hidden = false;
375 this.argChoices = undefined;
376 }
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 * @api private
430 */
431
432 _concatValue(value, previous) {
433 if (previous === this.defaultValue || !Array.isArray(previous)) {
434 return [value];
435 }
436
437 return previous.concat(value);
438 }
439
440 /**
441 * Only allow option value to be one of choices.
442 *
443 * @param {string[]} values
444 * @return {Option}
445 */
446
447 choices(values) {
448 this.argChoices = values;
449 this.parseArg = (arg, previous) => {
450 if (!values.includes(arg)) {
451 throw new InvalidOptionArgumentError(`Allowed choices are ${values.join(', ')}.`);
452 }
453 if (this.variadic) {
454 return this._concatValue(arg, previous);
455 }
456 return arg;
457 };
458 return this;
459 };
460
461 /**
462 * Return option name.
463 *
464 * @return {string}
465 */
466
467 name() {
468 if (this.long) {
469 return this.long.replace(/^--/, '');
470 }
471 return this.short.replace(/^-/, '');
472 };
473
474 /**
475 * Return option name, in a camelcase format that can be used
476 * as a object attribute key.
477 *
478 * @return {string}
479 * @api private
480 */
481
482 attributeName() {
483 return camelcase(this.name().replace(/^no-/, ''));
484 };
485
486 /**
487 * Check if `arg` matches the short or long flag.
488 *
489 * @param {string} arg
490 * @return {boolean}
491 * @api private
492 */
493
494 is(arg) {
495 return this.short === arg || this.long === arg;
496 };
497}
498
499/**
500 * CommanderError class
501 * @class
502 */
503class CommanderError extends Error {
504 /**
505 * Constructs the CommanderError class
506 * @param {number} exitCode suggested exit code which could be used with process.exit
507 * @param {string} code an id string representing the error
508 * @param {string} message human-readable description of the error
509 * @constructor
510 */
511 constructor(exitCode, code, message) {
512 super(message);
513 // properly capture stack trace in Node.js
514 Error.captureStackTrace(this, this.constructor);
515 this.name = this.constructor.name;
516 this.code = code;
517 this.exitCode = exitCode;
518 this.nestedError = undefined;
519 }
520}
521
522/**
523 * InvalidOptionArgumentError class
524 * @class
525 */
526class InvalidOptionArgumentError extends CommanderError {
527 /**
528 * Constructs the InvalidOptionArgumentError class
529 * @param {string} [message] explanation of why argument is invalid
530 * @constructor
531 */
532 constructor(message) {
533 super(1, 'commander.invalidOptionArgument', message);
534 // properly capture stack trace in Node.js
535 Error.captureStackTrace(this, this.constructor);
536 this.name = this.constructor.name;
537 }
538}
539
540class Command extends EventEmitter {
541 /**
542 * Initialize a new `Command`.
543 *
544 * @param {string} [name]
545 */
546
547 constructor(name) {
548 super();
549 this.commands = [];
550 this.options = [];
551 this.parent = null;
552 this._allowUnknownOption = false;
553 this._allowExcessArguments = true;
554 this._args = [];
555 this.rawArgs = null;
556 this._scriptPath = null;
557 this._name = name || '';
558 this._optionValues = {};
559 this._storeOptionsAsProperties = false;
560 this._actionResults = [];
561 this._actionHandler = null;
562 this._executableHandler = false;
563 this._executableFile = null; // custom name for executable
564 this._defaultCommandName = null;
565 this._exitCallback = null;
566 this._aliases = [];
567 this._combineFlagAndOptionalValue = true;
568 this._description = '';
569 this._argsDescription = undefined;
570 this._enablePositionalOptions = false;
571 this._passThroughOptions = false;
572
573 // see .configureOutput() for docs
574 this._outputConfiguration = {
575 writeOut: (str) => process.stdout.write(str),
576 writeErr: (str) => process.stderr.write(str),
577 getOutHelpWidth: () => process.stdout.isTTY ? process.stdout.columns : undefined,
578 getErrHelpWidth: () => process.stderr.isTTY ? process.stderr.columns : undefined,
579 outputError: (str, write) => write(str)
580 };
581
582 this._hidden = false;
583 this._hasHelpOption = true;
584 this._helpFlags = '-h, --help';
585 this._helpDescription = 'display help for command';
586 this._helpShortFlag = '-h';
587 this._helpLongFlag = '--help';
588 this._addImplicitHelpCommand = undefined; // Deliberately undefined, not decided whether true or false
589 this._helpCommandName = 'help';
590 this._helpCommandnameAndArgs = 'help [command]';
591 this._helpCommandDescription = 'display help for command';
592 this._helpConfiguration = {};
593 }
594
595 /**
596 * Define a command.
597 *
598 * There are two styles of command: pay attention to where to put the description.
599 *
600 * Examples:
601 *
602 * // Command implemented using action handler (description is supplied separately to `.command`)
603 * program
604 * .command('clone <source> [destination]')
605 * .description('clone a repository into a newly created directory')
606 * .action((source, destination) => {
607 * console.log('clone command called');
608 * });
609 *
610 * // Command implemented using separate executable file (description is second parameter to `.command`)
611 * program
612 * .command('start <service>', 'start named service')
613 * .command('stop [service]', 'stop named service, or all if no name supplied');
614 *
615 * @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`
616 * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)
617 * @param {Object} [execOpts] - configuration options (for executable)
618 * @return {Command} returns new command for action handler, or `this` for executable command
619 */
620
621 command(nameAndArgs, actionOptsOrExecDesc, execOpts) {
622 let desc = actionOptsOrExecDesc;
623 let opts = execOpts;
624 if (typeof desc === 'object' && desc !== null) {
625 opts = desc;
626 desc = null;
627 }
628 opts = opts || {};
629 const args = nameAndArgs.split(/ +/);
630 const cmd = this.createCommand(args.shift());
631
632 if (desc) {
633 cmd.description(desc);
634 cmd._executableHandler = true;
635 }
636 if (opts.isDefault) this._defaultCommandName = cmd._name;
637
638 cmd._outputConfiguration = this._outputConfiguration;
639
640 cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden
641 cmd._hasHelpOption = this._hasHelpOption;
642 cmd._helpFlags = this._helpFlags;
643 cmd._helpDescription = this._helpDescription;
644 cmd._helpShortFlag = this._helpShortFlag;
645 cmd._helpLongFlag = this._helpLongFlag;
646 cmd._helpCommandName = this._helpCommandName;
647 cmd._helpCommandnameAndArgs = this._helpCommandnameAndArgs;
648 cmd._helpCommandDescription = this._helpCommandDescription;
649 cmd._helpConfiguration = this._helpConfiguration;
650 cmd._exitCallback = this._exitCallback;
651 cmd._storeOptionsAsProperties = this._storeOptionsAsProperties;
652 cmd._combineFlagAndOptionalValue = this._combineFlagAndOptionalValue;
653 cmd._allowExcessArguments = this._allowExcessArguments;
654 cmd._enablePositionalOptions = this._enablePositionalOptions;
655
656 cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor
657 this.commands.push(cmd);
658 cmd._parseExpectedArgs(args);
659 cmd.parent = this;
660
661 if (desc) return this;
662 return cmd;
663 };
664
665 /**
666 * Factory routine to create a new unattached command.
667 *
668 * See .command() for creating an attached subcommand, which uses this routine to
669 * create the command. You can override createCommand to customise subcommands.
670 *
671 * @param {string} [name]
672 * @return {Command} new command
673 */
674
675 createCommand(name) {
676 return new Command(name);
677 };
678
679 /**
680 * You can customise the help with a subclass of Help by overriding createHelp,
681 * or by overriding Help properties using configureHelp().
682 *
683 * @return {Help}
684 */
685
686 createHelp() {
687 return Object.assign(new Help(), this.configureHelp());
688 };
689
690 /**
691 * You can customise the help by overriding Help properties using configureHelp(),
692 * or with a subclass of Help by overriding createHelp().
693 *
694 * @param {Object} [configuration] - configuration options
695 * @return {Command|Object} `this` command for chaining, or stored configuration
696 */
697
698 configureHelp(configuration) {
699 if (configuration === undefined) return this._helpConfiguration;
700
701 this._helpConfiguration = configuration;
702 return this;
703 }
704
705 /**
706 * The default output goes to stdout and stderr. You can customise this for special
707 * applications. You can also customise the display of errors by overriding outputError.
708 *
709 * The configuration properties are all functions:
710 *
711 * // functions to change where being written, stdout and stderr
712 * writeOut(str)
713 * writeErr(str)
714 * // matching functions to specify width for wrapping help
715 * getOutHelpWidth()
716 * getErrHelpWidth()
717 * // functions based on what is being written out
718 * outputError(str, write) // used for displaying errors, and not used for displaying help
719 *
720 * @param {Object} [configuration] - configuration options
721 * @return {Command|Object} `this` command for chaining, or stored configuration
722 */
723
724 configureOutput(configuration) {
725 if (configuration === undefined) return this._outputConfiguration;
726
727 Object.assign(this._outputConfiguration, configuration);
728 return this;
729 }
730
731 /**
732 * Add a prepared subcommand.
733 *
734 * See .command() for creating an attached subcommand which inherits settings from its parent.
735 *
736 * @param {Command} cmd - new subcommand
737 * @param {Object} [opts] - configuration options
738 * @return {Command} `this` command for chaining
739 */
740
741 addCommand(cmd, opts) {
742 if (!cmd._name) throw new Error('Command passed to .addCommand() must have a name');
743
744 // To keep things simple, block automatic name generation for deeply nested executables.
745 // Fail fast and detect when adding rather than later when parsing.
746 function checkExplicitNames(commandArray) {
747 commandArray.forEach((cmd) => {
748 if (cmd._executableHandler && !cmd._executableFile) {
749 throw new Error(`Must specify executableFile for deeply nested executable: ${cmd.name()}`);
750 }
751 checkExplicitNames(cmd.commands);
752 });
753 }
754 checkExplicitNames(cmd.commands);
755
756 opts = opts || {};
757 if (opts.isDefault) this._defaultCommandName = cmd._name;
758 if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation
759
760 this.commands.push(cmd);
761 cmd.parent = this;
762 return this;
763 };
764
765 /**
766 * Define argument syntax for the command.
767 */
768
769 arguments(desc) {
770 return this._parseExpectedArgs(desc.split(/ +/));
771 };
772
773 /**
774 * Override default decision whether to add implicit help command.
775 *
776 * addHelpCommand() // force on
777 * addHelpCommand(false); // force off
778 * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details
779 *
780 * @return {Command} `this` command for chaining
781 */
782
783 addHelpCommand(enableOrNameAndArgs, description) {
784 if (enableOrNameAndArgs === false) {
785 this._addImplicitHelpCommand = false;
786 } else {
787 this._addImplicitHelpCommand = true;
788 if (typeof enableOrNameAndArgs === 'string') {
789 this._helpCommandName = enableOrNameAndArgs.split(' ')[0];
790 this._helpCommandnameAndArgs = enableOrNameAndArgs;
791 }
792 this._helpCommandDescription = description || this._helpCommandDescription;
793 }
794 return this;
795 };
796
797 /**
798 * @return {boolean}
799 * @api private
800 */
801
802 _hasImplicitHelpCommand() {
803 if (this._addImplicitHelpCommand === undefined) {
804 return this.commands.length && !this._actionHandler && !this._findCommand('help');
805 }
806 return this._addImplicitHelpCommand;
807 };
808
809 /**
810 * Parse expected `args`.
811 *
812 * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
813 *
814 * @param {Array} args
815 * @return {Command} `this` command for chaining
816 * @api private
817 */
818
819 _parseExpectedArgs(args) {
820 if (!args.length) return;
821 args.forEach((arg) => {
822 const argDetails = {
823 required: false,
824 name: '',
825 variadic: false
826 };
827
828 switch (arg[0]) {
829 case '<':
830 argDetails.required = true;
831 argDetails.name = arg.slice(1, -1);
832 break;
833 case '[':
834 argDetails.name = arg.slice(1, -1);
835 break;
836 }
837
838 if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {
839 argDetails.variadic = true;
840 argDetails.name = argDetails.name.slice(0, -3);
841 }
842 if (argDetails.name) {
843 this._args.push(argDetails);
844 }
845 });
846 this._args.forEach((arg, i) => {
847 if (arg.variadic && i < this._args.length - 1) {
848 throw new Error(`only the last argument can be variadic '${arg.name}'`);
849 }
850 });
851 return this;
852 };
853
854 /**
855 * Register callback to use as replacement for calling process.exit.
856 *
857 * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing
858 * @return {Command} `this` command for chaining
859 */
860
861 exitOverride(fn) {
862 if (fn) {
863 this._exitCallback = fn;
864 } else {
865 this._exitCallback = (err) => {
866 if (err.code !== 'commander.executeSubCommandAsync') {
867 throw err;
868 } else {
869 // Async callback from spawn events, not useful to throw.
870 }
871 };
872 }
873 return this;
874 };
875
876 /**
877 * Call process.exit, and _exitCallback if defined.
878 *
879 * @param {number} exitCode exit code for using with process.exit
880 * @param {string} code an id string representing the error
881 * @param {string} message human-readable description of the error
882 * @return never
883 * @api private
884 */
885
886 _exit(exitCode, code, message) {
887 if (this._exitCallback) {
888 this._exitCallback(new CommanderError(exitCode, code, message));
889 // Expecting this line is not reached.
890 }
891 process.exit(exitCode);
892 };
893
894 /**
895 * Register callback `fn` for the command.
896 *
897 * Examples:
898 *
899 * program
900 * .command('help')
901 * .description('display verbose help')
902 * .action(function() {
903 * // output help here
904 * });
905 *
906 * @param {Function} fn
907 * @return {Command} `this` command for chaining
908 */
909
910 action(fn) {
911 const listener = (args) => {
912 // The .action callback takes an extra parameter which is the command or options.
913 const expectedArgsCount = this._args.length;
914 const actionArgs = args.slice(0, expectedArgsCount);
915 if (this._storeOptionsAsProperties) {
916 actionArgs[expectedArgsCount] = this; // backwards compatible "options"
917 } else {
918 actionArgs[expectedArgsCount] = this.opts();
919 }
920 actionArgs.push(this);
921
922 const actionResult = fn.apply(this, actionArgs);
923 // Remember result in case it is async. Assume parseAsync getting called on root.
924 let rootCommand = this;
925 while (rootCommand.parent) {
926 rootCommand = rootCommand.parent;
927 }
928 rootCommand._actionResults.push(actionResult);
929 };
930 this._actionHandler = listener;
931 return this;
932 };
933
934 /**
935 * Factory routine to create a new unattached option.
936 *
937 * See .option() for creating an attached option, which uses this routine to
938 * create the option. You can override createOption to return a custom option.
939 *
940 * @param {string} flags
941 * @param {string} [description]
942 * @return {Option} new option
943 */
944
945 createOption(flags, description) {
946 return new Option(flags, description);
947 };
948
949 /**
950 * Add an option.
951 *
952 * @param {Option} option
953 * @return {Command} `this` command for chaining
954 */
955 addOption(option) {
956 const oname = option.name();
957 const name = option.attributeName();
958
959 let defaultValue = option.defaultValue;
960
961 // preassign default value for --no-*, [optional], <required>, or plain flag if boolean value
962 if (option.negate || option.optional || option.required || typeof defaultValue === 'boolean') {
963 // when --no-foo we make sure default is true, unless a --foo option is already defined
964 if (option.negate) {
965 const positiveLongFlag = option.long.replace(/^--no-/, '--');
966 defaultValue = this._findOption(positiveLongFlag) ? this._getOptionValue(name) : true;
967 }
968 // preassign only if we have a default
969 if (defaultValue !== undefined) {
970 this._setOptionValue(name, defaultValue);
971 }
972 }
973
974 // register the option
975 this.options.push(option);
976
977 // when it's passed assign the value
978 // and conditionally invoke the callback
979 this.on('option:' + oname, (val) => {
980 const oldValue = this._getOptionValue(name);
981
982 // custom processing
983 if (val !== null && option.parseArg) {
984 try {
985 val = option.parseArg(val, oldValue === undefined ? defaultValue : oldValue);
986 } catch (err) {
987 if (err.code === 'commander.invalidOptionArgument') {
988 const message = `error: option '${option.flags}' argument '${val}' is invalid. ${err.message}`;
989 this._displayError(err.exitCode, err.code, message);
990 }
991 throw err;
992 }
993 } else if (val !== null && option.variadic) {
994 val = option._concatValue(val, oldValue);
995 }
996
997 // unassigned or boolean value
998 if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') {
999 // if no value, negate false, and we have a default, then use it!
1000 if (val == null) {
1001 this._setOptionValue(name, option.negate
1002 ? false
1003 : defaultValue || true);
1004 } else {
1005 this._setOptionValue(name, val);
1006 }
1007 } else if (val !== null) {
1008 // reassign
1009 this._setOptionValue(name, option.negate ? false : val);
1010 }
1011 });
1012
1013 return this;
1014 }
1015
1016 /**
1017 * Internal implementation shared by .option() and .requiredOption()
1018 *
1019 * @api private
1020 */
1021 _optionEx(config, flags, description, fn, defaultValue) {
1022 const option = this.createOption(flags, description);
1023 option.makeOptionMandatory(!!config.mandatory);
1024 if (typeof fn === 'function') {
1025 option.default(defaultValue).argParser(fn);
1026 } else if (fn instanceof RegExp) {
1027 // deprecated
1028 const regex = fn;
1029 fn = (val, def) => {
1030 const m = regex.exec(val);
1031 return m ? m[0] : def;
1032 };
1033 option.default(defaultValue).argParser(fn);
1034 } else {
1035 option.default(fn);
1036 }
1037
1038 return this.addOption(option);
1039 }
1040
1041 /**
1042 * Define option with `flags`, `description` and optional
1043 * coercion `fn`.
1044 *
1045 * The `flags` string contains the short and/or long flags,
1046 * separated by comma, a pipe or space. The following are all valid
1047 * all will output this way when `--help` is used.
1048 *
1049 * "-p, --pepper"
1050 * "-p|--pepper"
1051 * "-p --pepper"
1052 *
1053 * Examples:
1054 *
1055 * // simple boolean defaulting to undefined
1056 * program.option('-p, --pepper', 'add pepper');
1057 *
1058 * program.pepper
1059 * // => undefined
1060 *
1061 * --pepper
1062 * program.pepper
1063 * // => true
1064 *
1065 * // simple boolean defaulting to true (unless non-negated option is also defined)
1066 * program.option('-C, --no-cheese', 'remove cheese');
1067 *
1068 * program.cheese
1069 * // => true
1070 *
1071 * --no-cheese
1072 * program.cheese
1073 * // => false
1074 *
1075 * // required argument
1076 * program.option('-C, --chdir <path>', 'change the working directory');
1077 *
1078 * --chdir /tmp
1079 * program.chdir
1080 * // => "/tmp"
1081 *
1082 * // optional argument
1083 * program.option('-c, --cheese [type]', 'add cheese [marble]');
1084 *
1085 * @param {string} flags
1086 * @param {string} [description]
1087 * @param {Function|*} [fn] - custom option processing function or default value
1088 * @param {*} [defaultValue]
1089 * @return {Command} `this` command for chaining
1090 */
1091
1092 option(flags, description, fn, defaultValue) {
1093 return this._optionEx({}, flags, description, fn, defaultValue);
1094 };
1095
1096 /**
1097 * Add a required option which must have a value after parsing. This usually means
1098 * the option must be specified on the command line. (Otherwise the same as .option().)
1099 *
1100 * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.
1101 *
1102 * @param {string} flags
1103 * @param {string} [description]
1104 * @param {Function|*} [fn] - custom option processing function or default value
1105 * @param {*} [defaultValue]
1106 * @return {Command} `this` command for chaining
1107 */
1108
1109 requiredOption(flags, description, fn, defaultValue) {
1110 return this._optionEx({ mandatory: true }, flags, description, fn, defaultValue);
1111 };
1112
1113 /**
1114 * Alter parsing of short flags with optional values.
1115 *
1116 * Examples:
1117 *
1118 * // for `.option('-f,--flag [value]'):
1119 * .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour
1120 * .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`
1121 *
1122 * @param {Boolean} [combine=true] - if `true` or omitted, an optional value can be specified directly after the flag.
1123 */
1124 combineFlagAndOptionalValue(combine = true) {
1125 this._combineFlagAndOptionalValue = !!combine;
1126 return this;
1127 };
1128
1129 /**
1130 * Allow unknown options on the command line.
1131 *
1132 * @param {Boolean} [allowUnknown=true] - if `true` or omitted, no error will be thrown
1133 * for unknown options.
1134 */
1135 allowUnknownOption(allowUnknown = true) {
1136 this._allowUnknownOption = !!allowUnknown;
1137 return this;
1138 };
1139
1140 /**
1141 * Allow excess command-arguments on the command line. Pass false to make excess arguments an error.
1142 *
1143 * @param {Boolean} [allowExcess=true] - if `true` or omitted, no error will be thrown
1144 * for excess arguments.
1145 */
1146 allowExcessArguments(allowExcess = true) {
1147 this._allowExcessArguments = !!allowExcess;
1148 return this;
1149 };
1150
1151 /**
1152 * Enable positional options. Positional means global options are specified before subcommands which lets
1153 * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.
1154 * The default behaviour is non-positional and global options may appear anywhere on the command line.
1155 *
1156 * @param {Boolean} [positional=true]
1157 */
1158 enablePositionalOptions(positional = true) {
1159 this._enablePositionalOptions = !!positional;
1160 return this;
1161 };
1162
1163 /**
1164 * Pass through options that come after command-arguments rather than treat them as command-options,
1165 * so actual command-options come before command-arguments. Turning this on for a subcommand requires
1166 * positional options to have been enabled on the program (parent commands).
1167 * The default behaviour is non-positional and options may appear before or after command-arguments.
1168 *
1169 * @param {Boolean} [passThrough=true]
1170 * for unknown options.
1171 */
1172 passThroughOptions(passThrough = true) {
1173 this._passThroughOptions = !!passThrough;
1174 if (!!this.parent && passThrough && !this.parent._enablePositionalOptions) {
1175 throw new Error('passThroughOptions can not be used without turning on enablePositionalOptions for parent command(s)');
1176 }
1177 return this;
1178 };
1179
1180 /**
1181 * Whether to store option values as properties on command object,
1182 * or store separately (specify false). In both cases the option values can be accessed using .opts().
1183 *
1184 * @param {boolean} [storeAsProperties=true]
1185 * @return {Command} `this` command for chaining
1186 */
1187
1188 storeOptionsAsProperties(storeAsProperties = true) {
1189 this._storeOptionsAsProperties = !!storeAsProperties;
1190 if (this.options.length) {
1191 throw new Error('call .storeOptionsAsProperties() before adding options');
1192 }
1193 return this;
1194 };
1195
1196 /**
1197 * Store option value
1198 *
1199 * @param {string} key
1200 * @param {Object} value
1201 * @api private
1202 */
1203
1204 _setOptionValue(key, value) {
1205 if (this._storeOptionsAsProperties) {
1206 this[key] = value;
1207 } else {
1208 this._optionValues[key] = value;
1209 }
1210 };
1211
1212 /**
1213 * Retrieve option value
1214 *
1215 * @param {string} key
1216 * @return {Object} value
1217 * @api private
1218 */
1219
1220 _getOptionValue(key) {
1221 if (this._storeOptionsAsProperties) {
1222 return this[key];
1223 }
1224 return this._optionValues[key];
1225 };
1226
1227 /**
1228 * Parse `argv`, setting options and invoking commands when defined.
1229 *
1230 * The default expectation is that the arguments are from node and have the application as argv[0]
1231 * and the script being run in argv[1], with user parameters after that.
1232 *
1233 * Examples:
1234 *
1235 * program.parse(process.argv);
1236 * program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions
1237 * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
1238 *
1239 * @param {string[]} [argv] - optional, defaults to process.argv
1240 * @param {Object} [parseOptions] - optionally specify style of options with from: node/user/electron
1241 * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'
1242 * @return {Command} `this` command for chaining
1243 */
1244
1245 parse(argv, parseOptions) {
1246 if (argv !== undefined && !Array.isArray(argv)) {
1247 throw new Error('first parameter to parse must be array or undefined');
1248 }
1249 parseOptions = parseOptions || {};
1250
1251 // Default to using process.argv
1252 if (argv === undefined) {
1253 argv = process.argv;
1254 // @ts-ignore: unknown property
1255 if (process.versions && process.versions.electron) {
1256 parseOptions.from = 'electron';
1257 }
1258 }
1259 this.rawArgs = argv.slice();
1260
1261 // make it a little easier for callers by supporting various argv conventions
1262 let userArgs;
1263 switch (parseOptions.from) {
1264 case undefined:
1265 case 'node':
1266 this._scriptPath = argv[1];
1267 userArgs = argv.slice(2);
1268 break;
1269 case 'electron':
1270 // @ts-ignore: unknown property
1271 if (process.defaultApp) {
1272 this._scriptPath = argv[1];
1273 userArgs = argv.slice(2);
1274 } else {
1275 userArgs = argv.slice(1);
1276 }
1277 break;
1278 case 'user':
1279 userArgs = argv.slice(0);
1280 break;
1281 default:
1282 throw new Error(`unexpected parse option { from: '${parseOptions.from}' }`);
1283 }
1284 if (!this._scriptPath && require.main) {
1285 this._scriptPath = require.main.filename;
1286 }
1287
1288 // Guess name, used in usage in help.
1289 this._name = this._name || (this._scriptPath && path.basename(this._scriptPath, path.extname(this._scriptPath)));
1290
1291 // Let's go!
1292 this._parseCommand([], userArgs);
1293
1294 return this;
1295 };
1296
1297 /**
1298 * Parse `argv`, setting options and invoking commands when defined.
1299 *
1300 * Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise.
1301 *
1302 * The default expectation is that the arguments are from node and have the application as argv[0]
1303 * and the script being run in argv[1], with user parameters after that.
1304 *
1305 * Examples:
1306 *
1307 * program.parseAsync(process.argv);
1308 * program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions
1309 * program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]
1310 *
1311 * @param {string[]} [argv]
1312 * @param {Object} [parseOptions]
1313 * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'
1314 * @return {Promise}
1315 */
1316
1317 parseAsync(argv, parseOptions) {
1318 this.parse(argv, parseOptions);
1319 return Promise.all(this._actionResults).then(() => this);
1320 };
1321
1322 /**
1323 * Execute a sub-command executable.
1324 *
1325 * @api private
1326 */
1327
1328 _executeSubCommand(subcommand, args) {
1329 args = args.slice();
1330 let launchWithNode = false; // Use node for source targets so do not need to get permissions correct, and on Windows.
1331 const sourceExt = ['.js', '.ts', '.tsx', '.mjs', '.cjs'];
1332
1333 // Not checking for help first. Unlikely to have mandatory and executable, and can't robustly test for help flags in external command.
1334 this._checkForMissingMandatoryOptions();
1335
1336 // Want the entry script as the reference for command name and directory for searching for other files.
1337 let scriptPath = this._scriptPath;
1338 // Fallback in case not set, due to how Command created or called.
1339 if (!scriptPath && require.main) {
1340 scriptPath = require.main.filename;
1341 }
1342
1343 let baseDir;
1344 try {
1345 const resolvedLink = fs.realpathSync(scriptPath);
1346 baseDir = path.dirname(resolvedLink);
1347 } catch (e) {
1348 baseDir = '.'; // dummy, probably not going to find executable!
1349 }
1350
1351 // name of the subcommand, like `pm-install`
1352 let bin = path.basename(scriptPath, path.extname(scriptPath)) + '-' + subcommand._name;
1353 if (subcommand._executableFile) {
1354 bin = subcommand._executableFile;
1355 }
1356
1357 const localBin = path.join(baseDir, bin);
1358 if (fs.existsSync(localBin)) {
1359 // prefer local `./<bin>` to bin in the $PATH
1360 bin = localBin;
1361 } else {
1362 // Look for source files.
1363 sourceExt.forEach((ext) => {
1364 if (fs.existsSync(`${localBin}${ext}`)) {
1365 bin = `${localBin}${ext}`;
1366 }
1367 });
1368 }
1369 launchWithNode = sourceExt.includes(path.extname(bin));
1370
1371 let proc;
1372 if (process.platform !== 'win32') {
1373 if (launchWithNode) {
1374 args.unshift(bin);
1375 // add executable arguments to spawn
1376 args = incrementNodeInspectorPort(process.execArgv).concat(args);
1377
1378 proc = childProcess.spawn(process.argv[0], args, { stdio: 'inherit' });
1379 } else {
1380 proc = childProcess.spawn(bin, args, { stdio: 'inherit' });
1381 }
1382 } else {
1383 args.unshift(bin);
1384 // add executable arguments to spawn
1385 args = incrementNodeInspectorPort(process.execArgv).concat(args);
1386 proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });
1387 }
1388
1389 const signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
1390 signals.forEach((signal) => {
1391 // @ts-ignore
1392 process.on(signal, () => {
1393 if (proc.killed === false && proc.exitCode === null) {
1394 proc.kill(signal);
1395 }
1396 });
1397 });
1398
1399 // By default terminate process when spawned process terminates.
1400 // Suppressing the exit if exitCallback defined is a bit messy and of limited use, but does allow process to stay running!
1401 const exitCallback = this._exitCallback;
1402 if (!exitCallback) {
1403 proc.on('close', process.exit.bind(process));
1404 } else {
1405 proc.on('close', () => {
1406 exitCallback(new CommanderError(process.exitCode || 0, 'commander.executeSubCommandAsync', '(close)'));
1407 });
1408 }
1409 proc.on('error', (err) => {
1410 // @ts-ignore
1411 if (err.code === 'ENOENT') {
1412 const executableMissing = `'${bin}' does not exist
1413 - if '${subcommand._name}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
1414 - if the default executable name is not suitable, use the executableFile option to supply a custom name`;
1415 throw new Error(executableMissing);
1416 // @ts-ignore
1417 } else if (err.code === 'EACCES') {
1418 throw new Error(`'${bin}' not executable`);
1419 }
1420 if (!exitCallback) {
1421 process.exit(1);
1422 } else {
1423 const wrappedError = new CommanderError(1, 'commander.executeSubCommandAsync', '(error)');
1424 wrappedError.nestedError = err;
1425 exitCallback(wrappedError);
1426 }
1427 });
1428
1429 // Store the reference to the child process
1430 this.runningCommand = proc;
1431 };
1432
1433 /**
1434 * @api private
1435 */
1436 _dispatchSubcommand(commandName, operands, unknown) {
1437 const subCommand = this._findCommand(commandName);
1438 if (!subCommand) this.help({ error: true });
1439
1440 if (subCommand._executableHandler) {
1441 this._executeSubCommand(subCommand, operands.concat(unknown));
1442 } else {
1443 subCommand._parseCommand(operands, unknown);
1444 }
1445 };
1446
1447 /**
1448 * Process arguments in context of this command.
1449 *
1450 * @api private
1451 */
1452
1453 _parseCommand(operands, unknown) {
1454 const parsed = this.parseOptions(unknown);
1455 operands = operands.concat(parsed.operands);
1456 unknown = parsed.unknown;
1457 this.args = operands.concat(unknown);
1458
1459 if (operands && this._findCommand(operands[0])) {
1460 this._dispatchSubcommand(operands[0], operands.slice(1), unknown);
1461 } else if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) {
1462 if (operands.length === 1) {
1463 this.help();
1464 } else {
1465 this._dispatchSubcommand(operands[1], [], [this._helpLongFlag]);
1466 }
1467 } else if (this._defaultCommandName) {
1468 outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command
1469 this._dispatchSubcommand(this._defaultCommandName, operands, unknown);
1470 } else {
1471 if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
1472 // probably missing subcommand and no handler, user needs help
1473 this.help({ error: true });
1474 }
1475
1476 outputHelpIfRequested(this, parsed.unknown);
1477 this._checkForMissingMandatoryOptions();
1478
1479 // We do not always call this check to avoid masking a "better" error, like unknown command.
1480 const checkForUnknownOptions = () => {
1481 if (parsed.unknown.length > 0) {
1482 this.unknownOption(parsed.unknown[0]);
1483 }
1484 };
1485
1486 const commandEvent = `command:${this.name()}`;
1487 if (this._actionHandler) {
1488 checkForUnknownOptions();
1489 // Check expected arguments and collect variadic together.
1490 const args = this.args.slice();
1491 this._args.forEach((arg, i) => {
1492 if (arg.required && args[i] == null) {
1493 this.missingArgument(arg.name);
1494 } else if (arg.variadic) {
1495 args[i] = args.splice(i);
1496 args.length = Math.min(i + 1, args.length);
1497 }
1498 });
1499 if (args.length > this._args.length) {
1500 this._excessArguments(args);
1501 }
1502
1503 this._actionHandler(args);
1504 if (this.parent) this.parent.emit(commandEvent, operands, unknown); // legacy
1505 } else if (this.parent && this.parent.listenerCount(commandEvent)) {
1506 checkForUnknownOptions();
1507 this.parent.emit(commandEvent, operands, unknown); // legacy
1508 } else if (operands.length) {
1509 if (this._findCommand('*')) { // legacy default command
1510 this._dispatchSubcommand('*', operands, unknown);
1511 } else if (this.listenerCount('command:*')) {
1512 // skip option check, emit event for possible misspelling suggestion
1513 this.emit('command:*', operands, unknown);
1514 } else if (this.commands.length) {
1515 this.unknownCommand();
1516 } else {
1517 checkForUnknownOptions();
1518 }
1519 } else if (this.commands.length) {
1520 // This command has subcommands and nothing hooked up at this level, so display help.
1521 this.help({ error: true });
1522 } else {
1523 checkForUnknownOptions();
1524 // fall through for caller to handle after calling .parse()
1525 }
1526 }
1527 };
1528
1529 /**
1530 * Find matching command.
1531 *
1532 * @api private
1533 */
1534 _findCommand(name) {
1535 if (!name) return undefined;
1536 return this.commands.find(cmd => cmd._name === name || cmd._aliases.includes(name));
1537 };
1538
1539 /**
1540 * Return an option matching `arg` if any.
1541 *
1542 * @param {string} arg
1543 * @return {Option}
1544 * @api private
1545 */
1546
1547 _findOption(arg) {
1548 return this.options.find(option => option.is(arg));
1549 };
1550
1551 /**
1552 * Display an error message if a mandatory option does not have a value.
1553 * Lazy calling after checking for help flags from leaf subcommand.
1554 *
1555 * @api private
1556 */
1557
1558 _checkForMissingMandatoryOptions() {
1559 // Walk up hierarchy so can call in subcommand after checking for displaying help.
1560 for (let cmd = this; cmd; cmd = cmd.parent) {
1561 cmd.options.forEach((anOption) => {
1562 if (anOption.mandatory && (cmd._getOptionValue(anOption.attributeName()) === undefined)) {
1563 cmd.missingMandatoryOptionValue(anOption);
1564 }
1565 });
1566 }
1567 };
1568
1569 /**
1570 * Parse options from `argv` removing known options,
1571 * and return argv split into operands and unknown arguments.
1572 *
1573 * Examples:
1574 *
1575 * argv => operands, unknown
1576 * --known kkk op => [op], []
1577 * op --known kkk => [op], []
1578 * sub --unknown uuu op => [sub], [--unknown uuu op]
1579 * sub -- --unknown uuu op => [sub --unknown uuu op], []
1580 *
1581 * @param {String[]} argv
1582 * @return {{operands: String[], unknown: String[]}}
1583 */
1584
1585 parseOptions(argv) {
1586 const operands = []; // operands, not options or values
1587 const unknown = []; // first unknown option and remaining unknown args
1588 let dest = operands;
1589 const args = argv.slice();
1590
1591 function maybeOption(arg) {
1592 return arg.length > 1 && arg[0] === '-';
1593 }
1594
1595 // parse options
1596 let activeVariadicOption = null;
1597 while (args.length) {
1598 const arg = args.shift();
1599
1600 // literal
1601 if (arg === '--') {
1602 if (dest === unknown) dest.push(arg);
1603 dest.push(...args);
1604 break;
1605 }
1606
1607 if (activeVariadicOption && !maybeOption(arg)) {
1608 this.emit(`option:${activeVariadicOption.name()}`, arg);
1609 continue;
1610 }
1611 activeVariadicOption = null;
1612
1613 if (maybeOption(arg)) {
1614 const option = this._findOption(arg);
1615 // recognised option, call listener to assign value with possible custom processing
1616 if (option) {
1617 if (option.required) {
1618 const value = args.shift();
1619 if (value === undefined) this.optionMissingArgument(option);
1620 this.emit(`option:${option.name()}`, value);
1621 } else if (option.optional) {
1622 let value = null;
1623 // historical behaviour is optional value is following arg unless an option
1624 if (args.length > 0 && !maybeOption(args[0])) {
1625 value = args.shift();
1626 }
1627 this.emit(`option:${option.name()}`, value);
1628 } else { // boolean flag
1629 this.emit(`option:${option.name()}`);
1630 }
1631 activeVariadicOption = option.variadic ? option : null;
1632 continue;
1633 }
1634 }
1635
1636 // Look for combo options following single dash, eat first one if known.
1637 if (arg.length > 2 && arg[0] === '-' && arg[1] !== '-') {
1638 const option = this._findOption(`-${arg[1]}`);
1639 if (option) {
1640 if (option.required || (option.optional && this._combineFlagAndOptionalValue)) {
1641 // option with value following in same argument
1642 this.emit(`option:${option.name()}`, arg.slice(2));
1643 } else {
1644 // boolean option, emit and put back remainder of arg for further processing
1645 this.emit(`option:${option.name()}`);
1646 args.unshift(`-${arg.slice(2)}`);
1647 }
1648 continue;
1649 }
1650 }
1651
1652 // Look for known long flag with value, like --foo=bar
1653 if (/^--[^=]+=/.test(arg)) {
1654 const index = arg.indexOf('=');
1655 const option = this._findOption(arg.slice(0, index));
1656 if (option && (option.required || option.optional)) {
1657 this.emit(`option:${option.name()}`, arg.slice(index + 1));
1658 continue;
1659 }
1660 }
1661
1662 // Not a recognised option by this command.
1663 // Might be a command-argument, or subcommand option, or unknown option, or help command or option.
1664
1665 // An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands.
1666 if (maybeOption(arg)) {
1667 dest = unknown;
1668 }
1669
1670 // If using positionalOptions, stop processing our options at subcommand.
1671 if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) {
1672 if (this._findCommand(arg)) {
1673 operands.push(arg);
1674 if (args.length > 0) unknown.push(...args);
1675 break;
1676 } else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) {
1677 operands.push(arg);
1678 if (args.length > 0) operands.push(...args);
1679 break;
1680 } else if (this._defaultCommandName) {
1681 unknown.push(arg);
1682 if (args.length > 0) unknown.push(...args);
1683 break;
1684 }
1685 }
1686
1687 // If using passThroughOptions, stop processing options at first command-argument.
1688 if (this._passThroughOptions) {
1689 dest.push(arg);
1690 if (args.length > 0) dest.push(...args);
1691 break;
1692 }
1693
1694 // add arg
1695 dest.push(arg);
1696 }
1697
1698 return { operands, unknown };
1699 };
1700
1701 /**
1702 * Return an object containing options as key-value pairs
1703 *
1704 * @return {Object}
1705 */
1706 opts() {
1707 if (this._storeOptionsAsProperties) {
1708 // Preserve original behaviour so backwards compatible when still using properties
1709 const result = {};
1710 const len = this.options.length;
1711
1712 for (let i = 0; i < len; i++) {
1713 const key = this.options[i].attributeName();
1714 result[key] = key === this._versionOptionName ? this._version : this[key];
1715 }
1716 return result;
1717 }
1718
1719 return this._optionValues;
1720 };
1721
1722 /**
1723 * Internal bottleneck for handling of parsing errors.
1724 *
1725 * @api private
1726 */
1727 _displayError(exitCode, code, message) {
1728 this._outputConfiguration.outputError(`${message}\n`, this._outputConfiguration.writeErr);
1729 this._exit(exitCode, code, message);
1730 }
1731
1732 /**
1733 * Argument `name` is missing.
1734 *
1735 * @param {string} name
1736 * @api private
1737 */
1738
1739 missingArgument(name) {
1740 const message = `error: missing required argument '${name}'`;
1741 this._displayError(1, 'commander.missingArgument', message);
1742 };
1743
1744 /**
1745 * `Option` is missing an argument.
1746 *
1747 * @param {Option} option
1748 * @api private
1749 */
1750
1751 optionMissingArgument(option) {
1752 const message = `error: option '${option.flags}' argument missing`;
1753 this._displayError(1, 'commander.optionMissingArgument', message);
1754 };
1755
1756 /**
1757 * `Option` does not have a value, and is a mandatory option.
1758 *
1759 * @param {Option} option
1760 * @api private
1761 */
1762
1763 missingMandatoryOptionValue(option) {
1764 const message = `error: required option '${option.flags}' not specified`;
1765 this._displayError(1, 'commander.missingMandatoryOptionValue', message);
1766 };
1767
1768 /**
1769 * Unknown option `flag`.
1770 *
1771 * @param {string} flag
1772 * @api private
1773 */
1774
1775 unknownOption(flag) {
1776 if (this._allowUnknownOption) return;
1777 const message = `error: unknown option '${flag}'`;
1778 this._displayError(1, 'commander.unknownOption', message);
1779 };
1780
1781 /**
1782 * Excess arguments, more than expected.
1783 *
1784 * @param {string[]} receivedArgs
1785 * @api private
1786 */
1787
1788 _excessArguments(receivedArgs) {
1789 if (this._allowExcessArguments) return;
1790
1791 const expected = this._args.length;
1792 const s = (expected === 1) ? '' : 's';
1793 const forSubcommand = this.parent ? ` for '${this.name()}'` : '';
1794 const message = `error: too many arguments${forSubcommand}. Expected ${expected} argument${s} but got ${receivedArgs.length}.`;
1795 this._displayError(1, 'commander.excessArguments', message);
1796 };
1797
1798 /**
1799 * Unknown command.
1800 *
1801 * @api private
1802 */
1803
1804 unknownCommand() {
1805 const partCommands = [this.name()];
1806 for (let parentCmd = this.parent; parentCmd; parentCmd = parentCmd.parent) {
1807 partCommands.unshift(parentCmd.name());
1808 }
1809 const fullCommand = partCommands.join(' ');
1810 const message = `error: unknown command '${this.args[0]}'.` +
1811 (this._hasHelpOption ? ` See '${fullCommand} ${this._helpLongFlag}'.` : '');
1812 this._displayError(1, 'commander.unknownCommand', message);
1813 };
1814
1815 /**
1816 * Set the program version to `str`.
1817 *
1818 * This method auto-registers the "-V, --version" flag
1819 * which will print the version number when passed.
1820 *
1821 * You can optionally supply the flags and description to override the defaults.
1822 *
1823 * @param {string} str
1824 * @param {string} [flags]
1825 * @param {string} [description]
1826 * @return {this | string} `this` command for chaining, or version string if no arguments
1827 */
1828
1829 version(str, flags, description) {
1830 if (str === undefined) return this._version;
1831 this._version = str;
1832 flags = flags || '-V, --version';
1833 description = description || 'output the version number';
1834 const versionOption = this.createOption(flags, description);
1835 this._versionOptionName = versionOption.attributeName();
1836 this.options.push(versionOption);
1837 this.on('option:' + versionOption.name(), () => {
1838 this._outputConfiguration.writeOut(`${str}\n`);
1839 this._exit(0, 'commander.version', str);
1840 });
1841 return this;
1842 };
1843
1844 /**
1845 * Set the description to `str`.
1846 *
1847 * @param {string} [str]
1848 * @param {Object} [argsDescription]
1849 * @return {string|Command}
1850 */
1851 description(str, argsDescription) {
1852 if (str === undefined && argsDescription === undefined) return this._description;
1853 this._description = str;
1854 this._argsDescription = argsDescription;
1855 return this;
1856 };
1857
1858 /**
1859 * Set an alias for the command.
1860 *
1861 * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help.
1862 *
1863 * @param {string} [alias]
1864 * @return {string|Command}
1865 */
1866
1867 alias(alias) {
1868 if (alias === undefined) return this._aliases[0]; // just return first, for backwards compatibility
1869
1870 let command = this;
1871 if (this.commands.length !== 0 && this.commands[this.commands.length - 1]._executableHandler) {
1872 // assume adding alias for last added executable subcommand, rather than this
1873 command = this.commands[this.commands.length - 1];
1874 }
1875
1876 if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
1877
1878 command._aliases.push(alias);
1879 return this;
1880 };
1881
1882 /**
1883 * Set aliases for the command.
1884 *
1885 * Only the first alias is shown in the auto-generated help.
1886 *
1887 * @param {string[]} [aliases]
1888 * @return {string[]|Command}
1889 */
1890
1891 aliases(aliases) {
1892 // Getter for the array of aliases is the main reason for having aliases() in addition to alias().
1893 if (aliases === undefined) return this._aliases;
1894
1895 aliases.forEach((alias) => this.alias(alias));
1896 return this;
1897 };
1898
1899 /**
1900 * Set / get the command usage `str`.
1901 *
1902 * @param {string} [str]
1903 * @return {String|Command}
1904 */
1905
1906 usage(str) {
1907 if (str === undefined) {
1908 if (this._usage) return this._usage;
1909
1910 const args = this._args.map((arg) => {
1911 return humanReadableArgName(arg);
1912 });
1913 return [].concat(
1914 (this.options.length || this._hasHelpOption ? '[options]' : []),
1915 (this.commands.length ? '[command]' : []),
1916 (this._args.length ? args : [])
1917 ).join(' ');
1918 }
1919
1920 this._usage = str;
1921 return this;
1922 };
1923
1924 /**
1925 * Get or set the name of the command
1926 *
1927 * @param {string} [str]
1928 * @return {string|Command}
1929 */
1930
1931 name(str) {
1932 if (str === undefined) return this._name;
1933 this._name = str;
1934 return this;
1935 };
1936
1937 /**
1938 * Return program help documentation.
1939 *
1940 * @param {{ error: boolean }} [contextOptions] - pass {error:true} to wrap for stderr instead of stdout
1941 * @return {string}
1942 */
1943
1944 helpInformation(contextOptions) {
1945 const helper = this.createHelp();
1946 if (helper.helpWidth === undefined) {
1947 helper.helpWidth = (contextOptions && contextOptions.error) ? this._outputConfiguration.getErrHelpWidth() : this._outputConfiguration.getOutHelpWidth();
1948 }
1949 return helper.formatHelp(this, helper);
1950 };
1951
1952 /**
1953 * @api private
1954 */
1955
1956 _getHelpContext(contextOptions) {
1957 contextOptions = contextOptions || {};
1958 const context = { error: !!contextOptions.error };
1959 let write;
1960 if (context.error) {
1961 write = (arg) => this._outputConfiguration.writeErr(arg);
1962 } else {
1963 write = (arg) => this._outputConfiguration.writeOut(arg);
1964 }
1965 context.write = contextOptions.write || write;
1966 context.command = this;
1967 return context;
1968 }
1969
1970 /**
1971 * Output help information for this command.
1972 *
1973 * Outputs built-in help, and custom text added using `.addHelpText()`.
1974 *
1975 * @param {{ error: boolean } | Function} [contextOptions] - pass {error:true} to write to stderr instead of stdout
1976 */
1977
1978 outputHelp(contextOptions) {
1979 let deprecatedCallback;
1980 if (typeof contextOptions === 'function') {
1981 deprecatedCallback = contextOptions;
1982 contextOptions = undefined;
1983 }
1984 const context = this._getHelpContext(contextOptions);
1985
1986 const groupListeners = [];
1987 let command = this;
1988 while (command) {
1989 groupListeners.push(command); // ordered from current command to root
1990 command = command.parent;
1991 }
1992
1993 groupListeners.slice().reverse().forEach(command => command.emit('beforeAllHelp', context));
1994 this.emit('beforeHelp', context);
1995
1996 let helpInformation = this.helpInformation(context);
1997 if (deprecatedCallback) {
1998 helpInformation = deprecatedCallback(helpInformation);
1999 if (typeof helpInformation !== 'string' && !Buffer.isBuffer(helpInformation)) {
2000 throw new Error('outputHelp callback must return a string or a Buffer');
2001 }
2002 }
2003 context.write(helpInformation);
2004
2005 this.emit(this._helpLongFlag); // deprecated
2006 this.emit('afterHelp', context);
2007 groupListeners.forEach(command => command.emit('afterAllHelp', context));
2008 };
2009
2010 /**
2011 * You can pass in flags and a description to override the help
2012 * flags and help description for your command. Pass in false to
2013 * disable the built-in help option.
2014 *
2015 * @param {string | boolean} [flags]
2016 * @param {string} [description]
2017 * @return {Command} `this` command for chaining
2018 */
2019
2020 helpOption(flags, description) {
2021 if (typeof flags === 'boolean') {
2022 this._hasHelpOption = flags;
2023 return this;
2024 }
2025 this._helpFlags = flags || this._helpFlags;
2026 this._helpDescription = description || this._helpDescription;
2027
2028 const helpFlags = _parseOptionFlags(this._helpFlags);
2029 this._helpShortFlag = helpFlags.shortFlag;
2030 this._helpLongFlag = helpFlags.longFlag;
2031
2032 return this;
2033 };
2034
2035 /**
2036 * Output help information and exit.
2037 *
2038 * Outputs built-in help, and custom text added using `.addHelpText()`.
2039 *
2040 * @param {{ error: boolean }} [contextOptions] - pass {error:true} to write to stderr instead of stdout
2041 */
2042
2043 help(contextOptions) {
2044 this.outputHelp(contextOptions);
2045 let exitCode = process.exitCode || 0;
2046 if (exitCode === 0 && contextOptions && typeof contextOptions !== 'function' && contextOptions.error) {
2047 exitCode = 1;
2048 }
2049 // message: do not have all displayed text available so only passing placeholder.
2050 this._exit(exitCode, 'commander.help', '(outputHelp)');
2051 };
2052
2053 /**
2054 * Add additional text to be displayed with the built-in help.
2055 *
2056 * Position is 'before' or 'after' to affect just this command,
2057 * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands.
2058 *
2059 * @param {string} position - before or after built-in help
2060 * @param {string | Function} text - string to add, or a function returning a string
2061 * @return {Command} `this` command for chaining
2062 */
2063 addHelpText(position, text) {
2064 const allowedValues = ['beforeAll', 'before', 'after', 'afterAll'];
2065 if (!allowedValues.includes(position)) {
2066 throw new Error(`Unexpected value for position to addHelpText.
2067Expecting one of '${allowedValues.join("', '")}'`);
2068 }
2069 const helpEvent = `${position}Help`;
2070 this.on(helpEvent, (context) => {
2071 let helpStr;
2072 if (typeof text === 'function') {
2073 helpStr = text({ error: context.error, command: context.command });
2074 } else {
2075 helpStr = text;
2076 }
2077 // Ignore falsy value when nothing to output.
2078 if (helpStr) {
2079 context.write(`${helpStr}\n`);
2080 }
2081 });
2082 return this;
2083 }
2084};
2085
2086/**
2087 * Expose the root command.
2088 */
2089
2090exports = module.exports = new Command();
2091exports.program = exports; // More explicit access to global command.
2092
2093/**
2094 * Expose classes
2095 */
2096
2097exports.Command = Command;
2098exports.Option = Option;
2099exports.CommanderError = CommanderError;
2100exports.InvalidOptionArgumentError = InvalidOptionArgumentError;
2101exports.Help = Help;
2102
2103/**
2104 * Camel-case the given `flag`
2105 *
2106 * @param {string} flag
2107 * @return {string}
2108 * @api private
2109 */
2110
2111function camelcase(flag) {
2112 return flag.split('-').reduce((str, word) => {
2113 return str + word[0].toUpperCase() + word.slice(1);
2114 });
2115}
2116
2117/**
2118 * Output help information if help flags specified
2119 *
2120 * @param {Command} cmd - command to output help for
2121 * @param {Array} args - array of options to search for help flags
2122 * @api private
2123 */
2124
2125function outputHelpIfRequested(cmd, args) {
2126 const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpLongFlag || arg === cmd._helpShortFlag);
2127 if (helpOption) {
2128 cmd.outputHelp();
2129 // (Do not have all displayed text available so only passing placeholder.)
2130 cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)');
2131 }
2132}
2133
2134/**
2135 * Takes an argument and returns its human readable equivalent for help usage.
2136 *
2137 * @param {Object} arg
2138 * @return {string}
2139 * @api private
2140 */
2141
2142function humanReadableArgName(arg) {
2143 const nameOutput = arg.name + (arg.variadic === true ? '...' : '');
2144
2145 return arg.required
2146 ? '<' + nameOutput + '>'
2147 : '[' + nameOutput + ']';
2148}
2149
2150/**
2151 * Parse the short and long flag out of something like '-m,--mixed <value>'
2152 *
2153 * @api private
2154 */
2155
2156function _parseOptionFlags(flags) {
2157 let shortFlag;
2158 let longFlag;
2159 // Use original very loose parsing to maintain backwards compatibility for now,
2160 // which allowed for example unintended `-sw, --short-word` [sic].
2161 const flagParts = flags.split(/[ |,]+/);
2162 if (flagParts.length > 1 && !/^[[<]/.test(flagParts[1])) shortFlag = flagParts.shift();
2163 longFlag = flagParts.shift();
2164 // Add support for lone short flag without significantly changing parsing!
2165 if (!shortFlag && /^-[^-]$/.test(longFlag)) {
2166 shortFlag = longFlag;
2167 longFlag = undefined;
2168 }
2169 return { shortFlag, longFlag };
2170}
2171
2172/**
2173 * Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command).
2174 *
2175 * @param {string[]} args - array of arguments from node.execArgv
2176 * @returns {string[]}
2177 * @api private
2178 */
2179
2180function incrementNodeInspectorPort(args) {
2181 // Testing for these options:
2182 // --inspect[=[host:]port]
2183 // --inspect-brk[=[host:]port]
2184 // --inspect-port=[host:]port
2185 return args.map((arg) => {
2186 if (!arg.startsWith('--inspect')) {
2187 return arg;
2188 }
2189 let debugOption;
2190 let debugHost = '127.0.0.1';
2191 let debugPort = '9229';
2192 let match;
2193 if ((match = arg.match(/^(--inspect(-brk)?)$/)) !== null) {
2194 // e.g. --inspect
2195 debugOption = match[1];
2196 } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+)$/)) !== null) {
2197 debugOption = match[1];
2198 if (/^\d+$/.test(match[3])) {
2199 // e.g. --inspect=1234
2200 debugPort = match[3];
2201 } else {
2202 // e.g. --inspect=localhost
2203 debugHost = match[3];
2204 }
2205 } else if ((match = arg.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/)) !== null) {
2206 // e.g. --inspect=localhost:1234
2207 debugOption = match[1];
2208 debugHost = match[3];
2209 debugPort = match[4];
2210 }
2211
2212 if (debugOption && debugPort !== '0') {
2213 return `${debugOption}=${debugHost}:${parseInt(debugPort) + 1}`;
2214 }
2215 return arg;
2216 });
2217}