CLI 工具开发的常用包对比和介绍

04-CLI 工具开发

CLI 工具开发涉及命令行交互、终端美化、文件操作和模板生成等核心功能。

📑 目录


快速参考

工具选型速查表

工具类型 推荐工具 适用场景 备选方案
命令行解析 commander 功能丰富、API 友好 yargs(灵活)、meow(轻量)
交互式输入 inquirer 功能全面、生态丰富 prompts(轻量)、enquirer
终端美化 chalk 功能丰富、API 友好 picocolors(极轻量)、kleur
加载动画 ora 简单易用 -
进度条 cli-progress 文件上传/下载进度 -
文件操作 fs-extra Promise API、功能增强 -
文件匹配 glob 通配符匹配 -
模板引擎 handlebars 轻量、逻辑少 ejs、mustache

快速开始

bash 复制代码
# 1. 安装核心工具
pnpm add commander inquirer chalk ora fs-extra glob handlebars

# 2. 创建 CLI 入口文件
# src/cli.ts

# 3. 配置 package.json bin 字段

命令行交互

commander(推荐)

commander 是一款 Node.js 命令行解析工具,核心用途是解析命令行参数,让 CLI 工具的命令行交互更友好、专业。

优势

  • ✅ API 友好,链式调用
  • ✅ 功能丰富,支持子命令、选项、帮助信息
  • ✅ 生态完善,文档详细
  • ✅ 自动生成帮助信息

劣势

  • ❌ 体积较大(相比 meow)
安装
bash 复制代码
pnpm add commander
pnpm add @types/commander -D
基础用法
ts 复制代码
// src/cli.ts
import { program } from 'commander';

program
  .version('1.0.0', '-v, --version')
  .description('一个基于 commander + inquirer 的 CLI 工具示例');

// 定义无参数命令
program
  .command('init')
  .description('初始化项目')
  .action(() => {
    console.log('开始初始化项目...');
  });

// 定义带选项的命令
program
  .command('build')
  .description('打包项目')
  .option('-e, --env <env>', '打包环境', 'development')
  .option('-o, --outDir <dir>', '输出目录', 'dist')
  .action((options) => {
    console.log('开始打包...');
    console.log('打包环境:', options.env);
    console.log('输出目录:', options.outDir);
  });

program.parse(process.argv);
选项配置
选项格式 说明 示例
.option('-s, --single') 布尔型选项(无参数,存在即 true) your-cli --single{ single: true }
.option('-n, --name <name>') 必填参数选项 your-cli --name test{ name: 'test' }
.option('-a, --age [age]') 可选参数选项 your-cli --age 25{ age: 25 };不传则为 undefined
.option('--env <env>', '描述', 'dev') 带默认值的选项 不传 --env 时,默认 { env: 'dev' }
高级用法
ts 复制代码
// 子命令
program
  .command('create <name>')
  .description('创建新项目')
  .option('-t, --template <template>', '模板类型', 'default')
  .action((name, options) => {
    console.log(`创建项目 ${name},使用模板 ${options.template}`);
  });

// 必需选项
program.requiredOption('-c, --config <path>', '配置文件路径').parse();

// 自定义帮助信息
program.addHelpText('after', '\n示例:\n  $ my-cli init\n  $ my-cli build --env production');

yargs

yargs 是功能强大的命令行解析工具,支持位置参数、命令补全等高级功能。

优势

  • ✅ 功能强大,支持位置参数
  • ✅ 灵活的配置方式
  • ✅ 支持命令补全

劣势

  • ❌ API 相对复杂
  • ❌ 学习曲线较陡
安装
bash 复制代码
pnpm add yargs
pnpm add @types/yargs -D
基础用法
ts 复制代码
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';

const argv = yargs(hideBin(process.argv))
  .option('name', {
    alias: 'n',
    type: 'string',
    description: '项目名称',
    demandOption: true,
  })
  .option('template', {
    alias: 't',
    type: 'string',
    default: 'default',
    description: '模板类型',
  })
  .command('init <name>', '初始化项目', (yargs) => {
    return yargs.positional('name', {
      describe: '项目名称',
      type: 'string',
    });
  })
  .parseSync();

console.log(argv);

meow

meow 是轻量级的命令行解析工具,适合简单场景。

