The CLI provides a simple object oriented framework for building and managing command line programs, with clear separation of commands, middleware, args, a program instance, and more. It further streamlines the development process by utilizing Ink for terminal rendering, as manually handling ANSI escape sequences and terminal widths can be tedious and complicated.
The CLI makes heavy usage of the args package, which will be continually referenced throughout this documentation. It's encouraged to read and understand it first.
string) - Name of the theme module (without
boostprefixes) to load for terminal style/color changes.
|Called after a command has been registered.|
|Called before a command has been registered.|
|Called after a component has rendered.|
|Called after the program and command have been ran.|
|Called after a command has run but before a component will render.|
|Called before the program and command will run.|
|Called when a command has been found after parsing argv.|
|Called when a command wasn't found after parsing argv.|
|Called when the |
|Called when the help menu is rendered.|
The entry point of the command line is commonly referred to as the binary, or script, and is managed
Program class. This class handles the registration of commands, applying
middleware to argv (
process.argv), parsing argv into arguments
(options, flags, etc), running the found command with these argument, outputing to the terminal, and
finally cleaning up or handling failures.
Begin by importing and instantiating the
Program class, while passing required and optional
string) - A large banner to appear at the top of the index help interface.
string) - The name of the binary consumers enter on the command line. Must be in kebab-case. (Required)
string) - The character(s) displayed before command line usage examples.
string) - A string of text to display at the bottom of the index help interface.
string) - A string of text to display at the top of the index help interface, below the banner (if present).
string) - A human readable name for your program. (Required)
string) - Current version of your CLI program. Typically the version found in your
package.json. This is output when
--versionis passed. (Required)
Once commands and optional middleware have been registered, you must run
the program with
Program#runAndExit(), with the latter automatically passing
the exit code to
process.exitCode. Both methods require an argv list to run.
If you have any logic that should be bootstrapped before the program runs, and you would like to inherit the error handling of the CLI, you may pass an async-aware function as a 2nd argument when running.
Now that you have the basics of a program, you can set the
bin field in your
package.json. This should point
to the program-aware file you have defined previously. For example, if my program will be called
Boost offers 2 implementations for how the binary can be executed, the 1st is known as a stand-alone program. This implementation only supports 1 command known as the default command, which is immediately ran when the binary is. It does not support sub-commands.
To create a stand-alone binary, create and instantiate a command, then pass it to
Command#default(). The command's
path is ignored for this situation.
Some good examples of stand-alone binaries are
The 2nd implementation is opposite the stand-alone program, and is known as a multi-command program. When the binary is ran, and no arguments are passed, a help menu is displayed instead of executing the default command. Otherwise, if arguments are passed, a registered command will be ran based on matching path name.
To create a multi-command binary, create and instantiate multiple commands, and pass them all to
Command#register(). In the example below, the
boost binary would support the
boost uninstall, and
boost build commands.
Some good examples of stand-alone binaries are
Boost will parse provided argv (a list of string arguments, typically from
args (an object of options, flags, params, etc) for easier consumption. This process can be
Program#middleware(), which allows both argv and args to be read and mutated.
Middleware is a function, that receives the argv list as the 1st argument, and a parse callback as the 2nd argument. It must return an args object, which can be built by executing the parse callback. This allows both before, middle, and after implementations to be possible, as demonstrated below.
Middleware is async, so the parse callback must be awaited! This also means you can implement your own async functionality, like file system access, or network requests.
Boost provides the following built-in middleware for common scenarios.
checkNodeRequirement- Verify that the currently running Node.js
process.versionsatisfies the given semver range. If not, a console error will be logged, or an error will be thrown.
checkPackageOutdated- Verify that a package and its provided version is using the latest distribution version by checking against the NPM registry. If not, a console message will be logged.
Commands are self-encapsulated pieces of business logic that are ran when a matching path (a unique
argument) is found on the command line. To create a command, import and extend the abstract
Command class, and implement a
run() method. This method can be async and even render
However, that's not all required, as a command and it's features must be configured! Features may be defined with a declarative approach using TypeScript decorators, or an imperative approach with static class properties. Both variants will be demonstrated in the examples below.
All commands support the following metadata configuration, with
string) - A list of aliased paths. Will not show up in the help menu, but will match on the command line.
boolean) - Allow unknown options to be parsed, otherwise an error is thrown. Defaults to
boolean) - Allow variadic params to be parsed, otherwise an error is thrown. Defaults to
object) - A mapping of sub-command and option categories for this command only. Global options are automatically defined under the
string) - The category this command belongs to. Will be used to group in the parent command or program. Defaults to no category.
boolean) - Whether the command is deprecated or not. Will display a tag in the help menu. Defaults to
string) - A description of what the command is and does. Supports basic markdown for bold, italics, and underline. (Required)
boolean) - Whether the command should be hidden from the help menu or not. Will still match on the command line. Defaults to
string) - A unique name in which to match the command on the command line amongst a list of arguments (argv). (Required)
string | string) - Define one or many usage examples to display in the help menu.
When using decorators, import the
Config decorator and apply it to the
Command class. The path
and description are required, while all other metadata can be passed as an object. Otherwise, just
define static class properties of the same name!
Options are optional arguments that accept a value on the command line. When a
command is ran, each option is set as a class property based on the matching command line value, or
the provided default value. Like configuration above, options can be defined declaratively or
imperatively, with option types being passed to the 1st
Command generic slot.
Declarative: There are 5
Arg decorators to choose from when defining options, all of which are
defined on a class property, where the property name becomes the option name. For example, a
save would become the
--save option. Depending on the decorator, they support many
option settings, excluding
description, which are inferred, and
default which comes from the property value.
Imperative: If you prefer to use static properties, all options are defined through the single
options property, which requires a mapping of option names to
option settings. With this approach,
description are required,
default either being configured with a setting, or coming from the class property value. For
easier type safety, the
Options collection type can be used to type the static property.
By default, unknown options are not allowed and will throw an error. To allow, set the
allowUnknownOptions configuration setting to true. When enabled, all unknown options
will be set as a string object to the
Command#unknown class property.
Boost provides the follow options that are always available to all commands.
boolean) - Displays a help menu for the chosen command or the program itself. Available under the
string) - Display errors, messages, and the interface in the chosen locale (if supported). Locale must be in the format of "en" or "en-US". Available under the
boolean) - Display the current program version and exit. Available under the
Params are command line values that will be passed to
arguments. When defining params, all param settings are supported, and required
are mandatory. Param types are passed to the 2nd
Command generic slot.
Declarative: When using decorators, the
Arg.Params decorator must be defined on the
Command#run() method. It accepts an argument for each param you want to configure, in the order
they should be expected.
Imperative: If you prefer to use static properties, all params are defined through the single
params property, which requires an array of param settings. For easier
type safety, the
Params collection type can be used to type the static property.
By default, variadic params are not enabled and will throw an error when an unconfigured param is
found. To allow, set the
allowVariadicParams configuration setting to true. When
enabled, all extra params will spread onto the end of the
Command#run() method as strings.
Using the example above, it would look like the following.
Rest arguments are all arguments that come after a standalone
delimiter, and can be accessed using the
Command#rest property, which is an array of strings.
Of course commands can register their own commands, known as sub-commands -- it's commands all the way down! Sub-commands are configured exactly the same, with the key difference being that their path must be prefixed with their parent command's path, separated by a colon.
For example, say we have a scaffolding command, where each sub-command is the specific template to
generate. The parent path would be
scaffold, where a child would be
scaffond:controller, so on and so forth. You can see this in action below.
Sub-commands can now be executed on the command line by passing their full path, like so:
boost scaffold:model --name User.
This chapter assumes you have knowledge of React, JSX/TSX, and Ink. If you do not, it's highly encouraged to study those topics, but building CLIs with React is not necessarily a requirement as you can use logging instead.
With that being said, components can be rendered from a command by returning a React element from
Command#run(), or by calling the
Command#render() method. For a quick demonstration, let's
implement a component that writes to a file asynchronously.
Then we implement the command that returns and renders the component.
render() method allows for multiple and or different components to be rendered within
the same run cycle. Returning a component will only use 1. However, both can be used together!
Sometimes classes may be overkill for commands, so Boost offers a feature known as shorthand
commands, where only objects and functions are used. To utilize shorthand commands, call either the
Command#register() methods, with a unique path, settings object (config,
options, params, etc)), and run function.
Besides a different API, the arguments to the run function are also different. Instead of spread params like in a class, they are an options object, list of params, and list of rest arguments respectively.
If you want to access the logger, be sure to use a function declaration instead of an anonymous function, so that context binding works correctly!
Tasks are reusable functions that can be executed within any command, while gaining contextual and limited access to that command's instance. This promotes reusability and composition while avoiding inheritance related issues.
To use a task, create a function with any arguments and function body that you'd like. The function
body has access to the parent command's options, logger, and
rest arguments through
this. If using TypeScript, the
this special argument should
Now that are task is created, we can now execute it within a command using
method requires the task as a function reference, and all it's required arguments.
Tasks are a command only feature and cannot be executed from within a React component.
Categories are a mechanism for grouping commands and options in the help menu for easier
readability. They're shared between both commands and options for interoperability purposes, and can
be defined globally with
Program#categories(), or per command through
categories configuration. To make use of categories, define a mapping of keys to
category names and optional weights, like so.
Categories are sorted by weight first, then alphabetical second, so define
weight when you want
strict control of the category order. Uncategorized items have a weight of
0, and the built-in
globals have a weight of
Now that categories have been defined, be sure to set the category on your commands and options
category setting! Here's an example using decorators.
Boost integrates its very own logger so that logs can easily be sent to the configured
stderr. The logger is accessible using
Command#log() and associated methods.
The logger is also accessible within a component by using the
ProgramContext, like so.
It's highly encouraged to use the logger instead of the native console, so that logged messages do not interrupt the React rendering process, and write to the configured streams!
Themes allow consumers to alter the color of text and backgrounds for elements rendered with the
Style component. Themes are simply NPM modules that return an object of hexcodes or ANSI colors,
and can be enabled by defining the
BOOSTJS_CLI_THEME environment variable.
When defining a theme, we'll attempt to load from
otherwise we throw an error. Third-party modules are currently not supported.