Bootstrap

构建优雅、高效的 Nodejs 命令行工具 - 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很多生态例如clapindicatif等库的套壳,但是这的确能够让我们的Nodejs命令行工具更加优雅和强大。我采用类似citty的函数式的声明方式,这能够更加清晰的进行开发。

安装

我们将archons作为开发依赖安装:

  1. 使用npm安装:

    npm install --save-dev archons
    
  2. 使用pnpm安装:

    pnpm add -D archons
    
  3. 使用yarn安装:

    yarn add -D archons
    
  4. 使用bun安装:

    bun add -d archons
    

基本用法

  1. defineCommand 方法

    构建一个基本的命令(或子命令)。

  2. 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哦!

;