CLI(命令行工具,Command Line Interface)大家都十分相熟了,比方 create-react-app 等。咱们明天介绍一个 CLI 工具的开发框架,能够帮忙咱们疾速构建 CLI 工具。

oclif(发音为 'oh-cliff') 是一个命令行工具开发框架,功能丰富,开发不便。同时 oclif 还反对通过 TypeScript 来开发,对于习惯应用 TypeScript 的同学来说十分敌对。

根本用法

oclif 提供两种运行模式,一种是繁多命令模式,相似于 curl,通过各种参数应用不同的性能。另一种是多命令模式,相似于 git,能够定义子命令来实现不同的性能。

上面的两个样例别离展现了繁多命令模式和多命令模式的应用办法.

$ npx oclif single mynewcli? npm package name (mynewcli): mynewcli$ cd mynewcli$ ./bin/runhello world from ./src/index.js!

单命令模式下,会在 src 目录下生成一个 index.{ts,js} 文件,咱们在这个文件里定义命令。

$ npx oclif multi mynewcli? npm package name (mynewcli): mynewcli$ cd mynewcli$ ./bin/run --versionmynewcli/0.0.0 darwin-x64 node-v9.5.0$ ./bin/run --helpUSAGE  $ mynewcli [COMMAND]COMMANDS  hello  help   display help for mynewcli$ ./bin/run hellohello world from ./src/hello.js!

多命令模式下,会在 src 目录下生成一个 commands 目录,这个目录下的每一个文件就是一个子命令。比方 ./src/commands/hello.ts./src/commands/goodbye.ts

留神,多命令模式下命令和文件之间一个隐式的对应关系,比方 src/commands 目录下的文件是子命令。如果 src/commands 下是一个目录,则目录下的多个文件会造成一个 Topic

退出有如下目录构造:

package.jsonsrc/└── commands/    └── config/        ├── index.ts        ├── set.ts        └── get.ts

那么,命令最终的执行模式为: mynewcli configmynewcli config:setmynewcli config:get

定义命令

不论是繁多命令模式还是多命令模式,开发者只须要定义一个 class 继承 Command 类即可。

import Command from '@oclif/command'export class MyCommand extends Command {  static description = 'description of this example command'  async run() {    console.log('running my command')  }}

如上,在命令运行地时候,会主动执行 run 办法。

Command 还提供了很多工具办法,比方 this.logthis.warnthis.errorthis.exit 等,不便在运行过程中打印日志信息。

命令行工具通常都须要定义一些参数,oclif 反对两种参数定义模式,一种是 argument,用于定义有程序要求的参数,一种是 flag,用于定义没有程序要求的参数。

定义 argument

argument 的应用如下:

$ mycli firstArg secondArg # 参数程序不能乱

咱们能够这样定义 argument 参数:

import Command from '@oclif/command'export class MyCLI extends Command {  static args = [    {name: 'firstArg'},    {name: 'secondArg'},  ]  async run() {    // 通过对象的模式获取参数    const { args } = this.parse(MyCLI)    console.log(`running my command with args: ${args.firstArg}, ${args.secondArg}`)    // 也能够通过数组的模式获取参数    const { argv } = this.parse(MyCLI)    console.log(`running my command with args: ${argv[0]}, ${argv[1]}`)  }}

咱们能够对 argument 参数进行属性定义:

static args = [  {    name: 'file',               // 参数名称,之后通过 argv[name] 的模式获取参数    required: false,            // 是否必填    description: 'output file', // 参数形容    hidden: true,               // 是否从命令的 help 信息中暗藏    parse: input => 'output',   // 参数处理函数,能够扭转用户输出的值    default: 'world',           // 参数默认值    options: ['a', 'b'],        // 参数的可选范畴  }]

定义 flag

flag 的应用模式如下:

$ mycli --force --file=./myfile

咱们能够这样定义 flag 参数:

import Command, {flags} from '@oclif/command'export class MyCLI extends Command {  static flags = {    // 能够通过 --force 或 -f 来指定参数    force: flags.boolean({char: 'f'}),    file: flags.string(),  }  async run() {    const {flags} = this.parse(MyCLI)    if (flags.force) console.log('--force is set')    if (flags.file) console.log(`--file is: ${flags.file}`)  }}

咱们能够对 flag 参数进行属性定义:

static flags = {  name: flags.string({    char: 'n',                    // 参数短名称    description: 'name to print', // 参数形容    hidden: false,                // 是否从 help 信息中暗藏    multiple: false,              // 是否反对对这个参数设置多个值    env: 'MY_NAME',               // 默认值应用的环境变量的名称    options: ['a', 'b'],          // 可选值列表    parse: input => 'output',     // 对用户输出进行解决    default: 'world',             // 默认值,也能够是一个返回字符串的函数    required: false,              // 是否必填    dependsOn: ['extra-flag'],    // 依赖的其余 flag 参数列表    exclusive: ['extra-flag'],    // 不能一起应用的其余 flag 参数列表  }),  // 布尔值参数  force: flags.boolean({    char: 'f',    default: true,                // 默认值,能够是一个返回布尔值的函数  }),}

应用生命周期钩子

oclif 提供了一些生命周期钩子,能够让开发者在工具运行的各个阶段进行一些额定操作。

咱们能够这样定义一个钩子函数:

import { Hook } from '@oclif/config'export default const hook: Hook<'init'> = async function (options) {  console.log(`example init hook running before ${options.id}`)}

同时,还须要在 package.json 中注册这个钩子函数:

"oclif": {  "commands": "./lib/commands",  "hooks": {    "init": "./lib/hooks/init/example"  }}

oclif 还反对定义多个钩子函数,多个钩子函数会并行运行:

"oclif": {  "commands": "./lib/commands",  "hooks": {    "init": [      "./lib/hooks/init/example",      "./lib/hooks/init/another_hook"    ]  }}

目前反对的生命周期钩子如下:

  • init - 在 CLI 实现初始化之后,找到对应命令之前。
  • prerun - 在 init 实现,并找到对应命令之后,然而在命令运行之前。
  • postrun - 在命令运行完结之后,并没有谬误产生。
  • command_not_found - 没有找到对应命令,在展现错误信息之前。

应用插件

oclif 官网和社区提供了很多有用的插件能够供新开发的命令行工具应用,只须要在 package.json 中申明即可。

{  "name": "mycli",  "version": "0.0.0",  // ...  "oclif": {    "plugins": [      "@oclif/plugin-help",      "@oclif/plugin-not-found"    ]  }}

可用的插件有:

  • @oclif/plugin-not-found 当未找到命令的时候提供一个敌对的 "did you mean" 信息。
  • @oclif/plugin-plugins 容许用户给你的命令行工具增加插件。
  • @oclif/plugin-update 自动更新插件。
  • @oclif/plugin-help 帮忙信息插件。
  • @oclif/plugin-warn-if-update-available 当有可用更新时,展现一个正告信息提醒更新。
  • @oclif/plugin-autocomplete 提供 bash/zsh 的主动补全。

错误处理

命令行运行难免会出错,oclif 提供了两种错误处理的办法。

Command.catch

每个 Command 实例都有一个 catch 办法,开发者能够在这个办法中处理错误。

import {Command, flags} from '@oclif/command'export default class Hello extends Command {  async catch(error) {    // do something or    // re-throw to be handled globally    throw error;  }}

bin/runcatch

bin/run 是每个 oclif 命令行工具的入口文件,咱们能够通过 bin/runcatch 办法抓取谬误,包含 Command 中从新抛出的谬误。

.catch(require('@oclif/errors/handle'))//或.catch((error) => {  const oclifHandler = require('@oclif/errors/handle');  // do any extra work with error  return oclifHandler(error);})

其余性能

cli-ux

oclif 官网保护的 cli-ux 库提供了许多应用的性能。

  • 通过 cliux.prompt() 函数能够实现简略的交互性能。如果有更简单的交互需要,能够应用 inquirer。
  • 通过 cliux.action 能够实现旋转 loading 成果。
  • 通过 cliux.table 能够展现表格数据。

node-notifier

通过 node-notifier 能够实现跨平台的告诉信息展现。

常见面试知识点、技术解决方案、教程,都能够扫码关注公众号“众里千寻”获取,或者来这里 https://everfind.github.io 。