cleye
cleye
The intuitive command-line interface (CLI) development tool.
Features
- Minimal API surface
- Powerful flag parsing
- Strongly typed parameters and flags
- Command support
- Help documentation generation (customizable too!)
→ Try it out online
Support this project by starring and sharing it. Follow me to see what other cool projects I’m working on.
Install
npm i cleye
About
Cleye makes it very easy to develop command-line scripts in Node.js. It handles argv parsing to give you strongly typed parameters + flags and generates --help
documentation based on the provided information.
Here’s an example script that simply logs: Good morning/evening <name>!
:
greet.js:
import { cli } from 'cleye' // Parse argv const argv = cli({ name: 'greet.js', // Define parameters parameters: [ '<first name>', // First name is required '[last name]' // Last name is optional ], // Define flags/options flags: { // Parses `--time` as a string time: { type: String, description: 'Time of day to greet (morning or evening)', default: 'morning' } } }) const name = [argv._.firstName, argv._.lastName].filter(Boolean).join(' ') if (argv.flags.time === 'morning') { console.log(`Good morning ${name}!`) } else { console.log(`Good evening ${name}!`) }
🛠 In development, type hints are provided on parsed flags and parameters:
Type hints for Cleye’s output are very verbose and readable
📖 Generated help documentation can be viewed with the --help
flag:
$ node greet.js --help greet.js Usage: greet.js [flags...] <first name> [last name] Flags: -h, --help Show help --time <string> Time of day to greet (morning or evening) (default: "morning")
✅ Run the script to see it in action:
$ node greet.js John Doe --time evening Good evening John Doe!
Examples
Want to dive right into some code? Check out some of these examples:
- greet.js: Working example from above
-
npm install: Reimplementation of
npm install
‘s CLI -
tsc: Reimplementation of TypeScript
tsc
‘s CLI -
snap-tweet: Reimplementation of
snap-tweet
‘s CLI -
pkg-size: Reimplementation of
pkg-size
‘s CLI
Usage
Arguments
Arguments are values passed into the script that are not associated with any flags/options.
For example, in the following command, the first argument is file-a.txt
and the second is file-b.txt
:
$ my-script file-a.txt file-b.txt
Arguments can be accessed from the _
array-property of the returned object.
Example:
const argv = cli({ /* ... */ }) // $ my-script file-a.txt file-b.txt argv._ // => ["file-a.txt", "file-b.txt"] (string[])
Parameters
Parameters (aka positional arguments) are the names that map against argument values. Think of parameters as variable names and arguments as values associated with the variables.
Parameters can be defined in the parameters
array-property to make specific arguments accessible by name. This is useful for writing more readable code, enforcing validation, and generating help documentation.
Parameters are defined in the following formats:
-
Required parameters are indicated by angle brackets (eg.
<parameter name>
). -
Optional parameters are indicated by square brackets (eg.
[parameter name]
). -
Spread parameters are indicated by
...
suffix (eg.<parameter name...>
or[parameter name...]
).
Note, required parameters cannot come after optional parameters, and spread parameters must be last.
Parameters can be accessed in camelCase on the _
property of the returned object.
Example:
const argv = cli({ parameters: [ '<required parameter>', '[optional parameter]', '[optional spread...]' ] }) // $ my-script a b c d argv._.requiredParameter // => "a" (string) argv._.optionalParameter // => "b" (string | undefined) argv._.optionalSpread // => ["c", "d"] (string[])
End-of-flags
End-of-flags (--
) (aka end-of-options) allows users to pass in a subset of arguments. This is useful for passing in arguments that should be parsed separately from the rest of the arguments or passing in arguments that look like flags.
An example of this is npm run
:
$ npm run <script> -- <script arguments>
The --
indicates that all arguments afterwards should be passed into the script rather than npm.
All end-of-flag arguments will be accessible from argv._['--']
.
Additionally, you can specify --
in the parameters
array to parse end-of-flags arguments.
Example:
const argv = cli({ name: 'npm-run', parameters: [ '<script>', '--', '[arguments...]' ] }) // $ npm-run echo -- hello world argv._.script // => "echo" (string) argv._.arguments // => ["hello", "world] (string[])
Flags
Flags (aka Options) are key-value pairs passed into the script in the format --flag-name <value>
.
For example, in the following command, --file-a
has value data.json
and --file-b
has value file.txt
:
$ my-script --file-a data.json --file-b=file.txt
Parsing features
Cleye’s flag parsing is powered by type-flag
and comes with many features:
- Array & Custom types
-
Flag delimiters:
--flag value
,--flag=value
,--flag:value
, and--flag.value
-
Combined aliases:
-abcd 2
→-a -b -c -d 2
-
End of flags: Pass in
--
to end flag parsing -
Unknown flags: Unexpected flags stored in
unknownFlags
Read the type-flag docs to learn more.
Defining flags
Flags can be specified in the flag
object-property, where the key is the flag name, and the value is a flag type function or an object that describes the flag.
The flag name is recommended to be in camelCase as it will be interpreted to parse kebab-case equivalents.
The flag type function can be any function that accepts a string and returns the parsed value. Default JavaScript constructors should cover most use-cases: String, Number, Boolean, etc.
The flag description object can be used to store additional information about the flag, such as alias
, default
, and description
. To accept multiple values for a flag, wrap the type function in an array.
All of the provided information will be used to generate better help documentation.
Example:
const argv = cli({ flags: { someBoolean: Boolean, someString: { type: String, description: 'Some string flag', default: 'n/a' }, someNumber: { // Wrap the type function in an array to allow multiple values type: [Number], alias: 'n', description: 'Array of numbers. (eg. -n 1 -n 2 -n 3)' } } }) // $ my-script --some-boolean --some-string hello --some-number 1 -n 2 argv.flags.someBoolean // => true (boolean | undefined) argv.flags.someString // => "hello" (string) argv.flags.someNumber // => [1, 2] (number[])
Custom flag types & validation
Custom flag types can be created to validate flags and narrow types. Simply create a new function that accepts a string and returns the parsed value.
Here’s an example with a custom Size
type that narrows the flag type to "small" | "medium" | "large"
:
const possibleSizes = ['small', 'medium', 'large'] as const type Sizes = typeof possibleSizes[number] // => "small" | "medium" | "large" // Custom type function function Size(size: Sizes) { if (!possibleSizes.includes(size)) { throw new Error(`Invalid size: "${size}"`) } return size } const argv = cli({ flags: { size: { type: Size, description: 'Size of the pizza (small, medium, large)' } } }) // $ my-script --size large argv.flags.size // => "large" ("small" | "medium" | "large")
Default flags
By default, Cleye will try to handle the --help, -h
and --version
flags.
Help flag
Handling --help, -h
is enabled by default.
To disable it, set help
to false
. The help documentation can still be manually displayed by calling .showHelp(helpOptions)
on the returned object.
Version flag
To enable handling --version
, specify the version
property.
cli({ version: '1.2.3' })
$ my-script --version 1.2.3
The version is also shown in the help documentation. To opt out of handling --version
while still showing the version in --help
, pass the version into help.version
.
Commands
Commands allow organizing multiple “scripts” into a single script. An example of this is the npm install
command, which is essentially an “install” script inside the “npm” script, adjacent to other commands like npm run
.
Defining commands
A command can be created by importing the command
function and initializing it with a name. The rest of the options are the same as the cli
function.
Pass the created command into cli
option’s commands
array-property to register it:
npm.js
import { cli, command } from 'cleye' const argv = cli({ name: 'npm', version: '1.2.3', commands: [ command({ // Command name name: 'install', parameters: ['<package name>'], flags: { noSave: Boolean, saveDev: Boolean } }) ] }) // $ npm install lodash argv.command // => "install" (string) argv._.packageName // => "lodash" (string)
Depending on the command given, the resulting type can be narrowed:
Command callback
When a CLI app has many commands, it’s recommended to organize each command in its own file. With this structure, parsed output handling for each command is better placed where they are respectively defined rather than the single cli
output point. This can be done by passing a callback function into the command
function (callbacks are supported in the cli
function too).
Example:
install-command.js (install
command using callback)
import { command } from 'cleye' export const installCommand = command({ // Command name name: 'install', parameters: ['<package name>'], flags: { noSave: Boolean, saveDev: Boolean } }, (argv) => { // $ npm install lodash argv._.packageName // =>...