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. Windows API 进程相关笔记

    0. 前言 最近做了一个进程信息相关的项目,整理了一下自己做项目时的笔记,分享给大家 1. 相关概念 1.1 HANDLE 概念 HANDLE(句柄)是Windows操作系统中的一个概念. 在Wind ...

  2. SpringMVC学习08(拦截器)

    8.拦截器 概述 SpringMVC的处理器拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理.开发者可以自己定义一些拦截器来实现特定的功能. 过滤器与拦截器的区别: ...

  3. Pikachu-Over Permission模块

    一.概述 如果使用A用户的权限去操作B用户的数据,A的权限小于B的权限,如果能够成功操作,则称之为越权操作. 越权漏洞形成的原因是后台使用了 不合理的权限校验规则导致的. 一般越权漏洞容易出现在权限页 ...

  4. docker 安装mysql设置不区分表名大小写,创建minio,设置开机自启动

    环境deepin mysql : docker run -p 3306:3306 --name tyer-mysql --restart=always -v $PWD/conf:/etc/mysql/ ...

  5. NOIP 模拟 $27\; \rm 牛半仙的妹子Tree$

    题解 \(by\;zj\varphi\) 很妙的虚树题. 考虑若没有操作 \(2\),那么直接记录一下扩散到它的最短时间和询问时间相比即可,可以当作一个树上最短路. 有 \(2\) 操作怎么办,将操作 ...

  6. 题解 P3158 [CQOI2011]放棋子

    题解 本题是一个 \(DP\) 加 容斥,容斥的式子很好推,重点是如何想到和如何推出 \(DP\) 部分的式子. 因为不同种颜色的棋子不能放在同一行或同一列,所以不同种的棋子是相对独立的. 据此,我们 ...

  7. noip模拟15

    T1 恶心的数学题,还卡空间... 于是考虑数组二次调用,用完memset 记录一手二维前缀和对不同询问离线修改,最后一块回答即可 Code #include<cstdio> #inclu ...

  8. VMware + LInux + Xshell 连接环境设置

    一.安装好Centos后,打开设置: 网络连接四中模式桥接模式.NAT模式.仅主机和自定义模式: 1).桥接模式:更公司的局域网连接,等于另一台连接进公司的新电脑 2).NAT模式:与虚拟机使用的计算 ...

  9. .Net Core WebApi (一) --Config

    ASP.NET Core launchsettings.json 文件 位置:项目根文件夹的"Properties"文件夹中 使用:从 Visual Studio 或使用.NET ...

  10. list切片

    对list进行切片 取一个list的部分元素是非常常见的操作.比如,一个list如下: >>> L = ['Adam', 'Lisa', 'Bart', 'Paul'] 取前3个元素 ...