Smooth (CLI) operator 🎶
npm install sade> Smooth (CLI) Operator 🎶
Sade is a small but powerful tool for building command-line interface (CLI) applications for Node.js that are fast, responsive, and helpful!
It enables default commands, git-like subcommands, option flags with aliases, default option values with type-casting, required-vs-optional argument handling, command validation, and automated help text generation!
Your app's UX will be as smooth as butter... just like Sade's voice. 😉
```
$ npm install --save sade
Input:
`js
#!/usr/bin/env node
const sade = require('sade');
const prog = sade('my-cli');
prog
.version('1.0.5')
.option('--global, -g', 'An example global flag')
.option('-c, --config', 'Provide path to custom config', 'foo.config.js');
prog
.command('build
.describe('Build the source directory. Expects an index.js entry file.')> building from ${src} to ${dest}
.option('-o, --output', 'Change the name of the output file', 'bundle.js')
.example('build src build --global --config my-conf.js')
.example('build app public -o main.js')
.action((src, dest, opts) => {
console.log();
console.log('> these are extra opts', opts);
});
prog.parse(process.argv);
`
Output:
`a
$ my-cli --help
Usage
$ my-cli
Available Commands
build Build the source directory.
For more info, run any command with the --help flag
$ my-cli build --help
Options
-v, --version Displays current version
-g, --global An example global flag
-c, --config Provide path to custom config (default foo.config.js)
-h, --help Displays this message
$ my-cli build --help
Description
Build the source directory.
Expects an index.js entry file.
Usage
$ my-cli build
Options
-o, --output Change the name of the output file (default bundle.js)
-g, --global An example global flag
-c, --config Provide path to custom config (default foo.config.js)
-h, --help Displays this message
Examples
$ my-cli build src build --global --config my-conf.js
$ my-cli build app public -o main.js
`
- Define your global/program-wide version, options, description, and/or examples first.
_Once you define a Command, you can't access the global-scope again._
- Define all commands & options in the order that you want them to appear.
_Sade will not mutate or sort your CLI for you. Global options print before local options._
- Required arguments without values will error & exit
_An Insufficient arguments! error will be displayed along with a help prompt._
- Don't worry about manually displaying help~!
_Your help text is displayed automatically... including command-specific help text!_
- Automatic default/basic patterns
_Usage text will always append [options] & --help and --version are done for you._
- Only define what you want to display!
_Help text sections (example, options, etc) will only display if you provide values._
Subcommands are defined & parsed like any other command! When defining their usage, everything up until the first argument ([foo] or ) is interpreted as the command string.
They should be defined in the order that you want them to appear in your general --help output.
Lastly, it is _not_ necessary to define the subcommand's "base" as an additional command. However, if you choose to do so, it's recommended that you define it first for better visibility.
`js
const prog = sade('git');
// Not necessary for subcommands to work, but it's here anyway!
prog
.command('remote')
.describe('Manage set of tracked repositories')
.action(opts => {
console.log('~> Print current remotes...');
});
prog
.command('remote add
.action((name, url, opts) => {
console.log(~> Adding a new remote (${name}) to ${url});
});
prog
.command('remote rename
.action((old, nxt, opts) => {
console.log(~> Renaming from ${old} to ${nxt}~!);`
});
In certain circumstances, you may only need sade for a single-command CLI application.
> Note: Until v1.6.0, this made for an awkward pairing.
To enable this, you may make use of the isSingle argument. Doing so allows you to pass the program's entire usage text into the name argument.
With "Single Command Mode" enabled, your entire binary operates as one command. This means that any prog.command calls are disallowed & will instead throw an Error. Of course, you may still define a program version, a description, an example or two, and declare options. You are customizing the program's attributes as a whole.*
> * This is true for multi-command applications, too, up until your first prog.command() call!
Example
Let's reconstruct sirv-cli, which is a single-command application that (optionally) accepts a directory from which to serve files. It also offers a slew of option flags:
`js`
sade('sirv [dir]', true)
.version('1.0.0')
.describe('Run a static file server')
.example('public -qeim 31536000')
.example('--port 8080 --etag')
.example('my-app --dev')
.option('-D, --dev', 'Enable "dev" mode')
.option('-e, --etag', 'Enable "Etag" header')
// There are a lot...
.option('-H, --host', 'Hostname to bind', 'localhost')
.option('-p, --port', 'Port to bind', 5000)
.action((dir, opts) => {
// Program handler
})
.parse(process.argv);
When sirv --help is run, the generated help text is trimmed, fully aware that there's only one command in this program:
`
Description
Run a static file server
Usage
$ sirv [dir] [options]
Options
-D, --dev Enable "dev" mode
-e, --etag Enable "Etag" header
-H, --host Hostname to bind (default localhost)
-p, --port Port to bind (default 5000)
-v, --version Displays current version
-h, --help Displays this message
Examples
$ sirv public -qeim 31536000
$ sirv --port 8080 --etag
$ sirv my-app --dev
`
Command aliases are alternative names (aliases) for a command. They are often used as shortcuts or as typo relief!
The aliased names do not appear in the general help text.
Instead, they only appear within the Command-specific help text under an "Aliases" section.
Limitations
* You cannot assign aliases while in Single Command Mode
* You cannot call prog.alias() before defining any Commands (via prog.commmand())
* You, the developer, must keep track of which aliases have already been used and/or exist as Command names
Example
Let's reconstruct the npm install command as a Sade program:
`js`
sade('npm')
// ...
.command('install [package]', 'Install a package', {
alias: ['i', 'add', 'isntall']
})
.option('-P, --save-prod', 'Package will appear in your dependencies.')
.option('-D, --save-dev', 'Package will appear in your devDependencies.')
.option('-O, --save-optional', 'Package will appear in your optionalDependencies')
.option('-E, --save-exact', 'Save exact versions instead of using a semver range operator')
// ...
When we run npm --help we'll see this general help text:
`
Usage
$ npm
Available Commands
install Install a package
For more info, run any command with the --help flag
$ npm install --help
Options
-v, --version Displays current version
-h, --help Displays this message
`
When we run npm install --help — or the help flag with any of install's aliases — we'll see this command-specific help text:
`
Description
Install a package
Usage
$ npm install [package] [options]
Aliases
$ npm i
$ npm add
$ npm isntall
Options
-P, --save-prod Package will appear in your dependencies.
-D, --save-dev Package will appear in your devDependencies.
-O, --save-optional Package will appear in your optionalDependencies
-E, --save-exact Save exact versions instead of using a semver range operator
-h, --help Displays this message
`
Returns your chainable Sade instance, aka your
Program.#### name
Type:
String
Required: trueThe name of your
Program / binary application.#### isSingle
Type:
Boolean
Default: name.includes(' ');If your
Program is meant to have only one command.
When true, this simplifies your generated --help output such that:* the "root-level help" is your _only_ help text
* the "root-level help" does not display an
Available Commands section
* the "root-level help" does not inject $ name into the Usage section
* the "root-level help" does not display For more info, run any command with the --help flag textYou may customize the
Usage of your command by modifying the name argument directly.
Please read Single Command Mode for an example and more information.> Important: Whenever
name includes a custom usage, then isSingle is automatically assumed and enforced!$3
Create a new Command for your Program. This changes the current state of your Program.
All configuration methods (
prog.describe, prog.action, etc) will apply to this Command until another Command has been created!#### usage
Type:
StringThe usage pattern for your current Command. This will be included in the general or command-specific
--help output._Required_ arguments are wrapped with
< and > characters; for example, and ._Optional_ arguments are wrapped with
[ and ] characters; for example, [foo] and [bar].All arguments are positionally important, which means they are passed to your current Command's
handler function in the order that they were defined.When optional arguments are defined but don't receive a value, their positionally-equivalent function parameter will be
undefined.> Important: You must define & expect required arguments _before_ optional arguments!
`js
sade('foo') .command('greet ')
.action((adjective, noun, opts) => {
console.log(
Hello, ${adjective} ${noun}!);
}) .command('drive [color] [speed]')
.action((vehicle, color, speed, opts) => {
let arr = ['Driving my'];
arr.push(color ?
${color} ${vehicle} : vehicle);
speed && arr.push(at ${speed});
opts.yolo && arr.push('...YOLO!!');
let str = arr.join(' ');
console.log(str);
});
``sh
$ foo greet beautiful person
//=> Hello, beautiful person!
$ foo drive car
//=> Driving my car
$ foo drive car red
//=> Driving my red card
$ foo drive car blue 100mph --yolo
//=> Driving my blue car at 100mph ...YOLO!!
`
#### desc
Type:
String
Default: ''prog.describe.#### opts
Type:
Object
Default: {}##### opts.alias
Type:
String|ArrayOptionally define one or more aliases for the current Command.
When declared, the
opts.alias value is passed _directly_ to the prog.alias method.`js
// Program A is equivalent to Program B
// ---const A = sade('bin')
.command('build', 'My build command', { alias: 'b' })
.command('watch', 'My watch command', { alias: ['w', 'dev'] });
const B = sade('bin')
.command('build', 'My build command').alias('b')
.command('watch', 'My watch command').alias('w', 'dev');
`
##### opts.default
Type:
BooleanManually set/force the current Command to be the Program's default command. This ensures that the current Command will run if no command was specified.
> Important: If you run your Program without a Command _and_ without specifying a default command, your Program will exit with a
No command specified error.`js
const prog = sade('greet');prog.command('hello');
//=> only runs if ::
$ greet hello// $ greet
//=> error: No command specified.
prog.command('howdy', '', { default:true });
//=> runs as
$ greet OR $ greet howdy// $ greet
//=> runs 'howdy' handler
// $ greet foobar
//=> error: Invalid command
`
$3
Add a description to the current Command.
#### text
Type:
String|ArrayThe description text for the current Command. This will be included in the general or command-specific
--help output.Internally, your description will be separated into an
Array of sentences.For general
--help output, only the first sentence will be displayed. However, all sentences will be printed for command-specific --help text.> Note: Pass an
Array if you don't want internal assumptions. However, the first item is _always_ displayed in general help, so it's recommended to keep it short.
$3
Define one or more aliases for the current Command.
> Important: An error will be thrown if:
1) the program is in Single Command Mode; or
2)
prog.alias is called before any prog.command.#### names
Type:
StringThe list of alternative names (aliases) for the current Command.
For example, you may want to define shortcuts and/or common typos for the Command's full name.
> Important: Sade _does not_ check if the incoming
names are already in use by other Commands or their aliases.
During conflicts, the Command with the same name is given priority, otherwise the first Command (according to Program order) with name as an alias is chosen.The
prog.alias() is append-only, so calling it multiple times within a Command context will _keep_ all aliases, including those initially passed via opts.alias.`js
sade('bin')
.command('hello ', 'Greet someone by their name', {
alias: ['hey', 'yo']
})
.alias('hi', 'howdy')
.alias('hola', 'oi');
//=> hello aliases: hey, yo, hi, howdy, hola, oi
`
$3
Attach a callback to the current Command.
#### handler
Type:
FunctionThe function to run when the current Command is executed.
usage definition.All options, flags, and extra/unknown values are included as the last parameter.
> Note: Optional arguments are also passed as parameters & may be
undefined!`js
sade('foo')
.command('cp ')
.option('-f, --force', 'Overwrite without confirmation')
.option('-c, --clone-dir', 'Copy files to additional directory')
.option('-v, --verbose', 'Enable verbose output')
.action((src, dest, opts) => {
console.log( Copying files from ${src} --> ${dest});
opts.c && console.log(ALSO copying files from ${src} --> ${opts['clone-dir']});
console.log('My options:', opts);
})// $ foo cp original my-copy -v
//=> Copying files from original --> my-copy
//=> My options: { _:[], v:true, verbose:true }
// $ foo cp original my-copy --clone-dir my-backup
//=> Copying files from original --> my-copy
//=> ALSO copying files from original --> my-backup
//=> My options: { _:[], c:'my-backup', 'clone-dir':'my-backup' }
`
$3
Add an example for the current Command.
#### str
Type:
StringThe example string to add. This will be included in the general or command-specific
--help output.> Note: Your example's
str will be prefixed with your Program's name.
$3
Add an Option to the current Command.
#### flags
Type:
StringThe Option's flags, which may optionally include an alias.
You may use a comma (
,) or a space ( ) to separate the flags.> Note: The short & long flags can be declared in any order. However, the alias will always be displayed first.
> Important: If using hyphenated flag names, they will be accessible as declared within your
action() handler!`js
prog.option('--global'); // no alias
prog.option('-g, --global'); // alias first, comma
prog.option('--global -g'); // alias last, space
// etc...
`#### desc
Type:
StringThe description for the Option.
#### value
Type:
StringThe default value for the Option.
Flags and aliases, if parsed, are
true by default. See mri for more info.> Note: You probably only want to define a default
value if you're expecting a String or Number value type.If you _do_ pass a
String or Number value type, your flag value will be casted to the same type. See mri#options.default for info~!
$3
The
--version and -v flags will automatically output the Program version.#### str
Type:
String
Default: 0.0.0The new version number for your Program.
> Note: Your Program
version is 0.0.0 until you change it.$3
Parse a set of CLI arguments.
#### arr
Type:
ArrayYour Program's
process.argv input.> Important: Do not
.slice(2)! Doing so will break parsing~!#### opts
Type:
Object
Default: {}Additional
process.argv parsing config. See mri's options for details.> Important: These values _override_ any internal values!
`js
prog
.command('hello')
.option('-f, --force', 'My flag');
//=> currently has alias pair: f <--> forceprog.parse(process.argv, {
alias: {
f: ['foo', 'fizz']
},
default: {
abc: 123
}
});
//=> ADDS alias pair: f <--> foo
//=> REMOVES alias pair: f <--> force
//=> ADDS alias pair: f <--> fizz
//=> ADDS default: abc -> 123 (number)
`#### opts.unknown
Type:
Function
Default: undefinedCallback to run when an unspecified option flag has been found. This is passed directly to
mri.Your handler will receive the unknown flag (string) as its only argument.
You may return a string, which will be used as a custom error message. Otherwise, a default message is displayed.
`js
sade('sirv')
.command('start [dir]')
.parse(process.argv, {
unknown: arg => Custom error message: ${arg}
});/*
$ sirv start --foobar
ERROR
Custom error message: --foobar
Run
$ sirv --help for more info.
*/
`#### opts.lazy
Type:
Boolean
Default: falseIf true, Sade will not immediately execute the
action handler. Instead, parse() will return an object of { name, args, handler } shape, wherein the name is the command name, args is all arguments that _would be_ passed to the action handler, and handler is the function itself.From this, you may choose when to run the
handler function. You also have the option to further modify the args for any reason, if needed.`js
let { name, args, handler } = prog.parse(process.argv, { lazy:true });
console.log('> Received command: ', name);// later on...
handler.apply(null, args);
`$3
Manually display the help text for a given command. If no command name is provided, the general/global help is printed.
Your general and command-specific help text is automatically attached to the
--help and -h flags.> Note: You don't have to call this directly! It's automatically run when you
bin --help#### cmd
Type:
String
Default: null`The name of the command for which to display help. Otherwise displays the general help.
MIT © Luke Edwards