basant307/AI_Governance_Project
048
1const EventEmitter = require('node:events').EventEmitter;2const childProcess = require('node:child_process');3const path = require('node:path');4const fs = require('node:fs');5const process = require('node:process');6 7const { Argument, humanReadableArgName } = require('./argument.js');8const { CommanderError } = require('./error.js');9const { Help } = require('./help.js');10const { Option, DualOptions } = require('./option.js');11const { suggestSimilar } = require('./suggestSimilar');12 13class Command extends EventEmitter {14 /**15 * Initialize a new `Command`.16 *17 * @param {string} [name]18 */19 20 constructor(name) {21 super();22 /** @type {Command[]} */23 this.commands = [];24 /** @type {Option[]} */25 this.options = [];26 this.parent = null;27 this._allowUnknownOption = false;28 this._allowExcessArguments = true;29 /** @type {Argument[]} */30 this.registeredArguments = [];31 this._args = this.registeredArguments; // deprecated old name32 /** @type {string[]} */33 this.args = []; // cli args with options removed34 this.rawArgs = [];35 this.processedArgs = []; // like .args but after custom processing and collecting variadic36 this._scriptPath = null;37 this._name = name || '';38 this._optionValues = {};39 this._optionValueSources = {}; // default, env, cli etc40 this._storeOptionsAsProperties = false;41 this._actionHandler = null;42 this._executableHandler = false;43 this._executableFile = null; // custom name for executable44 this._executableDir = null; // custom search directory for subcommands45 this._defaultCommandName = null;46 this._exitCallback = null;47 this._aliases = [];48 this._combineFlagAndOptionalValue = true;49 this._description = '';50 this._summary = '';51 this._argsDescription = undefined; // legacy52 this._enablePositionalOptions = false;53 this._passThroughOptions = false;54 this._lifeCycleHooks = {}; // a hash of arrays55 /** @type {(boolean | string)} */56 this._showHelpAfterError = false;57 this._showSuggestionAfterError = true;58 59 // see .configureOutput() for docs60 this._outputConfiguration = {61 writeOut: (str) => process.stdout.write(str),62 writeErr: (str) => process.stderr.write(str),63 getOutHelpWidth: () =>64 process.stdout.isTTY ? process.stdout.columns : undefined,65 getErrHelpWidth: () =>66 process.stderr.isTTY ? process.stderr.columns : undefined,67 outputError: (str, write) => write(str),68 };69 70 this._hidden = false;71 /** @type {(Option | null | undefined)} */72 this._helpOption = undefined; // Lazy created on demand. May be null if help option is disabled.73 this._addImplicitHelpCommand = undefined; // undecided whether true or false yet, not inherited74 /** @type {Command} */75 this._helpCommand = undefined; // lazy initialised, inherited76 this._helpConfiguration = {};77 }78 79 /**80 * Copy settings that are useful to have in common across root command and subcommands.81 *82 * (Used internally when adding a command using `.command()` so subcommands inherit parent settings.)83 *84 * @param {Command} sourceCommand85 * @return {Command} `this` command for chaining86 */87 copyInheritedSettings(sourceCommand) {88 this._outputConfiguration = sourceCommand._outputConfiguration;89 this._helpOption = sourceCommand._helpOption;90 this._helpCommand = sourceCommand._helpCommand;91 this._helpConfiguration = sourceCommand._helpConfiguration;92 this._exitCallback = sourceCommand._exitCallback;93 this._storeOptionsAsProperties = sourceCommand._storeOptionsAsProperties;94 this._combineFlagAndOptionalValue =95 sourceCommand._combineFlagAndOptionalValue;96 this._allowExcessArguments = sourceCommand._allowExcessArguments;97 this._enablePositionalOptions = sourceCommand._enablePositionalOptions;98 this._showHelpAfterError = sourceCommand._showHelpAfterError;99 this._showSuggestionAfterError = sourceCommand._showSuggestionAfterError;100 101 return this;102 }103 104 /**105 * @returns {Command[]}106 * @private107 */108 109 _getCommandAndAncestors() {110 const result = [];111 // eslint-disable-next-line @typescript-eslint/no-this-alias112 for (let command = this; command; command = command.parent) {113 result.push(command);114 }115 return result;116 }117 118 /**119 * Define a command.120 *121 * There are two styles of command: pay attention to where to put the description.122 *123 * @example124 * // Command implemented using action handler (description is supplied separately to `.command`)125 * program126 * .command('clone <source> [destination]')127 * .description('clone a repository into a newly created directory')128 * .action((source, destination) => {129 * console.log('clone command called');130 * });131 *132 * // Command implemented using separate executable file (description is second parameter to `.command`)133 * program134 * .command('start <service>', 'start named service')135 * .command('stop [service]', 'stop named service, or all if no name supplied');136 *137 * @param {string} nameAndArgs - command name and arguments, args are `<required>` or `[optional]` and last may also be `variadic...`138 * @param {(object | string)} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable)139 * @param {object} [execOpts] - configuration options (for executable)140 * @return {Command} returns new command for action handler, or `this` for executable command141 */142 143 command(nameAndArgs, actionOptsOrExecDesc, execOpts) {144 let desc = actionOptsOrExecDesc;145 let opts = execOpts;146 if (typeof desc === 'object' && desc !== null) {147 opts = desc;148 desc = null;149 }150 opts = opts || {};151 const [, name, args] = nameAndArgs.match(/([^ ]+) *(.*)/);152 153 const cmd = this.createCommand(name);154 if (desc) {155 cmd.description(desc);156 cmd._executableHandler = true;157 }158 if (opts.isDefault) this._defaultCommandName = cmd._name;159 cmd._hidden = !!(opts.noHelp || opts.hidden); // noHelp is deprecated old name for hidden160 cmd._executableFile = opts.executableFile || null; // Custom name for executable file, set missing to null to match constructor161 if (args) cmd.arguments(args);162 this._registerCommand(cmd);163 cmd.parent = this;164 cmd.copyInheritedSettings(this);165 166 if (desc) return this;167 return cmd;168 }169 170 /**171 * Factory routine to create a new unattached command.172 *173 * See .command() for creating an attached subcommand, which uses this routine to174 * create the command. You can override createCommand to customise subcommands.175 *176 * @param {string} [name]177 * @return {Command} new command178 */179 180 createCommand(name) {181 return new Command(name);182 }183 184 /**185 * You can customise the help with a subclass of Help by overriding createHelp,186 * or by overriding Help properties using configureHelp().187 *188 * @return {Help}189 */190 191 createHelp() {192 return Object.assign(new Help(), this.configureHelp());193 }194 195 /**196 * You can customise the help by overriding Help properties using configureHelp(),197 * or with a subclass of Help by overriding createHelp().198 *199 * @param {object} [configuration] - configuration options200 * @return {(Command | object)} `this` command for chaining, or stored configuration201 */202 203 configureHelp(configuration) {204 if (configuration === undefined) return this._helpConfiguration;205 206 this._helpConfiguration = configuration;207 return this;208 }209 210 /**211 * The default output goes to stdout and stderr. You can customise this for special212 * applications. You can also customise the display of errors by overriding outputError.213 *214 * The configuration properties are all functions:215 *216 * // functions to change where being written, stdout and stderr217 * writeOut(str)218 * writeErr(str)219 * // matching functions to specify width for wrapping help220 * getOutHelpWidth()221 * getErrHelpWidth()222 * // functions based on what is being written out223 * outputError(str, write) // used for displaying errors, and not used for displaying help224 *225 * @param {object} [configuration] - configuration options226 * @return {(Command | object)} `this` command for chaining, or stored configuration227 */228 229 configureOutput(configuration) {230 if (configuration === undefined) return this._outputConfiguration;231 232 Object.assign(this._outputConfiguration, configuration);233 return this;234 }235 236 /**237 * Display the help or a custom message after an error occurs.238 *239 * @param {(boolean|string)} [displayHelp]240 * @return {Command} `this` command for chaining241 */242 showHelpAfterError(displayHelp = true) {243 if (typeof displayHelp !== 'string') displayHelp = !!displayHelp;244 this._showHelpAfterError = displayHelp;245 return this;246 }247 248 /**249 * Display suggestion of similar commands for unknown commands, or options for unknown options.250 *251 * @param {boolean} [displaySuggestion]252 * @return {Command} `this` command for chaining253 */254 showSuggestionAfterError(displaySuggestion = true) {255 this._showSuggestionAfterError = !!displaySuggestion;256 return this;257 }258 259 /**260 * Add a prepared subcommand.261 *262 * See .command() for creating an attached subcommand which inherits settings from its parent.263 *264 * @param {Command} cmd - new subcommand265 * @param {object} [opts] - configuration options266 * @return {Command} `this` command for chaining267 */268 269 addCommand(cmd, opts) {270 if (!cmd._name) {271 throw new Error(`Command passed to .addCommand() must have a name272- specify the name in Command constructor or using .name()`);273 }274 275 opts = opts || {};276 if (opts.isDefault) this._defaultCommandName = cmd._name;277 if (opts.noHelp || opts.hidden) cmd._hidden = true; // modifying passed command due to existing implementation278 279 this._registerCommand(cmd);280 cmd.parent = this;281 cmd._checkForBrokenPassThrough();282 283 return this;284 }285 286 /**287 * Factory routine to create a new unattached argument.288 *289 * See .argument() for creating an attached argument, which uses this routine to290 * create the argument. You can override createArgument to return a custom argument.291 *292 * @param {string} name293 * @param {string} [description]294 * @return {Argument} new argument295 */296 297 createArgument(name, description) {298 return new Argument(name, description);299 }300 301 /**302 * Define argument syntax for command.303 *304 * The default is that the argument is required, and you can explicitly305 * indicate this with <> around the name. Put [] around the name for an optional argument.306 *307 * @example308 * program.argument('<input-file>');309 * program.argument('[output-file]');310 *311 * @param {string} name312 * @param {string} [description]313 * @param {(Function|*)} [fn] - custom argument processing function314 * @param {*} [defaultValue]315 * @return {Command} `this` command for chaining316 */317 argument(name, description, fn, defaultValue) {318 const argument = this.createArgument(name, description);319 if (typeof fn === 'function') {320 argument.default(defaultValue).argParser(fn);321 } else {322 argument.default(fn);323 }324 this.addArgument(argument);325 return this;326 }327 328 /**329 * Define argument syntax for command, adding multiple at once (without descriptions).330 *331 * See also .argument().332 *333 * @example334 * program.arguments('<cmd> [env]');335 *336 * @param {string} names337 * @return {Command} `this` command for chaining338 */339 340 arguments(names) {341 names342 .trim()343 .split(/ +/)344 .forEach((detail) => {345 this.argument(detail);346 });347 return this;348 }349 350 /**351 * Define argument syntax for command, adding a prepared argument.352 *353 * @param {Argument} argument354 * @return {Command} `this` command for chaining355 */356 addArgument(argument) {357 const previousArgument = this.registeredArguments.slice(-1)[0];358 if (previousArgument && previousArgument.variadic) {359 throw new Error(360 `only the last argument can be variadic '${previousArgument.name()}'`,361 );362 }363 if (364 argument.required &&365 argument.defaultValue !== undefined &&366 argument.parseArg === undefined367 ) {368 throw new Error(369 `a default value for a required argument is never used: '${argument.name()}'`,370 );371 }372 this.registeredArguments.push(argument);373 return this;374 }375 376 /**377 * Customise or override default help command. By default a help command is automatically added if your command has subcommands.378 *379 * @example380 * program.helpCommand('help [cmd]');381 * program.helpCommand('help [cmd]', 'show help');382 * program.helpCommand(false); // suppress default help command383 * program.helpCommand(true); // add help command even if no subcommands384 *385 * @param {string|boolean} enableOrNameAndArgs - enable with custom name and/or arguments, or boolean to override whether added386 * @param {string} [description] - custom description387 * @return {Command} `this` command for chaining388 */389 390 helpCommand(enableOrNameAndArgs, description) {391 if (typeof enableOrNameAndArgs === 'boolean') {392 this._addImplicitHelpCommand = enableOrNameAndArgs;393 return this;394 }395 396 enableOrNameAndArgs = enableOrNameAndArgs ?? 'help [command]';397 const [, helpName, helpArgs] = enableOrNameAndArgs.match(/([^ ]+) *(.*)/);398 const helpDescription = description ?? 'display help for command';399 400 const helpCommand = this.createCommand(helpName);401 helpCommand.helpOption(false);402 if (helpArgs) helpCommand.arguments(helpArgs);403 if (helpDescription) helpCommand.description(helpDescription);404 405 this._addImplicitHelpCommand = true;406 this._helpCommand = helpCommand;407 408 return this;409 }410 411 /**412 * Add prepared custom help command.413 *414 * @param {(Command|string|boolean)} helpCommand - custom help command, or deprecated enableOrNameAndArgs as for `.helpCommand()`415 * @param {string} [deprecatedDescription] - deprecated custom description used with custom name only416 * @return {Command} `this` command for chaining417 */418 addHelpCommand(helpCommand, deprecatedDescription) {419 // If not passed an object, call through to helpCommand for backwards compatibility,420 // as addHelpCommand was originally used like helpCommand is now.421 if (typeof helpCommand !== 'object') {422 this.helpCommand(helpCommand, deprecatedDescription);423 return this;424 }425 426 this._addImplicitHelpCommand = true;427 this._helpCommand = helpCommand;428 return this;429 }430 431 /**432 * Lazy create help command.433 *434 * @return {(Command|null)}435 * @package436 */437 _getHelpCommand() {438 const hasImplicitHelpCommand =439 this._addImplicitHelpCommand ??440 (this.commands.length &&441 !this._actionHandler &&442 !this._findCommand('help'));443 444 if (hasImplicitHelpCommand) {445 if (this._helpCommand === undefined) {446 this.helpCommand(undefined, undefined); // use default name and description447 }448 return this._helpCommand;449 }450 return null;451 }452 453 /**454 * Add hook for life cycle event.455 *456 * @param {string} event457 * @param {Function} listener458 * @return {Command} `this` command for chaining459 */460 461 hook(event, listener) {462 const allowedValues = ['preSubcommand', 'preAction', 'postAction'];463 if (!allowedValues.includes(event)) {464 throw new Error(`Unexpected value for event passed to hook : '${event}'.465Expecting one of '${allowedValues.join("', '")}'`);466 }467 if (this._lifeCycleHooks[event]) {468 this._lifeCycleHooks[event].push(listener);469 } else {470 this._lifeCycleHooks[event] = [listener];471 }472 return this;473 }474 475 /**476 * Register callback to use as replacement for calling process.exit.477 *478 * @param {Function} [fn] optional callback which will be passed a CommanderError, defaults to throwing479 * @return {Command} `this` command for chaining480 */481 482 exitOverride(fn) {483 if (fn) {484 this._exitCallback = fn;485 } else {486 this._exitCallback = (err) => {487 if (err.code !== 'commander.executeSubCommandAsync') {488 throw err;489 } else {490 // Async callback from spawn events, not useful to throw.491 }492 };493 }494 return this;495 }496 497 /**498 * Call process.exit, and _exitCallback if defined.499 *500 * @param {number} exitCode exit code for using with process.exit501 * @param {string} code an id string representing the error502 * @param {string} message human-readable description of the error503 * @return never504 * @private505 */506 507 _exit(exitCode, code, message) {508 if (this._exitCallback) {509 this._exitCallback(new CommanderError(exitCode, code, message));510 // Expecting this line is not reached.511 }512 process.exit(exitCode);513 }514 515 /**516 * Register callback `fn` for the command.517 *518 * @example519 * program520 * .command('serve')521 * .description('start service')522 * .action(function() {523 * // do work here524 * });525 *526 * @param {Function} fn527 * @return {Command} `this` command for chaining528 */529 530 action(fn) {531 const listener = (args) => {532 // The .action callback takes an extra parameter which is the command or options.533 const expectedArgsCount = this.registeredArguments.length;534 const actionArgs = args.slice(0, expectedArgsCount);535 if (this._storeOptionsAsProperties) {536 actionArgs[expectedArgsCount] = this; // backwards compatible "options"537 } else {538 actionArgs[expectedArgsCount] = this.opts();539 }540 actionArgs.push(this);541 542 return fn.apply(this, actionArgs);543 };544 this._actionHandler = listener;545 return this;546 }547 548 /**549 * Factory routine to create a new unattached option.550 *551 * See .option() for creating an attached option, which uses this routine to552 * create the option. You can override createOption to return a custom option.553 *554 * @param {string} flags555 * @param {string} [description]556 * @return {Option} new option557 */558 559 createOption(flags, description) {560 return new Option(flags, description);561 }562 563 /**564 * Wrap parseArgs to catch 'commander.invalidArgument'.565 *566 * @param {(Option | Argument)} target567 * @param {string} value568 * @param {*} previous569 * @param {string} invalidArgumentMessage570 * @private571 */572 573 _callParseArg(target, value, previous, invalidArgumentMessage) {574 try {575 return target.parseArg(value, previous);576 } catch (err) {577 if (err.code === 'commander.invalidArgument') {578 const message = `${invalidArgumentMessage} ${err.message}`;579 this.error(message, { exitCode: err.exitCode, code: err.code });580 }581 throw err;582 }583 }584 585 /**586 * Check for option flag conflicts.587 * Register option if no conflicts found, or throw on conflict.588 *589 * @param {Option} option590 * @private591 */592 593 _registerOption(option) {594 const matchingOption =595 (option.short && this._findOption(option.short)) ||596 (option.long && this._findOption(option.long));597 if (matchingOption) {598 const matchingFlag =599 option.long && this._findOption(option.long)600 ? option.long601 : option.short;602 throw new Error(`Cannot add option '${option.flags}'${this._name && ` to command '${this._name}'`} due to conflicting flag '${matchingFlag}'603- already used by option '${matchingOption.flags}'`);604 }605 606 this.options.push(option);607 }608 609 /**610 * Check for command name and alias conflicts with existing commands.611 * Register command if no conflicts found, or throw on conflict.612 *613 * @param {Command} command614 * @private615 */616 617 _registerCommand(command) {618 const knownBy = (cmd) => {619 return [cmd.name()].concat(cmd.aliases());620 };621 622 const alreadyUsed = knownBy(command).find((name) =>623 this._findCommand(name),624 );625 if (alreadyUsed) {626 const existingCmd = knownBy(this._findCommand(alreadyUsed)).join('|');627 const newCmd = knownBy(command).join('|');628 throw new Error(629 `cannot add command '${newCmd}' as already have command '${existingCmd}'`,630 );631 }632 633 this.commands.push(command);634 }635 636 /**637 * Add an option.638 *639 * @param {Option} option640 * @return {Command} `this` command for chaining641 */642 addOption(option) {643 this._registerOption(option);644 645 const oname = option.name();646 const name = option.attributeName();647 648 // store default value649 if (option.negate) {650 // --no-foo is special and defaults foo to true, unless a --foo option is already defined651 const positiveLongFlag = option.long.replace(/^--no-/, '--');652 if (!this._findOption(positiveLongFlag)) {653 this.setOptionValueWithSource(654 name,655 option.defaultValue === undefined ? true : option.defaultValue,656 'default',657 );658 }659 } else if (option.defaultValue !== undefined) {660 this.setOptionValueWithSource(name, option.defaultValue, 'default');661 }662 663 // handler for cli and env supplied values664 const handleOptionValue = (val, invalidValueMessage, valueSource) => {665 // val is null for optional option used without an optional-argument.666 // val is undefined for boolean and negated option.667 if (val == null && option.presetArg !== undefined) {668 val = option.presetArg;669 }670 671 // custom processing672 const oldValue = this.getOptionValue(name);673 if (val !== null && option.parseArg) {674 val = this._callParseArg(option, val, oldValue, invalidValueMessage);675 } else if (val !== null && option.variadic) {676 val = option._concatValue(val, oldValue);677 }678 679 // Fill-in appropriate missing values. Long winded but easy to follow.680 if (val == null) {681 if (option.negate) {682 val = false;683 } else if (option.isBoolean() || option.optional) {684 val = true;685 } else {686 val = ''; // not normal, parseArg might have failed or be a mock function for testing687 }688 }689 this.setOptionValueWithSource(name, val, valueSource);690 };691 692 this.on('option:' + oname, (val) => {693 const invalidValueMessage = `error: option '${option.flags}' argument '${val}' is invalid.`;694 handleOptionValue(val, invalidValueMessage, 'cli');695 });696 697 if (option.envVar) {698 this.on('optionEnv:' + oname, (val) => {699 const invalidValueMessage = `error: option '${option.flags}' value '${val}' from env '${option.envVar}' is invalid.`;700 handleOptionValue(val, invalidValueMessage, 'env');701 });702 }703 704 return this;705 }706 707 /**708 * Internal implementation shared by .option() and .requiredOption()709 *710 * @return {Command} `this` command for chaining711 * @private712 */713 _optionEx(config, flags, description, fn, defaultValue) {714 if (typeof flags === 'object' && flags instanceof Option) {715 throw new Error(716 'To add an Option object use addOption() instead of option() or requiredOption()',717 );718 }719 const option = this.createOption(flags, description);720 option.makeOptionMandatory(!!config.mandatory);721 if (typeof fn === 'function') {722 option.default(defaultValue).argParser(fn);723 } else if (fn instanceof RegExp) {724 // deprecated725 const regex = fn;726 fn = (val, def) => {727 const m = regex.exec(val);728 return m ? m[0] : def;729 };730 option.default(defaultValue).argParser(fn);731 } else {732 option.default(fn);733 }734 735 return this.addOption(option);736 }737 738 /**739 * Define option with `flags`, `description`, and optional argument parsing function or `defaultValue` or both.740 *741 * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. A required742 * option-argument is indicated by `<>` and an optional option-argument by `[]`.743 *744 * See the README for more details, and see also addOption() and requiredOption().745 *746 * @example747 * program748 * .option('-p, --pepper', 'add pepper')749 * .option('-p, --pizza-type <TYPE>', 'type of pizza') // required option-argument750 * .option('-c, --cheese [CHEESE]', 'add extra cheese', 'mozzarella') // optional option-argument with default751 * .option('-t, --tip <VALUE>', 'add tip to purchase cost', parseFloat) // custom parse function752 *753 * @param {string} flags754 * @param {string} [description]755 * @param {(Function|*)} [parseArg] - custom option processing function or default value756 * @param {*} [defaultValue]757 * @return {Command} `this` command for chaining758 */759 760 option(flags, description, parseArg, defaultValue) {761 return this._optionEx({}, flags, description, parseArg, defaultValue);762 }763 764 /**765 * Add a required option which must have a value after parsing. This usually means766 * the option must be specified on the command line. (Otherwise the same as .option().)767 *768 * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space.769 *770 * @param {string} flags771 * @param {string} [description]772 * @param {(Function|*)} [parseArg] - custom option processing function or default value773 * @param {*} [defaultValue]774 * @return {Command} `this` command for chaining775 */776 777 requiredOption(flags, description, parseArg, defaultValue) {778 return this._optionEx(779 { mandatory: true },780 flags,781 description,782 parseArg,783 defaultValue,784 );785 }786 787 /**788 * Alter parsing of short flags with optional values.789 *790 * @example791 * // for `.option('-f,--flag [value]'):792 * program.combineFlagAndOptionalValue(true); // `-f80` is treated like `--flag=80`, this is the default behaviour793 * program.combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b`794 *795 * @param {boolean} [combine] - if `true` or omitted, an optional value can be specified directly after the flag.796 * @return {Command} `this` command for chaining797 */798 combineFlagAndOptionalValue(combine = true) {799 this._combineFlagAndOptionalValue = !!combine;800 return this;801 }802 803 /**804 * Allow unknown options on the command line.805 *806 * @param {boolean} [allowUnknown] - if `true` or omitted, no error will be thrown for unknown options.807 * @return {Command} `this` command for chaining808 */809 allowUnknownOption(allowUnknown = true) {810 this._allowUnknownOption = !!allowUnknown;811 return this;812 }813 814 /**815 * Allow excess command-arguments on the command line. Pass false to make excess arguments an error.816 *817 * @param {boolean} [allowExcess] - if `true` or omitted, no error will be thrown for excess arguments.818 * @return {Command} `this` command for chaining819 */820 allowExcessArguments(allowExcess = true) {821 this._allowExcessArguments = !!allowExcess;822 return this;823 }824 825 /**826 * Enable positional options. Positional means global options are specified before subcommands which lets827 * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions.828 * The default behaviour is non-positional and global options may appear anywhere on the command line.829 *830 * @param {boolean} [positional]831 * @return {Command} `this` command for chaining832 */833 enablePositionalOptions(positional = true) {834 this._enablePositionalOptions = !!positional;835 return this;836 }837 838 /**839 * Pass through options that come after command-arguments rather than treat them as command-options,840 * so actual command-options come before command-arguments. Turning this on for a subcommand requires841 * positional options to have been enabled on the program (parent commands).842 * The default behaviour is non-positional and options may appear before or after command-arguments.843 *844 * @param {boolean} [passThrough] for unknown options.845 * @return {Command} `this` command for chaining846 */847 passThroughOptions(passThrough = true) {848 this._passThroughOptions = !!passThrough;849 this._checkForBrokenPassThrough();850 return this;851 }852 853 /**854 * @private855 */856 857 _checkForBrokenPassThrough() {858 if (859 this.parent &&860 this._passThroughOptions &&861 !this.parent._enablePositionalOptions862 ) {863 throw new Error(864 `passThroughOptions cannot be used for '${this._name}' without turning on enablePositionalOptions for parent command(s)`,865 );866 }867 }868 869 /**870 * Whether to store option values as properties on command object,871 * or store separately (specify false). In both cases the option values can be accessed using .opts().872 *873 * @param {boolean} [storeAsProperties=true]874 * @return {Command} `this` command for chaining875 */876 877 storeOptionsAsProperties(storeAsProperties = true) {878 if (this.options.length) {879 throw new Error('call .storeOptionsAsProperties() before adding options');880 }881 if (Object.keys(this._optionValues).length) {882 throw new Error(883 'call .storeOptionsAsProperties() before setting option values',884 );885 }886 this._storeOptionsAsProperties = !!storeAsProperties;887 return this;888 }889 890 /**891 * Retrieve option value.892 *893 * @param {string} key894 * @return {object} value895 */896 897 getOptionValue(key) {898 if (this._storeOptionsAsProperties) {899 return this[key];900 }901 return this._optionValues[key];902 }903 904 /**905 * Store option value.906 *907 * @param {string} key908 * @param {object} value909 * @return {Command} `this` command for chaining910 */911 912 setOptionValue(key, value) {913 return this.setOptionValueWithSource(key, value, undefined);914 }915 916 /**917 * Store option value and where the value came from.918 *919 * @param {string} key920 * @param {object} value921 * @param {string} source - expected values are default/config/env/cli/implied922 * @return {Command} `this` command for chaining923 */924 925 setOptionValueWithSource(key, value, source) {926 if (this._storeOptionsAsProperties) {927 this[key] = value;928 } else {929 this._optionValues[key] = value;930 }931 this._optionValueSources[key] = source;932 return this;933 }934 935 /**936 * Get source of option value.937 * Expected values are default | config | env | cli | implied938 *939 * @param {string} key940 * @return {string}941 */942 943 getOptionValueSource(key) {944 return this._optionValueSources[key];945 }946 947 /**948 * Get source of option value. See also .optsWithGlobals().949 * Expected values are default | config | env | cli | implied950 *951 * @param {string} key952 * @return {string}953 */954 955 getOptionValueSourceWithGlobals(key) {956 // global overwrites local, like optsWithGlobals957 let source;958 this._getCommandAndAncestors().forEach((cmd) => {959 if (cmd.getOptionValueSource(key) !== undefined) {960 source = cmd.getOptionValueSource(key);961 }962 });963 return source;964 }965 966 /**967 * Get user arguments from implied or explicit arguments.968 * Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches.969 *970 * @private971 */972 973 _prepareUserArgs(argv, parseOptions) {974 if (argv !== undefined && !Array.isArray(argv)) {975 throw new Error('first parameter to parse must be array or undefined');976 }977 parseOptions = parseOptions || {};978 979 // auto-detect argument conventions if nothing supplied980 if (argv === undefined && parseOptions.from === undefined) {981 if (process.versions?.electron) {982 parseOptions.from = 'electron';983 }984 // check node specific options for scenarios where user CLI args follow executable without scriptname985 const execArgv = process.execArgv ?? [];986 if (987 execArgv.includes('-e') ||988 execArgv.includes('--eval') ||989 execArgv.includes('-p') ||990 execArgv.includes('--print')991 ) {992 parseOptions.from = 'eval'; // internal usage, not documented993 }994 }995 996 // default to using process.argv997 if (argv === undefined) {998 argv = process.argv;999 }1000 this.rawArgs = argv.slice();1001 1002 // extract the user args and scriptPath1003 let userArgs;1004 switch (parseOptions.from) {1005 case undefined:1006 case 'node':1007 this._scriptPath = argv[1];1008 userArgs = argv.slice(2);1009 break;1010 case 'electron':1011 // @ts-ignore: because defaultApp is an unknown property1012 if (process.defaultApp) {1013 this._scriptPath = argv[1];1014 userArgs = argv.slice(2);1015 } else {1016 userArgs = argv.slice(1);1017 }1018 break;1019 case 'user':1020 userArgs = argv.slice(0);1021 break;1022 case 'eval':1023 userArgs = argv.slice(1);1024 break;1025 default:1026 throw new Error(1027 `unexpected parse option { from: '${parseOptions.from}' }`,1028 );1029 }1030 1031 // Find default name for program from arguments.1032 if (!this._name && this._scriptPath)1033 this.nameFromFilename(this._scriptPath);1034 this._name = this._name || 'program';1035 1036 return userArgs;1037 }1038 1039 /**1040 * Parse `argv`, setting options and invoking commands when defined.1041 *1042 * Use parseAsync instead of parse if any of your action handlers are async.1043 *1044 * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!1045 *1046 * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:1047 * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that1048 * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged1049 * - `'user'`: just user arguments1050 *1051 * @example1052 * program.parse(); // parse process.argv and auto-detect electron and special node flags1053 * program.parse(process.argv); // assume argv[0] is app and argv[1] is script1054 * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]1055 *1056 * @param {string[]} [argv] - optional, defaults to process.argv1057 * @param {object} [parseOptions] - optionally specify style of options with from: node/user/electron1058 * @param {string} [parseOptions.from] - where the args are from: 'node', 'user', 'electron'1059 * @return {Command} `this` command for chaining1060 */1061 1062 parse(argv, parseOptions) {1063 const userArgs = this._prepareUserArgs(argv, parseOptions);1064 this._parseCommand([], userArgs);1065 1066 return this;1067 }1068 1069 /**1070 * Parse `argv`, setting options and invoking commands when defined.1071 *1072 * Call with no parameters to parse `process.argv`. Detects Electron and special node options like `node --eval`. Easy mode!1073 *1074 * Or call with an array of strings to parse, and optionally where the user arguments start by specifying where the arguments are `from`:1075 * - `'node'`: default, `argv[0]` is the application and `argv[1]` is the script being run, with user arguments after that1076 * - `'electron'`: `argv[0]` is the application and `argv[1]` varies depending on whether the electron application is packaged1077 * - `'user'`: just user arguments1078 *1079 * @example1080 * await program.parseAsync(); // parse process.argv and auto-detect electron and special node flags1081 * await program.parseAsync(process.argv); // assume argv[0] is app and argv[1] is script1082 * await program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0]1083 *1084 * @param {string[]} [argv]1085 * @param {object} [parseOptions]1086 * @param {string} parseOptions.from - where the args are from: 'node', 'user', 'electron'1087 * @return {Promise}1088 */1089 1090 async parseAsync(argv, parseOptions) {1091 const userArgs = this._prepareUserArgs(argv, parseOptions);1092 await this._parseCommand([], userArgs);1093 1094 return this;1095 }1096 1097 /**1098 * Execute a sub-command executable.1099 *1100 * @private1101 */1102 1103 _executeSubCommand(subcommand, args) {1104 args = args.slice();1105 let launchWithNode = false; // Use node for source targets so do not need to get permissions correct, and on Windows.1106 const sourceExt = ['.js', '.ts', '.tsx', '.mjs', '.cjs'];1107 1108 function findFile(baseDir, baseName) {1109 // Look for specified file1110 const localBin = path.resolve(baseDir, baseName);1111 if (fs.existsSync(localBin)) return localBin;1112 1113 // Stop looking if candidate already has an expected extension.1114 if (sourceExt.includes(path.extname(baseName))) return undefined;1115 1116 // Try all the extensions.1117 const foundExt = sourceExt.find((ext) =>1118 fs.existsSync(`${localBin}${ext}`),1119 );1120 if (foundExt) return `${localBin}${foundExt}`;1121 1122 return undefined;1123 }1124 1125 // Not checking for help first. Unlikely to have mandatory and executable, and can't robustly test for help flags in external command.1126 this._checkForMissingMandatoryOptions();1127 this._checkForConflictingOptions();1128 1129 // executableFile and executableDir might be full path, or just a name1130 let executableFile =1131 subcommand._executableFile || `${this._name}-${subcommand._name}`;1132 let executableDir = this._executableDir || '';1133 if (this._scriptPath) {1134 let resolvedScriptPath; // resolve possible symlink for installed npm binary1135 try {1136 resolvedScriptPath = fs.realpathSync(this._scriptPath);1137 } catch (err) {1138 resolvedScriptPath = this._scriptPath;1139 }1140 executableDir = path.resolve(1141 path.dirname(resolvedScriptPath),1142 executableDir,1143 );1144 }1145 1146 // Look for a local file in preference to a command in PATH.1147 if (executableDir) {1148 let localFile = findFile(executableDir, executableFile);1149 1150 // Legacy search using prefix of script name instead of command name1151 if (!localFile && !subcommand._executableFile && this._scriptPath) {1152 const legacyName = path.basename(1153 this._scriptPath,1154 path.extname(this._scriptPath),1155 );1156 if (legacyName !== this._name) {1157 localFile = findFile(1158 executableDir,1159 `${legacyName}-${subcommand._name}`,1160 );1161 }1162 }1163 executableFile = localFile || executableFile;1164 }1165 1166 launchWithNode = sourceExt.includes(path.extname(executableFile));1167 1168 let proc;1169 if (process.platform !== 'win32') {1170 if (launchWithNode) {1171 args.unshift(executableFile);1172 // add executable arguments to spawn1173 args = incrementNodeInspectorPort(process.execArgv).concat(args);1174 1175 proc = childProcess.spawn(process.argv[0], args, { stdio: 'inherit' });1176 } else {1177 proc = childProcess.spawn(executableFile, args, { stdio: 'inherit' });1178 }1179 } else {1180 args.unshift(executableFile);1181 // add executable arguments to spawn1182 args = incrementNodeInspectorPort(process.execArgv).concat(args);1183 proc = childProcess.spawn(process.execPath, args, { stdio: 'inherit' });1184 }1185 1186 if (!proc.killed) {1187 // testing mainly to avoid leak warnings during unit tests with mocked spawn1188 const signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];1189 signals.forEach((signal) => {1190 process.on(signal, () => {1191 if (proc.killed === false && proc.exitCode === null) {1192 // @ts-ignore because signals not typed to known strings1193 proc.kill(signal);1194 }1195 });1196 });1197 }1198 1199 // By default terminate process when spawned process terminates.1200 const exitCallback = this._exitCallback;