[

6月22日
-
10分钟阅读
[
保存

在咖啡馆里编码
如何用oclif构建一个简单的CLI
不喜欢shell脚本?用Node.js构建CLI工具吧!
Salesforce的开发人员为开源社区做出了很多贡献。在他们的众多贡献中,有一个重要的、但可能不太为人所知的项目,名为oclif。开放CLI框架在2018年初宣布,此后发展成为Salesforce CLI和Heroku CLI的基础。
在这篇文章中,我们将对oclif进行简要概述,然后我们将走过如何用oclif构建一个简单的CLI。
oclif的简要历史
Oclif最初是Heroku的一个内部项目。Heroku一直专注于开发者的体验,它的CLI为通过API与服务的合作设定了标准。毕竟,Heroku是用于部署的git push heroku 的创造者--一个现在被整个行业广泛使用的标准。
如果你曾经运行过heroku ps 或sfdx auth:list ,那么你就使用过oclif。从一开始,oclif就被设计成一个开放的、可扩展的、轻量级的框架,用于快速建立简单和复杂的CLI。
在发布四年多后,oclif已经成为构建CLI的权威框架。一些最受欢迎的oclif组件每周的下载量超过了一百万。oclif项目仍在积极开发中。
一些通过oclif构建的高知名度的公司或项目的例子包括。
为什么开发人员今天会选择oclif?
想建立CLI的原因有很多。也许你的公司有一个API,而你想让客户更容易地使用它。也许你使用的是内部的API,你想通过CLI运行命令来实现日常任务的自动化。在这些情况下,你总是可以写Powershell或Bash脚本,或从头开始建立自己的CLI,但Oclif是最好的选择。
Oclif是建立在Node.js上的。它可以在所有主要的操作系统上运行,并有多种分布选项。除了速度快之外,Oclif还自带文档,并支持插件,允许开发者构建和分享可重用的功能。随着oclif的迅速普及,越来越多的库、插件和有用的包开始出现。
例如,cli-ux 预先与@oclif/core 包打包,并提供常见的用户体验功能,如旋转器和表格,以及进度条,你可以将它们添加到你的CLI中。
很容易看出为什么oclif是成功的,而且应该是你构建CLI的选择。
我们的小型项目介绍
让我们为你将要建立的CLI设定一个场景。你想为你的激情之一建立自己的CLI:太空旅行。
你如此热爱太空旅行,以至于你观看了SpaceX的每一次发射直播,而且你经常查看HowManyPeopleAreInSpaceRightNow.com的页面,而不是你愿意承认的。你想通过建立一个太空旅行细节的CLI来简化这种痴迷,从一个简单的命令开始,显示目前在太空的人数。最近,你发现了一个名为Open Notify的服务,它有一个用于此目的的API端点。
我们将使用oclif generate命令来创建我们的项目,它将用一些合理的默认值来构建一个新的CLI项目支架。用这个命令创建的项目默认使用TypeScript--这也是我们项目要使用的,但也可以配置为使用vanilla JavaScript。
创建项目
首先,如果你还没有Node.js的话,你需要在本地安装它。oclif项目要求使用活跃的LTS版本的Node.js。
你可以通过这个命令验证你所安装的Node.js的版本。
/ $ node -vv16.15.0
接下来,在全局范围内安装oclif CLI。
/ $ npm install -g oclif
现在,是时候使用generate命令创建oclif项目了。
/ $ oclif generate space-cli _-----_ | | ╭──────────────────────────╮ |--(o)--| │ Time to build an oclif │ `---------´ │ CLI! Version: 3.0.1 │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y `Cloning into '/space-cli'...
在这一点上,你会遇到一些设置问题。对于这个项目,你可以把它们全部留空,使用默认值(用括号表示),或者你可以选择自己填写。最后一个问题将要求你选择一个软件包管理器。对于我们的例子,选择npm。
以oclif的hello world命令开始
从这里开始,oclif将为你完成创建你的CLI项目。在bin/ 文件夹中,你会发现一些node脚本,你可以在开发过程中运行这些脚本来测试你的CLI。这些脚本将运行dist/ 文件夹中的已建文件的命令。如果你只是按原样运行脚本,你会看到类似这样的信息。
/ $ cd space-cli/
默认情况下,如果你没有为CLI指定一个要运行的命令,它将显示帮助信息。让我们再试一次。
/space-cli $ ./bin/run hello > Error: Missing 1 required arg: > person Person to say hello to > See more help with --help
这一次,我们收到了一个错误。我们缺少一个必要的参数。我们需要指定我们要问候的人!
/space-cli $ ./bin/run hello John > Error: Missing required flag: > -f, --from FROM Whom is saying hello > See more help with --help
我们收到了另一条有用的错误信息。我们还需要指定打招呼的人,这次是用一个标志。
/space-cli $ ./bin/run hello John --from Janehello John from Jane! (./src/commands/hello/index.ts)
最后,我们已经正确地问候了约翰,我们可以看一下hello命令的代码,它可以在src/commands/hello/index.ts 。 它看起来像这样。
import {Command, Flags} from '@oclif/core'export default class Hello extends Command { static description = 'Say hello' static examples = [ `$ oex hello friend --from oclifhello friend from oclif! (./src/commands/hello/index.ts)`, ] static flags = { from: Flags.string({char: 'f', description: 'Whom is saying hello', required: true}), } static args = [{name: 'person', description: 'Person to say hello to', required: true}] async run(): Promise<void> { const {args, flags} = await this.parse(Hello) this.log(`hello ${args.person} from ${flags.from}! (./src/commands/hello/index.ts)`) }}
正如你所看到的,一个oclif命令被简单地定义为一个带有async run() 方法的类,不出意料地包含了命令运行时要执行的代码。此外,一些静态属性提供了额外的功能,尽管它们都是可选的。
description和examples属性是用于帮助信息的。flags属性是一个对象,它定义了命令可用的标志,其中对象的键与标志名称对应。我们稍后会更深入地研究这些。- 最后,
args是一个对象数组,代表该命令可以接受的参数和一些选项。
run() 方法解析了参数和标志,然后使用person 参数和from 标志打印出一条信息,使用this.log() (非阻塞的替代方法console.log )。注意标志和参数都是用required: true 配置的,这就可以得到验证和有用的错误信息,就像我们在先前的测试中看到的那样。
创建我们自己的命令
现在我们已经了解了命令的结构,我们准备编写我们自己的命令。我们把它叫做humans ,它将打印出目前在太空中的人数。你可以删除src/commands 中的hello文件夹,因为我们不再需要它了。oclif CLI也可以帮助我们建立新的命令框架。
/space-cli $ oclif generate command humans _-----_ | | ╭──────────────────────────╮ |--(o)--| │ Adding a command to │ `---------´ │ space-cli Version: 3.0.1 │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` create src\commands\humans.ts create test\commands\humans.test.tsNo change to package.json was detected. No package manager install will be executed.
现在我们有一个可以编辑的humans.ts 文件,我们可以开始编写我们的命令。我们将使用的Open Notify API端点可以在以下网址找到。 [http://open-notify.org/Open-Notify-API/People-In-Space/](http://open-notify.org/Open-Notify-API/People-In-Space/)
正如你在描述中所看到的,端点返回一个简单的JSON响应,其中有关于当前在太空中的人类的详细信息。将src/commands/humans.ts 中的代码替换为以下内容。
import {Command} from '@oclif/core'import {get} from 'node:http'export default class HumanCommand extends Command { static description = 'Get the number of humans currently in space.' static examples = [ '$ space-cli humans\nNumber of humans currently in space: 7', ] public async run(): Promise<void> { get('http://api.open-notify.org/astros.json', res => { res.on('data', d => { const details = JSON.parse(d) this.log(`Number of humans currently in space: ${details.number}`) }) }).on('error', e => { this.error(e) }) }}
下面是我们在上面的代码中所做的分解。
- 使用
http包向Open Notify端点发送一个请求。 - 解析JSON响应。
- 输出带有消息的数字。
- 捕捉并打印我们在此过程中可能遇到的任何错误。
对于这个命令的第一次迭代,我们不需要任何标志或参数,所以我们没有为这些定义任何属性。
测试我们的基本命令
现在,我们可以测试我们的新命令了。首先,我们要重建dist/ 文件,然后我们可以像之前的hello world例子一样运行我们的命令。
/spacecli $ npm run build> space-cli@0.0.0 build> shx rm -rf dist && tsc -b/spacecli $ ./bin/run humansNumber of humans currently in space: 7
很简单,不是吗?你现在有一个简单的CLI项目,通过oclif框架构建,可以立即告诉你太空中的人数。
用标志和更漂亮的用户界面加强我们的命令
知道目前有多少人在太空中是不错的,但我们可以得到更多的太空数据我们使用的端点提供了更多关于太空人的细节,包括他们的名字和他们所在的航天器。
我们将使我们的命令更进一步,演示如何使用标志,并给我们的命令一个更漂亮的用户界面。我们可以用cli-ux 包将我们的数据以表格的形式输出,该包已经被卷进@oclif/core (从1.2.0 版本开始)。为了确保我们能够访问cli-ux ,让我们更新我们的软件包。
/spacecli $ npm update
我们可以在我们的humans 命令中添加一个可选的--table 标志,以在一个表格中打印出这些数据。我们使用CliUx.ux.table() 函数来实现这个漂亮的输出。
import {Command, Flags, CliUx} from '@oclif/core'import {get} from 'node:http'export default class HumansCommand extends Command { static description = 'Get the number of humans currently in space.' static examples = [ '$ space-cli\nNumber of humans currently in space: 7', ] static flags = { table: Flags.boolean({char: 't', description: 'display who is in space and where with a table'}), } public async run(): Promise<void> { const {flags} = await this.parse(HumansCommand) get('http://api.open-notify.org/astros.json', res => { res.on('data', d => { const details = JSON.parse(d) this.log(`Number of humans currently in space: ${details.number}`) if (flags.table) { CliUx.ux.table(details.people, {name: {}, craft: {}}) } }) }).on('error', e => { this.error(e) }) }}
在我们更新的代码中,我们的第一步是带回了flags 属性。这一次我们定义了一个布尔标志--它要么在那里,要么不在那里--而不是将一个字符串作为参数的字符串标志。我们还在传入的选项对象中为该标志定义了一个描述和一个短语-t。
接下来,我们在我们的run 方法中解析该标志。如果它存在,我们就用CliUx.ux.table() 显示一个表格。第一个参数,details.people ,是我们要在表中显示的数据,而第二个参数是一个定义了表中各列的对象。在这种情况下,我们定义了一个name 和一个craft 列,每列都有一个空对象。(对于表格中的列有一些配置选项,但在本例中我们不需要任何配置)。Oclif会在我们传入的数据对象上寻找这些属性,并为我们处理其他的事情
我们可以建立并重新运行带有新表标志的命令,看看它是什么样子的。
/spacecli $ ./bin/run humans --tableNumber of humans currently in space: 10 Name Craft ───────────────── ──────── Oleg Artemyev ISS Denis Matveev ISS Sergey Korsakov ISS Kjell Lindgren ISS Bob Hines ISS Samantha Cristoforetti ISS Jessica Watkins ISS Cai Xuzhe Tiangong Chen Dong Tiangong Liu Yang Tiangong
很好!
在你自己的基础上增加一些功能
在这一点上,我们的例子项目是完整的,但你可以很容易地在它上面建立更多的功能。Open Notify服务提供了一个API端点来获取国际空间站的当前位置。你也可以添加这个功能,用一个命令,如space-cli iss ,在运行时返回位置。
关于分发?
你可能正在考虑分享你的新CLI的发行选项。你可以通过一个简单的命令把这个项目发布到npm。你可以创建一个tarball,在内部向你的团队或同事分发该项目。如果你想和macOS用户分享它,你也可以创建一个Homebrew公式。Oclif可以帮助你完成这些选项中的任何一个。
总结
在这篇文章的开头,我们回顾了oclif的历史,以及创建CLI时它应该成为你的首选的许多原因。它的一些优势包括速度、可扩展性和各种分发选项。我们学习了如何为CLI项目搭建脚手架并向其添加新的命令,并建立了一个简单的CLI作为例子。
现在你已经掌握了知识和新的工具,出去吧,变得危险起来。