Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/cacjs/cac

Simple yet powerful framework for building command-line apps.
https://github.com/cacjs/cac

cli command-line commander deno framework minimist node

Last synced: about 2 months ago
JSON representation

Simple yet powerful framework for building command-line apps.

Lists

README

        

2017-07-26 9 27 05

[![NPM version](https://img.shields.io/npm/v/cac.svg?style=flat)](https://npmjs.com/package/cac) [![NPM downloads](https://img.shields.io/npm/dm/cac.svg?style=flat)](https://npmjs.com/package/cac) [![CircleCI](https://circleci.com/gh/cacjs/cac/tree/master.svg?style=shield)](https://circleci.com/gh/cacjs/cac/tree/master) [![Codecov](https://badgen.net/codecov/c/github/cacjs/cac/master)](https://codecov.io/gh/cacjs/cac) [![donate](https://img.shields.io/badge/$-donate-ff69b4.svg?maxAge=2592000&style=flat)](https://github.com/egoist/donate) [![chat](https://img.shields.io/badge/chat-on%20discord-7289DA.svg?style=flat)](https://chat.egoist.moe) [![install size](https://badgen.net/packagephobia/install/cac)](https://packagephobia.now.sh/result?p=cac)

## Introduction

**C**ommand **A**nd **C**onquer is a JavaScript library for building CLI apps.

## Features

- **Super light-weight**: No dependency, just a single file.
- **Easy to learn**. There're only 4 APIs you need to learn for building simple CLIs: `cli.option` `cli.version` `cli.help` `cli.parse`.
- **Yet so powerful**. Enable features like default command, git-like subcommands, validation for required arguments and options, variadic arguments, dot-nested options, automated help message generation and so on.
- **Developer friendly**. Written in TypeScript.

## Table of Contents

- [Install](#install)
- [Usage](#usage)
- [Simple Parsing](#simple-parsing)
- [Display Help Message and Version](#display-help-message-and-version)
- [Command-specific Options](#command-specific-options)
- [Dash in option names](#dash-in-option-names)
- [Brackets](#brackets)
- [Negated Options](#negated-options)
- [Variadic Arguments](#variadic-arguments)
- [Dot-nested Options](#dot-nested-options)
- [Default Command](#default-command)
- [Supply an array as option value](#supply-an-array-as-option-value)
- [Error Handling](#error-handling)
- [With TypeScript](#with-typescript)
- [With Deno](#with-deno)
- [Projects Using CAC](#projects-using-cac)
- [References](#references)
- [CLI Instance](#cli-instance)
- [cac(name?)](#cacname)
- [cli.command(name, description, config?)](#clicommandname-description-config)
- [cli.option(name, description, config?)](#clioptionname-description-config)
- [cli.parse(argv?)](#cliparseargv)
- [cli.version(version, customFlags?)](#cliversionversion-customflags)
- [cli.help(callback?)](#clihelpcallback)
- [cli.outputHelp()](#clioutputhelp)
- [cli.usage(text)](#cliusagetext)
- [Command Instance](#command-instance)
- [command.option()](#commandoption)
- [command.action(callback)](#commandactioncallback)
- [command.alias(name)](#commandaliasname)
- [command.allowUnknownOptions()](#commandallowunknownoptions)
- [command.example(example)](#commandexampleexample)
- [command.usage(text)](#commandusagetext)
- [Events](#events)
- [FAQ](#faq)
- [How is the name written and pronounced?](#how-is-the-name-written-and-pronounced)
- [Why not use Commander.js?](#why-not-use-commanderjs)
- [Project Stats](#project-stats)
- [Contributing](#contributing)
- [Author](#author)

## Install

```bash
yarn add cac
```

## Usage

### Simple Parsing

Use CAC as simple argument parser:

```js
// examples/basic-usage.js
const cli = require('cac')()

cli.option('--type ', 'Choose a project type', {
default: 'node',
})

const parsed = cli.parse()

console.log(JSON.stringify(parsed, null, 2))
```

2018-11-26 12 28 03

### Display Help Message and Version

```js
// examples/help.js
const cli = require('cac')()

cli.option('--type [type]', 'Choose a project type', {
default: 'node',
})
cli.option('--name ', 'Provide your name')

cli.command('lint [...files]', 'Lint files').action((files, options) => {
console.log(files, options)
})

// Display help message when `-h` or `--help` appears
cli.help()
// Display version number when `-v` or `--version` appears
// It's also used in help message
cli.version('0.0.0')

cli.parse()
```

2018-11-25 8 21 14

### Command-specific Options

You can attach options to a command.

```js
const cli = require('cac')()

cli
.command('rm ', 'Remove a dir')
.option('-r, --recursive', 'Remove recursively')
.action((dir, options) => {
console.log('remove ' + dir + (options.recursive ? ' recursively' : ''))
})

cli.help()

cli.parse()
```

A command's options are validated when the command is used. Any unknown options will be reported as an error. However, if an action-based command does not define an action, then the options are not validated. If you really want to use unknown options, use [`command.allowUnknownOptions`](#commandallowunknownoptions).

command options

### Dash in option names

Options in kebab-case should be referenced in camelCase in your code:

```js
cli
.command('dev', 'Start dev server')
.option('--clear-screen', 'Clear screen')
.action((options) => {
console.log(options.clearScreen)
})
```

In fact `--clear-screen` and `--clearScreen` are both mapped to `options.clearScreen`.

### Brackets

When using brackets in command name, angled brackets indicate required command arguments, while square bracket indicate optional arguments.

When using brackets in option name, angled brackets indicate that a string / number value is required, while square bracket indicate that the value can also be `true`.

```js
const cli = require('cac')()

cli
.command('deploy ', 'Deploy a folder to AWS')
.option('--scale [level]', 'Scaling level')
.action((folder, options) => {
// ...
})

cli
.command('build [project]', 'Build a project')
.option('--out ', 'Output directory')
.action((folder, options) => {
// ...
})

cli.parse()
```

### Negated Options

To allow an option whose value is `false`, you need to manually specify a negated option:

```js
cli
.command('build [project]', 'Build a project')
.option('--no-config', 'Disable config file')
.option('--config ', 'Use a custom config file')
```

This will let CAC set the default value of `config` to true, and you can use `--no-config` flag to set it to `false`.

### Variadic Arguments

The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to add `...` to the start of argument name, just like the rest operator in JavaScript. Here is an example:

```js
const cli = require('cac')()

cli
.command('build [...otherFiles]', 'Build your app')
.option('--foo', 'Foo option')
.action((entry, otherFiles, options) => {
console.log(entry)
console.log(otherFiles)
console.log(options)
})

cli.help()

cli.parse()
```

2018-11-25 8 25 30

### Dot-nested Options

Dot-nested options will be merged into a single option.

```js
const cli = require('cac')()

cli
.command('build', 'desc')
.option('--env ', 'Set envs')
.example('--env.API_SECRET xxx')
.action((options) => {
console.log(options)
})

cli.help()

cli.parse()
```

2018-11-25 9 37 53

### Default Command

Register a command that will be used when no other command is matched.

```js
const cli = require('cac')()

cli
// Simply omit the command name, just brackets
.command('[...files]', 'Build files')
.option('--minimize', 'Minimize output')
.action((files, options) => {
console.log(files)
console.log(options.minimize)
})

cli.parse()
```

### Supply an array as option value

```bash
node cli.js --include project-a
# The parsed options will be:
# { include: 'project-a' }

node cli.js --include project-a --include project-b
# The parsed options will be:
# { include: ['project-a', 'project-b'] }
```

### Error Handling

To handle command errors globally:

```js
try {
// Parse CLI args without running the command
cli.parse(process.argv, { run: false })
// Run the command yourself
// You only need `await` when your command action returns a Promise
await cli.runMatchedCommand()
} catch (error) {
// Handle error here..
// e.g.
// console.error(error.stack)
// process.exit(1)
}
```

### With TypeScript

First you need `@types/node` to be installed as a dev dependency in your project:

```bash
yarn add @types/node --dev
```

Then everything just works out of the box:

```js
const { cac } = require('cac')
// OR ES modules
import { cac } from 'cac'
```

### With Deno

```ts
import { cac } from 'https://unpkg.com/cac/mod.ts'

const cli = cac('my-program')
```

## Projects Using CAC

Projects that use **CAC**:

- [VuePress](https://github.com/vuejs/vuepress): :memo: Minimalistic Vue-powered static site generator.
- [SAO](https://github.com/egoist/sao): ⚔️ Futuristic scaffolding tool.
- [DocPad](https://github.com/docpad/docpad): 🏹 Powerful Static Site Generator.
- [Poi](https://github.com/egoist/poi): ⚡️ Delightful web development.
- [bili](https://github.com/egoist/bili): 🥂 Schweizer Armeemesser for bundling JavaScript libraries.
- [Lad](https://github.com/ladjs/lad): 👦 Lad scaffolds a Koa webapp and API framework for Node.js.
- [Lass](https://github.com/lassjs/lass): 💁🏻 Scaffold a modern package boilerplate for Node.js.
- [Foy](https://github.com/zaaack/foy): 🏗 A lightweight and modern task runner and build tool for general purpose.
- [Vuese](https://github.com/vuese/vuese): 🤗 One-stop solution for vue component documentation.
- [NUT](https://github.com/nut-project/nut): 🌰 A framework born for microfrontends
- Feel free to add yours here...

## References

**💁 Check out [the generated docs](https://cac-api-doc.egoist.sh/classes/_cac_.cac.html) from source code if you want a more in-depth API references.**

Below is a brief overview.

### CLI Instance

CLI instance is created by invoking the `cac` function:

```js
const cac = require('cac')
const cli = cac()
```

#### cac(name?)

Create a CLI instance, optionally specify the program name which will be used to display in help and version message. When not set we use the basename of `argv[1]`.

#### cli.command(name, description, config?)

- Type: `(name: string, description: string) => Command`

Create a command instance.

The option also accepts a third argument `config` for additional command config:

- `config.allowUnknownOptions`: `boolean` Allow unknown options in this command.
- `config.ignoreOptionDefaultValue`: `boolean` Don't use the options's default value in parsed options, only display them in help message.

#### cli.option(name, description, config?)

- Type: `(name: string, description: string, config?: OptionConfig) => CLI`

Add a global option.

The option also accepts a third argument `config` for additional option config:

- `config.default`: Default value for the option.
- `config.type`: `any[]` When set to `[]`, the option value returns an array type. You can also use a conversion function such as `[String]`, which will invoke the option value with `String`.

#### cli.parse(argv?)

- Type: `(argv = process.argv) => ParsedArgv`

```ts
interface ParsedArgv {
args: string[]
options: {
[k: string]: any
}
}
```

When this method is called, `cli.rawArgs` `cli.args` `cli.options` `cli.matchedCommand` will also be available.

#### cli.version(version, customFlags?)

- Type: `(version: string, customFlags = '-v, --version') => CLI`

Output version number when `-v, --version` flag appears.

#### cli.help(callback?)

- Type: `(callback?: HelpCallback) => CLI`

Output help message when `-h, --help` flag appears.

Optional `callback` allows post-processing of help text before it is displayed:

```ts
type HelpCallback = (sections: HelpSection[]) => void

interface HelpSection {
title?: string
body: string
}
```

#### cli.outputHelp()

- Type: `() => CLI`

Output help message.

#### cli.usage(text)

- Type: `(text: string) => CLI`

Add a global usage text. This is not used by sub-commands.

### Command Instance

Command instance is created by invoking the `cli.command` method:

```js
const command = cli.command('build [...files]', 'Build given files')
```

#### command.option()

Basically the same as `cli.option` but this adds the option to specific command.

#### command.action(callback)

- Type: `(callback: ActionCallback) => Command`

Use a callback function as the command action when the command matches user inputs.

```ts
type ActionCallback = (
// Parsed CLI args
// The last arg will be an array if it's a variadic argument
...args: string | string[] | number | number[]
// Parsed CLI options
options: Options
) => any

interface Options {
[k: string]: any
}
```

#### command.alias(name)

- Type: `(name: string) => Command`

Add an alias name to this command, the `name` here can't contain brackets.

#### command.allowUnknownOptions()

- Type: `() => Command`

Allow unknown options in this command, by default CAC will log an error when unknown options are used.

#### command.example(example)

- Type: `(example: CommandExample) => Command`

Add an example which will be displayed at the end of help message.

```ts
type CommandExample = ((name: string) => string) | string
```

#### command.usage(text)

- Type: `(text: string) => Command`

Add a usage text for this command.

### Events

Listen to commands:

```js
// Listen to the `foo` command
cli.on('command:foo', () => {
// Do something
})

// Listen to the default command
cli.on('command:!', () => {
// Do something
})

// Listen to unknown commands
cli.on('command:*', () => {
console.error('Invalid command: %s', cli.args.join(' '))
process.exit(1)
})
```

## FAQ

### How is the name written and pronounced?

CAC, or cac, pronounced `C-A-C`.

This project is dedicated to our lovely C.C. sama. Maybe CAC stands for C&C as well :P

### Why not use Commander.js?

CAC is very similar to Commander.js, while the latter does not support dot nested options, i.e. something like `--env.API_SECRET foo`. Besides, you can't use unknown options in Commander.js either.

_And maybe more..._

Basically I made CAC to fulfill my own needs for building CLI apps like [Poi](https://poi.js.org), [SAO](https://sao.vercel.app) and all my CLI apps. It's small, simple but powerful :P

## Project Stats

![Alt](https://repobeats.axiom.co/api/embed/58caf6203631bcdb9bbe22f0728a0af1683dc0bb.svg 'Repobeats analytics image')

## Contributing

1. Fork it!
2. Create your feature branch: `git checkout -b my-new-feature`
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request :D

## Author

**CAC** © [EGOIST](https://github.com/egoist), Released under the [MIT](./LICENSE) License.

Authored and maintained by egoist with help from contributors ([list](https://github.com/cacjs/cac/contributors)).

> [Website](https://egoist.sh) · GitHub [@egoist](https://github.com/egoist) · Twitter [@\_egoistlily](https://twitter.com/_egoistlily)