真香!原来 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 config
,mynewcli config:set
和 mynewcli 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.log
、this.warn
、this.error
、this.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/run
的 catch
bin/run
是每个 oclif 命令行工具的入口文件,我们可以通过 bin/run
的 catch
方法抓取错误,包括 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 开发可以这么简单的更多相关文章
- 真香!Python开发工程师都选择这个数据库:因为它免费
数据库类别 既然我们要使用关系数据库,就必须选择一个关系数据库. 目前广泛使用的关系数据库也就这么几种: 付费的商用数据库: Oracle,典型的高富帅: SQL Server,微软自家产品,Wind ...
- JavaFX桌面应用-MVC模式开发,“真香”
使用mvc模块开发JavaFX桌面应用在JavaFX系列文章第一篇 JavaFX桌面应用开发-HelloWorld 已经提到过,这里单独整理使用mvc模式开发开发的流程. ~ JavaFX桌面应用开发 ...
- 我把公司 10 年老系统改造 Maven,真香!!
公司有几个老古董项目,应该是 10 年前开发的了,有一个是 JSP + Servlet,有一个还用的 SSH 框架,打包用的 Ant,是有多老啊,我想在座的各位很多都没听过吧. 为了持续集成.持续部署 ...
- 你只会用 StringBuilder?试试 StringJoiner,真香!
你只会用 StringBuilder/ StringBuffer 拼接字符串? 那你就 OUT 了!! 如果需要拼接分隔符的字符串,建议使用 Java 8 中的这款拼接神器:StringJoiner, ...
- 从Eclipse切换到IDEA工具,哎~真香!
从Eclipse切换到IDEA工具,哎~真香!(图) 个人观点:IDEA工具用了就回不去了!!!对比很多人写,我就不赘述了.我在这里主要介绍一下IDEA工具的一些使用上的技巧,毕竟我开始学习java的 ...
- git 日常使用从入门到真香
目录 git 日常使用从入门到真香 一.Git简介 二.Git常用命令 三.git操作流程 四.报错处理 git 日常使用从入门到真香 一.Git简介 Git是一个开源的分布式版本控制系统,可以有效. ...
- 真香系列之 Golang 升级
Golang 以前的依赖管理一直饱受诟病,社区的方案也层出不穷,比如 vendor, glide, godep 等.之前的依赖管理一直是依靠 GOPATH 或者将依赖代码下载到本地,这种方式都有劣势. ...
- VSCode六大通用插件真香合集
目录 一.background:设置心水背景图 安利理由: 安装及设置步骤: 设置过程中使用的代码: 成果展示: 注意: 二.Material Theme(VSCode主题)+Material Ico ...
- 曾经你说chrome浏览器天下第一,现在你却说Microsoft edge真香!呸,渣男!!
曾经你说chrome浏览器天下第一,现在你却说Microsoft edge真香!呸,渣男!! 一个月前我每天打卡搜索的时候,老是有微软新版浏览器的广告.我刚才是内心其实是抵触的,直到我发现了它的奇妙之 ...
随机推荐
- 从事 Android应用开发4年有余,现在工资7500。很不爽!怎么办?
最近到某论坛看到一个帖子: 坐标北京,在一个公司从事android应用开发4年有余(毕业至今没换过公司).公司利润越来越大,工资却每年长1000,如今才到7500.琢磨着换工作,又不想扔下这四年来逐步 ...
- markdown的摘要测试
123456789 1 123456789 2 123456789 3 123456789 4 123456789 5 123456789 6 粗体 123456 划线 123456 斜体 12345 ...
- SpringBoot | 3.3 整合MyBatis-Plus
目录 前言 1. 什么是MyBatis-Plus 1.1 BaseMapper接口 1.2 IService接口 2. 整合MyBatis-Plus以及CRUD功能 2.1 导入场景依赖 2.2 CR ...
- 解决ftp登录问题:500 OOPS: cannot change directory:/home/xxx 500 OOPS: child died
.personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...
- ABP框架使用Oracle数据库,并实现从SQLServer中进行数据迁移的处理
ABP框架的数据访问底层是基于EFCore(Entity Framework Core)的,是微软标志性且成熟的ORM,因此它本身是支持多种主流数据库MySQL,SqlServer,Oracle,SQ ...
- setsockopt中参数之SO_REUSEADDR的意义
1.setsockopt中参数之SO_REUSEADDR的意义 1.一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用. SO_REUSE ...
- ACL的配置
一.实验拓扑 实验要求: 二.实验编址 三.实验步骤: 1.启动设备(全选) 2.配置端口IP R1: R2: R3: R4: 2.搭建OSPF网络: R1: R2: R3: R4: 4.配置ACL控 ...
- 当Transactional碰到锁,有个大坑,要小心。
你好呀,我是why. 前几天在某平台看到一个技术问题,很有意思啊. 涉及到的两个技术点,大家平时开发使用的也比较多,但是属于一个小细节,深挖下去,还是有点意思的. 来,先带你看一下问题是什么,同时给你 ...
- 从net到java:MyBatis快速入门
第一:这不是net与java的对比,只是我学习java相关知识梳理的笔记. 第二:这也没有否认net,只是现在的工作需要自己会java. 第三:这不深入.只是我看了些官网和网上的视频,算是入门的总结. ...
- 【原创】JavaFx程序解决Jupyter Notebook导出PDF不显示中文
0.ATTENTION!!! JavaFx里是通过Java调用控制台执行的的jupyter和xelatex指令, 这些个指令需要在本地安装Jupyter和MikTeX之后才能正常在电脑上运行 1.[问 ...