From b99c4b4c0b0352ca7e103333f18be4993bd6137b Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 14 Feb 2020 22:38:15 +1300 Subject: [PATCH 01/12] Add factory method --- index.js | 17 ++++++++++++++++- tests/createCommand.test.js | 36 ++++++++++++++++++++++++++++++++++++ typings/commander-tests.ts | 3 +++ typings/index.d.ts | 8 ++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/createCommand.test.js diff --git a/index.js b/index.js index 7068137d2..bdb8ea914 100644 --- a/index.js +++ b/index.js @@ -160,7 +160,7 @@ class Command extends EventEmitter { } opts = opts || {}; const args = nameAndArgs.split(/ +/); - const cmd = new Command(args.shift()); + const cmd = this.createCommand(args.shift()); if (desc) { cmd.description(desc); @@ -189,6 +189,21 @@ class Command extends EventEmitter { return cmd; }; + /** + * Factory routine to create a new command. + * + * Used internally to create subcommands. May be overridden to + * customise subcommands. + * + * @param {string} [name] + * @return {Command} parent command for chaining + * @api public + */ + + createCommand(name) { + return new Command(name); + }; + /** * Add a prepared subcommand. * diff --git a/tests/createCommand.test.js b/tests/createCommand.test.js new file mode 100644 index 000000000..ef29365c6 --- /dev/null +++ b/tests/createCommand.test.js @@ -0,0 +1,36 @@ +const commander = require('../'); + +test('when createCommand then unattached', () => { + const program = new commander.Command(); + const cmd = program.createCommand(); + expect(program.commands.length).toBe(0); + expect(cmd.parent).toBeUndefined(); +}); + +test('when subclass overrides createCommand then subcommand is subclass', () => { + class MyClass extends commander.Command { + constructor(name) { + super(); + this.myProperty = 'myClass'; + }; + + createCommand(name) { + return new MyClass(name); + }; + }; + const program = new MyClass(); + const sub = program.command('sub'); + expect(sub.myProperty).toEqual('myClass'); +}); + +test('when override createCommand then subcommand is custom', () => { + function createCustom(name) { + const cmd = new commander.Command(); + cmd.myProperty = 'custom'; + return cmd; + } + const program = createCustom(); + program.createCommand = createCustom; + const sub = program.command('sub'); + expect(sub.myProperty).toEqual('custom'); +}); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 21fcb0ac7..ac3a8b05a 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -126,6 +126,9 @@ program const preparedCommand = new program.Command('prepared'); program.addCommand(preparedCommand); +const c1 = program.createCommand(); +const c2 = c1.createCommand(); + program .exitOverride(); diff --git a/typings/index.d.ts b/typings/index.d.ts index 39b4076e8..40364374b 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -85,6 +85,14 @@ declare namespace commander { */ command(nameAndArgs: string, description: string, opts?: commander.CommandOptions): this; + /** + * Factory routine to create a new command. + * + * Used internally to create subcommands. May be overridden to + * customise subcommands. + */ + createCommand(name?: string): this; + /** * Add a prepared subcommand. * From 57883f510ebc8bf6d1381d4c718bb1aee5898f7f Mon Sep 17 00:00:00 2001 From: John Gee Date: Fri, 14 Feb 2020 22:47:36 +1300 Subject: [PATCH 02/12] Fix return doc --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index bdb8ea914..92df608b8 100644 --- a/index.js +++ b/index.js @@ -196,7 +196,7 @@ class Command extends EventEmitter { * customise subcommands. * * @param {string} [name] - * @return {Command} parent command for chaining + * @return {Command} new command * @api public */ From 116405bc0142eba175cea0015386ecf5fa905d00 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 15 Feb 2020 11:13:01 +1300 Subject: [PATCH 03/12] Can not use "this" for return type of createCommand --- typings/commander-tests.ts | 24 +++++++++++++++++++++++- typings/index.d.ts | 2 +- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index ac3a8b05a..b1e4ff2fd 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -127,7 +127,29 @@ const preparedCommand = new program.Command('prepared'); program.addCommand(preparedCommand); const c1 = program.createCommand(); -const c2 = c1.createCommand(); +c1.version('1.2.3'); + +class MyCommand extends program.Command { + constructor(name?: string) { + super(name); + } + + createCommand(name?: string) { + return new MyCommand(name); + } + myFunction() {} + + // Modify return type for command (experimenting) + command(nameAndArgs: string, opts?: program.CommandOptions): MyCommand; + command(nameAndArgs: string, description: string, opts?: program.CommandOptions): this; + command(nameAndArgs: string, actionOptsOrExecDesc?: any, execOpts?: program.CommandOptions): MyCommand | this { + return super.command(nameAndArgs, actionOptsOrExecDesc, execOpts); + } +} +const myProgram = new MyCommand(); +myProgram.myFunction(); +const mySub = myProgram.command('sub'); +mySub.myFunction(); program .exitOverride(); diff --git a/typings/index.d.ts b/typings/index.d.ts index 40364374b..13ae77cb9 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -91,7 +91,7 @@ declare namespace commander { * Used internally to create subcommands. May be overridden to * customise subcommands. */ - createCommand(name?: string): this; + createCommand(name?: string): Command; /** * Add a prepared subcommand. From 86a8ae528de2aad10a809c72aee35db44cbf61bb Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 22 Feb 2020 13:56:46 +1300 Subject: [PATCH 04/12] Use return type of createCommand for subcommand --- tests/createCommand.test.js | 2 +- typings/commander-tests.ts | 6 +----- typings/index.d.ts | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/createCommand.test.js b/tests/createCommand.test.js index ef29365c6..e0450d7b2 100644 --- a/tests/createCommand.test.js +++ b/tests/createCommand.test.js @@ -4,7 +4,7 @@ test('when createCommand then unattached', () => { const program = new commander.Command(); const cmd = program.createCommand(); expect(program.commands.length).toBe(0); - expect(cmd.parent).toBeUndefined(); + expect(cmd.parent).toBeFalsy(); // (actually null, but use weaker test for unattached) }); test('when subclass overrides createCommand then subcommand is subclass', () => { diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index e1ed9377e..6a317c430 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -191,10 +191,6 @@ const onThis: commander.Command = program.on('--help', () => { }) const createInstance: commander.Command = program.createCommand(); class MyCommand extends commander.Command { - constructor(name?: string) { - super(name); - } - createCommand(name?: string) { return new MyCommand(name); } @@ -202,5 +198,5 @@ class MyCommand extends commander.Command { } const myProgram = new MyCommand(); myProgram.myFunction(); -const mySub: CommandWithoutOptionsAsProperties = myProgram.command('sub'); +const mySub = myProgram.command('sub'); mySub.myFunction(); diff --git a/typings/index.d.ts b/typings/index.d.ts index 95c3b2a5e..184c9932b 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -64,7 +64,7 @@ declare namespace commander { * @param opts - configuration options * @returns new command */ - command(nameAndArgs: string, opts?: CommandOptions): Command; + command(nameAndArgs: string, opts?: CommandOptions): ReturnType< this[ 'createCommand' ] >; /** * Define a command, implemented in a separate executable file. * From 045b01133c6026d1c557134366eeb57764e79d80 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 22 Feb 2020 14:58:29 +1300 Subject: [PATCH 05/12] Add mention of .command from .createCommand --- index.js | 6 +++--- typings/index.d.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 89a9ac1b1..d23f3d2d3 100644 --- a/index.js +++ b/index.js @@ -196,10 +196,10 @@ class Command extends EventEmitter { }; /** - * Factory routine to create a new command. + * Factory routine to create a new unattached command. * - * Used internally to create subcommands. May be overridden to - * customise subcommands. + * See .command() for creating an attached subcommand, which uses this routine to + * create the command. You can override createCommand to customise subcommands. * * @param {string} [name] * @return {Command} new command diff --git a/typings/index.d.ts b/typings/index.d.ts index 184c9932b..19d4a8276 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -86,10 +86,10 @@ declare namespace commander { command(nameAndArgs: string, description: string, opts?: commander.CommandOptions): this; /** - * Factory routine to create a new command. + * Factory routine to create a new unattached command. * - * Used internally to create subcommands. May be overridden to - * customise subcommands. + * 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; From af3a2ee4d5901bf9d2a1ee9ad3f4dbe7842d7a33 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 22 Feb 2020 15:35:38 +1300 Subject: [PATCH 06/12] Remove trailing space --- index.js | 2 +- typings/index.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index d23f3d2d3..59209cf6d 100644 --- a/index.js +++ b/index.js @@ -198,7 +198,7 @@ class Command extends EventEmitter { /** * Factory routine to create a new unattached command. * - * See .command() for creating an attached subcommand, which uses this routine to + * See .command() for creating an attached subcommand, which uses this routine to * create the command. You can override createCommand to customise subcommands. * * @param {string} [name] diff --git a/typings/index.d.ts b/typings/index.d.ts index 19d4a8276..95d5e664c 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -88,7 +88,7 @@ declare namespace commander { /** * Factory routine to create a new unattached command. * - * See .command() for creating an attached subcommand, which uses this routine to + * 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; From fa202c4fe9810f17018719d540b5b84332f6e3f7 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sat, 22 Feb 2020 18:04:26 +1300 Subject: [PATCH 07/12] Add examples for createCommand --- examples/custom-command-class.js | 33 +++++++++++++++++++++++++++++ examples/custom-command-function.js | 33 +++++++++++++++++++++++++++++ typings/commander-tests.ts | 3 ++- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 examples/custom-command-class.js create mode 100644 examples/custom-command-function.js diff --git a/examples/custom-command-class.js b/examples/custom-command-class.js new file mode 100644 index 000000000..1ffda0394 --- /dev/null +++ b/examples/custom-command-class.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +// const commander = require('commander'); // (normal include) +const commander = require('../'); // include commander in git clone of commander repo + +class MyCommand extends commander.Command { + createCommand(name) { + const cmd = new MyCommand(name); + cmd.option('-d,--debug', 'output options'); + return cmd; + } +}; + +const program = new MyCommand(); +program + .command('demo') + .option('--port ', 'specify port number', 80) + .action((cmd) => { + if (cmd.debug) { + console.log('Options:'); + console.log(cmd.opts()); + console.log(); + } + + console.log(`Start serve on port ${cmd.port}`); + }); + +program.parse(); + +// Try the following: +// node custom-command-class.js help demo +// node custom-command-class.js demo --debug +// node custom-command-class.js demo --debug --port 8080 diff --git a/examples/custom-command-function.js b/examples/custom-command-function.js new file mode 100644 index 000000000..9387bfd10 --- /dev/null +++ b/examples/custom-command-function.js @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +// const commander = require('commander'); // (normal include) +const commander = require('../'); // include commander in git clone of commander repo + +const program = commander.createCommand(); + +// Customise subcommand creation +program.createCommand = (name) => { + const cmd = commander.createCommand(name); + cmd.option('-d,--debug', 'output options'); + return cmd; +}; + +program + .command('demo') + .option('--port ', 'specify port number', 80) + .action((cmd) => { + if (cmd.debug) { + console.log('Options:'); + console.log(cmd.opts()); + console.log(); + } + + console.log(`Start serve on port ${cmd.port}`); + }); + +program.parse(); + +// Try the following: +// node custom-command-function.js help demo +// node custom-command-function.js demo --debug +// node custom-command-function.js demo --debug --port 8080 diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 6a317c430..0dc607137 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -188,7 +188,8 @@ const onThis: commander.Command = program.on('--help', () => { }) // createCommand -const createInstance: commander.Command = program.createCommand(); +const createInstance1: commander.Command = program.createCommand(); +const createInstance2: commander.Command = program.createCommand('name'); class MyCommand extends commander.Command { createCommand(name?: string) { From 3940a5dd5abae6df737ff3c43102ce7eb70d50f4 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 23 Feb 2020 11:42:58 +1300 Subject: [PATCH 08/12] Explain example and make a little more realistic --- examples/custom-command-class.js | 11 +++++++---- examples/custom-command-function.js | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/custom-command-class.js b/examples/custom-command-class.js index 1ffda0394..cda3d06ac 100644 --- a/examples/custom-command-class.js +++ b/examples/custom-command-class.js @@ -3,6 +3,9 @@ // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo +// Use a class override of createCommand to customise subcommands, +// in this example by adding --debug option. + class MyCommand extends commander.Command { createCommand(name) { const cmd = new MyCommand(name); @@ -13,7 +16,7 @@ class MyCommand extends commander.Command { const program = new MyCommand(); program - .command('demo') + .command('serve') .option('--port ', 'specify port number', 80) .action((cmd) => { if (cmd.debug) { @@ -28,6 +31,6 @@ program program.parse(); // Try the following: -// node custom-command-class.js help demo -// node custom-command-class.js demo --debug -// node custom-command-class.js demo --debug --port 8080 +// node custom-command-class.js help serve +// node custom-command-class.js serve --debug +// node custom-command-class.js serve --debug --port 8080 diff --git a/examples/custom-command-function.js b/examples/custom-command-function.js index 9387bfd10..f4d58c0e5 100644 --- a/examples/custom-command-function.js +++ b/examples/custom-command-function.js @@ -3,6 +3,9 @@ // const commander = require('commander'); // (normal include) const commander = require('../'); // include commander in git clone of commander repo +// Override createCommand directly to customise subcommands, +// in this example by adding --debug option. + const program = commander.createCommand(); // Customise subcommand creation @@ -13,7 +16,7 @@ program.createCommand = (name) => { }; program - .command('demo') + .command('serve') .option('--port ', 'specify port number', 80) .action((cmd) => { if (cmd.debug) { @@ -28,6 +31,6 @@ program program.parse(); // Try the following: -// node custom-command-function.js help demo -// node custom-command-function.js demo --debug -// node custom-command-function.js demo --debug --port 8080 +// node custom-command-function.js help serve +// node custom-command-function.js serve --debug +// node custom-command-function.js serve --debug --port 8080 From 052e3093e74b63f0ae231009eb59ac3c65aa1c0d Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 23 Feb 2020 14:47:05 +1300 Subject: [PATCH 09/12] Add comments pointing from .addCommand to .command One of the downsides of extra ways of adding and creating commands is confusion with the more common way. --- index.js | 2 ++ typings/index.d.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/index.js b/index.js index 59209cf6d..7de8db5f6 100644 --- a/index.js +++ b/index.js @@ -213,6 +213,8 @@ class Command extends EventEmitter { /** * Add a prepared subcommand. * + * See .command() for creating an attached subcommand which inherits settings from its parent. + * * @param {Command} cmd - new subcommand * @return {Command} parent command for chaining * @api public diff --git a/typings/index.d.ts b/typings/index.d.ts index 95d5e664c..356d8139f 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -96,6 +96,8 @@ declare namespace commander { /** * Add a prepared subcommand. * + * See .command() for creating an attached subcommand which inherits settings from its parent. + * * @returns parent command for chaining */ addCommand(cmd: Command): this; From 841740d2ef0c4498fa733e995fcaf26b08255f6a Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 23 Feb 2020 16:39:18 +1300 Subject: [PATCH 10/12] Add createCommand to README --- Readme.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Readme.md b/Readme.md index 33a12a2d7..6b6b015e5 100644 --- a/Readme.md +++ b/Readme.md @@ -36,6 +36,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [.parse() and .parseAsync()](#parse-and-parseasync) - [Avoiding option name clashes](#avoiding-option-name-clashes) - [TypeScript](#typescript) + - [createCommand()](#createcommand) - [Node options such as `--harmony`](#node-options-such-as---harmony) - [Debugging stand-alone executable subcommands](#debugging-stand-alone-executable-subcommands) - [Override exit handling](#override-exit-handling) @@ -630,6 +631,19 @@ If you use `ts-node` and stand-alone executable subcommands written as `.ts` fi node -r ts-node/register pm.ts ``` +### createCommand() + +This factory function creates a new command rather than a subcommand. It is exported and may be used instead of using `new`, like: + +```js +const { createCommand } = require('commander'); +const program = createCommand(); +``` + +`createCommand` is also a method of the Command object. This gets used internally +when creating subcommands using `.command()`, and you may override it to +customise the new subcommand (examples using [subclass](./examples/custom-command-class.js) and [function](./examples/custom-command-function.js)). + ### Node options such as `--harmony` You can enable `--harmony` option in two ways: From cb44c62401994d303f56dbffde4f40118dc6c645 Mon Sep 17 00:00:00 2001 From: John Gee Date: Sun, 23 Feb 2020 17:46:59 +1300 Subject: [PATCH 11/12] Shift command/subcommand contrast --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 6b6b015e5..0d954265d 100644 --- a/Readme.md +++ b/Readme.md @@ -633,14 +633,14 @@ node -r ts-node/register pm.ts ### createCommand() -This factory function creates a new command rather than a subcommand. It is exported and may be used instead of using `new`, like: +This factory function creates a new command. It is exported and may be used instead of using `new`, like: ```js const { createCommand } = require('commander'); const program = createCommand(); ``` -`createCommand` is also a method of the Command object. This gets used internally +`createCommand` is also a method of the Command object, and creates a new command rather than a subcommand. This gets used internally when creating subcommands using `.command()`, and you may override it to customise the new subcommand (examples using [subclass](./examples/custom-command-class.js) and [function](./examples/custom-command-function.js)). From 18e967c71759e184b2d3253710cbf7c1fe476fca Mon Sep 17 00:00:00 2001 From: John Gee Date: Mon, 2 Mar 2020 19:22:53 +1300 Subject: [PATCH 12/12] Use single quotes in ts like in js, and clean up whitespace in new code --- 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 356d8139f..9b4eadff3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -24,7 +24,7 @@ declare namespace commander { type OptionConstructor = { new (flags: string, description?: string): Option }; interface ParseOptions { - from: "node" | "electron" | "user"; + from: 'node' | 'electron' | 'user'; } interface Command { @@ -64,7 +64,7 @@ declare namespace commander { * @param opts - configuration options * @returns new command */ - command(nameAndArgs: string, opts?: CommandOptions): ReturnType< this[ 'createCommand' ] >; + command(nameAndArgs: string, opts?: CommandOptions): ReturnType; /** * Define a command, implemented in a separate executable file. *