优势

  • ✅ 轻量级,体积小
  • ✅ 配置简单
  • ✅ 自动处理帮助信息

劣势

  • ❌ 功能相对简单
  • ❌ 不支持复杂命令结构
安装
bash 复制代码
pnpm add meow
基础用法
ts 复制代码
import meow from 'meow';

const cli = meow(
  `
  用法
    $ my-cli <input>

  选项
    --name, -n  项目名称
    --template, -t  模板类型

  示例
    $ my-cli init --name my-project
`,
  {
    importMeta: import.meta,
    flags: {
      name: {
        type: 'string',
        alias: 'n',
      },
      template: {
        type: 'string',
        alias: 't',
        default: 'default',
      },
    },
  },
);

console.log(cli.input[0]); // 命令参数
console.log(cli.flags); // 选项

命令行解析工具对比

工具 体积 配置复杂度 功能丰富度 适用场景
commander 较大 ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 功能丰富的 CLI 工具
yargs 较大 ⭐⭐ ⭐⭐⭐⭐⭐ 需要位置参数的场景
meow 轻量级 ⭐⭐⭐⭐⭐ ⭐⭐⭐ 简单 CLI 工具

选型建议

  • 功能丰富的 CLI 工具:commander(推荐)
  • 需要位置参数:yargs
  • 简单工具:meow

交互式输入

inquirer(推荐)

当命令行参数无法满足需求(如让用户选择框架、输入密码、确认操作)时,inquirer 提供「交互式输入」。

优势

  • ✅ 功能全面,支持多种交互类型
  • ✅ 生态丰富,插件多
  • ✅ 文档完善

劣势

  • ❌ 体积较大
  • ❌ 配置相对复杂
安装
bash 复制代码
pnpm add inquirer
pnpm add @types/inquirer -D
基础用法
ts 复制代码
import { program } from 'commander';
import inquirer from 'inquirer';
import chalk from 'chalk';

program
  .command('init')
  .description('初始化项目')
  .action(async () => {
    console.log(chalk.blue('📦 开始初始化项目...'));

    const answers = await inquirer.prompt([
      {
        type: 'input',
        name: 'projectName',
        message: '请输入项目名称:',
        default: 'my-project',
        validate: (value) => {
          if (!value.trim()) return '项目名称不能为空!';
          return true;
        },
      },
      {
        type: 'list',
        name: 'framework',
        message: '请选择项目框架:',
        choices: [
          { name: 'React + TypeScript', value: 'react-ts' },
          { name: 'Vue + TypeScript', value: 'vue-ts' },
          { name: 'Vanilla JS', value: 'vanilla' },
        ],
        default: 'react-ts',
      },
      {
        type: 'checkbox',
        name: 'modules',
        message: '请选择需要的功能模块:',
        choices: ['路由', '状态管理', 'UI 组件库', 'ESLint/Prettier'],
        default: ['路由', 'ESLint/Prettier'],
      },
      {
        type: 'confirm',
        name: 'initGit',
        message: '是否初始化 Git 仓库?',
        default: true,
      },
    ]);

    console.log(chalk.green('\n✅ 项目配置如下:'));
    console.log('项目名称:', answers.projectName);
    console.log('框架:', answers.framework);
    console.log('功能模块:', answers.modules.join(', '));
    console.log('初始化 Git:', answers.initGit ? '是' : '否');
  });

program.parse(process.argv);
核心交互类型
类型 用途 关键配置
input 普通文本输入(如项目名称、邮箱) message、default、validate
password 密码输入(输入内容隐藏) 同 input,自动隐藏输入
list 单选(如框架选择、环境选择) choices(选项数组)、default
checkbox 多选(如功能模块、依赖选择) choices、default(默认选中项)
confirm 二选一确认(是 / 否) message、default(true/false)
rawlist 带编号的单选(按数字选择) 同 list,选项前显示编号
autocomplete 带自动补全的输入(如文件路径) 需配合 inquirer-autocomplete-prompt 插件

prompts

prompts 是轻量级的交互式输入工具,API 简洁。

优势

  • ✅ 轻量级,体积小
  • ✅ API 简洁
  • ✅ 支持取消操作(Ctrl+C)

劣势

  • ❌ 功能相对简单
  • ❌ 生态较小
