构建优雅、高效的 Nodejs 命令行工具 - Archons
目录
- 项目简介
- 安装
- 基本用法
- 样例
- 创建一个简单的命令行工具
- 使用`archons`上下文创建进度条
- 最后
项目地址: https://github.com/noctisynth/archons
Bug反馈或功能请求:https://github.com/noctisynth/archons/issues
项目简介
Archons意思是“执政官”,我使用Rust作为Nodejs的Native层并将方法通过napi-rs
导出到Nodejs,这使得我们可以借助Rust来帮助Nodejs使用者创建高效、强大和优雅的终端命令行工具。
创建这个项目首先是由于citty
项目的启发,但是citty
很多更高级的功能并不完善,个人感觉社区活跃度也不高,因为我的PR至今没人处理。于是我便想着另起炉灶,但是既然都重写了,为什么不RIIR(Rewrite it in Rust)呢?Rust的命令行工具已经有了一套完整的生态,我们完全可以让Nodejs工具在Rust的肩膀上新生。
于是Archons应运而生。
从本质上来讲,我只是对Rust很多生态例如clap
、indicatif
等库的套壳,但是这的确能够让我们的Nodejs命令行工具更加优雅和强大。我采用类似citty
的函数式的声明方式,这能够更加清晰的进行开发。
安装
我们将archons
作为开发依赖安装:
-
使用
npm
安装:npm install --save-dev archons
-
使用
pnpm
安装:pnpm add -D archons
-
使用
yarn
安装:yarn add -D archons
-
使用
bun
安装:bun add -d archons
基本用法
-
defineCommand
方法构建一个基本的命令(或子命令)。
-
run
方法将一个命令作为主命令并运行。
样例
所有样例:https://github.com/noctisynth/archons/tree/main/examples
命令行选项所有可用参数:https://github.com/noctisynth/archons/blob/main/index.d.ts#L66-L210
创建一个简单的命令行工具
Archons 中参数的行为将尽量与 clap 保持一致,部分参数由于Rust与Nodejs的差异,在代码文档中均有标注。
import { defineCommand, run, type Context } from 'archons';
// 子命令
const dev = defineCommand({
meta: {
name: 'dev',
about: 'Run development server',
},
options: {
port: {
type: 'option',
parser: 'number',
default: '3000',
},
},
callback: (ctx: Context) => {
console.log(ctx); // 这里输出完整的上下文
console.log(ctx.args.port) // 这里port的值,缺省值为3000
}
})
const main = defineCommand({
meta: {
name: 'simple',
version: '0.0.1',
about: 'A simple command line tool',
styled: true, // 启用色彩
},
options: {
name: {
type: 'positional', // 位置参数
required: true, // 必须传入
help: 'Name of the person to greet',
},
verbose: {
type: 'option',
parser: 'boolean',
action: 'store',
help: 'Enable verbose output',
global: true // 全局参数,会被子命令继承
},
},
// 子命令
subcommands: {
dev,
},
callback: (ctx: Context) => {
console.log(ctx);
}
})
run(main) // 执行主命令
使用archons
上下文创建进度条
Archons 中进度条的创建行为与 indicatif 一致。
import { type Context, defineCommand, run } from 'archons'
const spinner = defineCommand({
meta: {
name: 'spinner',
},
options: {
'enable-steady-tick': {
type: 'option',
action: 'store',
},
},
callback: async (ctx: Context) => {
const spinner = ctx.createSpinner()
spinner.setMessage('loading')
spinner.tick()
let i = 100
const interval = ctx.args.interval as number
if (ctx.args['enable-steady-tick']) {
spinner.println('Enabled steady tick')
spinner.enableSteadyTick(interval)
while (i--) {
if (i < 30) {
spinner.setMessage('Disabled steady tick for now')
spinner.disableSteadyTick()
}
await new Promise((resolve) => setTimeout(resolve, interval))
}
} else {
spinner.println('Disabled steady tick')
while (i--) {
spinner.tick()
await new Promise((resolve) => setTimeout(resolve, interval))
}
}
spinner.finishWithMessage('✨ finished')
},
})
const bar = defineCommand({
meta: {
name: 'bar',
},
options: {
clear: {
type: 'option',
action: 'store',
help: 'Clear the progress bar',
parser: 'boolean',
},
},
callback: async (ctx: Context) => {
const bar = ctx.createProgressBar(ctx.args.total as number)
bar.setTemplate('{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos:>5}/{len:5} {msg}')
bar.setProgressChars('=>-')
let i = 100
const interval = ctx.args.interval as number
while (i--) {
bar.inc(1)
await new Promise((resolve) => setTimeout(resolve, interval))
}
if (ctx.args.clear) {
bar.finishAndClear()
} else {
bar.finish()
}
console.log('✨ finished')
},
})
const main = defineCommand({
meta: {
name: 'progressbar',
styled: true,
subcommandRequired: true,
},
options: {
interval: {
type: 'option',
numArgs: '1',
default: '100',
global: true,
help: 'Interval of spinner',
parser: 'number',
},
total: {
type: 'option',
numArgs: '1',
default: '100',
global: true,
help: 'Total of progress bar',
parser: 'number',
},
},
subcommands: {
spinner,
bar,
},
})
run(main)
最后
如果你认为这个项目有所帮助,欢迎在GitHub给我一个Star哦!