From 14e9c6125bfe04ee5a5404556045f5cabaad3405 Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 3 Jul 2019 23:46:11 +1200 Subject: [PATCH 1/9] Defined .command as an overload, with reworked documentstion --- typings/commander-tests.ts | 7 ++- typings/index.d.ts | 88 ++++++++++++++------------------------ 2 files changed, 37 insertions(+), 58 deletions(-) diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 474db4e87..94353c40e 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -82,6 +82,7 @@ program.on('--help', () => { program .command('allow-unknown-option') + .description("description") .allowUnknownOption() .action(() => { console.log('unknown option is allowed'); @@ -94,6 +95,10 @@ program console.log(cmd, env); }); +program + .command("name1", "description") + .command("name2", "description", { isDefault:true }) + program.parse(process.argv); -console.log('stuff'); \ No newline at end of file +console.log('stuff'); diff --git a/typings/index.d.ts b/typings/index.d.ts index 4637acdaa..6daacf71e 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -49,65 +49,39 @@ declare namespace local { version(str: string, flags?: string, description?: string): Command; /** - * Add command `name`. - * - * The `.action()` callback is invoked when the - * command `name` is specified via __ARGV__, - * and the remaining arguments are applied to the - * function for access. - * - * When the `name` is "*" an un-matched command - * will be passed as the first arg, followed by - * the rest of __ARGV__ remaining. - * + * Define a command, implemented using an action handler. + * * @example - * program - * .version('0.0.1') - * .option('-C, --chdir ', 'change the working directory') - * .option('-c, --config ', 'set config path. defaults to ./deploy.conf') - * .option('-T, --no-tests', 'ignore test hook') - * - * program - * .command('setup') - * .description('run remote setup commands') - * .action(function() { - * console.log('setup'); - * }); - * - * program - * .command('exec ') - * .description('run the given remote command') - * .action(function(cmd) { - * console.log('exec "%s"', cmd); - * }); - * - * program - * .command('teardown [otherDirs...]') - * .description('run teardown commands') - * .action(function(dir, otherDirs) { - * console.log('dir "%s"', dir); - * if (otherDirs) { - * otherDirs.forEach(function (oDir) { - * console.log('dir "%s"', oDir); - * }); - * } - * }); - * - * program - * .command('*') - * .description('deploy the given env') - * .action(function(env) { - * console.log('deploying "%s"', env); - * }); - * - * program.parse(process.argv); - * - * @param {string} name - * @param {string} [desc] for git-style sub-commands - * @param {CommandOptions} [opts] command options - * @returns {Command} the new command + * ``` + * 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 be `variadic...` + * @param opts - configuration options + * @returns new command + */ + command(nameAndArgs: string, opts?: commander.CommandOptions): Command; + /** + * Define a command, implemented in a separate executable file. + * + * @example + * ``` + * program + * .command('start ', 'start named service') + * .command('stop [service]', 'stop named serice, or all if no name supplied'); + * ``` + * + * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may be `variadic...` + * @param description - description of executable command + * @param opts - configuration options + * @returns top level command for chaining more command definitions */ - command(name: string, desc?: string, opts?: commander.CommandOptions): Command; + command(nameAndArgs: string, description: string, opts?: commander.CommandOptions): Command; /** * Define argument syntax for the top-level command. From 446e3312ff505b576beda0c7bc26ab07324cf951 Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 5 Jul 2019 21:22:46 +1200 Subject: [PATCH 2/9] Add TSDoc remarks about command description Description is the key difference between the overloads, so be explicit about how it is suppliied. --- typings/index.d.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/typings/index.d.ts b/typings/index.d.ts index 6daacf71e..5408fc292 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -51,6 +51,9 @@ declare namespace local { /** * Define a command, implemented using an action handler. * + * @remarks + * The command description is supplied using `.description`, not as a parameter to `.command`. + * * @example * ``` * program @@ -69,6 +72,9 @@ declare namespace local { /** * Define a command, implemented in a separate executable file. * + * @remarks + * The command description is supplied as the second parameter to `.command`. + * * @example * ``` * program From 9923963b7098c72eca2cf4e631f51f2ec0dee9fd Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 6 Jul 2019 00:00:19 +1200 Subject: [PATCH 3/9] Rework .command JSDoc to clarify two styles --- index.js | 68 +++++++++++++++----------------------------------------- 1 file changed, 18 insertions(+), 50 deletions(-) diff --git a/index.js b/index.js index 2bd6c36a2..06a13700e 100644 --- a/index.js +++ b/index.js @@ -109,73 +109,41 @@ function Command(name) { } /** - * Add command `name`. + * Define a command. * - * The `.action()` callback is invoked when the - * command `name` is specified via __ARGV__, - * and the remaining arguments are applied to the - * function for access. - * - * When the `name` is "*" an un-matched command - * will be passed as the first arg, followed by - * the rest of __ARGV__ remaining. + * There are two styles of command: pay attention to where to put the description. * * Examples: * + * // Command implemented using action handler (description is supplied separately to `.command`) * program - * .version('0.0.1') - * .option('-C, --chdir ', 'change the working directory') - * .option('-c, --config ', 'set config path. defaults to ./deploy.conf') - * .option('-T, --no-tests', 'ignore test hook') - * - * program - * .command('setup') - * .description('run remote setup commands') - * .action(function() { - * console.log('setup'); - * }); - * - * program - * .command('exec ') - * .description('run the given remote command') - * .action(function(cmd) { - * console.log('exec "%s"', cmd); - * }); - * - * program - * .command('teardown [otherDirs...]') - * .description('run teardown commands') - * .action(function(dir, otherDirs) { - * console.log('dir "%s"', dir); - * if (otherDirs) { - * otherDirs.forEach(function (oDir) { - * console.log('dir "%s"', oDir); - * }); - * } + * .command('clone [destination]') + * .description('clone a repository into a newly created directory') + * .action((source, destination) => { + * console.log('clone command called'); * }); * + * // Command implemented using separate executable file (description is second parameter to `.command`) * program - * .command('*') - * .description('deploy the given env') - * .action(function(env) { - * console.log('deploying "%s"', env); - * }); + * .command('start ', 'start named service') + * .command('stop [service]', 'stop named serice, or all if no name supplied'); * - * program.parse(process.argv); - * - * @param {String} name - * @param {String} [desc] for git-style sub-commands - * @return {Command} the new command + * @param {string} nameAndArgs - command name and arguments, args are `` or `[optional]` and last may be `variadic...` + * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable) + * @param {Object} [execOpts] - configuration options (for executable) + * @return {Command} returns new command for action handler, or top-level command for executable command * @api public */ -Command.prototype.command = function(name, desc, opts) { +Command.prototype.command = function(nameAndArgs, actionOptsOrExecDesc, execOpts) { + var desc = actionOptsOrExecDesc; + var opts = execOpts; if (typeof desc === 'object' && desc !== null) { opts = desc; desc = null; } opts = opts || {}; - var args = name.split(/ +/); + var args = nameAndArgs.split(/ +/); var cmd = new Command(args.shift()); if (desc) { From fc3298b7ec0e1ece2997ce213204df96113c38d7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 6 Jul 2019 00:01:16 +1200 Subject: [PATCH 4/9] Cosmetic changes to example --- typings/index.d.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/typings/index.d.ts b/typings/index.d.ts index 5408fc292..d1e5c3ac7 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -55,13 +55,13 @@ declare namespace local { * The command description is supplied using `.description`, not as a parameter to `.command`. * * @example - * ``` - * program - * .command('clone [destination]') - * .description('clone a repository into a newly created directory') - * .action((source, destination) => { - * console.log('clone command called'); - * }); + * ```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 be `variadic...` @@ -76,10 +76,10 @@ declare namespace local { * The command description is supplied as the second parameter to `.command`. * * @example - * ``` - * program - * .command('start ', 'start named service') - * .command('stop [service]', 'stop named serice, or all if no name supplied'); + * ```ts + * program + * .command('start ', 'start named service') + * .command('stop [service]', 'stop named serice, or all if no name supplied'); * ``` * * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may be `variadic...` From 346542ac58b68bbdf67a2e27aa061b9250432ff1 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 7 Jul 2019 19:54:07 +1200 Subject: [PATCH 5/9] Minor changes in comments --- index.js | 2 +- typings/index.d.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 06a13700e..ba477a56c 100644 --- a/index.js +++ b/index.js @@ -128,7 +128,7 @@ function Command(name) { * .command('start ', 'start named service') * .command('stop [service]', 'stop named serice, or all if no name supplied'); * - * @param {string} nameAndArgs - command name and arguments, args are `` or `[optional]` and last may be `variadic...` + * @param {string} nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` * @param {Object|string} [actionOptsOrExecDesc] - configuration options (for action), or description (for executable) * @param {Object} [execOpts] - configuration options (for executable) * @return {Command} returns new command for action handler, or top-level command for executable command diff --git a/typings/index.d.ts b/typings/index.d.ts index d1e5c3ac7..9fafd0101 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -64,7 +64,7 @@ declare namespace local { * }); * ``` * - * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may be `variadic...` + * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may also be `variadic...` * @param opts - configuration options * @returns new command */ @@ -82,7 +82,7 @@ declare namespace local { * .command('stop [service]', 'stop named serice, or all if no name supplied'); * ``` * - * @param nameAndArgs - command name and arguments, args are `` or `[optional]` and last may be `variadic...` + * @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 top level command for chaining more command definitions From 255cb1ea78aff20fa877b4fd0eedaf83a33cda57 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 7 Jul 2019 20:21:40 +1200 Subject: [PATCH 6/9] Rework (sub)command coverage in README --- Readme.md | 122 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 51 deletions(-) diff --git a/Readme.md b/Readme.md index 9ec05af3f..2b56827d3 100644 --- a/Readme.md +++ b/Readme.md @@ -232,39 +232,61 @@ the same syntax for flags as the `option` method. The version flags can be named program.version('0.0.1', '-v, --vers', 'output the current version'); ``` -## Command-specific options +## Commands -You can attach options to a command. +You can specify (sub)commands for your top-level command using `.command`. There are two ways these can be implemented: using an action handler attached to the command, or as a separate executable file (described in more detail later). In the first parameter to `.command` you specify the command name and any command arguments. The arguments may be `` or `[optional]`, and the last argument may also be `variadic...`. -```js -#!/usr/bin/env node - -var program = require('commander'); +For example: +```js +// Command implemented using action handler (description is supplied separately to `.command`) +// Returns new command for configuring. program - .command('rm ') - .option('-r, --recursive', 'Remove recursively') - .action(function (dir, cmd) { - console.log('remove ' + dir + (cmd.recursive ? ' recursively' : '')) - }) + .command('clone [destination]') + .description('clone a repository into a newly created directory') + .action((source, destination) => { + console.log('clone command called'); + }); -program.parse(process.argv) +// Command implemented using separate executable file (description is second parameter to `.command`) +// Returns top-level command for adding more commands. +program + .command('start ', 'start named service') + .command('stop [service]', 'stop named serice, or all if no name supplied'); ``` -A command's options are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated. +### Specify the argument syntax -## Variadic arguments +You use `.arguments` to specify the arguments for the top-level command, and for subcommands they are included in the `.command` call. Angled brackets (e.g. ``) indicate required input. Square brackets (e.g. `[optional]`) indicate optional input. - The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to - append `...` to the argument name. Here is an example: ```js -#!/usr/bin/env node +var program = require('commander'); -/** - * Module dependencies. - */ +program + .version('0.1.0') + .arguments(' [env]') + .action(function (cmd, env) { + cmdValue = cmd; + envValue = env; + }); + +program.parse(process.argv); + +if (typeof cmdValue === 'undefined') { + console.error('no command given!'); + process.exit(1); +} +console.log('command:', cmdValue); +console.log('environment:', envValue || "no environment given"); +``` + +### Variadic arguments + + The last argument of a command can be variadic, and only the last argument. To make an argument variadic you + append `...` to the argument name. For example: +```js var program = require('commander'); program @@ -282,36 +304,37 @@ program program.parse(process.argv); ``` - An `Array` is used for the value of a variadic argument. This applies to `program.args` as well as the argument passed - to your action as demonstrated above. +The variadic argument is passed to the action handler as an array. -## Specify the argument syntax +### Action handler (sub)commands -```js -#!/usr/bin/env node +You can add options to a command that uses an action handler. +The action handler gets passed a parameter for each argument you declared, and one additional argument which is the +command object itself. This command argument has the values for the command-specific options added as properties. +```js var program = require('commander'); program - .version('0.1.0') - .arguments(' [env]') - .action(function (cmd, env) { - cmdValue = cmd; - envValue = env; - }); - -program.parse(process.argv); + .command('rm ') + .option('-r, --recursive', 'Remove recursively') + .action(function (dir, cmdObj) { + console.log('remove ' + dir + (cmdObj.recursive ? ' recursively' : '')) + }) -if (typeof cmdValue === 'undefined') { - console.error('no command given!'); - process.exit(1); -} -console.log('command:', cmdValue); -console.log('environment:', envValue || "no environment given"); +program.parse(process.argv) ``` -Angled brackets (e.g. ``) indicate required input. Square brackets (e.g. `[env]`) indicate optional input. -## Git-style sub-commands +A command's options on the command line are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated. + +Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. + +### Git-style executable (sub)commands + +When `.command()` is invoked with a description argument, this tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools. +Commander will search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-subcommand`, like `pm-install`, `pm-search`. + +You handle the options for an executable (sub)command in the executable, and don't declare them at the top-level. ```js // file: ./examples/pm @@ -325,19 +348,10 @@ program .parse(process.argv); ``` -When `.command()` is invoked with a description argument, no `.action(callback)` should be called to handle sub-commands, otherwise there will be an error. This tells commander that you're going to use separate executables for sub-commands, much like `git(1)` and other popular tools. -The commander will try to search the executables in the directory of the entry script (like `./examples/pm`) with the name `program-command`, like `pm-install`, `pm-search`. - -Options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the subcommand from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified. +Configuration options can be passed with the call to `.command()`. Specifying `true` for `opts.noHelp` will remove the command from the generated help output. Specifying `true` for `opts.isDefault` will run the subcommand if no other subcommand is specified. If the program is designed to be installed globally, make sure the executables have proper modes, like `755`. -### `--harmony` - -You can enable `--harmony` option in two ways: -* Use `#! /usr/bin/env node --harmony` in the sub-commands scripts. Note some os version don’t support this pattern. -* Use the `--harmony` option when call the command, like `node --harmony examples/pm publish`. The `--harmony` option will be preserved when spawning sub-command process. - ## Automated --help The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free: @@ -486,6 +500,12 @@ If you use `ts-node` and git-style sub-commands written as `.ts` files, you nee node -r ts-node/register pm.ts ``` +### Node options such as `--harmony` + +You can enable `--harmony` option in two ways: +* Use `#! /usr/bin/env node --harmony` in the sub-commands scripts. (Note Windows does not support this pattern.) +* Use the `--harmony` option when call the command, like `node --harmony examples/pm publish`. The `--harmony` option will be preserved when spawning sub-command process. + ## Examples ```js From 1450f33bac2cfec2ee8c7c7a4b372289c2fd8e4f Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 7 Jul 2019 20:25:04 +1200 Subject: [PATCH 7/9] Update CHANGELOG with #938 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d929c4b1..c3b11a6e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * can now define both `--foo` and `--no-foo` * custom event listeners: `--no-foo` on cli now emits `option:no-foo` (previously `option:foo`) * default value: defining `--no-foo` after defining `--foo` leaves the default value unchanged (previously set it to false) + * Change docs for `.command` to contrast action handler vs git-style executable. TypeScript now uses overloaded function. (#938) 2.20.0 / 2019-04-02 ================== From 1d0f3bcb8a55009e12a7d6e74c2eba15e36919e2 Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 8 Jul 2019 17:45:03 +1200 Subject: [PATCH 8/9] Minor improvements to command and argument coverage --- Readme.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 2b56827d3..104ffa101 100644 --- a/Readme.md +++ b/Readme.md @@ -252,7 +252,7 @@ program // Returns top-level command for adding more commands. program .command('start ', 'start named service') - .command('stop [service]', 'stop named serice, or all if no name supplied'); + .command('stop [service]', 'stop named service, or all if no name supplied'); ``` ### Specify the argument syntax @@ -281,8 +281,6 @@ console.log('command:', cmdValue); console.log('environment:', envValue || "no environment given"); ``` -### Variadic arguments - The last argument of a command can be variadic, and only the last argument. To make an argument variadic you append `...` to the argument name. For example: @@ -304,7 +302,7 @@ program program.parse(process.argv); ``` -The variadic argument is passed to the action handler as an array. +The variadic argument is passed to the action handler as an array. (And this also applies to `program.args`.) ### Action handler (sub)commands From 42506653cbddbb0964115cfe7e2d50c64a4dbb9e Mon Sep 17 00:00:00 2001 From: John Gee Date: Wed, 10 Jul 2019 22:04:30 +1200 Subject: [PATCH 9/9] Fix indentation in code blocks in README --- Readme.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index 104ffa101..034d8511d 100644 --- a/Readme.md +++ b/Readme.md @@ -217,12 +217,12 @@ $ custom --list x,y,z The optional `version` method adds handling for displaying the command version. The default option flags are `-V` and `--version`, and when present the command prints the version number and exits. ```js - program.version('0.0.1'); +program.version('0.0.1'); ``` ```bash - $ ./examples/pizza -V - 0.0.1 +$ ./examples/pizza -V +0.0.1 ``` You may change the flags and description by passing additional parameters to the `version` method, using @@ -267,15 +267,15 @@ program .version('0.1.0') .arguments(' [env]') .action(function (cmd, env) { - cmdValue = cmd; - envValue = env; + cmdValue = cmd; + envValue = env; }); program.parse(process.argv); if (typeof cmdValue === 'undefined') { - console.error('no command given!'); - process.exit(1); + console.error('no command given!'); + process.exit(1); } console.log('command:', cmdValue); console.log('environment:', envValue || "no environment given");