Ecosyste.ms: Awesome

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

https://github.com/fxjs-modules/cli

fibjs Cli builder
https://github.com/fxjs-modules/cli

Last synced: 2 days ago
JSON representation

fibjs Cli builder

Lists

README

        

[![NPM version](https://img.shields.io/npm/v/@fxjs/cli.svg?style=flat)](https://npmjs.com/package/@fxjs/cli)
[![NPM downloads](https://img.shields.io/npm/dm/@fxjs/cli.svg?style=flat)](https://npmjs.com/package/@fxjs/cli)
[![Build Status](https://travis-ci.org/fxjs-modules/cli.svg)](https://travis-ci.org/fxjs-modules/cli)
[![Build status](https://ci.appveyor.com/api/projects/status/61m8kfc4hg7msuap?svg=true)](https://ci.appveyor.com/project/richardo2016/cli)

## Introduction

**FCli** is a fibjs Cli builder, almost migration of [cac.js] from node.js to fibjs

## Features

- **Commander.js Like**: There's a large deal of users of [commander.js] in node.js ecosystem, FCli has similar APIs with [commander.js].
- **Easy to learn**. There're only 4 APIs you need to learn for building simple CLIs: `cli.option` `cli.version` `cli.help` `cli.parse`.
- **utility features**. Enable features like
- default command
- **git-like** subcommands
- validation for required arguments and options
- rest arguments
- dot-nested options, automated help message generation and so on.

## 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)
- [Brackets](#brackets)
- [Rest Arguments](#rest-arguments)
- [Dot-style Options](#dot-nested-options)
- [Default Command](#default-command)
- [Supply an array as option value](#supply-an-array-as-option-value)
- [With TypeScript](#with-typescript)
- [With Deno](#with-deno)
- [References](#references)
- [CLI Instance](#cli-instance)
- [@fxjs/cli(name?)](#fxjscliname)
- [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(subCommand?)](#clioutputhelpsubcommand)
- [Command Instance](#command-instance)
- [command.option()](#commandoption)
- [command.action(callback)](#commandactioncallback)
- [command.alias(name)](#commandaliasname)
- [command.example(example)](#commandexampleexample)
- [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)
- [Contributing](#contributing)
- [Author](#author)

## Install

```bash
npm i -S @fxjs/cli
# or
fibjs --install @fxjs/cli
```

## Usage

### Simple Parsing

Use FCli as simple argument parser:

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

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

const parsed = cli.parse()

console.dir(parsed)
```

image

### Display Help Message and Version

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

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()
```

image

### Command-specific Options

You can attach options to a command.

```js
const cli = require('@fxjs/cli')()

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()
```

image

A command's options are validated when the command is used. Any unknown options will be reported as an error by default. 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, pass `allowUnknownOptions: true` when initializing the command, like this:

```js
const cli = require('@fxjs/cli')()

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

### 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('@fxjs/cli')()

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()
```

To allow an option whose value is `false`, you need to manually speicfy a negative 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 FCli set the default value of `config` to true, and you can use `--no-config` flag to set it to `false`.

### Rest Arguments

The last argument of a command can be rest, and only the last argument. To make an argument rest 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('@fxjs/cli')()

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()
```

image

### Dot-style Options

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

```js
const cli = require('@fxjs/cli')()

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

cli.help()

cli.parse()
```

image

### Default Command

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

```js
const cli = require('@fxjs/cli')()

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'] }
```

## References

### CLI Instance

CLI instance is created by invoking the `@fxjs/cli` function:

```js
const cli = require('@fxjs/cli')()
```

#### @fxjs/cli(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(subCommand?)

- Type: `(subCommand?: boolean) => CLI`

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.

### 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 an varadic 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 FCli 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
```

### 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: %', cli.args.join(' '))
process.exit(1)
})
```

## Q & A

### Why not cac.js directly?

**@fxjs/cli** is inspired by [cac.js]

[cac.js] is one lightweight, fast cli builder for node.js/deno app, [egoist], author of [cac.js], were managed to make convenient and elegant tool for javascript ecosystem.

In fact, I trid to make PR to [cac.js] to make it support fibjs, in a way, it's much better than [commander.js] in **cross-platform**, you would never find and node.js specific API in [cac.js]! As a contrast, `require('child_process')` is just writeen in [commander.js]'s source code, that made it difficult running [commander.js] in fibjs.

But there's some typically better APIs in fibjs that cac.js cannot use --- if do so, [cac.js]'s source code would contains many fibjs-only codes, useless for node.js/deno, that would mutilate elegant structure of [cac.js]

The 2nd best, I copid test cases and examples from [cac.js] as initial test case as **@fxjs/cli**, then I reimplement almost features of [cac.js] and add some fibjs specific features in **@fxjs/cli**.

Thx much to [egoist] and other contributors' of [cac.js] :)

## License

- [MIT](./LICENSE)
- [cac.js MIT License]

[commander.js]:https://www.npmjs.com/package/commander
[cac.js]:https://github.com/cacjs/cac
[egoist]:https://github.com/egoist
[cac.js MIT License]:https://github.com/cacjs/cac/blob/e5f32f7c9dc83a4f12ccea2acbf43c814773c7f3/LICENSE