clibuilder
v9.0.0
Published
A CLI building library
Downloads
1,276
Readme
CLI Builder
A highly customizable command line application builder.
What's new in v8
Key highlights:
- Support standalone CLI
name
andversion
are now required and not read frompackage.json
.
- Plugins are loaded through config
- This drastically improve startup time, as it does not scan
node_modules
anymore. - Also better support other package manager such as
yarn PnP
andpnpm
.
- This drastically improve startup time, as it does not scan
keywords
are now used for plugin lookup.- Distribute
ESM
along withCJS
.
Feature Highlights
- support default commands and sub-commands
my-cli cmd1 cmd2 cmd3
- configuration file support
- plugin support: write commands in separate packages and reuse by multiple CLI
- type inference and validation for config, arguments, and options
using zod (exported asz
)
Install
# npm
npm install clibuilder
# yarn
yarn add clibuilder
# pnpm
pnpm install clibuilder
#rush
rush add -p clibuilder
Usage
You can use clibuilder
to create your command line application in many ways.
The most basic way looks like this:
// Define your app
const app = cli({ name: 'app', version: '1.0.0' })
.default({ run() { /* ...snip... */ }})
// Use your app
app.parse(process.argv)
.catch(e => /* handle error */process.exit(e?.code || 1))
You can add additional named commands and sub-commands:
cli({ ... })
.command({ name: 'hello', run() { this.ui.info('hello world') }})
.command({
name: 'repo',
commands:[
command({ name: 'create', run() { /* ..snip.. */ }})
]
})
Command can have alias:
cli({ ... })
.command({
name: 'search-packages',
alias: ['sp'],
/* ..snip.. */
})
// call as: `my-cli sp`
You can specify arguments:
cli({ ... }).default({
arguments: [
// type defaults to string
{ name: 'name', description: 'your name' }
],
run(args) { this.ui.info(`hello, ${args.name}`) }
})
cli({ ... }).command({
name: 'sum',
arguments: [
// using `zod` to specify number[]
{ name: 'values', description: 'values to add', type: z.array(z.number()) }
],
run(args) {
// inferred as number[]
return args.values.reduce((p, v) => p + v, 0)
}
})
Of course, you can also specify options:
cli({ ... }).default({
options: {
// type defaults to boolean
'no-progress': { description: 'disable progress bar' },
run(args) {
if (args['no-progress']) this.ui.info('disable progress bar')
}
}
})
and you can add option alias too:
cli({ ... }).command({
options: {
project: {
alias: ['p']
}
}
})
You can use z
to mark argument and/or options as optional
cli({... }).default({
arguments: [{ name: 'a', description: '', type: z.optional(z.string()) }],
options: {
y: { type: z.optional(z.number()) }
}
})
If you invoke a command expecting a config
,
the config will be loaded.
Each command defines their own config.
cli({ ... })
.default({
config: z.object({ presets: z.string() }),
run() {
this.ui.info(`presets: ${this.config.presets}`)
}
})
Config
Config file can be written in JSON
, YAML
, cjs
, or mjs
.
Common filename are supported:
.{name}.<cjs|mjs|js|json|yaml|yml>
.{name}rc.<cjs|mjs|js|json|yaml|yml>
{name}.<cjs|mjs|js|json|yaml|yml>
{name}rc.<cjs|mjs|js|json|yaml|yml>
You can override the config name too:
cli({ config: 'alt-config.json' })
Plugins
One of the key features of clibuilder
is supporting plugins.
Plugins are defined inside the config:
{
"plugins": ["my-cli-plugin"]
}
Defining Plugins
clibuilder
allows you to build plugins to add commands to your application.
i.e. You can build your application in a distributed fashion.
To create a plugin:
- export a
activate(ctx: PluginActivationContext)
function - add the keywords in your
package.json
to make it searchable
import { command, PluginActivationContext } from 'clibuilder'
// in plugin package
const sing = command({ ... })
const dance = command({ ... })
export function activate({ addCommand }: PluginCli.ActivationContext) {
addCommand({
name: 'miku',
commands: [sing, dance]
})
}
// in plugin's package.json
{
"keywords": ['your-app-plugin', 'vocaloid']
}
The CLI can search for plugins using the keywords
values.
Testing
testCommand()
can be used to test your command:
import { command, testCommand } from 'clibuilder'
test('some test', async () => {
const { result, messages } = await testCommand(command({
name: 'cmd-a',
run() {
this.ui.info('miku')
return 'x'
}
}), 'cmd-a')
expect(result).toBe('x')
expect(messages).toBe('miku')
})
shebang
To make your CLI easily executable, you can add shebang to your script:
#!/usr/bin/env node
// your code