From ad30296b83550d36e668d5eb5b234eacaac7adb7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 15 May 2021 16:56:54 +1200 Subject: [PATCH 1/6] Simplify typings by removing default export of program --- typings/index.d.ts | 1338 ++++++++++++++++++++------------------- typings/index.test-d.ts | 9 +- 2 files changed, 679 insertions(+), 668 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 668790ba8..60924557e 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -5,682 +5,690 @@ /* eslint-disable @typescript-eslint/method-signature-style */ /* eslint-disable @typescript-eslint/no-explicit-any */ -declare namespace commander { - - interface CommanderError extends Error { - code: string; - exitCode: number; - message: string; - nestedError?: string; - } - type CommanderErrorConstructor = new (exitCode: number, code: string, message: string) => CommanderError; - - // eslint-disable-next-line @typescript-eslint/no-empty-interface - interface InvalidArgumentError extends CommanderError { - } - type InvalidArgumentErrorConstructor = new (message: string) => InvalidArgumentError; - - interface Argument { - description: string; - required: boolean; - variadic: boolean; - - /** - * Return argument name. - */ - name(): string; - } - - interface Option { - flags: string; - description: string; - - required: boolean; // A value must be supplied when the option is specified. - optional: boolean; // A value is optional when the option is specified. - variadic: boolean; - mandatory: boolean; // The option must have a value after parsing, which usually means it must be specified on command line. - optionFlags: string; - short?: string; - long?: string; - negate: boolean; - defaultValue?: any; - defaultValueDescription?: string; - parseArg?: (value: string, previous: T) => T; - hidden: boolean; - argChoices?: string[]; - - /** - * Set the default value, and optionally supply the description to be displayed in the help. - */ - default(value: any, description?: string): this; - - /** - * Calculate the full description, including defaultValue etc. - */ - fullDescription(): string; - - /** - * Set the custom handler for processing CLI option arguments into option values. - */ - argParser(fn: (value: string, previous: T) => T): this; - - /** - * Whether the option is mandatory and must have a value after parsing. - */ - makeOptionMandatory(mandatory?: boolean): this; - - /** - * Hide option in help. - */ - hideHelp(hide?: boolean): this; - - /** - * Validation of option argument failed. - * Intended for use from custom argument processing functions. - */ - argumentRejected(messsage: string): never; - - /** - * Only allow option value to be one of choices. - */ - choices(values: string[]): this; +export class CommanderError extends Error { + code: string; + exitCode: number; + message: string; + nestedError?: string; + + /** + * Constructs the CommanderError class + * @param {number} exitCode suggested exit code which could be used with process.exit + * @param {string} code an id string representing the error + * @param {string} message human-readable description of the error + * @constructor + */ + constructor(exitCode: number, code: string, message: string); +} - /** - * Return option name. - */ +export class InvalidArgumentError extends CommanderError { + /** + * Constructs the InvalidArgumentError class + * @param {string} [message] explanation of why argument is invalid + * @constructor + */ + constructor(message: string); +} +export { InvalidArgumentError as InvalidOptionArgumentError }; // deprecated old name + +export class Argument { + description: string; + required: boolean; + variadic: boolean; + + /** + * Initialize a new command argument with the given name and description. + * The default is that the argument is required, and you can explicitly + * indicate this with <> around the name. Put [] around the name for an optional argument. + * + * @param {string} name + * @param {string} [description] + */ + constructor(arg: string, description?: string); + + /** + * Return argument name. + */ name(): string; - - /** - * Return option name, in a camelcase format that can be used - * as a object attribute key. - */ - attributeName(): string; } - type OptionConstructor = new (flags: string, description?: string) => Option; - type ArgumentConstructor = new (arg: string, description?: string) => Argument; - - interface Help { - /** output helpWidth, long lines are wrapped to fit */ - helpWidth?: number; - sortSubcommands: boolean; - sortOptions: boolean; - - /** Get the command term to show in the list of subcommands. */ - subcommandTerm(cmd: Command): string; - /** Get the command description to show in the list of subcommands. */ - subcommandDescription(cmd: Command): string; - /** Get the option term to show in the list of options. */ - optionTerm(option: Option): string; - /** Get the option description to show in the list of options. */ - optionDescription(option: Option): string; - /** Get the argument term to show in the list of arguments. */ - argumentTerm(argument: Argument): string; - /** Get the argument description to show in the list of arguments. */ - argumentDescription(argument: Argument): string; - - /** Get the command usage to be displayed at the top of the built-in help. */ - commandUsage(cmd: Command): string; - /** Get the description for the command. */ - commandDescription(cmd: Command): string; - - /** Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one. */ - visibleCommands(cmd: Command): Command[]; - /** Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one. */ - visibleOptions(cmd: Command): Option[]; - /** Get an array of the arguments which have descriptions. */ - visibleArguments(cmd: Command): Argument[]; - - /** Get the longest command term length. */ - longestSubcommandTermLength(cmd: Command, helper: Help): number; - /** Get the longest option term length. */ - longestOptionTermLength(cmd: Command, helper: Help): number; - /** Get the longest argument term length. */ - longestArgumentTermLength(cmd: Command, helper: Help): number; - /** Calculate the pad width from the maximum term length. */ - padWidth(cmd: Command, helper: Help): number; - - /** - * Wrap the given string to width characters per line, with lines after the first indented. - * Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted. - */ - wrap(str: string, width: number, indent: number, minColumnWidth?: number): string; - - /** Generate the built-in help text. */ - formatHelp(cmd: Command, helper: Help): string; - } - type HelpConstructor = new () => Help; - type HelpConfiguration = Partial; - - interface ParseOptions { - from: 'node' | 'electron' | 'user'; - } - interface HelpContext { // optional parameter for .help() and .outputHelp() - error: boolean; - } - interface AddHelpTextContext { // passed to text function used with .addHelpText() - error: boolean; - command: Command; - } - interface OutputConfiguration { - writeOut?(str: string): void; - writeErr?(str: string): void; - getOutHelpWidth?(): number; - getErrHelpWidth?(): number; - outputError?(str: string, write: (str: string) => void): void; - - } - - type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll'; - - interface OptionValues { - [key: string]: any; - } - - interface Command { - args: string[]; - commands: Command[]; - parent: Command | null; - - /** - * Set the program version to `str`. - * - * This method auto-registers the "-V, --version" flag - * which will print the version number when passed. - * - * You can optionally supply the flags and description to override the defaults. - */ - version(str: string, flags?: string, description?: string): this; - - /** - * Define a command, implemented using an action handler. - * - * @remarks - * The command description is supplied using `.description`, not as a parameter to `.command`. - * - * @example - * ```ts - * program - * .command('clone [destination]') - * .description('clone a repository into a newly created directory') - * .action((source, destination) => { - * console.log('clone command called'); - * }); - * ``` - * - * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` - * @param opts - configuration options - * @returns new command - */ - command(nameAndArgs: string, opts?: CommandOptions): ReturnType; - /** - * Define a command, implemented in a separate executable file. - * - * @remarks - * The command description is supplied as the second parameter to `.command`. - * - * @example - * ```ts - * program - * .command('start ', 'start named service') - * .command('stop [service]', 'stop named service, or all if no name supplied'); - * ``` - * - * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` - * @param description - description of executable command - * @param opts - configuration options - * @returns `this` command for chaining - */ - command(nameAndArgs: string, description: string, opts?: commander.ExecutableCommandOptions): this; - - /** - * Factory routine to create a new unattached command. - * - * See .command() for creating an attached subcommand, which uses this routine to - * create the command. You can override createCommand to customise subcommands. - */ - createCommand(name?: string): Command; - - /** - * Add a prepared subcommand. - * - * See .command() for creating an attached subcommand which inherits settings from its parent. - * - * @returns `this` command for chaining - */ - addCommand(cmd: Command, opts?: CommandOptions): this; - - /** - * Factory routine to create a new unattached argument. - * - * See .argument() for creating an attached argument, which uses this routine to - * create the argument. You can override createArgument to return a custom argument. - */ - createArgument(name: string, description?: string): Argument; - - /** - * Define argument syntax for command. - * - * The default is that the argument is required, and you can explicitly - * indicate this with <> around the name. Put [] around the name for an optional argument. - * - * @example - * - * program.argument(''); - * program.argument('[output-file]'); - * - * @returns `this` command for chaining - */ - argument(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; - argument(name: string, description?: string, defaultValue?: any): this; - - /** - * Define argument syntax for command, adding a prepared argument. - * - * @returns `this` command for chaining - */ - addArgument(arg: Argument): this; - - /** - * Define argument syntax for command, adding multiple at once (without descriptions). - * - * See also .argument(). - * - * @example - * - * program.arguments(' [env]'); - * - * @returns `this` command for chaining - */ - arguments(names: string): this; - - /** - * Override default decision whether to add implicit help command. - * - * addHelpCommand() // force on - * addHelpCommand(false); // force off - * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details - * - * @returns `this` command for chaining - */ - addHelpCommand(enableOrNameAndArgs?: string | boolean, description?: string): this; - - /** - * Register callback to use as replacement for calling process.exit. - */ - exitOverride(callback?: (err: CommanderError) => never|void): this; - - /** - * You can customise the help with a subclass of Help by overriding createHelp, - * or by overriding Help properties using configureHelp(). - */ - createHelp(): Help; - - /** - * You can customise the help by overriding Help properties using configureHelp(), - * or with a subclass of Help by overriding createHelp(). - */ - configureHelp(configuration: HelpConfiguration): this; - /** Get configuration */ - configureHelp(): HelpConfiguration; - - /** - * The default output goes to stdout and stderr. You can customise this for special - * applications. You can also customise the display of errors by overriding outputError. - * - * The configuration properties are all functions: - * - * // functions to change where being written, stdout and stderr - * writeOut(str) - * writeErr(str) - * // matching functions to specify width for wrapping help - * getOutHelpWidth() - * getErrHelpWidth() - * // functions based on what is being written out - * outputError(str, write) // used for displaying errors, and not used for displaying help - */ - configureOutput(configuration: OutputConfiguration): this; - /** Get configuration */ - configureOutput(): OutputConfiguration; - - /** - * Register callback `fn` for the command. - * - * @example - * program - * .command('help') - * .description('display verbose help') - * .action(function() { - * // output help here - * }); - * - * @returns `this` command for chaining - */ - action(fn: (...args: any[]) => void | Promise): this; - - /** - * Define option with `flags`, `description` and optional - * coercion `fn`. - * - * The `flags` string contains the short and/or long flags, - * separated by comma, a pipe or space. The following are all valid - * all will output this way when `--help` is used. - * - * "-p, --pepper" - * "-p|--pepper" - * "-p --pepper" - * - * @example - * // simple boolean defaulting to false - * program.option('-p, --pepper', 'add pepper'); - * - * --pepper - * program.pepper - * // => Boolean - * - * // simple boolean defaulting to true - * program.option('-C, --no-cheese', 'remove cheese'); - * - * program.cheese - * // => true - * - * --no-cheese - * program.cheese - * // => false - * - * // required argument - * program.option('-C, --chdir ', 'change the working directory'); - * - * --chdir /tmp - * program.chdir - * // => "/tmp" - * - * // optional argument - * program.option('-c, --cheese [type]', 'add cheese [marble]'); - * - * @returns `this` command for chaining - */ - option(flags: string, description?: string, defaultValue?: string | boolean): this; - option(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; - /** @deprecated since v7, instead use choices or a custom function */ - option(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this; - - /** - * Define a required option, which must have a value after parsing. This usually means - * the option must be specified on the command line. (Otherwise the same as .option().) - * - * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. - */ - requiredOption(flags: string, description?: string, defaultValue?: string | boolean): this; - requiredOption(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; - /** @deprecated since v7, instead use choices or a custom function */ - requiredOption(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this; - - /** - * Factory routine to create a new unattached option. - * - * See .option() for creating an attached option, which uses this routine to - * create the option. You can override createOption to return a custom option. - */ - - createOption(flags: string, description?: string): Option; - - /** - * Add a prepared Option. - * - * See .option() and .requiredOption() for creating and attaching an option in a single call. - */ - addOption(option: Option): this; - - /** - * Whether to store option values as properties on command object, - * or store separately (specify false). In both cases the option values can be accessed using .opts(). - * - * @returns `this` command for chaining - */ - storeOptionsAsProperties(): this & OptionValues; - storeOptionsAsProperties(storeAsProperties: true): this & OptionValues; - storeOptionsAsProperties(storeAsProperties?: boolean): this; - - /** - * Alter parsing of short flags with optional values. - * - * @example - * // for `.option('-f,--flag [value]'): - * .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour - * .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b` - * - * @returns `this` command for chaining - */ - combineFlagAndOptionalValue(combine?: boolean): this; - - /** - * Allow unknown options on the command line. - * - * @returns `this` command for chaining - */ - allowUnknownOption(allowUnknown?: boolean): this; - - /** - * Allow excess command-arguments on the command line. Pass false to make excess arguments an error. - * - * @returns `this` command for chaining - */ - allowExcessArguments(allowExcess?: boolean): this; - - /** - * Enable positional options. Positional means global options are specified before subcommands which lets - * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions. - * - * The default behaviour is non-positional and global options may appear anywhere on the command line. - * - * @returns `this` command for chaining - */ - enablePositionalOptions(positional?: boolean): this; - - /** - * Pass through options that come after command-arguments rather than treat them as command-options, - * so actual command-options come before command-arguments. Turning this on for a subcommand requires - * positional options to have been enabled on the program (parent commands). - * - * The default behaviour is non-positional and options may appear before or after command-arguments. - * - * @returns `this` command for chaining - */ - passThroughOptions(passThrough?: boolean): this; - - /** - * Parse `argv`, setting options and invoking commands when defined. - * - * The default expectation is that the arguments are from node and have the application as argv[0] - * and the script being run in argv[1], with user parameters after that. - * - * Examples: - * - * program.parse(process.argv); - * program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions - * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0] - * - * @returns `this` command for chaining - */ - parse(argv?: string[], options?: ParseOptions): this; - - /** - * Parse `argv`, setting options and invoking commands when defined. - * - * Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise. - * - * The default expectation is that the arguments are from node and have the application as argv[0] - * and the script being run in argv[1], with user parameters after that. - * - * Examples: - * - * program.parseAsync(process.argv); - * program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions - * program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0] - * - * @returns Promise - */ - parseAsync(argv?: string[], options?: ParseOptions): Promise; - - /** - * Parse options from `argv` removing known options, - * and return argv split into operands and unknown arguments. - * - * @example - * argv => operands, unknown - * --known kkk op => [op], [] - * op --known kkk => [op], [] - * sub --unknown uuu op => [sub], [--unknown uuu op] - * sub -- --unknown uuu op => [sub --unknown uuu op], [] - */ - parseOptions(argv: string[]): commander.ParseOptionsResult; - - /** - * Return an object containing options as key-value pairs - */ - opts(): OptionValues; - - /** - * Set the description. - * - * @returns `this` command for chaining - */ - - description(str: string): this; - /** @deprecated since v8, instead use .argument to add command argument with description */ - description(str: string, argsDescription: {[argName: string]: string}): this; - /** - * Get the description. - */ - description(): string; - - /** - * Set an alias for the command. - * - * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help. - * - * @returns `this` command for chaining - */ - alias(alias: string): this; - /** - * Get alias for the command. - */ - alias(): string; - /** - * Set aliases for the command. - * - * Only the first alias is shown in the auto-generated help. - * - * @returns `this` command for chaining - */ - aliases(aliases: string[]): this; - /** - * Get aliases for the command. - */ - aliases(): string[]; - - /** - * Set the command usage. - * - * @returns `this` command for chaining - */ - usage(str: string): this; - /** - * Get the command usage. - */ - usage(): string; - - /** - * Set the name of the command. - * - * @returns `this` command for chaining - */ - name(str: string): this; - /** - * Get the name of the command. - */ - name(): string; - - /** - * Output help information for this command. - * - * Outputs built-in help, and custom text added using `.addHelpText()`. - * - */ - outputHelp(context?: HelpContext): void; - /** @deprecated since v7 */ - outputHelp(cb?: (str: string) => string): void; - - /** - * Return command help documentation. - */ - helpInformation(context?: HelpContext): string; +export class Option { + flags: string; + description: string; + + required: boolean; // A value must be supplied when the option is specified. + optional: boolean; // A value is optional when the option is specified. + variadic: boolean; + mandatory: boolean; // The option must have a value after parsing, which usually means it must be specified on command line. + optionFlags: string; + short?: string; + long?: string; + negate: boolean; + defaultValue?: any; + defaultValueDescription?: string; + parseArg?: (value: string, previous: T) => T; + hidden: boolean; + argChoices?: string[]; + + constructor(flags: string, description?: string); + + /** + * Set the default value, and optionally supply the description to be displayed in the help. + */ + default(value: any, description?: string): this; + + /** + * Calculate the full description, including defaultValue etc. + */ + fullDescription(): string; + + /** + * Set the custom handler for processing CLI option arguments into option values. + */ + argParser(fn: (value: string, previous: T) => T): this; + + /** + * Whether the option is mandatory and must have a value after parsing. + */ + makeOptionMandatory(mandatory?: boolean): this; + + /** + * Hide option in help. + */ + hideHelp(hide?: boolean): this; + + /** + * Validation of option argument failed. + * Intended for use from custom argument processing functions. + */ + argumentRejected(messsage: string): never; + + /** + * Only allow option value to be one of choices. + */ + choices(values: string[]): this; + + /** + * Return option name. + */ + name(): string; + + /** + * Return option name, in a camelcase format that can be used + * as a object attribute key. + */ + attributeName(): string; +} - /** - * You can pass in flags and a description to override the help - * flags and help description for your command. Pass in false - * to disable the built-in help option. - */ - helpOption(flags?: string | boolean, description?: string): this; +export class Help { + /** output helpWidth, long lines are wrapped to fit */ + helpWidth?: number; + sortSubcommands: boolean; + sortOptions: boolean; + + constructor(); + + /** Get the command term to show in the list of subcommands. */ + subcommandTerm(cmd: Command): string; + /** Get the command description to show in the list of subcommands. */ + subcommandDescription(cmd: Command): string; + /** Get the option term to show in the list of options. */ + optionTerm(option: Option): string; + /** Get the option description to show in the list of options. */ + optionDescription(option: Option): string; + /** Get the argument term to show in the list of arguments. */ + argumentTerm(argument: Argument): string; + /** Get the argument description to show in the list of arguments. */ + argumentDescription(argument: Argument): string; + + /** Get the command usage to be displayed at the top of the built-in help. */ + commandUsage(cmd: Command): string; + /** Get the description for the command. */ + commandDescription(cmd: Command): string; + + /** Get an array of the visible subcommands. Includes a placeholder for the implicit help command, if there is one. */ + visibleCommands(cmd: Command): Command[]; + /** Get an array of the visible options. Includes a placeholder for the implicit help option, if there is one. */ + visibleOptions(cmd: Command): Option[]; + /** Get an array of the arguments which have descriptions. */ + visibleArguments(cmd: Command): Argument[]; + + /** Get the longest command term length. */ + longestSubcommandTermLength(cmd: Command, helper: Help): number; + /** Get the longest option term length. */ + longestOptionTermLength(cmd: Command, helper: Help): number; + /** Get the longest argument term length. */ + longestArgumentTermLength(cmd: Command, helper: Help): number; + /** Calculate the pad width from the maximum term length. */ + padWidth(cmd: Command, helper: Help): number; + + /** + * Wrap the given string to width characters per line, with lines after the first indented. + * Do not wrap if insufficient room for wrapping (minColumnWidth), or string is manually formatted. + */ + wrap(str: string, width: number, indent: number, minColumnWidth?: number): string; + + /** Generate the built-in help text. */ + formatHelp(cmd: Command, helper: Help): string; +} +export type HelpConfiguration = Partial; - /** - * Output help information and exit. - * - * Outputs built-in help, and custom text added using `.addHelpText()`. - */ - help(context?: HelpContext): never; - /** @deprecated since v7 */ - help(cb?: (str: string) => string): never; +export interface ParseOptions { + from: 'node' | 'electron' | 'user'; +} +export interface HelpContext { // optional parameter for .help() and .outputHelp() + error: boolean; +} +export interface AddHelpTextContext { // passed to text function used with .addHelpText() + error: boolean; + command: Command; +} +export interface OutputConfiguration { + writeOut?(str: string): void; + writeErr?(str: string): void; + getOutHelpWidth?(): number; + getErrHelpWidth?(): number; + outputError?(str: string, write: (str: string) => void): void; - /** - * Add additional text to be displayed with the built-in help. - * - * Position is 'before' or 'after' to affect just this command, - * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. - */ - addHelpText(position: AddHelpTextPosition, text: string): this; - addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string): this; +} - /** - * Add a listener (callback) for when events occur. (Implemented using EventEmitter.) - */ - on(event: string | symbol, listener: (...args: any[]) => void): this; - } - type CommandConstructor = new (name?: string) => Command; +type AddHelpTextPosition = 'beforeAll' | 'before' | 'after' | 'afterAll'; - interface CommandOptions { - hidden?: boolean; - isDefault?: boolean; - /** @deprecated since v7, replaced by hidden */ - noHelp?: boolean; - } - interface ExecutableCommandOptions extends CommandOptions { - executableFile?: string; - } +export interface OptionValues { + [key: string]: any; +} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface ParseOptionsResult { - operands: string[]; - unknown: string[]; - } +export class Command { + args: string[]; + commands: Command[]; + parent: Command | null; + + constructor(name?: string); + + /** + * Set the program version to `str`. + * + * This method auto-registers the "-V, --version" flag + * which will print the version number when passed. + * + * You can optionally supply the flags and description to override the defaults. + */ + version(str: string, flags?: string, description?: string): this; + + /** + * Define a command, implemented using an action handler. + * + * @remarks + * The command description is supplied using `.description`, not as a parameter to `.command`. + * + * @example + * ```ts + * program + * .command('clone [destination]') + * .description('clone a repository into a newly created directory') + * .action((source, destination) => { + * console.log('clone command called'); + * }); + * ``` + * + * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` + * @param opts - configuration options + * @returns new command + */ + command(nameAndArgs: string, opts?: CommandOptions): ReturnType; + /** + * Define a command, implemented in a separate executable file. + * + * @remarks + * The command description is supplied as the second parameter to `.command`. + * + * @example + * ```ts + * program + * .command('start ', 'start named service') + * .command('stop [service]', 'stop named service, or all if no name supplied'); + * ``` + * + * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` + * @param description - description of executable command + * @param opts - configuration options + * @returns `this` command for chaining + */ + command(nameAndArgs: string, description: string, opts?: ExecutableCommandOptions): this; + + /** + * Factory routine to create a new unattached command. + * + * See .command() for creating an attached subcommand, which uses this routine to + * create the command. You can override createCommand to customise subcommands. + */ + createCommand(name?: string): Command; + + /** + * Add a prepared subcommand. + * + * See .command() for creating an attached subcommand which inherits settings from its parent. + * + * @returns `this` command for chaining + */ + addCommand(cmd: Command, opts?: CommandOptions): this; + + /** + * Factory routine to create a new unattached argument. + * + * See .argument() for creating an attached argument, which uses this routine to + * create the argument. You can override createArgument to return a custom argument. + */ + createArgument(name: string, description?: string): Argument; + + /** + * Define argument syntax for command. + * + * The default is that the argument is required, and you can explicitly + * indicate this with <> around the name. Put [] around the name for an optional argument. + * + * @example + * + * program.argument(''); + * program.argument('[output-file]'); + * + * @returns `this` command for chaining + */ + argument(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; + argument(name: string, description?: string, defaultValue?: any): this; + + /** + * Define argument syntax for command, adding a prepared argument. + * + * @returns `this` command for chaining + */ + addArgument(arg: Argument): this; + + /** + * Define argument syntax for command, adding multiple at once (without descriptions). + * + * See also .argument(). + * + * @example + * + * program.arguments(' [env]'); + * + * @returns `this` command for chaining + */ + arguments(names: string): this; + + /** + * Override default decision whether to add implicit help command. + * + * addHelpCommand() // force on + * addHelpCommand(false); // force off + * addHelpCommand('help [cmd]', 'display help for [cmd]'); // force on with custom details + * + * @returns `this` command for chaining + */ + addHelpCommand(enableOrNameAndArgs?: string | boolean, description?: string): this; + + /** + * Register callback to use as replacement for calling process.exit. + */ + exitOverride(callback?: (err: CommanderError) => never|void): this; + + /** + * You can customise the help with a subclass of Help by overriding createHelp, + * or by overriding Help properties using configureHelp(). + */ + createHelp(): Help; + + /** + * You can customise the help by overriding Help properties using configureHelp(), + * or with a subclass of Help by overriding createHelp(). + */ + configureHelp(configuration: HelpConfiguration): this; + /** Get configuration */ + configureHelp(): HelpConfiguration; + + /** + * The default output goes to stdout and stderr. You can customise this for special + * applications. You can also customise the display of errors by overriding outputError. + * + * The configuration properties are all functions: + * + * // functions to change where being written, stdout and stderr + * writeOut(str) + * writeErr(str) + * // matching functions to specify width for wrapping help + * getOutHelpWidth() + * getErrHelpWidth() + * // functions based on what is being written out + * outputError(str, write) // used for displaying errors, and not used for displaying help + */ + configureOutput(configuration: OutputConfiguration): this; + /** Get configuration */ + configureOutput(): OutputConfiguration; + + /** + * Register callback `fn` for the command. + * + * @example + * program + * .command('help') + * .description('display verbose help') + * .action(function() { + * // output help here + * }); + * + * @returns `this` command for chaining + */ + action(fn: (...args: any[]) => void | Promise): this; + + /** + * Define option with `flags`, `description` and optional + * coercion `fn`. + * + * The `flags` string contains the short and/or long flags, + * separated by comma, a pipe or space. The following are all valid + * all will output this way when `--help` is used. + * + * "-p, --pepper" + * "-p|--pepper" + * "-p --pepper" + * + * @example + * // simple boolean defaulting to false + * program.option('-p, --pepper', 'add pepper'); + * + * --pepper + * program.pepper + * // => Boolean + * + * // simple boolean defaulting to true + * program.option('-C, --no-cheese', 'remove cheese'); + * + * program.cheese + * // => true + * + * --no-cheese + * program.cheese + * // => false + * + * // required argument + * program.option('-C, --chdir ', 'change the working directory'); + * + * --chdir /tmp + * program.chdir + * // => "/tmp" + * + * // optional argument + * program.option('-c, --cheese [type]', 'add cheese [marble]'); + * + * @returns `this` command for chaining + */ + option(flags: string, description?: string, defaultValue?: string | boolean): this; + option(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; + /** @deprecated since v7, instead use choices or a custom function */ + option(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this; + + /** + * Define a required option, which must have a value after parsing. This usually means + * the option must be specified on the command line. (Otherwise the same as .option().) + * + * The `flags` string contains the short and/or long flags, separated by comma, a pipe or space. + */ + requiredOption(flags: string, description?: string, defaultValue?: string | boolean): this; + requiredOption(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; + /** @deprecated since v7, instead use choices or a custom function */ + requiredOption(flags: string, description: string, regexp: RegExp, defaultValue?: string | boolean): this; + + /** + * Factory routine to create a new unattached option. + * + * See .option() for creating an attached option, which uses this routine to + * create the option. You can override createOption to return a custom option. + */ + + createOption(flags: string, description?: string): Option; + + /** + * Add a prepared Option. + * + * See .option() and .requiredOption() for creating and attaching an option in a single call. + */ + addOption(option: Option): this; + + /** + * Whether to store option values as properties on command object, + * or store separately (specify false). In both cases the option values can be accessed using .opts(). + * + * @returns `this` command for chaining + */ + storeOptionsAsProperties(): this & OptionValues; + storeOptionsAsProperties(storeAsProperties: true): this & OptionValues; + storeOptionsAsProperties(storeAsProperties?: boolean): this; + + /** + * Alter parsing of short flags with optional values. + * + * @example + * // for `.option('-f,--flag [value]'): + * .combineFlagAndOptionalValue(true) // `-f80` is treated like `--flag=80`, this is the default behaviour + * .combineFlagAndOptionalValue(false) // `-fb` is treated like `-f -b` + * + * @returns `this` command for chaining + */ + combineFlagAndOptionalValue(combine?: boolean): this; + + /** + * Allow unknown options on the command line. + * + * @returns `this` command for chaining + */ + allowUnknownOption(allowUnknown?: boolean): this; + + /** + * Allow excess command-arguments on the command line. Pass false to make excess arguments an error. + * + * @returns `this` command for chaining + */ + allowExcessArguments(allowExcess?: boolean): this; + + /** + * Enable positional options. Positional means global options are specified before subcommands which lets + * subcommands reuse the same option names, and also enables subcommands to turn on passThroughOptions. + * + * The default behaviour is non-positional and global options may appear anywhere on the command line. + * + * @returns `this` command for chaining + */ + enablePositionalOptions(positional?: boolean): this; + + /** + * Pass through options that come after command-arguments rather than treat them as command-options, + * so actual command-options come before command-arguments. Turning this on for a subcommand requires + * positional options to have been enabled on the program (parent commands). + * + * The default behaviour is non-positional and options may appear before or after command-arguments. + * + * @returns `this` command for chaining + */ + passThroughOptions(passThrough?: boolean): this; + + /** + * Parse `argv`, setting options and invoking commands when defined. + * + * The default expectation is that the arguments are from node and have the application as argv[0] + * and the script being run in argv[1], with user parameters after that. + * + * Examples: + * + * program.parse(process.argv); + * program.parse(); // implicitly use process.argv and auto-detect node vs electron conventions + * program.parse(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0] + * + * @returns `this` command for chaining + */ + parse(argv?: string[], options?: ParseOptions): this; + + /** + * Parse `argv`, setting options and invoking commands when defined. + * + * Use parseAsync instead of parse if any of your action handlers are async. Returns a Promise. + * + * The default expectation is that the arguments are from node and have the application as argv[0] + * and the script being run in argv[1], with user parameters after that. + * + * Examples: + * + * program.parseAsync(process.argv); + * program.parseAsync(); // implicitly use process.argv and auto-detect node vs electron conventions + * program.parseAsync(my-args, { from: 'user' }); // just user supplied arguments, nothing special about argv[0] + * + * @returns Promise + */ + parseAsync(argv?: string[], options?: ParseOptions): Promise; + + /** + * Parse options from `argv` removing known options, + * and return argv split into operands and unknown arguments. + * + * @example + * argv => operands, unknown + * --known kkk op => [op], [] + * op --known kkk => [op], [] + * sub --unknown uuu op => [sub], [--unknown uuu op] + * sub -- --unknown uuu op => [sub --unknown uuu op], [] + */ + parseOptions(argv: string[]): ParseOptionsResult; + + /** + * Return an object containing options as key-value pairs + */ + opts(): OptionValues; + + /** + * Set the description. + * + * @returns `this` command for chaining + */ + + description(str: string): this; + /** @deprecated since v8, instead use .argument to add command argument with description */ + description(str: string, argsDescription: {[argName: string]: string}): this; + /** + * Get the description. + */ + description(): string; + + /** + * Set an alias for the command. + * + * You may call more than once to add multiple aliases. Only the first alias is shown in the auto-generated help. + * + * @returns `this` command for chaining + */ + alias(alias: string): this; + /** + * Get alias for the command. + */ + alias(): string; + + /** + * Set aliases for the command. + * + * Only the first alias is shown in the auto-generated help. + * + * @returns `this` command for chaining + */ + aliases(aliases: string[]): this; + /** + * Get aliases for the command. + */ + aliases(): string[]; + + /** + * Set the command usage. + * + * @returns `this` command for chaining + */ + usage(str: string): this; + /** + * Get the command usage. + */ + usage(): string; + + /** + * Set the name of the command. + * + * @returns `this` command for chaining + */ + name(str: string): this; + /** + * Get the name of the command. + */ + name(): string; + + /** + * Output help information for this command. + * + * Outputs built-in help, and custom text added using `.addHelpText()`. + * + */ + outputHelp(context?: HelpContext): void; + /** @deprecated since v7 */ + outputHelp(cb?: (str: string) => string): void; + + /** + * Return command help documentation. + */ + helpInformation(context?: HelpContext): string; + + /** + * You can pass in flags and a description to override the help + * flags and help description for your command. Pass in false + * to disable the built-in help option. + */ + helpOption(flags?: string | boolean, description?: string): this; + + /** + * Output help information and exit. + * + * Outputs built-in help, and custom text added using `.addHelpText()`. + */ + help(context?: HelpContext): never; + /** @deprecated since v7 */ + help(cb?: (str: string) => string): never; + + /** + * Add additional text to be displayed with the built-in help. + * + * Position is 'before' or 'after' to affect just this command, + * and 'beforeAll' or 'afterAll' to affect this command and all its subcommands. + */ + addHelpText(position: AddHelpTextPosition, text: string): this; + addHelpText(position: AddHelpTextPosition, text: (context: AddHelpTextContext) => string): this; + + /** + * Add a listener (callback) for when events occur. (Implemented using EventEmitter.) + */ + on(event: string | symbol, listener: (...args: any[]) => void): this; +} - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface CommanderStatic extends Command { - program: Command; - Command: CommandConstructor; - Option: OptionConstructor; - Argument: ArgumentConstructor; - CommanderError: CommanderErrorConstructor; - InvalidArgumentError: InvalidArgumentErrorConstructor; - /** @deprecated since v8, replaced by InvalidArgumentError */ - InvalidOptionArgumentError: InvalidArgumentErrorConstructor; - Help: HelpConstructor; - } +export interface CommandOptions { + hidden?: boolean; + isDefault?: boolean; + /** @deprecated since v7, replaced by hidden */ + noHelp?: boolean; +} +export interface ExecutableCommandOptions extends CommandOptions { + executableFile?: string; +} +export interface ParseOptionsResult { + operands: string[]; + unknown: string[]; } -// Declaring namespace AND global -// eslint-disable-next-line @typescript-eslint/no-redeclare -declare const commander: commander.CommanderStatic; -export = commander; +export function createCommand(name?: string): Command; +export function createOption(flags: string, description?: string): Option; +export function createArgument(name: string, description?: string): Argument; + +export const program: Command; diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index f23dba79b..170b31965 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -1,13 +1,14 @@ import * as commander from './index'; -import {expectType, expectAssignable} from 'tsd'; +import {expectType} from 'tsd'; // We are are not just checking return types here, we are also implicitly checking that the expected syntax is allowed. const program: commander.Command = new commander.Command(); -// program.silly; // <-- Error, hurrah! +// @ts-expect-error Check that Command is strongly typed and does not allow arbitrary properties +program.silly; // <-- Error, hurrah! // Check for exported global Command object(s) -expectAssignable(commander); // legacy default export of program +// expectAssignable(commander); // legacy default export of program expectType(commander.program); // Check export classes and functions exist @@ -18,6 +19,8 @@ expectType(new commander.CommanderError(1, 'code', 'me expectType(new commander.InvalidArgumentError('message')); expectType(new commander.InvalidOptionArgumentError('message')); expectType(commander.createCommand()); +expectType(commander.createOption('--demo')); +expectType(commander.createArgument('')); // Command properties expectType(program.args); From 8cb926c618ea8ba44dcc7abafc8c3e6bd3be107b Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 15 May 2021 17:03:54 +1200 Subject: [PATCH 2/6] Switch any to unknown per lint suggestion --- typings/index.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 60924557e..8d834449c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -75,7 +75,7 @@ export class Option { /** * Set the default value, and optionally supply the description to be displayed in the help. */ - default(value: any, description?: string): this; + default(value: unknown, description?: string): this; /** * Calculate the full description, including defaultValue etc. @@ -295,7 +295,7 @@ export class Command { * @returns `this` command for chaining */ argument(flags: string, description: string, fn: (value: string, previous: T) => T, defaultValue?: T): this; - argument(name: string, description?: string, defaultValue?: any): this; + argument(name: string, description?: string, defaultValue?: unknown): this; /** * Define argument syntax for command, adding a prepared argument. From 01143dee029b0fa8dd9c1914cf0cd5930e9113f1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 16 May 2021 13:09:52 +1200 Subject: [PATCH 3/6] Add typings change to deprecated.md --- docs/deprecated.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/deprecated.md b/docs/deprecated.md index 148ba18b6..49118a000 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -42,7 +42,9 @@ const { Command } = require('commander'); comnst program = new Command() ``` -Removed from README in Commander v5. Deprecated from Commander v7. +- Removed from README in Commander v5. +- Deprecated from Commander v7. +- Removed from TypeScript declarations in Commander v8. ## Callback to .help() and .outputHelp() From 7d9e1df06e3ab1144735139d04aa80199666117a Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 16 May 2021 18:18:58 +1200 Subject: [PATCH 4/6] Make getOptionValue and setOptionValue public --- Readme.md | 5 ++++- index.js | 40 ++++++++++++++++++------------------ tests/command.chain.test.js | 6 ++++++ tests/options.getset.test.js | 24 ++++++++++++++++++++++ typings/index.d.ts | 10 +++++++++ typings/index.test-d.ts | 7 +++++++ 6 files changed, 71 insertions(+), 21 deletions(-) create mode 100644 tests/options.getset.test.js diff --git a/Readme.md b/Readme.md index 39c532f10..f3b2fe800 100644 --- a/Readme.md +++ b/Readme.md @@ -95,7 +95,10 @@ const program = new Command(); Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|'). -The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler. Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc. +The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler. +You can also use `.getOptionValue()` and `.setOptionValue()`. + +Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc. Multiple short flags may optionally be combined in a single argument following the dash: boolean flags, followed by a single option taking a value (possibly followed by the value). For example `-a -b -p 80` may be written as `-ab -p80` or even `-abp80`. diff --git a/index.js b/index.js index 0f7860c4a..862358cda 100644 --- a/index.js +++ b/index.js @@ -1094,11 +1094,11 @@ class Command extends EventEmitter { // when --no-foo we make sure default is true, unless a --foo option is already defined if (option.negate) { const positiveLongFlag = option.long.replace(/^--no-/, '--'); - defaultValue = this._findOption(positiveLongFlag) ? this._getOptionValue(name) : true; + defaultValue = this._findOption(positiveLongFlag) ? this.getOptionValue(name) : true; } // preassign only if we have a default if (defaultValue !== undefined) { - this._setOptionValue(name, defaultValue); + this.setOptionValue(name, defaultValue); } } @@ -1108,7 +1108,7 @@ class Command extends EventEmitter { // when it's passed assign the value // and conditionally invoke the callback this.on('option:' + oname, (val) => { - const oldValue = this._getOptionValue(name); + const oldValue = this.getOptionValue(name); // custom processing if (val !== null && option.parseArg) { @@ -1129,15 +1129,15 @@ class Command extends EventEmitter { if (typeof oldValue === 'boolean' || typeof oldValue === 'undefined') { // if no value, negate false, and we have a default, then use it! if (val == null) { - this._setOptionValue(name, option.negate + this.setOptionValue(name, option.negate ? false : defaultValue || true); } else { - this._setOptionValue(name, val); + this.setOptionValue(name, val); } } else if (val !== null) { // reassign - this._setOptionValue(name, option.negate ? false : val); + this.setOptionValue(name, option.negate ? false : val); } }); @@ -1325,34 +1325,34 @@ class Command extends EventEmitter { }; /** - * Store option value + * Retrieve option value. * * @param {string} key - * @param {Object} value - * @api private + * @return {Object} value */ - _setOptionValue(key, value) { + getOptionValue(key) { if (this._storeOptionsAsProperties) { - this[key] = value; - } else { - this._optionValues[key] = value; + return this[key]; } + return this._optionValues[key]; }; /** - * Retrieve option value + * Store option value. * * @param {string} key - * @return {Object} value - * @api private + * @param {Object} value + * @return {Command} `this` command for chaining */ - _getOptionValue(key) { + setOptionValue(key, value) { if (this._storeOptionsAsProperties) { - return this[key]; + this[key] = value; + } else { + this._optionValues[key] = value; } - return this._optionValues[key]; + return this; }; /** @@ -1764,7 +1764,7 @@ class Command extends EventEmitter { // Walk up hierarchy so can call in subcommand after checking for displaying help. for (let cmd = this; cmd; cmd = cmd.parent) { cmd.options.forEach((anOption) => { - if (anOption.mandatory && (cmd._getOptionValue(anOption.attributeName()) === undefined)) { + if (anOption.mandatory && (cmd.getOptionValue(anOption.attributeName()) === undefined)) { cmd.missingMandatoryOptionValue(anOption); } }); diff --git a/tests/command.chain.test.js b/tests/command.chain.test.js index 79c08a815..9cfe15158 100644 --- a/tests/command.chain.test.js +++ b/tests/command.chain.test.js @@ -165,4 +165,10 @@ describe('Command methods that should return this for chaining', () => { const result = program.enablePositionalOptions(); expect(result).toBe(program); }); + + test('when call .setOptionValue() then returns this', () => { + const program = new Command(); + const result = program.setOptionValue(); + expect(result).toBe(program); + }); }); diff --git a/tests/options.getset.test.js b/tests/options.getset.test.js new file mode 100644 index 000000000..adb15e639 --- /dev/null +++ b/tests/options.getset.test.js @@ -0,0 +1,24 @@ +const commander = require('../'); + +describe.each([true, false])('storeOptionsAsProperties is %s', (storeOptionsAsProperties) => { + test('when option specified on CLI then value returned by getOptionValue', () => { + const program = new commander.Command(); + program + .storeOptionsAsProperties(storeOptionsAsProperties) + .option('--cheese [type]', 'cheese type'); + const cheeseType = 'blue'; + program.parse(['node', 'test', '--cheese', cheeseType]); + expect(program.getOptionValue('cheese')).toBe(cheeseType); + }); + + test('when setOptionValue then value returned by opts', () => { + const program = new commander.Command(); + const cheeseType = 'blue'; + // Note: opts() only returns declared options when options stored as properties + program + .storeOptionsAsProperties(storeOptionsAsProperties) + .option('--cheese [type]', 'cheese type') + .setOptionValue('cheese', cheeseType); + expect(program.opts().cheese).toBe(cheeseType); + }); +}); diff --git a/typings/index.d.ts b/typings/index.d.ts index 8d834449c..d0f2c7d50 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -465,6 +465,16 @@ export class Command { storeOptionsAsProperties(storeAsProperties: true): this & OptionValues; storeOptionsAsProperties(storeAsProperties?: boolean): this; + /** + * Retrieve option value. + */ + getOptionValue(key: string): any; + + /** + * Store option value. + */ + setOptionValue(key: string, value: unknown): this; + /** * Alter parsing of short flags with optional values. * diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index 170b31965..677786982 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -139,6 +139,13 @@ expectType(program.storeOptionsAsPro expectType(program.storeOptionsAsProperties(true)); expectType(program.storeOptionsAsProperties(false)); +// getOptionValue +void program.getOptionValue('example'); + +// setOptionValue +expectType(program.setOptionValue('example', 'value')); +expectType(program.setOptionValue('example', true)); + // combineFlagAndOptionalValue expectType(program.combineFlagAndOptionalValue()); expectType(program.combineFlagAndOptionalValue(false)); From 4bacf3a787d06997beb62dfffd4f6f8e37cf3e71 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 17 May 2021 19:59:07 +1200 Subject: [PATCH 5/6] Fix comment change lost in merge --- typings/index.test-d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index ead029de4..0e2a389a1 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -7,7 +7,7 @@ const program: commander.Command = new commander.Command(); // @ts-expect-error Check that Command is strongly typed and does not allow arbitrary properties program.silly; // <-- Error, hurrah! -// Check for exported global Command object(s) +// Check for exported global Command object expectType(commander.program); // Check export classes and functions exist From d4697f654b469002dd011c158bb1a772855719f2 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 17 May 2021 20:01:13 +1200 Subject: [PATCH 6/6] Tweak wording --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index f3b2fe800..f4e991325 100644 --- a/Readme.md +++ b/Readme.md @@ -96,7 +96,7 @@ const program = new Command(); Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|'). The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler. -You can also use `.getOptionValue()` and `.setOptionValue()`. +You can also use `.getOptionValue()` and `.setOptionValue()` to work with a single option value. Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc.