真香!原来 CLI 开发可以这么简单

发布时间:2022-07-05 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了真香!原来 CLI 开发可以这么简单脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

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/run
hello world From ./src/index.js!

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

$ npx oclif multi mynewcli
? npm package name (mynewcli): mynewcli
$ cd mynewcli
$ ./bin/run --version
mynewcli/0.0.0 darwin-x64 node-v9.5.0
$ ./bin/run --help
usage
  $ mynewcli [COMMAND]

COMMANDS
  hello
  help   display help for mynewcli

$ ./bin/run hello
hello world from ./src/hello.js!

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

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

加入有如下目录结构:

package.json
src/
└── 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 。

真香!原来 CLI 开发可以这么简单

脚本宝典总结

以上是脚本宝典为你收集整理的真香!原来 CLI 开发可以这么简单全部内容,希望文章能够帮你解决真香!原来 CLI 开发可以这么简单所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。