1 | <img width="945" alt="2017-07-26 9 27 05" src="https://user-images.githubusercontent.com/8784712/28623641-373450f4-7249-11e7-854d-1b076dab274d.png">
|
2 |
|
3 | [![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)
|
4 |
|
5 | ## Introduction
|
6 |
|
7 | **C**ommand **A**nd **C**onquer is a JavaScript library for building CLI apps.
|
8 |
|
9 | ## Features
|
10 |
|
11 | - **Super light-weight**: No dependency, just a single file.
|
12 | - **Easy to learn**. There're only 4 APIs you need to learn for building simple CLIs: `cli.option` `cli.version` `cli.help` `cli.parse`.
|
13 | - **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.
|
14 | - **Developer friendly**. Written in TypeScript.
|
15 |
|
16 | ## Table of Contents
|
17 |
|
18 |
|
19 |
|
20 | - [Install](#install)
|
21 | - [Usage](#usage)
|
22 | - [Simple Parsing](#simple-parsing)
|
23 | - [Display Help Message and Version](#display-help-message-and-version)
|
24 | - [Command-specific Options](#command-specific-options)
|
25 | - [Dash in option names](#dash-in-option-names)
|
26 | - [Brackets](#brackets)
|
27 | - [Negated Options](#negated-options)
|
28 | - [Variadic Arguments](#variadic-arguments)
|
29 | - [Dot-nested Options](#dot-nested-options)
|
30 | - [Default Command](#default-command)
|
31 | - [Supply an array as option value](#supply-an-array-as-option-value)
|
32 | - [Error Handling](#error-handling)
|
33 | - [With TypeScript](#with-typescript)
|
34 | - [With Deno](#with-deno)
|
35 | - [Projects Using CAC](#projects-using-cac)
|
36 | - [References](#references)
|
37 | - [CLI Instance](#cli-instance)
|
38 | - [cac(name?)](#cacname)
|
39 | - [cli.command(name, description, config?)](#clicommandname-description-config)
|
40 | - [cli.option(name, description, config?)](#clioptionname-description-config)
|
41 | - [cli.parse(argv?)](#cliparseargv)
|
42 | - [cli.version(version, customFlags?)](#cliversionversion-customflags)
|
43 | - [cli.help(callback?)](#clihelpcallback)
|
44 | - [cli.outputHelp(subCommand?)](#clioutputhelpsubcommand)
|
45 | - [Command Instance](#command-instance)
|
46 | - [command.option()](#commandoption)
|
47 | - [command.action(callback)](#commandactioncallback)
|
48 | - [command.alias(name)](#commandaliasname)
|
49 | - [command.allowUnknownOptions()](#commandallowunknownoptions)
|
50 | - [command.example(example)](#commandexampleexample)
|
51 | - [Events](#events)
|
52 | - [FAQ](#faq)
|
53 | - [How is the name written and pronounced?](#how-is-the-name-written-and-pronounced)
|
54 | - [Why not use Commander.js?](#why-not-use-commanderjs)
|
55 | - [Contributing](#contributing)
|
56 | - [Author](#author)
|
57 |
|
58 |
|
59 |
|
60 | ## Install
|
61 |
|
62 | ```bash
|
63 | yarn add cac
|
64 | ```
|
65 |
|
66 | ## Usage
|
67 |
|
68 | ### Simple Parsing
|
69 |
|
70 | Use CAC as simple argument parser:
|
71 |
|
72 | ```js
|
73 | // examples/basic-usage.js
|
74 | const cli = require('cac')()
|
75 |
|
76 | cli.option('--type <type>', 'Choose a project type', {
|
77 | default: 'node'
|
78 | })
|
79 |
|
80 | const parsed = cli.parse()
|
81 |
|
82 | console.log(JSON.stringify(parsed, null, 2))
|
83 | ```
|
84 |
|
85 | <img width="500" alt="2018-11-26 12 28 03" src="https://user-images.githubusercontent.com/8784712/48981576-2a871000-f112-11e8-8151-80f61e9b9908.png">
|
86 |
|
87 | ### Display Help Message and Version
|
88 |
|
89 | ```js
|
90 | // examples/help.js
|
91 | const cli = require('cac')()
|
92 |
|
93 | cli.option('--type [type]', 'Choose a project type', {
|
94 | default: 'node'
|
95 | })
|
96 | cli.option('--name <name>', 'Provide your name')
|
97 |
|
98 | cli.command('lint [...files]', 'Lint files').action((files, options) => {
|
99 | console.log(files, options)
|
100 | })
|
101 |
|
102 | // Display help message when `-h` or `--help` appears
|
103 | cli.help()
|
104 | // Display version number when `-v` or `--version` appears
|
105 | // It's also used in help message
|
106 | cli.version('0.0.0')
|
107 |
|
108 | cli.parse()
|
109 | ```
|
110 |
|
111 | <img width="500" alt="2018-11-25 8 21 14" src="https://user-images.githubusercontent.com/8784712/48979012-acb20d00-f0ef-11e8-9cc6-8ffca00ab78a.png">
|
112 |
|
113 | ### Command-specific Options
|
114 |
|
115 | You can attach options to a command.
|
116 |
|
117 | ```js
|
118 | const cli = require('cac')()
|
119 |
|
120 | cli
|
121 | .command('rm <dir>', 'Remove a dir')
|
122 | .option('-r, --recursive', 'Remove recursively')
|
123 | .action((dir, options) => {
|
124 | console.log('remove ' + dir + (options.recursive ? ' recursively' : ''))
|
125 | })
|
126 |
|
127 | cli.help()
|
128 |
|
129 | cli.parse()
|
130 | ```
|
131 |
|
132 | 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).
|
133 |
|
134 | <img alt="command options" width="500" src="https://user-images.githubusercontent.com/8784712/49065552-49dc8500-f259-11e8-9c7b-a7c32d70920e.png">
|
135 |
|
136 | ### Dash in option names
|
137 |
|
138 | Options in kebab-case should be referenced in camelCase in your code:
|
139 |
|
140 | ```js
|
141 | cli
|
142 | .command('dev', 'Start dev server')
|
143 | .option('--clear-screen', 'Clear screen')
|
144 | .action(options => {
|
145 | console.log(options.clearScreen)
|
146 | })
|
147 | ```
|
148 |
|
149 | In fact `--clear-screen` and `--clearScreen` are both mapped to `options.clearScreen`.
|
150 |
|
151 | ### Brackets
|
152 |
|
153 | When using brackets in command name, angled brackets indicate required command arguments, while square bracket indicate optional arguments.
|
154 |
|
155 | 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`.
|
156 |
|
157 | ```js
|
158 | const cli = require('cac')()
|
159 |
|
160 | cli
|
161 | .command('deploy <folder>', 'Deploy a folder to AWS')
|
162 | .option('--scale [level]', 'Scaling level')
|
163 | .action((folder, options) => {
|
164 | // ...
|
165 | })
|
166 |
|
167 | cli
|
168 | .command('build [project]', 'Build a project')
|
169 | .option('--out <dir>', 'Output directory')
|
170 | .action((folder, options) => {
|
171 | // ...
|
172 | })
|
173 |
|
174 | cli.parse()
|
175 | ```
|
176 |
|
177 | ### Negated Options
|
178 |
|
179 | To allow an option whose value is `false`, you need to manually specify a negated option:
|
180 |
|
181 | ```js
|
182 | cli
|
183 | .command('build [project]', 'Build a project')
|
184 | .option('--no-config', 'Disable config file')
|
185 | .option('--config <path>', 'Use a custom config file')
|
186 | ```
|
187 |
|
188 | This will let CAC set the default value of `config` to true, and you can use `--no-config` flag to set it to `false`.
|
189 |
|
190 | ### Variadic Arguments
|
191 |
|
192 | 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:
|
193 |
|
194 | ```js
|
195 | const cli = require('cac')()
|
196 |
|
197 | cli
|
198 | .command('build <entry> [...otherFiles]', 'Build your app')
|
199 | .option('--foo', 'Foo option')
|
200 | .action((entry, otherFiles, options) => {
|
201 | console.log(entry)
|
202 | console.log(otherFiles)
|
203 | console.log(options)
|
204 | })
|
205 |
|
206 | cli.help()
|
207 |
|
208 | cli.parse()
|
209 | ```
|
210 |
|
211 | <img width="500" alt="2018-11-25 8 25 30" src="https://user-images.githubusercontent.com/8784712/48979056-47125080-f0f0-11e8-9d8f-3219e0beb0ed.png">
|
212 |
|
213 | ### Dot-nested Options
|
214 |
|
215 | Dot-nested options will be merged into a single option.
|
216 |
|
217 | ```js
|
218 | const cli = require('cac')()
|
219 |
|
220 | cli
|
221 | .command('build', 'desc')
|
222 | .option('--env <env>', 'Set envs')
|
223 | .example('--env.API_SECRET xxx')
|
224 | .action(options => {
|
225 | console.log(options)
|
226 | })
|
227 |
|
228 | cli.help()
|
229 |
|
230 | cli.parse()
|
231 | ```
|
232 |
|
233 | <img width="500" alt="2018-11-25 9 37 53" src="https://user-images.githubusercontent.com/8784712/48979771-6ada9400-f0fa-11e8-8192-e541b2cfd9da.png">
|
234 |
|
235 | ### Default Command
|
236 |
|
237 | Register a command that will be used when no other command is matched.
|
238 |
|
239 | ```js
|
240 | const cli = require('cac')()
|
241 |
|
242 | cli
|
243 | // Simply omit the command name, just brackets
|
244 | .command('[...files]', 'Build files')
|
245 | .option('--minimize', 'Minimize output')
|
246 | .action((files, options) => {
|
247 | console.log(files)
|
248 | console.log(options.minimize)
|
249 | })
|
250 |
|
251 | cli.parse()
|
252 | ```
|
253 |
|
254 | ### Supply an array as option value
|
255 |
|
256 | ```bash
|
257 | node cli.js --include project-a
|
258 | # The parsed options will be:
|
259 | # { include: 'project-a' }
|
260 |
|
261 | node cli.js --include project-a --include project-b
|
262 | # The parsed options will be:
|
263 | # { include: ['project-a', 'project-b'] }
|
264 | ```
|
265 |
|
266 | ### Error Handling
|
267 |
|
268 | To handle command errors globally:
|
269 |
|
270 | ```js
|
271 | try {
|
272 | // Parse CLI args without running the command
|
273 | cli.parse(process.argv, { run: false })
|
274 | // Run the command yourself
|
275 | // You only need `await` when your command action returns a Promise
|
276 | await cli.runMatchedCommand()
|
277 | } catch (error) {
|
278 | // Handle error here..
|
279 | // e.g.
|
280 | // console.error(error.stack)
|
281 | // process.exit(1)
|
282 | }
|
283 | ```
|
284 |
|
285 | ### With TypeScript
|
286 |
|
287 | First you need `@types/node` to be installed as a dev dependency in your project:
|
288 |
|
289 | ```bash
|
290 | yarn add @types/node --dev
|
291 | ```
|
292 |
|
293 | Then everything just works out of the box:
|
294 |
|
295 | ```js
|
296 | const { cac } = require('cac')
|
297 | // OR ES modules
|
298 | import { cac } from 'cac'
|
299 | ```
|
300 |
|
301 | ### With Deno
|
302 |
|
303 | ```ts
|
304 | // @deno-types="https://unpkg.com/cac/mod.d.ts"
|
305 | import { cac } from 'https://unpkg.com/cac/mod.js'
|
306 |
|
307 | const cli = cac('my-program')
|
308 | ```
|
309 |
|
310 | ## Projects Using CAC
|
311 |
|
312 | Projects that use **CAC**:
|
313 |
|
314 | - [VuePress](https://github.com/vuejs/vuepress): :memo: Minimalistic Vue-powered static site generator.
|
315 | - [SAO](https://github.com/egoist/sao): ⚔️ Futuristic scaffolding tool.
|
316 | - [DocPad](https://github.com/docpad/docpad): 🏹 Powerful Static Site Generator.
|
317 | - [Poi](https://github.com/egoist/poi): ⚡️ Delightful web development.
|
318 | - [bili](https://github.com/egoist/bili): 🥂 Schweizer Armeemesser for bundling JavaScript libraries.
|
319 | - [lass](https://github.com/lassjs/lass): 💁🏻 Scaffold a modern package boilerplate for Node.js.
|
320 | - [Foy](https://github.com/zaaack/foy): 🏗 A lightweight and modern task runner and build tool for general purpose.
|
321 | - [Vuese](https://github.com/vuese/vuese): 🤗 One-stop solution for vue component documentation.
|
322 | - [NUT](https://github.com/nut-project/nut): 🌰 A framework born for microfrontends
|
323 | - Feel free to add yours here...
|
324 |
|
325 | ## References
|
326 |
|
327 | **💁 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.**
|
328 |
|
329 | Below is a brief overview.
|
330 |
|
331 | ### CLI Instance
|
332 |
|
333 | CLI instance is created by invoking the `cac` function:
|
334 |
|
335 | ```js
|
336 | const cac = require('cac')
|
337 | const cli = cac()
|
338 | ```
|
339 |
|
340 | #### cac(name?)
|
341 |
|
342 | 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]`.
|
343 |
|
344 | #### cli.command(name, description, config?)
|
345 |
|
346 | - Type: `(name: string, description: string) => Command`
|
347 |
|
348 | Create a command instance.
|
349 |
|
350 | The option also accepts a third argument `config` for additional command config:
|
351 |
|
352 | - `config.allowUnknownOptions`: `boolean` Allow unknown options in this command.
|
353 | - `config.ignoreOptionDefaultValue`: `boolean` Don't use the options's default value in parsed options, only display them in help message.
|
354 |
|
355 | #### cli.option(name, description, config?)
|
356 |
|
357 | - Type: `(name: string, description: string, config?: OptionConfig) => CLI`
|
358 |
|
359 | Add a global option.
|
360 |
|
361 | The option also accepts a third argument `config` for additional option config:
|
362 |
|
363 | - `config.default`: Default value for the option.
|
364 | - `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`.
|
365 |
|
366 | #### cli.parse(argv?)
|
367 |
|
368 | - Type: `(argv = process.argv) => ParsedArgv`
|
369 |
|
370 | ```ts
|
371 | interface ParsedArgv {
|
372 | args: string[]
|
373 | options: {
|
374 | [k: string]: any
|
375 | }
|
376 | }
|
377 | ```
|
378 |
|
379 | When this method is called, `cli.rawArgs` `cli.args` `cli.options` `cli.matchedCommand` will also be available.
|
380 |
|
381 | #### cli.version(version, customFlags?)
|
382 |
|
383 | - Type: `(version: string, customFlags = '-v, --version') => CLI`
|
384 |
|
385 | Output version number when `-v, --version` flag appears.
|
386 |
|
387 | #### cli.help(callback?)
|
388 |
|
389 | - Type: `(callback?: HelpCallback) => CLI`
|
390 |
|
391 | Output help message when `-h, --help` flag appears.
|
392 |
|
393 | Optional `callback` allows post-processing of help text before it is displayed:
|
394 |
|
395 | ```ts
|
396 | type HelpCallback = (sections: HelpSection[]) => void
|
397 |
|
398 | interface HelpSection {
|
399 | title?: string
|
400 | body: string
|
401 | }
|
402 | ```
|
403 |
|
404 | #### cli.outputHelp(subCommand?)
|
405 |
|
406 | - Type: `(subCommand?: boolean) => CLI`
|
407 |
|
408 | Output help message. Optional `subCommand` argument if you want to output the help message for the matched sub-command instead of the global help message.
|
409 |
|
410 | ### Command Instance
|
411 |
|
412 | Command instance is created by invoking the `cli.command` method:
|
413 |
|
414 | ```js
|
415 | const command = cli.command('build [...files]', 'Build given files')
|
416 | ```
|
417 |
|
418 | #### command.option()
|
419 |
|
420 | Basically the same as `cli.option` but this adds the option to specific command.
|
421 |
|
422 | #### command.action(callback)
|
423 |
|
424 | - Type: `(callback: ActionCallback) => Command`
|
425 |
|
426 | Use a callback function as the command action when the command matches user inputs.
|
427 |
|
428 | ```ts
|
429 | type ActionCallback = (
|
430 | // Parsed CLI args
|
431 | // The last arg will be an array if it's a variadic argument
|
432 | ...args: string | string[] | number | number[]
|
433 | // Parsed CLI options
|
434 | options: Options
|
435 | ) => any
|
436 |
|
437 | interface Options {
|
438 | [k: string]: any
|
439 | }
|
440 | ```
|
441 |
|
442 | #### command.alias(name)
|
443 |
|
444 | - Type: `(name: string) => Command`
|
445 |
|
446 | Add an alias name to this command, the `name` here can't contain brackets.
|
447 |
|
448 | #### command.allowUnknownOptions()
|
449 |
|
450 | - Type: `() => Command`
|
451 |
|
452 | Allow unknown options in this command, by default CAC will log an error when unknown options are used.
|
453 |
|
454 | #### command.example(example)
|
455 |
|
456 | - Type: `(example: CommandExample) => Command`
|
457 |
|
458 | Add an example which will be displayed at the end of help message.
|
459 |
|
460 | ```ts
|
461 | type CommandExample = ((name: string) => string) | string
|
462 | ```
|
463 |
|
464 | ### Events
|
465 |
|
466 | Listen to commands:
|
467 |
|
468 | ```js
|
469 | // Listen to the `foo` command
|
470 | cli.on('command:foo', () => {
|
471 | // Do something
|
472 | })
|
473 |
|
474 | // Listen to the default command
|
475 | cli.on('command:!', () => {
|
476 | // Do something
|
477 | })
|
478 |
|
479 | // Listen to unknown commands
|
480 | cli.on('command:*', () => {
|
481 | console.error('Invalid command: %s', cli.args.join(' '))
|
482 | process.exit(1)
|
483 | })
|
484 | ```
|
485 |
|
486 | ## FAQ
|
487 |
|
488 | ### How is the name written and pronounced?
|
489 |
|
490 | CAC, or cac, pronounced `C-A-C`.
|
491 |
|
492 | This project is dedicated to our lovely C.C. sama. Maybe CAC stands for C&C as well :P
|
493 |
|
494 | <img src="http://i.giphy.com/v3FeH4swox9mg.gif" width="400"/>
|
495 |
|
496 | ### Why not use Commander.js?
|
497 |
|
498 | 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.
|
499 |
|
500 | _And maybe more..._
|
501 |
|
502 | Basically I made CAC to fulfill my own needs for building CLI apps like [Poi](https://poi.js.org), [SAO](https://saojs.org) and all my CLI apps. It's small, simple but powerful :P
|
503 |
|
504 | ## Contributing
|
505 |
|
506 | 1. Fork it!
|
507 | 2. Create your feature branch: `git checkout -b my-new-feature`
|
508 | 3. Commit your changes: `git commit -am 'Add some feature'`
|
509 | 4. Push to the branch: `git push origin my-new-feature`
|
510 | 5. Submit a pull request :D
|
511 |
|
512 | ## Author
|
513 |
|
514 | **CAC** © [EGOIST](https://github.com/egoist), Released under the [MIT](./LICENSE) License.<br>
|
515 | Authored and maintained by egoist with help from contributors ([list](https://github.com/cacjs/cac/contributors)).
|
516 |
|
517 | > [Website](https://egoist.sh) · GitHub [@egoist](https://github.com/egoist) · Twitter [@\_egoistlily](https://twitter.com/_egoistlily)
|