mcpField
N
npx-cli

npx-cli

github.com/jwynia开发179304 次安装35 周前更新
代码仓库

关于 npx-cli

面向单包仓库的技能指南,用于构建并发布可通过 npx 执行的命令行工具。它以 Bun 为主要开发工具链,同时要求产物可在 Node.js 环境运行。

主要功能

  1. 01Bun 优先的工具链配置
  2. 02生成 CLI 项目脚手架
  3. 03解析参数与子命令
  4. 04分离 CLI 与业务逻辑
  5. 05配置测试、检查与发布流程

适用场景

  1. 01从头创建 npx 命令行工具
  2. 02为现有库添加 CLI
  3. 03配置参数解析和终端交互
  4. 04将 CLI 包发布到 npm

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jwynia/agent-skills --skill npx-cli

安装后在 Claude Code 中输入/npx-cli确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

reference/
cli-patterns.md
esm-cjs-guide.md
publishing-workflow.md
strict-typescript.md
scripts/
scaffold.ts
templates/
biome.json
bunup.config.ts
eslint.config.ts
gitignore
package.json.hbs
tsconfig.json
vitest.config.ts
templates/scripts/
postbuild.ts
templates/src/
cli.test.ts.hbs
cli.ts.hbs
index.test.ts.hbs
index.ts.hbs

详细介绍

npx CLI 工具开发(Bun 优先)

使用 Bun 作为主要运行时和工具链,构建并发布可通过 npx 执行的命令行工具,生成适用于所有 npm/npx 用户(Node.js 运行时)的二进制文件。

何时使用此技能

适用于:

  • 从头开始创建新的 CLI 工具
  • 构建可通过 npx 执行的二进制文件
  • 为 CLI 设置参数解析、子命令或终端 UX
  • 将 CLI 工具发布到 npm
  • 将 CLI 添加到现有库包中

不适用于:

  • 构建不带 CLI 的库(请使用 npm-package 技能)
  • 构建应用程序(而非已发布的包)
  • 在 monorepo 中工作(此技能面向单包仓库)

工具链

关注事项工具原因
运行时 / 包管理器Bun安装、运行和转译速度快
打包工具Bunup原生支持 Bun、双入口(库 + CLI)、生成 .d.ts
参数解析citty约 3KB,原生支持 TypeScript,自动生成帮助信息,提供 runMain()
终端颜色picocolors约 7KB,支持 CJS+ESM,自动检测
TypeScriptmodule: "nodenext"、strict: true + 其他选项最大程度确保正确性
格式化 + 基础 lintBiome v2快速、单一工具
类型感知 lintESLint + typescript-eslint深入的类型安全检查
测试Vitest隔离、模拟、覆盖率
版本管理Changesets基于文件、明确
发布npm publish --provenance可信发布 / OIDC

搭建新的 CLI

运行脚手架脚本:

bun run <skill-path>/scripts/scaffold.ts ./my-cli \
  --name my-cli \
  --bin my-cli \
  --description "What this CLI does" \
  --author "Your Name" \
  --license MIT

选项:

  • --bin <name> — npx 的二进制名称(默认为不带作用域的包名)
  • --cli-only — 不导出库,仅包含 CLI 二进制文件
  • --no-eslint — 跳过 ESLint,仅使用 Biome

然后安装依赖:

cd my-cli
bun install
bun add -d bunup typescript vitest @vitest/coverage-v8 @biomejs/biome @changesets/cli
bun add citty picocolors
bun add -d eslint typescript-eslint  # unless --no-eslint

项目结构

双入口(库 + CLI)——默认
my-cli/
├── src/
│   ├── index.ts            # Library exports (programmatic API)
│   ├── index.test.ts       # Unit tests for library
│   ├── cli.ts              # CLI entry point (imports from index.ts)
│   └── cli.test.ts         # CLI integration tests
├── dist/
│   ├── index.js            # Library bundle
│   ├── index.d.ts          # Type declarations
│   └── cli.js              # CLI binary (with shebang)
├── .changeset/
│   └── config.json
├── package.json
├── tsconfig.json
├── bunup.config.ts
├── biome.json
├── eslint.config.ts
├── vitest.config.ts
├── .gitignore
├── README.md
└── LICENSE
仅 CLI(不导出库)

结构相同,但不包含 src/index.ts 和 src/index.test.ts。package.json 中不设置 exports 字段,仅设置 bin。

架构模式

将逻辑与 CLI 接线分离。 CLI 入口(cli.ts)是一个轻量包装器,负责:

  1. 使用 citty 解析参数
  2. 调用库/核心模块
  3. 格式化终端输出

所有业务逻辑都放在可导入的模块(index.ts 或内部模块)中。这样无需启动进程即可对逻辑进行单元测试。

cli.ts → imports from → index.ts / core modules
                              ↑
                         unit tests

关键规则(必须遵守)

此处同样适用 npm-package 技能中的所有规则。以下附加规则专用于 CLI 包:

