🛠 用 Node.js 和 commander 快速搭建一个 CLI 工具骨架(gix 实战)

🛠 用 Node.js 和 commander 快速搭建一个 CLI 工具骨架(gix 实战)

在上一篇中,我们聊了聊为什么我要开发一个 Git CLI 工具 ------ gix,解决我日常开发中繁琐重复的 Git 操作。

这篇文章,我们正式开工,从 0 到 1 搭建一个 CLI 工具的基础骨架,包含命令解析、参数传递、交互式输入等核心功能。


🧰 技术选型

工具 用途
Node.js 编写 CLI 工具的基础平台
TypeScript 类型安全、增强开发体验
commander 命令行参数解析
prompts 交互式输入(比 inquirer 更轻量)
chalk 控制终端输出样式
execa 执行 Git 命令

📁 项目初始化

我们用 pnpm 创建项目:

csharp 复制代码
pnpm init

安装依赖:

sql 复制代码
pnpm add commander prompts chalk execa
pnpm add -D typescript tsx @types/node

创建 tsconfig.json:

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "outDir": "dist",
    "strict": true
  },
  "include": ["src"]
}

🧱 文件结构

bash 复制代码
/packages/cli
  ├── src
  │   ├── index.ts         # 入口文件
  │   ├── commands
  │   │   ├── squash.ts    # squash 命令逻辑
  │   │   └── merge.ts     # merge 命令逻辑
  │   └── utils
  │       └── git.ts       # Git 命令封装
  ├── package.json
  └── tsconfig.json

🚪 Step 1:配置命令行入口

在 src/index.ts 中配置 CLI 主入口:

javascript 复制代码
#!/usr/bin/env node

import { Command } from 'commander'
import { runSquash } from './commands/squash'
import { runMerge } from './commands/merge'

const program = new Command()

program.name('gix').description('Git 扩展工具集').version('0.0.1')

program
  .command('squash')
  .description('合并最近的多次提交')
  .option('-n, --count <number>', '合并次数', '2')
  .option('--all', '合并所有提交')
  .action(runSquash)

program
  .command('merge')
  .description('交互式合并两个 commit')
  .option('-f, --from <hash>', '起始 commit')
  .option('-t, --to <hash>', '结束 commit')
  .option('-m, --message <msg>', '合并后的 commit 信息')
  .action(runMerge)

program.parse()

在 package.json 中加入:

json 复制代码
"bin": {
  "gix": "dist/index.js"
}

然后加上执行权限:

bash 复制代码
chmod +x dist/index.js

✨ Step 2:实现 squash 命令逻辑

src/commands/squash.ts

typescript 复制代码
import prompts from 'prompts'
import { execGit } from '../utils/git'
import chalk from 'chalk'

export async function runSquash(opts: { count: string; all?: boolean }) {
  const count = opts.all ? 'HEAD~$(git rev-list --count HEAD)' : `HEAD~${opts.count}`

  const { message } = await prompts({
    type: 'text',
    name: 'message',
    message: '请输入新的 commit 信息:'
  })

  if (!message) {
    console.log(chalk.red('❌ commit 信息不能为空'))
    return
  }

  await execGit(`reset --soft ${count}`)
  await execGit(`commit -m "${message}" --no-verify`)

  console.log(chalk.green('🎉 squash 成功完成!'))
}

🔧 Step 3:封装 Git 命令工具函数

src/utils/git.ts

javascript 复制代码
import { execa } from 'execa'

export async function execGit(cmd: string) {
  const [cmdName, ...args] = cmd.split(' ')
  await execa(cmdName, args, { stdio: 'inherit' })
}

✅ 试运行

bash 复制代码
pnpm build
pnpm link
gix squash -n 2

输出:

sql 复制代码
? 请输入新的 commit 信息:feat: 优化用户界面逻辑
🎉 squash 成功完成!

💡 小结

至此,我们完成了:

  • 初始化 CLI 工具项目结构

  • 配置 commander 命令解析

  • 支持交互式输入(prompts)

  • 执行 Git 命令(execa)

gix 的核心命令正在一步步搭建中 🚀


🔮 下一篇预告

下一篇我会分享如何构建更复杂的交互命令(如 gix merge),支持选择 commit、填写信息、自动推送,并通过 prompts 提升使用体验。

欢迎点赞 + 收藏 + 评论支持我继续更系列文章!

相关推荐
wflynn6 分钟前
GitHub 日榜趋势速报 | 2026-10-30
开源·github
jktantan19 分钟前
(二)Vue 3 的 attrs 不是响应式:el-form 扩展属性同步的三种方案
前端·javascript·vue.js·element-plus
前端 贾公子39 分钟前
第 5 章:Codex 从可运行程序到可交付工具
java·前端·数据库
miofly1 小时前
Anthropic为Claude四款模型加水印,两款旧模型留调参漏洞
开源·github
用户360055579001 小时前
Kestrel_VLLM - 26 · 发射空档三步曲:事件计时 → nsys → 判别实验
前端
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(116):Hybrid Memory——用互补策展与离线巩固构建长期记忆
论文阅读·人工智能·学习·开源·github
Thneonl1 小时前
Claude Code 频繁 Compacting?发布次日我根治了 2 个痛点
后端·rust
echome8881 小时前
JavaScript async/await 错误处理:别让 Promise 静默失败
开发语言·前端·javascript·promise
PedroQue991 小时前
v0.2.0 架构升级:class + reactive 重塑状态管理
前端·uni-app
鶴哥只手遮天1 小时前
Angular 22 源码级深度解析-第 02 章 编译链路总览:从模板字符串到 `ɵɵ` 指令
前端·开源·angular.js