Tips & Tricks
Inheritance​
Because they're just plain old ES6 classes, commands can easily extend each other and inherit options:
ts
abstract classBaseCommand extendsCommand {cwd =Option .String (`--cwd`, {hidden : true});Âabstractexecute ():Promise <number | void>;}ÂclassFooCommand extendsBaseCommand {foo =Option .String (`-f,--foo`);Âasyncexecute () {this.context .stdout .write (`Hello from ${this.cwd ??process .cwd ()}!\n`);this.context .stdout .write (`This is foo: ${this.foo }.\n`);}}
ts
abstract classBaseCommand extendsCommand {cwd =Option .String (`--cwd`, {hidden : true});Âabstractexecute ():Promise <number | void>;}ÂclassFooCommand extendsBaseCommand {foo =Option .String (`-f,--foo`);Âasyncexecute () {this.context .stdout .write (`Hello from ${this.cwd ??process .cwd ()}!\n`);this.context .stdout .write (`This is foo: ${this.foo }.\n`);}}
Positionals can also be inherited. They will be consumed in order starting from the superclass:
ts
abstract classBaseCommand extendsCommand {foo =Option .String ();Âabstractexecute ():Promise <number | void>;}ÂclassFooCommand extendsBaseCommand {bar =Option .String ();Âasyncexecute () {this.context .stdout .write (`This is foo: ${this.foo }.\n`);this.context .stdout .write (`This is bar: ${this.bar }.\n`);}}
ts
abstract classBaseCommand extendsCommand {foo =Option .String ();Âabstractexecute ():Promise <number | void>;}ÂclassFooCommand extendsBaseCommand {bar =Option .String ();Âasyncexecute () {this.context .stdout .write (`This is foo: ${this.foo }.\n`);this.context .stdout .write (`This is bar: ${this.bar }.\n`);}}
hello world => Command {"foo": "hello", "bar": "world"}
hello world => Command {"foo": "hello", "bar": "world"}
Adding options to existing commands​
Adding options to existing commands can be achieved via inheritance and required options:
ts
classGreetCommand extendsCommand {staticpaths = [[`greet`]];Âname =Option .String ();Âgreeting =Option .String (`--greeting`, `Hello`);Âasyncexecute ():Promise <number | void> {this.context .stdout .write (`${this.greeting } ${this.name }!\n`);}}ÂclassGreetWithReverseCommand extendsGreetCommand {reverse =Option .Boolean (`--reverse`, {required : true});Âasyncexecute () {return await this.cli .run ([`greet`, this.reverse ? this.name .split (``).reverse ().join (``) : this.name , `--greeting`, this.greeting ]);}}
ts
classGreetCommand extendsCommand {staticpaths = [[`greet`]];Âname =Option .String ();Âgreeting =Option .String (`--greeting`, `Hello`);Âasyncexecute ():Promise <number | void> {this.context .stdout .write (`${this.greeting } ${this.name }!\n`);}}ÂclassGreetWithReverseCommand extendsGreetCommand {reverse =Option .Boolean (`--reverse`, {required : true});Âasyncexecute () {return await this.cli .run ([`greet`, this.reverse ? this.name .split (``).reverse ().join (``) : this.name , `--greeting`, this.greeting ]);}}
greet john => "Hello john!\n" greet john --greeting hey => "hey john!\n" greet john --reverse => "Hello nhoj!\n" greet john --greeting hey --reverse => "hey nhoj!\n"
greet john => "Hello john!\n" greet john --greeting hey => "hey john!\n" greet john --reverse => "Hello nhoj!\n" greet john --greeting hey --reverse => "hey nhoj!\n"
danger
To add an option to an existing command, you need to know its Command
class. This means that if you want to add 2 options by using 2 different commands (e.g. if your application uses different plugins that can register their own commands), you need one of the Command
classes to extend the other one and not the base.
Lazy evaluation​
Many commands have the following form:
ts
import {uniqBy } from 'lodash';ÂclassMyCommand extendsCommand {asyncexecute () {// ...}}
ts
import {uniqBy } from 'lodash';ÂclassMyCommand extendsCommand {asyncexecute () {// ...}}
While it works just fine, if you have a lot of commands that each have their own sets of dependencies (here lodash
), the overall startup time may suffer. This is because the import
statements will always be eagerly evaluated, even if the command doesn't end up being selected for execution.
To solve this problem you can move your imports inside the body of the execute
function - thus making sure they'll only be evaluated if actually relevant:
ts
classMyCommand extendsCommand {asyncexecute () {const {uniqBy } = await import(`lodash`);// ...}}
ts
classMyCommand extendsCommand {asyncexecute () {const {uniqBy } = await import(`lodash`);// ...}}
This strategy is slightly harder to read, so it may not be necessary in every situation. If you like living on the edge, the babel-plugin-lazy-import
plugin is meant to automatically apply this kind of transformation - although it requires you to run Babel on your sources.