Node 开发 CLI 工具简易指南

25 年 8 月 19 日 星期二 (已编辑)
1170 字
6 分钟

Node 开发 CLI 工具简易指南

开发 CLI 工具其实没想象中那么复杂。作为一个经常需要重复执行各种任务的开发者,我发现自己写小工具会比找现成工具更顺手。今天就来聊聊怎么快速上手开发一个实用的命令行工具。

为什么要开发 CLI 工具?

日常开发中,我们经常遇到这些场景:

  • 每次部署都要执行一堆命令
  • 代码检查、格式化总是忘记某些步骤
  • 团队协作时,大家的操作流程不统一
  • 重复性工作太多,但又不值得搞个复杂的自动化系统

这时候,一个简单的 CLI 工具就能解决大部分问题。

搭建基础框架

项目准备

先创建项目结构:

shell
mkdir my-cli-tool
cd my-cli-tool
npm init -y

安装核心依赖:

shell
npm install yargs
npm install -D typescript @types/node

为什么选择 yargs?它能帮我们处理命令解析、参数验证、帮助信息生成等琐事,让我们专注于业务逻辑。

配置文件

package.json 里最重要的是 bin 字段,它告诉系统这个包提供了哪些可执行命令:

json
{
  "bin": {
    "mytool": "./dist/index.js"
  },
  "type": "module"
}

核心实现思路

入口文件设计

主入口文件其实就是个"指挥官",负责接收命令并分发给对应的处理模块:

typescript
#!/usr/bin/env node
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';

// 导入各种命令
import deployCommand from './commands/deploy.js';
import checkCommand from './commands/check.js';

yargs(hideBin(process.argv))
  .command(deployCommand)
  .command(checkCommand)
  .help()
  .argv;

关键在于那行 shebang #!/usr/bin/env node,它让系统知道用 Node.js 来执行这个文件。

命令模块化

每个功能做成独立的命令模块,结构清晰,维护方便:

typescript
// commands/deploy.js
export default {
  command: 'deploy [env]',
  describe: '部署到指定环境',
  builder: (yargs) => {
    return yargs
      .positional('env', {
        describe: '目标环境',
        choices: ['dev', 'test', 'prod'],
        default: 'dev'
      })
      .option('force', {
        type: 'boolean',
        describe: '强制部署'
      });
  },
  handler: async (argv) => {
    console.log(`开始部署到 ${argv.env}...`);
    // 实际部署逻辑
  }
};

实用技巧分享

让用户体验更友好

添加一些贴心的细节:

typescript
// 彩色输出
const colors = {
  green: '\x1b[32m',
  red: '\x1b[31m',
  yellow: '\x1b[33m',
  reset: '\x1b[0m'
};

const log = {
  success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
  error: (msg) => console.error(`${colors.red}✗${colors.reset} ${msg}`),
  warn: (msg) => console.warn(`${colors.yellow}!${colors.reset} ${msg}`)
};

配置文件支持

大多数工具都需要配置,可以这样处理:

typescript
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';

function loadConfig() {
  const configPath = join(process.cwd(), '.mytool.json');
  
  if (existsSync(configPath)) {
    return JSON.parse(readFileSync(configPath, 'utf8'));
  }
  
  return {}; // 默认配置
}

进度反馈

对于耗时操作,给用户一些反馈:

typescript
// 简单的加载动画
let dots = 0;
const spinner = setInterval(() => {
  process.stdout.write(`\r处理中${'.'.repeat(dots % 4)}`);
  dots++;
}, 200);

// 完成后清理
clearInterval(spinner);
console.log('\n完成!');

常见需求的解决方案

交互式输入

有时需要用户输入一些信息:

shell
npm install inquirer
typescript
import inquirer from 'inquirer';

const answers = await inquirer.prompt([
  {
    type: 'confirm',
    name: 'continue',
    message: '确定要执行这个操作吗?'
  }
]);

if (!answers.continue) {
  console.log('操作已取消');
  process.exit(0);
}

文件操作

CLI 工具经常需要读写文件:

typescript
import { promises as fs } from 'fs';

// 检查文件是否存在
async function fileExists(path) {
  try {
    await fs.access(path);
    return true;
  } catch {
    return false;
  }
}

// 安全地写入文件
async function writeFileIfNotExists(path, content) {
  if (await fileExists(path)) {
    const answer = await inquirer.prompt({
      type: 'confirm',
      name: 'overwrite',
      message: `${path} 已存在,是否覆盖?`
    });
    
    if (!answer.overwrite) return;
  }
  
  await fs.writeFile(path, content);
}

调试和发布

本地测试

开发期间,用 npm link 来本地测试:

shell
npm run build
npm link
# 现在可以直接使用你的命令了
mytool deploy --env test

错误处理

别忘了处理异常情况:

typescript
process.on('uncaughtException', (error) => {
  console.error('程序出现错误:', error.message);
  process.exit(1);
});

// 在异步操作中
try {
  await riskyOperation();
} catch (error) {
  log.error(`操作失败:${error.message}`);
  process.exit(1);
}

一些建议

保持简单:CLI 工具最大的优势就是简单直接。如果发现逻辑越来越复杂,可能需要重新考虑设计。

文档很重要:写个简单的 README,说明常用命令和参数。用户(包括未来的自己)会感谢你的。

版本管理:每次更新都更新版本号,方便追踪问题。

测试一下:至少在不同环境下手动测试一遍主要功能。

总结

开发 CLI 工具其实就是把日常重复的工作自动化。不需要追求完美,能解决实际问题就是好工具。

从简单开始,逐步完善。可能一开始只是几行脚本,慢慢就发展成了团队必备的生产力工具。

最重要的是,动手去做。第一个版本可能很粗糙,但至少迈出了第一步。后续的优化和功能添加都是水到渠成的事情。

文章标题:Node 开发 CLI 工具简易指南

文章作者:linaaaqi

文章链接:https://linaaaqi.com/posts/node/node-开发-cli-工具简易指南[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。