diff --git a/lib/command.js b/lib/command.js index 7a0ed8b77..f05e244b9 100644 --- a/lib/command.js +++ b/lib/command.js @@ -74,6 +74,9 @@ class Command extends EventEmitter { this._persistentOptionValues = {}; this._persistentOptionValueSources = {}; this.resetParseState(); + + /** @type {boolean | undefined} */ + this._asyncParsing = undefined; } resetParseState() { @@ -804,7 +807,10 @@ Expecting one of '${allowedValues.join("', '")}'`); */ setOptionValueWithSource(key, value, source) { - this._setPersistentOptionValueWithSource(key, value, source); + const set = this._asyncParsing === undefined + ? this._setPersistentOptionValueWithSource + : this._setNonPersistentOptionValueWithSource; + set(key, value, source); return this; } @@ -1307,81 +1313,87 @@ Expecting one of '${allowedValues.join("', '")}'`); * @api private */ - _parseCommand(operands, unknown) { - const parsed = this.parseOptions(unknown); - this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env - this._parseOptionsImplied(); - operands = operands.concat(parsed.operands); - unknown = parsed.unknown; - this.args = operands.concat(unknown); - - if (operands && this._findCommand(operands[0])) { - return this._dispatchSubcommand(operands[0], operands.slice(1), unknown); - } - if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) { - return this._dispatchHelpCommand(operands[1]); - } - if (this._defaultCommandName) { - outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command - return this._dispatchSubcommand(this._defaultCommandName, operands, unknown); - } - if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) { - // probably missing subcommand and no handler, user needs help (and exit) - this.help({ error: true }); - } + _parseCommand(operands, unknown, async) { + this._asyncParsing = async; - outputHelpIfRequested(this, parsed.unknown); - this._checkForMissingMandatoryOptions(); - this._checkForConflictingOptions(); + try { + const parsed = this.parseOptions(unknown); + this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env + this._parseOptionsImplied(); + operands = operands.concat(parsed.operands); + unknown = parsed.unknown; + this.args = operands.concat(unknown); - // We do not always call this check to avoid masking a "better" error, like unknown command. - const checkForUnknownOptions = () => { - if (parsed.unknown.length > 0) { - this.unknownOption(parsed.unknown[0]); + if (operands && this._findCommand(operands[0])) { + return this._dispatchSubcommand(operands[0], operands.slice(1), unknown); } - }; - - const commandEvent = `command:${this.name()}`; - if (this._actionHandler) { - checkForUnknownOptions(); - this._processArguments(); - - let actionResult; - actionResult = this._chainOrCallHooks(actionResult, 'preAction'); - actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this.processedArgs)); - if (this.parent) { - actionResult = this._chainOrCall(actionResult, () => { - this.parent.emit(commandEvent, operands, unknown); // legacy - }); + if (this._hasImplicitHelpCommand() && operands[0] === this._helpCommandName) { + return this._dispatchHelpCommand(operands[1]); } - actionResult = this._chainOrCallHooks(actionResult, 'postAction'); - return actionResult; - } - if (this.parent && this.parent.listenerCount(commandEvent)) { - checkForUnknownOptions(); - this._processArguments(); - this.parent.emit(commandEvent, operands, unknown); // legacy - } else if (operands.length) { - if (this._findCommand('*')) { // legacy default command - return this._dispatchSubcommand('*', operands, unknown); + if (this._defaultCommandName) { + outputHelpIfRequested(this, unknown); // Run the help for default command from parent rather than passing to default command + return this._dispatchSubcommand(this._defaultCommandName, operands, unknown); } - if (this.listenerCount('command:*')) { - // skip option check, emit event for possible misspelling suggestion - this.emit('command:*', operands, unknown); + if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) { + // probably missing subcommand and no handler, user needs help (and exit) + this.help({ error: true }); + } + + outputHelpIfRequested(this, parsed.unknown); + this._checkForMissingMandatoryOptions(); + this._checkForConflictingOptions(); + + // We do not always call this check to avoid masking a "better" error, like unknown command. + const checkForUnknownOptions = () => { + if (parsed.unknown.length > 0) { + this.unknownOption(parsed.unknown[0]); + } + }; + + const commandEvent = `command:${this.name()}`; + if (this._actionHandler) { + checkForUnknownOptions(); + this._processArguments(); + + let actionResult; + actionResult = this._chainOrCallHooks(actionResult, 'preAction'); + actionResult = this._chainOrCall(actionResult, () => this._actionHandler(this.processedArgs)); + if (this.parent) { + actionResult = this._chainOrCall(actionResult, () => { + this.parent.emit(commandEvent, operands, unknown); // legacy + }); + } + actionResult = this._chainOrCallHooks(actionResult, 'postAction'); + return actionResult; + } + if (this.parent && this.parent.listenerCount(commandEvent)) { + checkForUnknownOptions(); + this._processArguments(); + this.parent.emit(commandEvent, operands, unknown); // legacy + } else if (operands.length) { + if (this._findCommand('*')) { // legacy default command + return this._dispatchSubcommand('*', operands, unknown); + } + if (this.listenerCount('command:*')) { + // skip option check, emit event for possible misspelling suggestion + this.emit('command:*', operands, unknown); + } else if (this.commands.length) { + this.unknownCommand(); + } else { + checkForUnknownOptions(); + this._processArguments(); + } } else if (this.commands.length) { - this.unknownCommand(); + checkForUnknownOptions(); + // This command has subcommands and nothing hooked up at this level, so display help (and exit). + this.help({ error: true }); } else { checkForUnknownOptions(); this._processArguments(); + // fall through for caller to handle after calling .parse() } - } else if (this.commands.length) { - checkForUnknownOptions(); - // This command has subcommands and nothing hooked up at this level, so display help (and exit). - this.help({ error: true }); - } else { - checkForUnknownOptions(); - this._processArguments(); - // fall through for caller to handle after calling .parse() + } finally { + this._asyncParsing = undefined; } }