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 提供了两种错误处理的方法。

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 开发可以这么简单的更多相关文章

  1. 真香!Python开发工程师都选择这个数据库:因为它免费

    数据库类别 既然我们要使用关系数据库,就必须选择一个关系数据库. 目前广泛使用的关系数据库也就这么几种: 付费的商用数据库: Oracle,典型的高富帅: SQL Server,微软自家产品,Wind ...

  2. JavaFX桌面应用-MVC模式开发,“真香”

    使用mvc模块开发JavaFX桌面应用在JavaFX系列文章第一篇 JavaFX桌面应用开发-HelloWorld 已经提到过,这里单独整理使用mvc模式开发开发的流程. ~ JavaFX桌面应用开发 ...

  3. 我把公司 10 年老系统改造 Maven,真香!!

    公司有几个老古董项目,应该是 10 年前开发的了,有一个是 JSP + Servlet,有一个还用的 SSH 框架,打包用的 Ant,是有多老啊,我想在座的各位很多都没听过吧. 为了持续集成.持续部署 ...

  4. 你只会用 StringBuilder?试试 StringJoiner,真香!

    你只会用 StringBuilder/ StringBuffer 拼接字符串? 那你就 OUT 了!! 如果需要拼接分隔符的字符串,建议使用 Java 8 中的这款拼接神器:StringJoiner, ...

  5. 从Eclipse切换到IDEA工具,哎~真香!

    从Eclipse切换到IDEA工具,哎~真香!(图) 个人观点:IDEA工具用了就回不去了!!!对比很多人写,我就不赘述了.我在这里主要介绍一下IDEA工具的一些使用上的技巧,毕竟我开始学习java的 ...

  6. git 日常使用从入门到真香

    目录 git 日常使用从入门到真香 一.Git简介 二.Git常用命令 三.git操作流程 四.报错处理 git 日常使用从入门到真香 一.Git简介 Git是一个开源的分布式版本控制系统,可以有效. ...

  7. 真香系列之 Golang 升级

    Golang 以前的依赖管理一直饱受诟病,社区的方案也层出不穷,比如 vendor, glide, godep 等.之前的依赖管理一直是依靠 GOPATH 或者将依赖代码下载到本地,这种方式都有劣势. ...

  8. VSCode六大通用插件真香合集

    目录 一.background:设置心水背景图 安利理由: 安装及设置步骤: 设置过程中使用的代码: 成果展示: 注意: 二.Material Theme(VSCode主题)+Material Ico ...

  9. 曾经你说chrome浏览器天下第一,现在你却说Microsoft edge真香!呸,渣男!!

    曾经你说chrome浏览器天下第一,现在你却说Microsoft edge真香!呸,渣男!! 一个月前我每天打卡搜索的时候,老是有微软新版浏览器的广告.我刚才是内心其实是抵触的,直到我发现了它的奇妙之 ...

随机推荐

  1. make CLI Comfortable When Working in Multiple Directoies

    alias alias is command-line counterpart of hotstring of AutoHotkey, for example: alias vboat='vi ~/d ...

  2. 批量删除gmail邮件

    以删除tor.com发送的邮件为例说明. 首先点击邮件搜索框右边的三角,在"发件人"下面写上"tor.com": 点"搜索"按钮,看一下范围 ...

  3. Java工具类-输入输出流

    输入输出流 1.概念 输入输出流:文件复制,上传 输出流: System.out.println() 写操作,程序将字符流写入到"目的地",比如打印机和文件等 输入流 :Scann ...

  4. sqli-labs lesson 23

    less 23: 这里通过验证?id=1'# 发现还是报错 观察代码: 这里涉及一个函数mixed preg_replace(mixed $pattern,mixed $replacement,mix ...

  5. Sqli-Labs less11-12

    less-11 11关以后已经和前几关不同.页面由get方式变成了类似form表单的post方式的登陆界面,我们不能直接看到数据,所以要用到burp抓包. 抓包方式前面已经说过,这里直接使用,我们先输 ...

  6. stm32 connot enter debug mode

    dap 可以发现设备,stlink jlink 均无法发现设备,但是都不能下载.connot enter debug mode ,发现是vdda 未连接

  7. Flink项目实战(一)---核心概念及基本使用

    前言.flink介绍: Apache Flink 是一个分布式处理引擎,用于在无界和有界数据流上进行有状态的计算.通过对时间精确控制以及状态化控制,Flink能够运行在任何处理无界流的应用中,同时对有 ...

  8. 题解 matrix

    传送门 无比毒瘤的dp题,而且伪装地好像很可做的样子 考场上我给它氪了差不多一个小时最后还是只能扔了个20pts状压走人 以下思路基本均来源于题解: 对于此题,题面中三个限制条件: (1)第 i 行第 ...

  9. Ubuntu完全卸载Docker步骤

    Ubuntu完全卸载Docker步骤:https://www.jianshu.com/p/c03044dbeaaf

  10. map中使用箭头函数遇到的坑

    箭头函数提供了更简洁和更短的语法,其中一个可用功能是可以将函数编写为具有隐式返回值的lambda表达式.这对于功能样式代码很方便,比如使用函数映射数组: const numbers = [1,2,3, ...