安装
bash 复制代码
pnpm add prompts
基础用法
ts 复制代码
import prompts from 'prompts';

const response = await prompts([
  {
    type: 'text',
    name: 'projectName',
    message: '项目名称',
    initial: 'my-project',
    validate: (value) => (value.trim() ? true : '项目名称不能为空'),
  },
  {
    type: 'select',
    name: 'framework',
    message: '选择框架',
    choices: [
      { title: 'React', value: 'react' },
      { title: 'Vue', value: 'vue' },
    ],
  },
]);

console.log(response);

enquirer

enquirer 是现代化的交互式输入工具,支持自定义提示符。

优势

  • ✅ 现代化设计
  • ✅ 支持自定义提示符
  • ✅ API 灵活

劣势

  • ❌ 文档相对较少
  • ❌ 生态较小
安装
bash 复制代码
pnpm add enquirer
基础用法
ts 复制代码
import { prompt } from 'enquirer';

const response = await prompt({
  type: 'input',
  name: 'projectName',
  message: '项目名称',
  initial: 'my-project',
});

console.log(response);

交互式输入工具对比

工具 体积 配置复杂度 功能丰富度 生态 适用场景
inquirer 较大 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐ 功能全面的 CLI 工具
prompts 轻量级 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ 简单交互场景
enquirer 中等 ⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐ 需要自定义提示符

选型建议

  • 功能全面的 CLI 工具:inquirer(推荐)
  • 简单交互场景:prompts
  • 需要自定义提示符:enquirer

终端美化

chalk(推荐)

chalk 是 Node.js 终端日志彩色打印工具,核心用途是给终端输出的文字添加颜色、背景色、加粗 / 下划线等样式。

优势

  • ✅ 功能丰富,API 友好
  • ✅ 支持链式调用
  • ✅ 生态完善

劣势

  • ❌ 体积较大(相比 picocolors)
安装
bash 复制代码
pnpm add chalk
封装日志函数
ts 复制代码
// src/utils/logger.ts
import chalk from 'chalk';

export enum LogType {
  SUCCESS = 'success',
  ERROR = 'error',
  WARN = 'warn',
  INFO = 'info',
}

export const log = (message: string, type: LogType = LogType.INFO) => {
  const prefixMap = {
    [LogType.SUCCESS]: chalk.green('✅'),
    [LogType.ERROR]: chalk.bold.red('❌'),
    [LogType.WARN]: chalk.yellow('⚠️'),
    [LogType.INFO]: chalk.blue('ℹ️'),
  };

  const colorMap = {
    [LogType.SUCCESS]: chalk.green,
    [LogType.ERROR]: chalk.red,
    [LogType.WARN]: chalk.yellow,
    [LogType.INFO]: chalk.blue,
  };

  const prefix = prefixMap[type];
  const color = colorMap[type];
  console.log(`${prefix} ${color(message)}`);
};

export const logSuccess = (message: string) => log(message, LogType.SUCCESS);
export const logError = (message: string) => log(message, LogType.ERROR);
export const logWarn = (message: string) => log(message, LogType.WARN);
export const logInfo = (message: string) => log(message, LogType.INFO);
使用
ts 复制代码
import { logSuccess, logError, logWarn, logInfo } from './utils/logger';

logInfo('正在初始化项目...');
logWarn('当前 Node 版本低于推荐版本');
logSuccess('项目初始化完成!');
logError('配置文件缺失,请检查 config.json');

picocolors

picocolors 是极轻量的终端颜色库,体积仅 0.5KB。

优势

  • ✅ 极轻量(0.5KB)
  • ✅ API 简洁
  • ✅ 性能好

劣势

  • ❌ 功能相对简单
  • ❌ 不支持链式调用
安装
bash 复制代码
pnpm add picocolors
基础用法
ts 复制代码
import pc from 'picocolors';

console.log(pc.green('成功'));
console.log(pc.red('错误'));
console.log(pc.bold(pc.blue('加粗蓝色')));

kleur

kleur 是轻量级的终端颜色库,API 类似 chalk。

优势

  • ✅ 轻量级(体积小)
  • ✅ API 类似 chalk,迁移成本低
  • ✅ 支持链式调用

劣势

  • ❌ 功能相对简单
安装
bash 复制代码
pnpm add kleur
基础用法
ts 复制代码
import kleur from 'kleur';

