cli-er
Tool for building advanced CLI applications using a definition object. It implements a folder structure strategy that helps organize all the logic, also including help-generation
cli.js:
import Cli from "cli-er";
const definition = {...}
new Cli(definition).run();
Invocation:
node cli.js [namespace(s)|command] [OPTIONS]
Example
Given the following definition (docker.js):
const definition = {
builder: {
options: {
build: {
kind: "command",
type: "string",
description: "Build an image from a Dockerfile",
},
},
},
debug: {
type: "boolean",
aliases: ["-D", "--debug"],
default: false,
},
};
it will allow us to structure the code as follows:
.
├─ docker.js
└─ builder
└── build.js
so we can then execute:
node docker.js builder build . -- someother-external-option
which will try to invoke, in order:
/builder/build/index.js
/builder/build.js
/builder/index.js
/builder.js
/index.js
/docker.js
with the parsed options (only the first two are default imports, the rest are named imports using the command name, in this case build
).
This allows us to organize and structure the logic nicely.
You can check the full docker-based example for a more in-depth demo.
Usage
cli-er
default-exports a class, which takes in the definition object and an optional argument CliOptions. The available methods are the following:
parse(args)
Parses the given list of arguments based on the provided definition, and returns an object containing the resulting options, and the calculated location where the script is expected to be found. If any error is generated during the process, they will be registered inside an errors
field.
A custom parser for an Option may be used, with the following signature:
type parser = (input: ValueParserInput) => ValueParserOutput;
type ValueParserInput = {
/** Value to process */
value: string | undefined;
/** Current value of the option */
current: OptionValue;
/** Option definition */
option: Option & { key: string };
/** Method for formatting errors */
format: typeof CliError.format;
}
type ValueParserOutput = {
/** Final value for the parsed option */
value?: any;
/** Number of additional arguments that the parser consumed. For example, a boolean option
* might not consume any additional arguments ("--show-config", next=0) while a string option
* would ("--path path-value", next=1). The main case of `next=0` is when incoming value
* is `undefined` */
next?: number;
/** Error generated during parsing */
error?: string;
}
This allows to create custom parsers for any type of input (check the custom-option-parser example for a hint), or to override the existing parsers logic.
If no command is found in the parsing process, an error with a suggestion (the closest to the one suplied) will be returned.
You can define an option as required (required: true
), which will verify that such option is present in the provided arguments, setting an error otherwise.
This library also interprets the delimiter --
to stop parsing, including the remaning arguments as an array inside ParsingOutput.options.__
Positional options (Option.positional: boolean | number
) allow asigning an option to a determinate position in the arguments (full example here), p.e:
new Cli({ projectName: { positional: 0} }, { cliName: "create-project" });
// $ create-project newproject [...]
// => { options: { projectName: "newproject" }}
The execution of the above example would be:
{ "options": { "build": ".", "debug": false, "_": ["someother-external-option"] }, "location": ["builder", "build"] }
run(args?)
Parses the given options by calling parse(args)
, and executes the script in the computed location, forwarding the parsed options. Alternatively, a command can be defined with an action
function that will be called when the command is matched. If no arguments are provided, the args value defaults to process.argv.slice(2)
.
With the following example (action.js):
new Cli({
cmd: {
options: {
log: { type: "boolean" },
},
action: ({ options }) => {
if (options.log) {
console.log("Log from cmd");
}
},
},
}).run();
invoking with node action.js cmd --log
will print "Log from cmd" into the console.
This method has three main behaviours: print version, print help and execute a command:
- print version: if autoincluded version is enabled and version option is provided, version will be printed.
- print help: if autoincluded help is enabled and help option is provided, or a cli without
rootCommand
is invoked without location, or a namespace is invoked, help will be generated. If any errors configured inCliOptions.errors.onGenerateHelp
are generated, they will be outputted before the help. - execute command: if any errors configured in
CliOptions.errors.onExecuteCommand
are generated, they will be printed and execution will end with status1
. Otherwise, the script location will be calculated, and the corresponding script executed.
If a cli application does not have registered a root command (logic executed without any supplied namespace/command), it should be configured with CliOptions.rootCommand: false
. By doing this, when the cli application is invoked with no arguments, full help will be shown (see this docker example).
You also use CliOptions.rootCommand
to define a default command to execute, when no command/namespace is supplied (check this webpack-cli example).
help(location?)
Generates and outputs help message based on the provided definition. Given the following code (test.js):
const definition = {
nms: {
description: "Description for the namespace",
options: {
cmd: {
kind: "command",
type: "string",
description: "Description for the command",
},
},
},
gcmd: {
kind: "command",
description: "Description for global command",
},
globalOption: {
aliases: ["-g", "--global"],
default: "globalvalue",
description: "Option shared between all commands",
},
};
new Cli(definition, { cliDescription: "Cli for testing purposes" }).help();
will output:
Usage: test NAMESPACE|COMMAND [OPTIONS]
Cli for testing purposes
Namespaces:
nms Description for the namespace
Commands:
gcmd Description for global command
Options:
-g, --global Option shared between all commands (default: globalvalue)
-h, --help Display global help, or scoped to a namespace/command
The optional argument location enables the generation of scoped help. For the above definition, the following code:
new Cli(definition).help(["nms"]);
will output:
Usage: test nms COMMAND [OPTIONS]
Description for the namespace
Commands:
cmd Description for the command
Options:
-g, --global Option shared between all commands (default: globalvalue)
-h, --help Display global help, or scoped to a namespace/command
Any DefinitionElement
can be hidden from the generated help by using hidden:true
on its definition.
Note
help-generation option is auto-included by default. This can be configured via CliOptions.help
version()
Prints the formatted version of the current cli application: finds the package.json for the current application, and prints its name and version.
Note
version-generation option is auto-included by default. This can be configured via CliOptions.version
CliOptions
A configuration object may be provided in the class constructor. The available options are:
baseLocation
Location of the main cli application.
Default: path.dirname(entryFile)
baseScriptLocation
Base path where the ProcessingOutput.location
will start from
Default: path.dirname(entryFile)
commandsPath
Path where the single-command scripts (not contained in any namespace) are stored, starting from CliOptions.baseScriptLocation
Default: "commands"
errors
Configuration related to when errors should be displayed. The order of the lists containing the error-types matters, as it changes which error-messages are shown first (elements appearing first have a higher order of precedence). The available error-types are:
command_not_found
: an unknown argument is encountered when a command was expectedoption_wrong_value
: the option parser considers the given value as incorrect (e.g.type:number
when given"a123"
)option_required
: an option is marked as required but is not providedoption_missing_value
: an option is provided without its corresponding valueoption_not_found
: an unknown argument is encountered when an option was expected
errors.onGenerateHelp
List of error-types that will get displayed before help
Default: ["command_not_found"]
errors.onExecuteCommand
List of error-types that will cause to end execution with exit(1)
Default: ["command_not_found", "option_wrong_value", "option_required", "option_missing_value", "option_not_found"]
help
Help-related configuration
help.autoInclude
Whether to generate help option
Default: true
help.aliases
Aliases to be used for help option
Default: ["-h", "--help"]
help.description
Description for the option
help.template
Template to be used when generating help. There are five distinct sections: usage, description, namespaces, commands and options. This can be used to include a header/footer, change the order of the sections, or remove a section altogether. If a section has no content, it will be removed along with any line-breaks that follow. You can see a use-case for this in the docker example
Default: \n{usage}\n{description}\n{namespaces}\n{commands}\n{options}\n
version
Version-related configuration
version.autoInclude
Whether to generate version option
Default: true
version.aliases
Aliases to be used for version option
Default: ["-v", "--version"]
version.description
Description for the option
rootCommand
Whether the cli implements a root command (invocation with no additional namespaces/commands).
If a string is provided, it will be used as the default command to execute
Default: true
logger
Logger to be used by the cli. It contains two methods, log
and error
, that can be used to add a prefix to the log (e.g. "error ") or change the output color, as demonstrated in this docker example.
Default: ./src/cli-logger.ts
cliName
Cli name to be used instead of the one defined in package.json
Default: packageJson.name
cliVersion
Cli version to be used instead of the one defined in package.json
Default: packageJson.version
cliDescription
Cli description to be used instead of the one defined in package.json
Default: packageJson.description
debug
Enable debug mode. This is intended for the development phase of the cli. It will:
- Print verbose errors when a script in not found in the expected path during
Cli.run
. - Print warnings for deprecated properties/methods, useful for detecting and applying these changes before updating to the next version.
Its value can also be configured using an enviroment variable:
$ CLIER_DEBUG=1 node cli.js
Default: process.env.CLIER_DEBUG
messages
Object containing the messages to be used in the Cli, to override the default ones defined by this library. This enables internationalization and customization of cli-native messages. You can see a use-case in this intl-cli example
Default: defined in ./src/cli-messages and ./src/cli-errors
Typescript cli
You can check this example on how to write a full typescript cli application.