diff --git a/lib/command.js b/lib/command.js index 907962f56..131a145ac 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1271,7 +1271,13 @@ Expecting one of '${allowedValues.join("', '")}'`); 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 + // Run the help for default command from parent rather than passing to default command + const helpOptionProvided = this._hasHelpOption && unknown.find( + arg => arg === this._helpOption.long || arg === this._helpOption.short + ); + if (helpOptionProvided) { + this.emit('option:' + this._helpOption.name()); + } return this._dispatchSubcommand(this._defaultCommandName, operands, unknown); } if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) { @@ -1279,13 +1285,12 @@ Expecting one of '${allowedValues.join("', '")}'`); 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) { + if (unknown.length > 0) { this.unknownOption(parsed.unknown[0]); } }; @@ -1445,6 +1450,7 @@ Expecting one of '${allowedValues.join("', '")}'`); // parse options let activeVariadicOption = null; + let subcommandEncountered = false; while (args.length) { const arg = args.shift(); @@ -1462,25 +1468,30 @@ Expecting one of '${allowedValues.join("', '")}'`); activeVariadicOption = null; if (maybeOption(arg)) { - const option = this._findOption(arg); - // recognised option, call listener to assign value with possible custom processing - if (option) { - if (option.required) { - const value = args.shift(); - if (value === undefined) this.optionMissingArgument(option); - this.emit(`option:${option.name()}`, value); - } else if (option.optional) { - let value = null; - // historical behaviour is optional value is following arg unless an option - if (args.length > 0 && !maybeOption(args[0])) { - value = args.shift(); + const isHelpOption = this._hasHelpOption && this._helpOption.is(arg); + // Help option is always positional, skip when encountered after subcommand. + if (!(isHelpOption && subcommandEncountered)) { + // Options added via .option() / .addOption() have precedence over help option. + const option = this._findOption(arg) ?? (isHelpOption && this._helpOption); + // recognised option, call listener to assign value with possible custom processing + if (option) { + if (option.required) { + const value = args.shift(); + if (value === undefined) this.optionMissingArgument(option); + this.emit(`option:${option.name()}`, value); + } else if (option.optional) { + let value = null; + // historical behaviour is optional value is following arg unless an option + if (args.length > 0 && !maybeOption(args[0])) { + value = args.shift(); + } + this.emit(`option:${option.name()}`, value); + } else { // boolean flag + this.emit(`option:${option.name()}`); } - this.emit(`option:${option.name()}`, value); - } else { // boolean flag - this.emit(`option:${option.name()}`); + activeVariadicOption = option.variadic ? option : null; + continue; } - activeVariadicOption = option.variadic ? option : null; - continue; } } @@ -1511,34 +1522,43 @@ Expecting one of '${allowedValues.join("', '")}'`); } // Not a recognised option by this command. - // Might be a command-argument, or subcommand option, or unknown option, or help command or option. - - // An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands. - if (maybeOption(arg)) { - dest = unknown; - } - - // If using positionalOptions, stop processing our options at subcommand. - if ((this._enablePositionalOptions || this._passThroughOptions) && operands.length === 0 && unknown.length === 0) { + // Might be a subcommand, or command-argument, or help option encountered after subcommand, or subcommand option, or unknown option. + + const allArgsConsumedAsOptionsSoFar = operands.length === 0 && unknown.length === 0; + if (!subcommandEncountered && allArgsConsumedAsOptionsSoFar) { + subcommandEncountered = true; // reset to false later if arg is not a subcommand + const stopAtSubcommand = ( + this._enablePositionalOptions || this._passThroughOptions + ); if (this._findCommand(arg)) { - operands.push(arg); - if (args.length > 0) unknown.push(...args); - break; + if (stopAtSubcommand) { + operands.push(arg); + unknown.push(...args); + break; + } } else if (arg === this._helpCommandName && this._hasImplicitHelpCommand()) { - operands.push(arg); - if (args.length > 0) operands.push(...args); - break; + if (stopAtSubcommand) { + operands.push(arg, ...args); + break; + } } else if (this._defaultCommandName) { - unknown.push(arg); - if (args.length > 0) unknown.push(...args); - break; + if (stopAtSubcommand) { + unknown.push(arg, ...args); + break; + } + } else { + subcommandEncountered = false; } } - // If using passThroughOptions, stop processing options at first command-argument. + // An unknown option means further arguments also classified as unknown so can be reprocessed by subcommands. + if (maybeOption(arg)) { + dest = unknown; + } + + // If using passThroughOptions, stop processing options at first command-argument / unknown option. if (this._passThroughOptions) { - dest.push(arg); - if (args.length > 0) dest.push(...args); + dest.push(arg, ...args); break; } @@ -2059,9 +2079,12 @@ Expecting one of '${allowedValues.join("', '")}'`); this._hasHelpOption = true; this._helpFlags = flags = flags || this._helpFlags; this._helpDescription = description = description || this._helpDescription; - this._helpOption = this.createOption(flags, description); - + this.on('option:' + this._helpOption.name(), () => { + this.outputHelp(); + // (Do not have all displayed text available so only passing placeholder.) + this._exit(0, 'commander.helpDisplayed', '(outputHelp)'); + }); return this; } @@ -2116,23 +2139,6 @@ Expecting one of '${allowedValues.join("', '")}'`); } } -/** - * Output help information if help flags specified - * - * @param {Command} cmd - command to output help for - * @param {Array} args - array of options to search for help flags - * @api private - */ - -function outputHelpIfRequested(cmd, args) { - const helpOption = cmd._hasHelpOption && args.find(arg => arg === cmd._helpOption.long || arg === cmd._helpOption.short); - if (helpOption) { - cmd.outputHelp(); - // (Do not have all displayed text available so only passing placeholder.) - cmd._exit(0, 'commander.helpDisplayed', '(outputHelp)'); - } -} - /** * Scan arguments and increment port number for inspect calls (to avoid conflicts when spawning new command). *