console.log(kleur.green('成功'));
console.log(kleur.red('错误'));
console.log(kleur.bold().blue('加粗蓝色'));

ora(展示加载动画)

ora 是一款 Node.js 终端加载动画工具,核心用途是在耗时操作时显示「加载中」动画 + 提示文字。

安装
bash 复制代码
pnpm add ora
封装加载动画函数
ts 复制代码
// src/utils/loader.ts
import ora from 'ora';
import chalk from 'chalk';

export const withLoader = async <T>(message: string, asyncFn: () => Promise<T>): Promise<T> => {
  const spinner = ora(chalk.bold.blue(message)).start();
  try {
    const result = await asyncFn();
    spinner.succeed(chalk.green('✅ 操作完成!'));
    return result;
  } catch (error) {
    spinner.fail(chalk.bold.red(`❌ 操作失败:${(error as Error).message}`));
    throw error;
  }
};
使用示例
ts 复制代码
import { withLoader } from './utils/loader';

await withLoader('正在请求接口数据...', async () => {
  await new Promise((resolve) => setTimeout(resolve, 1500));
});
高级用法
ts 复制代码
import ora from 'ora';

const spinner = ora('加载中...').start();

// 更新文本
spinner.text = '处理中...';

// 成功
spinner.succeed('完成!');

// 失败
spinner.fail('失败!');

// 警告
spinner.warn('警告!');

// 信息
spinner.info('信息');

cli-progress(进度条)

cli-progress 用于显示文件上传/下载、批量处理等操作的进度。

安装
bash 复制代码
pnpm add cli-progress
基础用法
ts 复制代码
import cliProgress from 'cli-progress';

const bar = new cliProgress.SingleBar({
  format: '进度 |{bar}| {percentage}% | {value}/{total}',
  barCompleteChar: '\u2588',
  barIncompleteChar: '\u2591',
  hideCursor: true,
});

bar.start(100, 0);

// 模拟进度
for (let i = 0; i <= 100; i++) {
  await new Promise((resolve) => setTimeout(resolve, 50));
  bar.update(i);
}

bar.stop();

boxen(边框框)

boxen 用于在终端中创建带边框的文本框,适合显示重要信息。

安装
bash 复制代码
pnpm add boxen
基础用法
ts 复制代码
import boxen from 'boxen';
import chalk from 'chalk';

const message = boxen(chalk.green('✅ 项目初始化完成!'), {
  padding: 1,
  margin: 1,
  borderStyle: 'round',
  borderColor: 'green',
});

console.log(message);

cli-table3(表格展示)

cli-table3 用于在终端中展示表格数据,适合显示配置信息、对比数据等。

安装
bash 复制代码
pnpm add cli-table3
pnpm add @types/cli-table3 -D
基础用法
ts 复制代码
import Table from 'cli-table3';

const table = new Table({
  head: ['工具', '用途', '推荐度'],
  colWidths: [20, 30, 10],
});

table.push(
  ['commander', '命令行解析', '⭐⭐⭐⭐⭐'],
  ['inquirer', '交互式输入', '⭐⭐⭐⭐⭐'],
  ['chalk', '终端美化', '⭐⭐⭐⭐⭐'],
);

console.log(table.toString());

终端美化工具对比

工具 体积 功能 适用场景
chalk 较大 颜色、样式 功能丰富的 CLI 工具
picocolors 极轻量 基础颜色 对体积敏感的项目
kleur 轻量级 颜色、样式 轻量级 CLI 工具
ora 中等 加载动画 耗时操作提示
cli-progress 中等 进度条 文件处理进度
boxen 轻量级 边框框 重要信息展示
cli-table3 中等 表格 数据展示

选型建议

  • 功能丰富的 CLI 工具:chalk(推荐)
  • 对体积敏感:picocolors
  • 需要进度条:cli-progress
  • 需要表格展示:cli-table3

文件操作

fs-extra(操作文件系统)

fs-extra 在 Node.js 原生 fs 模块基础上做了增强,核心优势:

  • 完全兼容原生 fs 模块(可直接替换 fs 使用)
  • 所有 API 支持 Promise(无需手动封装 util.promisify)
  • 新增高频实用功能(递归创建目录、递归删除目录、复制文件 / 目录等)
