An opinionated solution for building CLI applications using Node.js.
- Understanding CLI Application Categories
- Understanding CLI Command invocation
- Introduction to
cli-max
- Getting Started
cli-max
in Action (example)- API in detail
CLI Applications can be categorized into two types
- Single Command Apps
- Multi Command Apps
Single Command Apps
Single Command Apps usually have just one main action.
Examples: mkdir
, touch
, ls
, etc.,
Multi Command Apps
Multi Command Apps are usually a collection of actions.
They have sub-commands that perform different operations.
Examples: git
, npm
, etc.,
$ command sub-command <...[--options]> <...parameters>
Examples
$ mkdir "New Folder"
mkdir
is the command- the value
New Folder
is called a parameter or an argument.
$ git pull --quiet origin master
git
is the commandpull
is the sub-command--quiet
is an optionorigin
andmaster
are the parameters
cli-max
is a library that tries to make the experience of building a CLI app easy and pleasant.
The API of cli-max
consciously tries to make your code more declarative and thus be easier to build, understand and maintain.
- Single and Multi Command CLI Apps, both can be implemented using
cli-max
- A Command can be configured as a default. and this will be executed when no valid command name is passed at runtime
- Sub-Commands can have aliases, making it easy for end-users to pass alternative command names
- Options can have aliases too (supports both short flags and long alternatives)
- Options can be configured with default values
- "help" details for every command is auto-generated using the details provided in the configuration
The approach to building CLI apps using cli-max
, can broadly be described as a two-step process:
- Configure
- Execute
You can configure and implement the functionality of your CLI app using the API createCLI()
.
function createCLI(command: Command, config: CLIConfig): ExecuteFn
The createCLI()
API takes two parameters of types Command and CLIConfig respectively.
You can build a Single Command CLI App by implementing all its functionality in the action
property of the command
argument passed to createCLI()
.
For building Multi Command CLI App, you can configure sub-commands and implement their functionality in corresponding action
property of each sub-command.
If invoked with proper configuration as mentioned above createCLI()
API returns a function.
This function is what is used in the next step.
The function returned by the createCLI()
API is what invokes the actual functionality.
One can call the function returned by createCLI()
as cli
or execute
or run
or parse
or compute
or anything similar.
This function encapsulates the idea of processing and evaluating the actual arguments and flags passed at runtime and hence any of the above names.
The type for the function returned by createCLI()
is ExecuteFn
Note: To explain easily, this document will from now on refer this function as
executeFn
. But, remember it can be named anything per your convenience.
const executeFn = createCLI(someCommand, someConfig); // step-1
const result = executeFn(process.argv); // step-2
The executeFn
expects the arguments received by the Node.js process at runtime.
One can access these arguments passed to a Node.js process from the global property process.argv
. (read more about process.argv)
Invoking executeFn
with runtime arguments properly will parse the arguments and execute the corresponding command configured in the 1st step.
executeFn
also returns the output value of the command executed from the configured commands.
Some insight on process.argv
process.argv
is an array of the command line arguments received by the Node.js process
Launching a Node.js process as
$ node process-args.js one two=three four
would generate the process.argv
with the following values
[
"/path/to/the/node/executable",
"/path/to/the/file/process-args.js",
"one",
"two=three",
"four"
]
const execute = createCLI({
name: 'give',
description: 'Greetings made easy!',
subCommands: [
{
name: 'greetings',
action: ({ flags: { to } }) => {
console.log(`Hello ${to}! How are you?`);
},
options:[
{
name: 'to',
aliases: ['t'],
description: 'this option specifies whom to greet',
required: false,
defaultValue: 'there',
},
],
isDefault: true,
},
{
name: 'compliment',
action: ({ flags: { to } }) => {
console.log(`Hey ${to}, You look good! :)`);
},
options:[
{
name: 'to',
aliases: ['t'],
description: 'this option specifies whom to complient',
required: false,
defaultValue: 'there',
},
],
}
],
});
execute(process.argv);
/*
Output:
$ give greetings
Hello there! How are you?
$ give greetings --to John
Hello John! How are you?
$ give compliment
Hey there, You look good! :)
$ give greetings --to John
Hey John, You look good! :)
*/
API:
Types:
Interfaces:
This API is used to configure the command
, sub-commands
, options
and actions
function createCLI(command: Command, config: CLIConfig): ExecuteFn
params
Expects two arguments
return value:
returns a function of type ExecuteFn
This is the type for the function returned by createCLI() API
type ExecuteFn = function (processArgs: string[]): any
params
Expects one argument
processArgs
- an array of strings
processArgs
is supposed to be process.argv i.e, the list of arguments received by the process
return value
returns the output of the command executed from the list of sub-commands configured
This is the type for the callback function that is configured in a Command or a SubCommand to implement its functionality.
An action
callback configured in a Command
or a SubCommand
gets invoked automatically when executeFn
receives the command-name in the processArgs
.
type Action = function (params: ActionParams): any;
params
Receives one argument
params
- an object of type ActionParams
This is the type for the function that generates "help" details for a command
or a sub-command
.
This function getHelp
can be accessed as a property in the ActionParams received by the action
.
If you want to display "help" for a command
, you can just invoke this function (available as a property in the object received by the action
configured for the command
) and it would return the "help" details generated specifically for the command
in context.
type GetHelpFn = function (): string;
This type encapsulates the details needed to configure a command
{
name: string;
description: string;
usage: string;
action?: Action;
options?: Option[];
subCommands?: SubCommand[];
}
Note: To auto-generate proper "help" details for a
command
, it is assumed that at the least thename
,description
andusage
properties are assigned non-empty values.
To create a Single Command CLI App one can just implement the action property in the Command object being passed to createCLI()
API.
To create a Multi Command CLI App one has to configure subCommands
property in the command
object that is passed to createCLI()
API. For these Apps the main action property is optional but it can be used to handle cases where end-user invokes just the main command.
This type encapsulates the details needed to configure a sub-command
{
name: string;
description: string;
usage: string;
action: Action;
aliases?: string[];
options?: Option[];
isDefault?: boolean;
}
Note: To auto-generate proper "help" details for a
sub-command
, it is assumed that at the least thename
,description
andusage
properties are assigned non-empty values.
This type encapsulates the details needed to configure an option for either a command or a sub-command
{
name: string;
aliases: string[];
description: string;
defaultValue: any;
required: boolean;
}
Note: To auto-generate proper "help" details for
options
in acommand
or asub-command
, it is assumed that at the least thename
anddescription
for individual options are assigned non-empty values.
This type represents the object passed to an action
of a command or a sub-command
{
parameters: string[];
flags: RuntimeFlags;
getHelp: GetHelpFn;
}
This type represents the collection of flags received at runtime.
each flag and its value received at runtime is assigned as a property and the corresponding value in this object
{
[key: string]: any;
}
This type encapsulates the customization options that can be passed to the createCLI() API
{
generateHelp?: boolean;
prettyHelp?: boolean;
paddingInDetails?: number;
}