gluegun
Version:
A delightful toolkit for building Node-powered CLIs.
358 lines (270 loc) • 9.72 kB
Markdown
Gluegun provides a builder that lets you initialize and configure Gluegun to work with your CLI. It lets you load & execute commands, extensions, and plugins.
_Note: Check out the [sniff](./sniff.md) module for detecting if your environment is able to run._
Here's a kitchen sink version, which we're about to cover.
```js
const { build } = 'gluegun'
const cli = build('movie')
.src(__dirname)
.plugin('~/.movie/movie-imdb')
.plugins('./node_modules', { pattern: 'movie-' })
.help()
.version()
.defaultCommand()
.command({ name: 'hi', run: toolbox => toolbox.print.info('hi!') })
.exclude(['filesystem', 'semver', 'system', 'prompt', 'http'])
.checkForUpdates(5) // check for updates randomly about 5% of the time
.create()
await cli.run()
```
## build
Grab the `build` function from `gluegun`.
```js
const { build } = require('gluegun')
```
Now let's build a `gluegun` cli environment by configuring various features.
```js
const cli = build('mycli')
```
The `mycli` brand that you pass into `build` is used through-out gluegun for things like configuration file names and folder names for plugins. You can also set it later, like this:
```js
const cli = build().brand('movie')
```
Out of the box, this CLI does very little. And by very little I mean nothing. So let's configure this. We'll be chaining the `build()` function from here.
## src
This sets where the default commands and extensions are located, in
`commands` and `extensions` folders, respectively.
```js
const cli = build('movie').src(__dirname)
```
When you run a command, it'll first load extensions in this folder and then check the commands in this folder for the right command.
```sh
# run a command with arguments
$ movie actors Kingpin
# run a command with arguments & options
$ movie producers "Planes, Trains, & Automobiles" --sort age
```
## plugin
Additional functionality can be added to the `gluegun` object with [plugins](./plugins.md). Plugins can be yours or your users.
_Hint: `src` and `plugin` are almost identical under the hood. The only thing they do differently is `src` will be loaded first and be the "default plugin"._
A plugin is a folder (or, more often, an NPM package) that contains a structure - something like this:
```
movie-credits
commands
actors.js
producers.js
extensions
retrieve-imdb.js
templates
actor-view.js.ejs
movie.config.js
```
You can load a plugin from a directory:
```js
const cli = build('movie')
.src(__dirname)
.plugin('~/.movie/movie-imdb')
```
## plugins
You can also load multiple plugins within a directory.
```js
const cli = build('movie')
.src(__dirname)
.plugin('~/.movie/movie-imdb')
.plugins('./node_modules', { pattern: 'movie-' })
```
`plugins` supports a `fs-jetpack` [matching pattern](https://github.com/szwacz/fs-jetpack#findpath-searchoptions) so you can filter out a subset of directories instead of just all.
```js
.plugins('./node_modules', { matching: 'movies-*' })
```
If you would like to keep plugins hidden and not available at the command line:
```js
.plugins('./node_modules', { matching: 'movies-*', hidden: true })
```
When plugins are hidden they can still be run directly from the cli.
## help
Gluegun ships with a somewhat adequate `help` screen out of the box. Add it to your
CLI easily by calling `.help()`.
```js
const cli = build('movie')
.src(__dirname)
.plugins('./node_modules', { pattern: 'movie-' })
.plugin('~/.movie/movie-imdb')
.help()
```
You can also pass in a function or command object here:
```js
.help(toolbox => toolbox.print.info('No help for you!'))
.help({
name: 'help',
alias: 'helpmeplease',
hidden: true,
dashed: true,
run: toolbox => toolbox.print.info('No help for you!')
})
```
## version
You usually like to be able to run `--version` to see your CLI's version from the command
line, so add it easily with `.version()`.
```js
const cli = build('movie')
.src(__dirname)
.plugins('./node_modules', { pattern: 'movie-' })
.plugin('~/.movie/movie-imdb')
.help()
.version()
```
Just like `help` above, you can pass in a function or command object to configure it further.
## defaultCommand
If the user runs your CLI and doesn't supply any matching parameters, it'll run this command
instead. Note that you can do this by supplying a `<brand>.js` file in your `./commands`
folder as well.
```js
const cli = build('movie')
.src(__dirname)
.plugins('./node_modules', { pattern: 'movie-' })
.plugin('~/.movie/movie-imdb')
.help()
.version()
.defaultCommand()
```
Just like `help` and `version` above, you can pass in a function or command object if
you prefer more control.
## command
If you want to pass in commands directly to the runtime builder, you can do that with `.command()`.
```js
const cli = build('movie')
.src(__dirname)
.plugins('./node_modules', { pattern: 'movie-' })
.plugin('~/.movie/movie-imdb')
.help()
.version()
.defaultCommand()
.command({ name: 'hi', run: toolbox => toolbox.print.info('hi!') })
```
In this case, if you ran `movie hi`, it would run the function provided and print out 'hi!'.
You must provide an object with at least a `name` and a `run` function, which can be
`async` or regular.
## exclude
If you don't need certain core extensions, you can skip loading them (thus improving startup time) by using `.exclude()`. Just pass in an array of string names for the core extensions you don't need.
```js
const cli = build('movie').exclude([
'meta',
'strings',
'print',
'filesystem',
'semver',
'system',
'prompt',
'http',
'template',
'patching',
])
```
If you find you need one of these extensions for just _one_ command but don't want to load it for _all_ of your commands, you can always load it separately from the Gluegun toolbox, like this:
```js
const { prompt } = require('gluegun')
// or
const { prompt } = require('gluegun/prompt')
```
For reference, the core extensions that incur the biggest startup performance penalty are (timing varies per machine, but this gives some sense of scale):
```
prompt +100ms
print +45ms
http +30ms
system +10ms
```
_Note about TypeScript and `exclude`:_ Please note that the TypeScript type `GluegunToolbox` (as of Gluegun 2.1.x) always assumes that core extensions are included, even if you excluded them in the builder. In this case, it's recommended that you create your own `FooToolbox` (or similar) and update the interface to match your preferred configuration. Example:
```typescript
// wherever your types are, say, `./src/types.ts`
import { GluegunToolbox } from 'gluegun'
export interface FooToolbox extends GluegunToolbox {
prompt: null
print: null
http: null
system: null
}
// in a command
import { FooToolbox } from '../types'
module.exports = {
run: async (toolbox: FooToolbox) => {
// ... use toolbox with your excluded extensions
},
}
```
## checkForUpdates
This allows you to check for updates every so often. Because we don't track how often your CLI is run, instead, we allow you to set a percentage chance of checking for updates. We recommend somewhere between 1-20, depending on how often your CLI is run. If you want to run it every time, set it to 100.
```js
const cli = build('movie')
.src(__dirname)
.plugins('./node_modules', { pattern: 'movie-' })
.plugin('~/.movie/movie-imdb')
.help()
.version()
.defaultCommand()
.command({ name: 'hi', run: toolbox => toolbox.print.info('hi!') })
.checkForUpdates(5)
```
## create
At this point, we've been configuring our CLI. When we're ready, we call `create()`:
```js
const cli = build('movie')
.src(__dirname)
.plugins('./node_modules', { pattern: 'movie-' })
.plugin('~/.movie/movie-imdb')
.help()
.version()
.defaultCommand()
.command({ name: 'hi', run: toolbox => toolbox.print.info('hi!') })
.checkForUpdates(5)
.create()
```
This command applies the configuration that you were just chaining, and turns it into a `runtime cli` which supports calling `run()`.
And now we're ready to run:
```js
cli.run()
```
With no parameters, `gluegun` will parse the command line arguments looking for the command to run.
```sh
# list the plugins
$ movie
# run a command
$ movie quote
# run a command with options
$ movie quote --funny
# run a command with arguments
$ movie actors Kingpin
# run a command with arguments & options
$ movie producers "Planes, Trains, & Automobiles" --sort age
```
## run
`gluegun` can also be `run()` with options.
```js
await cli.run('quote random "*johnny"', {
funny: true,
genre: 'Horror',
weapon: 'axe',
})
```
There's a few situations that make this useful.
1. Maybe you like to use `meow` or `commander` to parse the command line.
2. Maybe your interface isn't a CLI.
3. Maybe you want to run several commands in a row.
4. Maybe this is your program and you don't like strangers telling you how to code.
Bottom line is, you get to pick. It's yours. `gluegun` is just glue.
## configuration
Each plugin can have its own configuration file where it places defaults. These defaults can then be overridden by reading defaults from a configuration file or entry in `package.json`. We use [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) for this.
It will read the plugin name from the `name` key and the defaults will be read from the `defaults` section. Each section underneath `default` can be used to override the sections of the plugin. Since that was horribly explained, here's an example.
```js
// in movies.config.js
module.exports = {
name: 'movies',
defaults: {
movie: {
cache: '~/.movies/cache',
},
another: {
count: 100,
},
},
}
```