安装
bash 复制代码
pnpm add fs-extra
pnpm add @types/fs-extra -D
核心用法
ts 复制代码
import fs from 'fs-extra';
import path from 'path';

// 递归创建目录
await fs.ensureDir(path.resolve(__dirname, 'a/b/c'));

// 递归删除目录
await fs.remove(path.resolve(__dirname, 'a'));

// 复制文件/目录
await fs.copy(src, dest);

// 写入 JSON 文件
await fs.writeJson(jsonPath, { name: 'test', version: '1.0.0' }, { spaces: 2 });

// 读取 JSON 文件
const config = await fs.readJson(jsonPath);

// 判断文件/目录是否存在
const exists = await fs.pathExists(path);
常用 API
API 名称 核心用途 优势对比(vs 原生 fs)
fs.ensureDir(path) 递归创建目录(不存在则创建,存在则忽略) 原生需手动递归,fs-extra 一键实现
fs.remove(path) 递归删除文件 / 目录(支持任意层级) 原生需先遍历目录,fs-extra 一键删除
fs.copy(src, dest) 复制文件 / 目录(自动创建目标目录) 原生需区分文件 / 目录,fs-extra 自动适配
fs.writeJson(path, data) 写入 JSON 文件(自动 stringify) 原生需手动 JSON.stringify,fs-extra 简化步骤
fs.readJson(path) 读取 JSON 文件(自动 parse) 原生需手动 JSON.parse,fs-extra 简化步骤
fs.pathExists(path) 判断文件 / 目录是否存在(返回 boolean) 原生需用 fs.access 捕获错误,fs-extra 直接返回

glob(匹配文件)

glob 解决「按规则批量查找文件」的需求,支持用通配符(如 ***?)匹配文件路径。

安装
bash 复制代码
pnpm add glob
pnpm add @types/glob -D
核心用法
ts 复制代码
import glob from 'glob';
import path from 'path';

// 同步匹配
const files = glob.sync('src/**/*.ts', {
  cwd: process.cwd(),
  ignore: ['src/test/**/*'],
});

// 异步匹配(推荐)
const files = await glob.promise('src/**/*.{ts,js}', {
  cwd: process.cwd(),
  dot: true,
});

// 流式匹配(适合大量文件)
const stream = glob.stream('src/**/*.ts');
stream.on('data', (filePath) => {
  console.log('匹配到文件:', filePath);
});
常见通配符规则
通配符 含义 示例 匹配结果
* 匹配当前目录下的任意字符(不含子目录) src/*.ts src/index.tssrc/utils.ts
** 匹配任意层级的目录(递归) src/**/*.ts src/index.tssrc/a/b/utils.ts
? 匹配单个字符 src/file?.ts src/file1.tssrc/file2.ts
[] 匹配括号内的任意一个字符 src/[ab].ts src/a.tssrc/b.ts
! 排除匹配的文件 src/**/*.ts + !src/test.ts 所有 .ts 文件,排除 src/test.ts

模板生成

handlebars(生成模板文件)

handlebars 是一款逻辑少、轻量型的模板引擎,核心用途是「将数据与模板结合,动态生成文本内容」。

安装
bash 复制代码
pnpm add handlebars
pnpm add @types/handlebars -D
基础用法
ts 复制代码
import handlebars from 'handlebars';

// 1. 定义模板
const template = `{
  "name": "{{ projectName }}",
  "version": "{{ version }}",
  "description": "{{ description }}"
}`;

// 2. 编译模板
const compiledTemplate = handlebars.compile(template);

// 3. 传入数据渲染
const data = {
  projectName: 'my-ts-pkg',
  version: '1.0.0',
  description: '基于 TS + 双模式的 npm 包',
};

