跳到主要内容

node cli 编写指南

本文字数: 2149阅读需 7 分钟
沐晨

简介

最近想要使用 node 写一个命令行工具,因网络上的分享的文章都过于冗长,且大部分都是脚手架、 commander 的版本,所以就有了这篇文章,只保留编写命令行工具所需的章节。

这里使用 yargstypescript 构建一个像 git 那样多级子命令的基础模版。已开源至 cli-template

什么是 yargs

yargs 官方文档

通过链式 api 配置的方式处理命令行输入参数,提供子命令、参数校验、类型格式化、帮助提示、中间件等功能

基本用法

#!/usr/bin/env node

require('yargs')
.scriptName("pirate-parser")
.usage('$0 <cmd> [args]')
.command({
command: 'hello [name]',
describe: 'welcome ter yargs!',
builder: function (yargs) {
return yargs.options({
'name': {
type: 'string',
default: 'Cambi',
describe: 'the name to say hello to'
}
})
},
handler: function (argv) {
console.log('hello', argv.name, 'welcome to yargs!')
}
})
.help()
.argv

$ pirate-parser --help

pirate-parser <cmd> [args]

Commands:
pirate-parser hello [name] welcome ter yargs!

Options:
--help Show help [boolean]

添加命令行参数

官方提供了多种方式配置参数,如.alias(); .array()等,但推荐使用 builder 的方式组织参数配置,方便管理。

基本参数类型

command({
builder: function (yargs) {
return yargs.options({
a: { type: 'boolean' },
b: { type: 'string' },
c: { type: 'number' },
file: {
type: 'string',
// 设置必填项,执行命令时未输入参数会提示:"缺少必须的选项:b"
require: true,
// 要求选项必须提供一个值
requiresArg: true,
// 别名,短参数名, 使用时输入 -file、-f 都是一样的
alias: 'f',
// 默认值
default: '/etc/passwd',
// 参数描述
describe: 'x marks the spot',
// 在 help 中隐藏此选项的提示
hidden: true
}
})
},
})

array

yargs.options({
arr: { type: 'array' }
})

$ cli --arr=1 --arr=2 --arr=3

# argv 输出
{
arr: [1, 2, 3]
}

枚举

输入的值只能使用给定的值,输入其他值抛出异常

yargs.options({
type: { choices: ['1', '2', '3'] }
})

$ cli --type=2

# argv 输出
{
type: '2'
}

计数器

# 将其解析值设置为参数出现的次数而不是true or false。因此默认值为0。
yargs.options({
count: { type: 'count' }
})

$ cli --count --count

# argv 输出
{
arr: 2
}

嵌套对象路径

# 在 typescript 中的 type 字段没有 object 类型
# 目前只能当做 unknown 处理,且对象子参数无格式校验,需手动处理。
yargs.options({
foo: {
# type: 'object' 这行不用写
}
})

$ cli --foo.bar.baz=33 --foo.quux=5

# argv 输出
{
foo: { bar: { baz: 33 }, quux: 5 }
}

提示参数过时

yargs.options({
hidden: { type: 'boolean', deprecated: 'use --display' },
'display': { type: 'string' }
})

# --help 输出
> cli
Options:
--hidden [deprecated: use --display] [boolean]
--display [string]

参数分组

yargs.options({
debug: { type: 'boolean', group: 'debug:' }
'log-level': { choices: ['debug', 'info', 'warn', 'error'], group: 'debug:' }
})

# --help 输出
> cli

debug:
--debug [布尔]
--log-level [可选值: "debug", "info", "warn", "error"]

特殊参数

$ hello A -n tom B C

# argv 输出
{
# 这里可以获取非连词线开头的参数。
_: ['A', 'B', 'C'],
# 默认值是执行文件名,也可以通过 api:scriptName 设置。
'$0': 'examples/cli.js',
}

创建子命令

通过 command 方法,设置 Git 风格的子命令。

hello.ts
export interface IOptions {
tip: string
}
const cmd: CommandModule<{}, IOptions> = {
// 作为配置主命令时 command 选项的 hello 可以替换为 $0
// 必选参数使用<>包裹,如:<param>;
// 可选参数使用[]包裹,如:[param];
// 推荐 command 只填子命令名称,参数通过 builder 配置
command: "hello <tip>",
describe: '子命令',
builder: function (yargs: Argv) {
return yargs
.options({
tip: {
type: string
}
})
// 命令使用格式
.usage('Usage: hello [options]')
.usage('Usage: [-f=<path>] [--debug]')
.example([
// 具体用法示例
['$0 --file "~/config.json"', 'Use custom config'],
['$0 --debug', 'Start in debug mode']
]);
},
handler: async (argv) => {
},
}

export default cmd;

在 main.ts 引入

main.ts
import hello from 'hello';

const parser = yargs(hideBin(process.argv))
.command(hello)
$ hello --help

Commands:
index.ts 主命令 [默认值]
index.ts hello 子命令

