Brother comannd line tool
Create type-safe CLIs to align local development and pipeline workflows.
Powered by vite.
Ever wanted a type safe, easy to set up CLI that is automatically self-updating locally and can be used in CI / CD workflows? Brocolito aims to support you with that.
You create some directory for your CLI code and place two mandatory files in it:
package.json
- the
name
will be the name of your CLI, e.g.cli
. - the
dependencies
should containbrocolito
- you can install it e.g. via
pnpm install brocolito
- you can install it e.g. via
- the
scripts
should contain e.g."build": "brocolito build"
- the
- the
src/main.ts
is your index file for the CLI code- below you can find a minimal code sample
Run the build script to set up the CLI, from the above example with pnpm build
.
You will get printed how to make the CLI globally available for you.
Additional recommendations:
- Install
devDependencies
:typescript
and@types/node
- Add
tsconfig.json
- Add
lint
script for your CLI
Feel free to check out the herein provided example.
In your src/main.ts
you create this code:
import { CLI } from 'brocolito';
CLI.command('hello').action(() => console.log('hello world'));
CLI.parse(); // this needs to be executed after all "commands" were set up
Now you can run cli -h
to see the help message or cli hello
to print hello world
.
For more advanced features see below.
Please check the used custom actions of this repository yourself. It uses pnpm to install the CLI dependencies and a simple setup to set up the CLI.
In this workflow you can see it in action.
- Create an initial setup using
create-brocolito-cli
package- Then you can set the above up using
pnpm create brocolito-cli
- Then you can set the above up using
- Include some more examples of interesting CLI relevant features that can be adopted
The CLI contains already many features right away. It was inspired by commander and cac, but it has a slightly different API and some features that I was found to be missing like native subcommand and tab completion support.
Using the -h
or --help
options on any command, subcommand or top-level will display an
automatically generated help text from your CLI configuration.
As soon as you have locally set up the completion, you will get automatically suggestions
based on your CLI configuration. Run cli completion
to set up the tab completion.
If you want to use an alias for e.g. one of your subcommands like cli get data
, you have
to register it to make the completion work like so: CLI.alias('cgd', 'cli get data')
.
Options are basically named parameters for your command. The specified option names are
accessible under their camelCase name. E.g. foo-bar
becomes fooBar
. Values fo
BC12
r options can
be specified using a space or =
as separator, e.g. --my-option=foo
and --my-option foo
are identically treated, whereas --my-option
without parameters is only valid for option
flags and will be treated as boolean true
.
- Specification:
--option-name
- Parameter type:
boolean
- Completion:
true
orfalse
- Code example:
CLI.command('hello', 'prints hello world')
.option('--with-exclamation-mark', 'append exclamation mark')
.action(({ withExclamationMark }) => console.log(`hello world${withExclamationMark ? '!' : ''}`));
- Shell examples:
cli hello --with-exclamation-mark
cli hello --with-exclamation-mark=true
cli hello --with-exclamation-mark false
- Specification:
--option-name <string>
- Parameter type:
string | undefined
- Completion: none
- Code example:
CLI.command('hello', 'prints greeting')
.option('--name <string>', 'who to greet?')
.action(({ name }) => console.log(`hello ${name}`));
- Shell example:
cli hello --name mark
- Specification:
--option-name <file>
- Parameter type:
string | undefined
- Completion: local file system
- Code example:
CLI.command('char-count', 'count characters')
.option('--content <file>', 'what file to use?')
.action(async ({ content }) => console.log((await fs.readFile(content, 'utf-8')).length));
- Shell example:
cli char-count --content ./foo.txt
Args are basically unnamed parameters or parameter lists. The specified arg names are
accessible under their camelCase name. E.g. foo-bar
becomes fooBar
. You can have as
many different args for the same command as you like. You cannot have args and subcommands
on a command. If you cannot have another arg after an arg list.
- Specification:
<arg-name>
- Parameter type:
string
- Completion: none
- Code example:
CLI.command('hello', 'prints greeting')
.arg('<arg-name>', 'greeting name')
.action(({ argName }) => console.log(`hello ${name}`));
- Shell example:
cli hello mark
- Specification:
<file:arg-name>
- Parameter type:
string
- Completion: local file system
- Code example:
CLI.command('exists', 'checks existance')
.arg('<file:file-name>', 'what file to check?')
.action(({ fileName }) => console.log(fs.existsSync(fileName)));
- Shell example:
cli exists /tmp/someFile.js
- Specifications:
<arg-name...>
or<file:arg-name...>
- Parameter type:
string[]
- Completions: none or local file system
- Code example:
CLI.command('exists', 'checks existance')
.arg('<file:file-names...>', 'what files to check?')
.action(({ fileNames }) => console.log(fileNames.map((f) => fs.existsSync(f))));
- Shell example:
cli exists /tmp/someFile.js ./foo.txt
Subcommands allow you to create a grouped functionality of commands within other commands. Subcommands can be as deeply nested as you like. If a command has subcommands it cannot have args. Options are inherited from the command to all afterwards specified subcommands. Every subcommand can use further options, args or subcommands as any regular command.
- Code example:
CLI.command('string', 'do something with strings')
.option('--error', 'logs as error')
.subcommand('trim', 'trims a string', (sub) => {
sub.arg('<str>').action(({ str, error }) => console[error ? 'error' : 'log'](str.trim()));
})
.subcommand('length', 'counts the chars', (sub) => {
sub.arg('<str>').action(({ str, error }) => console[error ? 'error' : 'log'](str.length));
})
- Shell examples:
cli string trim " foo"
cli string length "lorem ipsum"
brocolito
makes use of some utilities that are also made available for your CLI.
complainAndExit(msg: string)
: Prints the given message in red and immediately stops the process without printing a whole error stack.pc
: Default export of the picocolors package to add colors to your printed output.prompts
: Default export of the prompts package to make use of interactive shell prompts.
If you are using external dependencies, these have to be listed in your package.json
under
"dependencies"
. In case of build-in NodeJS dependencies, make sure to use the prefixed package
names, e.g. node:fs
instead of fs
. Otherwise, the dependencies might end up included in your
resulting bundle or aren't correctly resolved at all.