二进制文件配置
  1. 发布的 bin 文件始终使用 #!/usr/bin/env node。 绝不使用 #!/usr/bin/env bun。绝大多数 npx 用户都没有安装 Bun。

  2. 将 bin 指向 dist/ 中编译后的 JavaScript。 绝不指向 TypeScript 源码。npx 用户不会拥有你的构建工具链。

  3. 确保 bin 文件具有可执行权限。 构建脚本会在编译后执行 chmod +x dist/cli.js。

  4. 以 Node.js 为目标进行构建。 Bunup 的输出必须能在 Node.js 上运行,不能依赖 Bun 运行时功能。

包配置
  1. 始终在 package.json 中使用 "type": "module"。

  2. 每个 exports 块中,types 都必须是第一个条件。

  3. 使用 files: ["dist"]。 仅使用白名单。

  4. 对于双用途包(库 + CLI): exports 字段公开库 API,bin 字段公开 CLI。二者相互独立——bin 不属于 exports。

代码质量
  1. 禁止使用 any。 使用 unknown 并进行类型收窄。

  2. 仅导入类型时使用 import type。

  3. 妥善处理错误。 CLI 用户不应看到原始堆栈跟踪。使用 citty 的 runMain() 自动处理错误,并通过 process.on('SIGINT', ...) 执行清理。

  4. 使用恰当的退出码。 成功时为 0,错误时为 1,参数错误时为 2,收到 SIGINT 时为 130。

参考文档

修改配置前请先阅读:

使用 citty 解析参数

单个命令
import { defineCommand, runMain } from 'citty';

const main = defineCommand({
  meta: { name: 'my-cli', version: '1.0.0', description: '...' },
  args: {
    input: { type: 'positional', description: 'Input file', required: true },
    output: { alias: 'o', type: 'string', description: 'Output path', default: './out' },
    verbose: { alias: 'v', type: 'boolean', description: 'Verbose output', default: false },
  },
  run({ args }) {
    // args is fully typed
  },
});

void runMain(main);
子命令
import { defineCommand, runMain } from 'citty';

const init = defineCommand({ meta: { name: 'init' }, /* ... */ });
const build = defineCommand({ meta: { name: 'build' }, /* ... */ });

const main = defineCommand({
  meta: { name: 'my-cli', version: '1.0.0' },
  subCommands: { init, build },
});

void runMain(main);

完整示例(包括错误处理、颜色和加载动画)请参阅 reference/cli-patterns.md。

测试策略

单元测试 — 测试逻辑
// src/index.test.ts
import { describe, it, expect } from 'vitest';
import { processInput } from './index.js';

describe('processInput', () => {
  it('handles valid input', () => {
    expect(processInput('test')).toBe('expected');
  });
});
集成测试 — 测试二进制文件

先构建(bun run build),然后启动编译后的二进制文件:

// src/cli.test.ts
import { describe, it, expect } from 'vitest';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const exec = promisify(execFile);

describe('CLI', () => {
  it('prints help', async () => {
    const { stdout } = await exec('node', ['./dist/cli.js', '--help']);
    expect(stdout).toContain('my-cli');
  });
});

开发工作流

# Write code and tests
bun run test:watch    # Vitest watch mode

# Check everything
bun run lint          # Biome + ESLint
bun run typecheck     # tsc --noEmit
bun run test          # Vitest

# Build and try the CLI locally
bun run build
node ./dist/cli.js --help
node ./dist/cli.js some-input

# Prepare release
bunx changeset
bunx changeset version

# Publish
bun run release       # Build + npm publish --provenance

之后添加子命令

  1. 为每个子命令创建一个新文件:src/commands/init.ts、src/commands/build.ts
  2. 每个文件都导出一个 defineCommand() 结果
  3. 导入并将其接入主命令的 subCommands
  4. 将逻辑放在可测试的模块中,命令只作为薄封装

将仅含 CLI 的包转换为双用途包(库 + CLI)

  1. 创建包含公共 API 的 src/index.ts
  2. 更新 bunup.config.ts,包含两个入口点
  3. 在 package.json 中现有的 bin 旁添加 exports 字段
  4. 添加 .d.ts 生成配置:dts: { entry: ['src/index.ts'] }

Bun 特有的注意事项

  • bun build 不会生成 .d.ts 文件。 使用 Bunup 或 tsc --emitDeclarationOnly。
  • bun build 不会转换较新语法。 ES2022+ 将原样发布。
  • bun publish 不支持 --provenance。 请使用 npm publish。
  • bun publish 使用 NPM_CONFIG_TOKEN,而不是 NODE_AUTH_TOKEN。
  • 切勿在已发布的包中使用 #!/usr/bin/env bun。 用户并没有安装 Bun。
  • Bunup banner 会为所有输出文件添加 shebang,包括库入口。如果这有问题,请使用构建后脚本,仅为 dist/cli.js 添加 shebang。

评论

支持 Markdown