Options:
...
提示

别忘了在每个子命令中也要在 builder 的 yargs 实例里提供usageexample

使用帮助提示

只需在 main 里面配置一次 help, 每个子命令都可以使用。

main.ts
const argv = require('yargs')
// 配置一个隐式命令来显示帮助手册字符串
.help('help', '查看命令行帮助')
.alias('h', 'help');
// 在 help 末尾打印的消息
.epilog('如需了解更多信息,请在以下网址查看使用手册 http://example.com')
$ cli hello --help

选项:
-h, --help 查看命令行帮助 [boolean]

示例:
...

如需了解更多信息,请在以下网址查看使用手册 http://example.com

异常拦截

const argv = require('yargs')
.fail(function (msg, err, yargs) {
// handler执行的异常继续向上抛出
if (err) throw err // preserve stack
// 输出帮助信息
console.error(yargs.help());
// 提示参数输入异常
console.error(chalk.red(`\n\n\n===== Execute failed. =====\n\n${msg}\n`));
process.exit(1);
})

调试

使用 ts-node 启动

$ pnpm test
# 传递参数时需添加 --
$ pnpm test -- --file ./config.json

作为 bin 文件启动

1、在 package.json 添加内容

{
"bin": {
"cli": "./bin.js"
}
}

2、添加入口文件

#!/usr/bin/env node

# 将构建目录(dist)下的 index.js 作为脚手架的启动文件
require('../dist/index')

3、连接到环境变量运行

$ npm link . # 挂载到全局环境变量
$ cli -help # 启动
$ npm unlink . # 卸载
提示

在移动项目文件夹时再 unlink 会失败,需要手动进系统目录删除命令,查询位置可以使用命令 where cli

发布到 npm

完善包信息 package.json

{
//包名,在包名称前加自己的 npm 账户名,采用 npm scope 的方式,包目录的组织方式和普通包不一样,而且可以有效的避免和他人的包名冲突
"name": "@mucen/cli"
// 表示包的入口文件
"main": "./lib/index.js",
// 关键字,方面别人搜索到你的包
"keywords": ["typescript", "cli", "template"],
// 作者
"author": "mucen",
// 告诉 npm,publish 时发布哪些文件到 npm 仓库
"files": ["package.json", "README.md", "dist"],
// 项目仓库
"repository": {
"type": "git",
"url": "https://github.com/liyongning/ts-cli.git"
},
}

1、增加发布命令

{
"script": {
"publish": "pnpm build; npm publish --access public"
}
}

2、执行发布

npm login
pnpm publish

其他事项

1、返回值 根据 Unix 传统,程序执行成功返回 0,否则返回 1 。

if (err) {
process.exit(1);
} else {
process.exit(0);
}

2、监听信号事件。

process.on('SIGINT', function () {
console.log('Got a SIGINT');
process.exit(0);
});

发送信号的方法如下:

$ kill -s SIGINT [process_id]

常用工具包

rollup

比webpack更加适合开发js库的构建工具

inquirer

提供交互式命令界面

const yargs = require('yargs');
const inquirer = require('inquirer');

const askName = async () => {
const answers = await inquirer.prompt([
{
message: 'What is your name?',
name: 'name',
type: 'string'
}
]);

console.log(`Hello, ${answers.name}!`);
};
const argv = yargs()
.command('ask', 'use inquirer to prompt for your name', () => {}, askName)
.help('h').argv;

chalk

彩色日志输出

clear-console

清空命令行输出

download-git-repo

下载 git 仓库代码

ora

图标、loading 动画

figlet

控制台输出艺术字

node-fs-extra

系统fs模块的扩展,提供了更多便利的 API,支持 promise

pug

高性能模版引擎

debug

轻量级 debug lib, 通过环境变量切换不同模块的 log 输出

progress

在控制台中显示进度条,如:downloading [===== ] 39/bps 29% 3.7s

版本升级提示

pacote

获取 npm package manifests,可用于提示版本升级等功能

调用系统命令

zx

编写 shell 脚本更舒适的工具, 写法如下:

// 全局引入 `$`
import 'zx/globals'

let branch = await $`git branch --show-current`
await $`dep deploy --branch=${branch}`
提示
  1. 需要在 package.json 设置 "type": "module"; tsconfig.json 设置 "module": "ESNext"
  2. ts-node 需要如下命令启动:node --experimental-specifier-resolution=node --loader ts-node/esm src/index.ts

不使用 ESModule 的话还可以选择 shelljs

优秀社区项目

yeoman

Yeoman 是一个通用的脚手架系统,允许创建任何类型的应用程序。它允许快速开始新项目并简化现有项目的维护。如创建新项目、模块、工作流程

plop

微型生成器工具,创建路由、控制器、组件等

参考

前端工程化实战 - 企业级 CLI 开发

搭建自己的 typescript 项目 + 开发自己的脚手架工具 ts-cli

Node.js 命令行程序开发教程 ---- 阮一峰

Loading Comments...