const result = compiledTemplate(data);
console.log(result);
核心语法
  • 变量占位符{{ 变量名 }}(支持嵌套对象)
  • 条件判断{{#if 条件}}...{{else}}...{{/if}}
  • 循环遍历{{#each 数组}}...{{/each}}
  • 注释{{! 注释内容 }}
高级用法
ts 复制代码
// 注册辅助函数
handlebars.registerHelper('uppercase', (str) => {
  return str.toUpperCase();
});

// 使用辅助函数
const template = `{{ uppercase name }}`;

完整示例

结合所有工具,实现一个完整的 CLI 工具:

ts 复制代码
import { program } from 'commander';
import inquirer from 'inquirer';
import chalk from 'chalk';
import ora from 'ora';
import fs from 'fs-extra';
import glob from 'glob';
import handlebars from 'handlebars';
import path from 'path';
import boxen from 'boxen';

program.version('1.0.0', '-v, --version').description('CLI 工具示例');

program
  .command('init [projectName]')
  .description('初始化项目')
  .option('-t, --template <template>', '模板类型', 'default')
  .option('-y, --yes', '跳过交互式询问', false)
  .action(async (projectName, options) => {
    console.log(chalk.blue('📦 开始初始化项目...'));

    let answers: any = {};

    // 如果提供了项目名称且使用了 --yes,跳过交互
    if (projectName && options.yes) {
      answers = {
        projectName,
        framework: 'react-ts',
        modules: ['路由', 'ESLint/Prettier'],
        initGit: true,
      };
    } else {
      // 交互式询问
      answers = await inquirer.prompt([
        {
          type: 'input',
          name: 'projectName',
          message: '项目名称',
          default: projectName || 'my-project',
          validate: (value) => {
            if (!value.trim()) return '项目名称不能为空!';
            if (!/^[a-z0-9-]+$/.test(value)) {
              return '项目名称只能包含小写字母、数字和连字符!';
            }
            return true;
          },
        },
        {
          type: 'list',
          name: 'framework',
          message: '选择框架',
          choices: [
            { name: 'React + TypeScript', value: 'react-ts' },
            { name: 'Vue + TypeScript', value: 'vue-ts' },
            { name: 'Vanilla JS', value: 'vanilla' },
          ],
          default: 'react-ts',
        },
        {
          type: 'checkbox',
          name: 'modules',
          message: '选择功能模块',
          choices: ['路由', '状态管理', 'UI 组件库', 'ESLint/Prettier'],
          default: ['路由', 'ESLint/Prettier'],
        },
        {
          type: 'confirm',
          name: 'initGit',
          message: '是否初始化 Git 仓库?',
          default: true,
        },
      ]);
    }

    const spinner = ora(chalk.blue('正在生成项目文件...')).start();

    try {
      // 1. 检查目录是否存在
      const targetDir = path.resolve(process.cwd(), answers.projectName);
      if (await fs.pathExists(targetDir)) {
        spinner.fail(chalk.red(`目录 ${answers.projectName} 已存在!`));
        process.exit(1);
      }

      // 2. 创建项目目录
      await fs.ensureDir(targetDir);

      // 3. 读取模板文件
      const templateDir = path.resolve(__dirname, '../templates', options.template);
      const templateFiles = await glob.promise('**/*.hbs', {
        cwd: templateDir,
        dot: true,
      });

      // 4. 渲染模板并写入文件
      for (const templateFile of templateFiles) {
        const templatePath = path.resolve(templateDir, templateFile);
        const templateContent = await fs.readFile(templatePath, 'utf8');

        const compiled = handlebars.compile(templateContent);
        const renderedContent = compiled(answers);

        const targetFile = templateFile.replace(/\.hbs$/, '');
        const targetPath = path.resolve(targetDir, targetFile);

        await fs.ensureDir(path.dirname(targetPath));
        await fs.writeFile(targetPath, renderedContent, 'utf8');
      }

      // 5. 初始化 Git(如果选择)
      if (answers.initGit) {
        spinner.text = '正在初始化 Git 仓库...';
        // 这里可以调用 git 命令
      }

      spinner.succeed(chalk.green('项目初始化完成!'));

      // 6. 显示成功信息
      const successMessage = boxen(
        chalk.green(`✅ 项目 ${answers.projectName} 创建成功!\n\n`) +
          chalk.cyan(`cd ${answers.projectName}\n`) +
          chalk.cyan('npm install\n') +
          chalk.cyan('npm run dev'),
        {
          padding: 1,
          margin: 1,
          borderStyle: 'round',
          borderColor: 'green',
        },
      );

      console.log(successMessage);
    } catch (error) {
      spinner.fail(chalk.red(`初始化失败:${(error as Error).message}`));
      process.exit(1);
    }
  });

program
  .command('build')
  .description('构建项目')
  .option('-e, --env <env>', '构建环境', 'production')
  .option('-o, --outDir <dir>', '输出目录', 'dist')
  .action(async (options) => {
    const spinner = ora(chalk.blue('正在构建项目...')).start();

    try {
      // 模拟构建过程
      await new Promise((resolve) => setTimeout(resolve, 2000));

      spinner.succeed(chalk.green(`构建完成!输出目录:${options.outDir}`));
    } catch (error) {
      spinner.fail(chalk.red(`构建失败:${(error as Error).message}`));
      process.exit(1);
    }
  });

program.parse(process.argv);

最佳实践

  1. 错误处理:使用 try-catch 捕获错误,提供友好的错误提示
  2. 用户体验:使用 ora 显示加载状态,使用 chalk 美化输出
  3. 参数验证:在 inquirer 中使用 validate 验证用户输入
  4. 文件操作:使用 fs-extra 的 Promise API,避免回调地狱
  5. 模板管理:将模板文件放在独立目录,使用 glob 批量处理
  6. 命令结构:使用 commander 组织命令,保持清晰的命令层次
  7. 帮助信息:为每个命令添加清晰的描述和示例

常见问题

命令行解析相关问题

Q: commander 和 yargs 如何选择?

A:

  • commander:API 友好,适合大多数场景(推荐)
  • yargs:需要位置参数或复杂参数解析时使用

Q: 如何获取未定义的选项?

A:

ts 复制代码
// commander
program.parse();
const unknownOptions = program.opts();

// yargs
const argv = yargs.parse();
const unknown = argv._; // 未定义的参数

交互式输入相关问题

Q: inquirer 和 prompts 如何选择?

A:

  • inquirer:功能全面,生态丰富(推荐)
  • prompts:轻量级,简单场景使用

Q: 如何中断交互式输入?

A:

ts 复制代码
// inquirer 会自动处理 Ctrl+C
// prompts 需要手动处理
const response = await prompts({
  type: 'text',
  name: 'value',
  message: '输入值',
  onCancel: () => {
    console.log('已取消');
    process.exit(0);
  },
});

终端美化相关问题

Q: chalk 和 picocolors 如何选择?

A:

  • chalk:功能丰富,适合大多数场景(推荐)
  • picocolors:对体积敏感的项目使用

Q: 如何检测终端是否支持颜色?

A:

ts 复制代码
import chalk from 'chalk';

// chalk 会自动检测,不支持时自动禁用颜色
// 手动检测
const supportsColor = chalk.supportsColor;

文件操作相关问题

Q: fs-extra 和原生 fs 的区别?

A:

  • fs-extra:Promise API、递归操作、JSON 操作更便捷
  • 原生 fs:需要手动封装 Promise、手动递归

Q: glob 如何排除多个文件?

A:

ts 复制代码
const files = await glob.promise('src/**/*.ts', {
  ignore: ['src/test/**/*', 'src/**/*.test.ts'],
});

模板生成相关问题

Q: handlebars 和其他模板引擎的区别?

A:

  • handlebars:逻辑少、轻量级(推荐)
  • ejs:支持 JavaScript 代码,功能强大但体积大
  • mustache:无逻辑模板,但功能较少

Q: 如何在模板中使用辅助函数?

A:

ts 复制代码
handlebars.registerHelper('eq', (a, b) => a === b);

// 模板中使用
// {{#if (eq value "test")}}...{{/if}}

参考资源

相关推荐
Chen不旧39 分钟前
关于用户权限的设计,前端和后端都需要考虑
前端·权限
DsirNg40 分钟前
前端和运维的哪些爱
前端
7***318840 分钟前
Go-Gin Web 框架完整教程
前端·golang·gin
FinClip42 分钟前
FinClip助力银行整合多个App,打造一站式超级应用
前端
火柴就是我1 小时前
从头写一个自己的app
android·前端·flutter
FinClip1 小时前
小程序如何一键生成鸿蒙APP?FinClip助力企业快速布局Harmony OS生态
前端
月下点灯1 小时前
🔄记住这张图,脑子跟着浏览器的事件循环(Event Loop)转起来了
前端·javascript·浏览器
邹小邹-AI1 小时前
Rust + 前端:下一个十年的“王炸组合”
开发语言·前端·rust
行走在顶尖1 小时前
vue3+ant-design-vue
前端