Node.js命令行工具开发

文章目录

Node.js命令行工具开发

  • 开发一个类似npm create vite@latestnpx xxx这样的Node.js命令行工具

实例效果展示

  • 模板提供了两个简单的列子
    • 1.创建一个命令行工具模板项目,也就是当前这个项目的源代码
    • 2.从远程拉取创建一个uniapp的模板项目
  • 先全局安装
bash 复制代码
npm i -g rxm-node-cli
# 安装完成后
# 安装完成后 执行命令行工具,先不指定模板,会通过命令行交互选择模板
rxm-node-cli create project-name
# 或者 
npx rxm-node-cli create project-name
# 指定模板
npx rxm-node-cli create project-name -t cli-template
npx rxm-node-cli create project-name -t uni-template

初始化项目

bash 复制代码
mkdir rxm-node-cli-tool
cd rxm-node-cli-tool
npm init -y

基本结构

text 复制代码
rxm-node-cli-tool/
├── bin/
│   └── cli.js        # 命令行入口文件
├── lib/
│   └── index.js      # 主要逻辑代码
├── package.json
└── README.md

配置package.json

  • bin下的rxm-node-cli就是命令行工具名称
json 复制代码
{
  "name": "rxm-node-cli",
  "version": "1.0.3",
  "bin": {
    "rxm-node-cli": "./bin/cli.js"
  },
  "scripts": {
    "dev": "node bin/cli.js create",
    "upgrade": "standard-version"
  },
  "standard-version": {
    "message": "docs: %s [skip ci]"
  },
  "dependencies": {
    "chalk": "^4.1.0",
    "commander": "^9.0.0",
    "download-git-repo": "^3.0.2",
    "fs-extra": "^11.3.2",
    "inquirer": "^8.0.0"
  },
  "devDependencies": {
    "standard-version": "^9.5.0"
  }
}

创建启动文件

  • 根目录下创建 bin/cli.js
js 复制代码
#!/usr/bin/env node

const { program } = require('commander');
const pkg = require('../package.json');
program
  .name('rxm-node-cli')
  .description('cli命令行开发模板');
program
  .version(pkg.version)
  .command('create <project-name>')
  .description('创建一个新项目')
  .option('-t, --template <name>', '指定模板') // 子命令可以配置多个,多次调用option方法即可。得到的option对象会合并
  .action((name,options) => {
    require('../lib/index')({name,template:options.template});
  });

program.parse(process.argv); // process.argv是启动命令行时传入的参数数组

创建入口文件及其它配置文件

1.lib/index.js 入口文件

js 复制代码
// lib/index.js 
const tool = require('./create-cli.js');
const uni = require('./create-uni')
const theme = require('./theme');
const inquirer = require('inquirer');

module.exports = async function ({ name: projectName, template }) {
  if (!projectName) return theme.error('请输入项目名称')
  if (!template) {
    const { action } = await inquirer.prompt([
      {
        name: 'action',
        type: 'list',
        message: '请选择模板类型:',
        choices: [
          { name: 'cli-template', value: 'cli-template' },
          { name: 'uni-template', value: 'uni-template' }
        ]
      }
    ]);
    template = action
  }
  if (template === 'cli-template') return tool(projectName)
  if (template === 'uni-template') return uni(projectName)
  theme.error('模板不存在')
};

2.lib/theme.js 封装打印带样式的文本到终端的函数

js 复制代码
// lib/theme.js
const chalk = require('chalk');

module.exports = {
  error: text=>{
    console.log(chalk.red.bold(text))
  },
  success: text=>{
    console.log(chalk.green(text))
  },
  successBold: text=>{
    console.log(chalk.bold.green(text))
  },
  warning: text=>{
    console.log(chalk.yellow.underline(text))
  }
};

3.lib/utlis.js 复用模块封装

js 复制代码
const inquirer = require('inquirer');
const fs = require('fs');
const pfs = require('fs-extra');
const theme = require('./theme');

module.exports = {
  /**
   * 创建前
   */
  async beforCreate(projectName) {
    const { action } = await inquirer.prompt([
      {
        name: 'action',
        type: 'list',
        message: '是否开始创建:',
        choices: [
          { name: '是', value: 'yes' },
          { name: '否', value: false }
        ]
      }
    ]);
    if (!action) return
    theme.success(`创建项目: ${projectName}`)
    // 检查目录是否已存在
    if (fs.existsSync(projectName)) {
      // 交互式询问用户是否覆盖
      const { action } = await inquirer.prompt([
        {
          name: 'action',
          type: 'list',
          message: '目录已存在,请选择:',
          choices: [
            { name: '覆盖', value: 'overwrite' },
            { name: '取消', value: false }
          ]
        }
      ]);
      // action等于 choices选择的value
      if (!action) return;
      if (action === 'overwrite') {
        console.log(`\n删除 ${projectName}...`);
        // 删除目录
        fs.rmSync(projectName, { recursive: true, force: true });
      }
    }

    // 创建项目目录
    fs.mkdirSync(projectName);
    return true
  },
  /**
   * initJson
   */
  initJson(projectName) {
    const json = require('../package.json')
    json.name = projectName
    json.version = '1.0.0'
    console.log('创建package.json...')
    return pfs.outputFile(
      `${projectName}/package.json`,
      JSON.stringify(json, null, 2)
    );
  }
}

4.lib/create-cli.js cli创建逻辑

js 复制代码
const path = require('path');
const fs = require('fs');
const pfs = require('fs-extra');
const theme = require('./theme');
const utlis = require('./utlis');
const basePath = path.join(__dirname, '../')
// 拷贝目录
async function copyDirectorySync (src, projectName) {
  await pfs.ensureDir(projectName+'/'+src);
  const url = path.join(basePath, src)
  // 读取源目录内容
  const items = fs.readdirSync(url);
  for (const item of items) {
    pfs.outputFile(
      `${projectName}/${src}/${item}`,
      fs.readFileSync(path.join(url, item), 'utf-8')
    )
  }
}
module.exports = async function (projectName) {
  await utlis.beforCreate(projectName)
  await pfs.ensureDir(projectName);
  await utlis.initJson(projectName)
  await copyDirectorySync('lib', projectName)
  await copyDirectorySync('bin', projectName)
  // 拷贝 README.md
  pfs.outputFile(
    `${projectName}/README.md`,
    fs.readFileSync(path.join(basePath, 'README.md'), 'utf-8')
  )
  theme.success('\n项目创建成功')
  theme.successBold(`cd ${projectName}`)
  theme.successBold(`npm install`)
}

5.lib/create-uni.js uni项目模板创建

js 复制代码
// lib/create-uni.js 
const path = require('path');
const fs = require('fs');
const theme = require('./theme');
const utlis = require('./utlis');
const { promisify } = require('util');
const download = promisify(require('download-git-repo'));
const ora = require('ora');
const chalk = require('chalk');

module.exports = async function (projectName) {
  await utlis.beforCreate(projectName);
  const spinner = ora('正在创建项目...').start();

  try {
    spinner.text = '正在下载模板...';
    // 确保使用绝对路径
    const projectPath = path.resolve(process.cwd(), projectName);
    await download('direct:https://gitee.com/ren_jinming/uniapp-vue3-ts-uview-plus.git', projectPath, { clone: true });
    console.log('下载完成');

    // 修改package.json
    spinner.text = '正在修改package.json...';
    const packageJsonPath = path.join(projectPath, 'package.json');

    // 检查文件是否存在
    if (!fs.existsSync(packageJsonPath)) {
      throw new Error(`package.json not found at ${packageJsonPath}`);
    }

    // 读取并修改package.json
    const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
    packageJson.name = projectName;

    // 写入修改后的内容
    fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
    console.log('修改完成');

    spinner.succeed(chalk.green('项目创建成功!'));
    theme.successBold(`cd ${projectName}`);
    theme.successBold(`npm install`);
    console.log('使用Hbuild X打开项目')
  } catch (error) {
    console.error(error);
    spinner.warn('项目创建失败');
  }
}

命令行工具测试

  • 本地测试
bash 复制代码
# cli-test作为创建项目的名称
npm run dev cli-test
  • 将命令行工具链接到全局测试
bash 复制代码
# 安装
npm link
# 如果上面的命令报错可以换一个
npm link --force
# 查看支持哪些命令
rxm-node-cli
# 运行命令行 查询版本
rxm-node-cli -V
# 创建指定的模板
rxm-node-cli create text-project -t cli-template
rxm-node-cli create text-project -t uni-template
# 不指定模板,通过命令行选择
rxm-node-cli create text-project
# 或者用npx 的形式执行
npx rxm-node-cli create text-project

版本管理

  • 安装 standard-version 用于版本管理:
bash 复制代码
npm install -D standard-version
  • 更新 package.json 配置升级命令及提交msg
bash 复制代码
{
  "scripts": {
    "upgrade": "standard-version"
  },
  "standard-version":{
    "message": "docs: %s [skip ci]"
  },
}
  • 推荐自动升级方案 会根据 Git 提交记录自动升级版本号,也可以手动设置版本
bash 复制代码
pnpm upgrade
# 或者
npx standard-version
# 它会读取git的提交信息 并将相关的记录写入到 CHANGELOG.md

发布npm

bash 复制代码
# 执行
npm login
# 在终端输入账号
Username: xxxx (回车)
# 输入密码
Password: xxxx (这里输入密码终端是没有交互反馈的,所以只要确认输入完直接回车)
# 输入邮箱会发送验证码
Email: (this IS public): xxx 回车
# 输入邮箱验证码
Enter one-time password: xxxx 回车
# 登录成功后显示 Logged in as renjinming on https://registry.npmjs.org/.

# 模拟发布
npm pack --dry-run
# 执行后会打印出要上传的信息 如下:
Debugger attached.
npm notice 
npm notice 📦  rxm-node-cli@1.0.4
npm notice === Tarball Contents === 
npm notice 503B  CHANGELOG.md     
npm notice 596B  README.md        
npm notice 622B  bin/cli.js       
npm notice 1.1kB lib/create-cli.js
npm notice 1.6kB lib/create-uni.js
npm notice 788B  lib/index.js     
npm notice 312B  lib/theme.js     
npm notice 1.6kB lib/utlis.js     
npm notice 481B  package.json     
npm notice === Tarball Details === 
npm notice name:          rxm-node-cli
npm notice version:       1.0.4
npm notice filename:      rxm-node-cli-1.0.4.tgz
npm notice package size:  3.0 kB
npm notice unpacked size: 7.6 kB
npm notice shasum:        2efbb29c6d3731a2abb6333b386f39526b8d1e69
npm notice integrity:     sha512-BrixVR69X/dJd[...]Brt8xuFQWYwnQ==
npm notice total files:   9
npm notice 
rxm-node-cli-1.0.4.tgz
Waiting for the debugger to disconnect...
# 实际发布
npm publish
#如果版本号以存在,须要修改版本信息,不然提交会403 执行上面的升级命令在进行发布
➔ 若包名冲突或无权限,会报错。
  • 发布403错误

如果发布403可能是包名已经存在。可以用下面的命令试试看有没有,如果存在可以修改package.json的name重新定义包名

bash 复制代码
npm view rxm-node-cli   # 检查包名占用

发布完成测试

  • 发布成功验证
bash 复制代码
# 查询包在npm上的所有版本
npm view rxm-node-cli versions
  • 安装使用
bash 复制代码
npm i -g rxm-node-cli
# 安装完成后 执行命令行工具
rxm-node-cli create project-name
# 或者 
npx rxm-node-cli create project-name
# 指定模板
npx rxm-node-cli create project-name -t cli-template
npx rxm-node-cli create project-name -t uni-template

各包的用途

commander

  • commander 是 Node.js 最流行的命令行工具开发框架,用于快速构建 CLI(Command Line Interface)应用程序。它可以实现如下功能:
  1. 解析命令行参数(选项、子命令等)
  2. 自动生成帮助信息--help
  3. 提供用户友好的命令行交互体验
核心功能
1. 定义命令和选项
js 复制代码
// app.js
const { program } = require('commander');

program
  .name('rxm-node-cli')                     // 工具名称
  .description('一个示例CLI工具')       // 描述
  .version('1.0.0');                   // 版本号

program
  .command('create <project-name>')    // 定义子命令
  .description('创建新项目')           // 命令描述
  .option('-t, --template <name>', '指定模板') // 命令选项 // 子命令可以配置多个,多次调用option方法即可。得到的option对象会合并
  .action((name, options) => {         // 执行逻辑
    console.log(`创建项目 ${name},使用模板: ${options.template || '默认'}`);
  });

program.parse(); // 必须调用

// 运行 node app.js create my-project -t vue
// 输出 创建项目 my-project,使用模板: vue
1.自动生成的帮助信息
  • 运行时会自动添加 -h, --help 选项:
bash 复制代码
node app.js --help
  • 输出示例:
text 复制代码
Usage: rxm-node-cli [options] [command]

一个示例CLI工具

Options:
  -V, --version           输出版本号
  -h, --help              显示帮助信息

Commands:
  create <project-name>   创建新项目
  help [command]          显示命令帮助
核心概念
1. 选项(Options)
js 复制代码
program
  .option('-d, --debug', '开启调试模式')       // 布尔选项
  .option('-p, --port <number>', '端口号', 80) // 带默认值的选项
  .option('-c, --config <path>', '配置文件');  // 必填选项
2. 命令(Commands)
js 复制代码
program
  .command('add <file>')            // 必需参数
  .description('添加文件')
  .action((file) => {
    console.log(`添加文件: ${file}`);
  });
3. 子命令(Subcommands)
js 复制代码
program
  .command('server')
  .description('服务器操作')
  .command('start')                // 子命令
  .action(() => {
    console.log('启动服务器');
  });
实际应用场景
1. 创建脚手架工具(如create-react-app
js 复制代码
program
  .command('create <app-name>')
  .description('创建新应用')
  .action((name) => {
    // 下载模板、初始化项目等操作
  });
2. 构建开发服务器
js 复制代码
program
  .command('start')
  .option('-p, --port <number>', '端口号', 3000)
  .action((options) => {
    startDevServer(options.port);
  });

chalk

  • chalk 是 Node.js 中最流行的终端字符串样式库,专门用于在命令行工具中输出彩色文本和丰富的格式(如加粗、下划线等)。它能让你的 CLI 工具拥有更美观、更专业的输出效果。
核心功能
1. 改变文字颜色
js 复制代码
const chalk = require('chalk');

console.log(chalk.red('错误信息'));      // 红色文字
console.log(chalk.blue('提示信息'));     // 蓝色文字
console.log(chalk.green('成功信息'));    // 绿色文字
2. 改变背景色
js 复制代码
console.log(chalk.bgRed.white('白字红底')); 
console.log(chalk.bgGreen.black('黑字绿底'));
3. 添加文本样式
js 复制代码
console.log(chalk.bold('加粗文本'));
console.log(chalk.underline('下划线文本'));
console.log(chalk.dim('暗淡文本'));
4. 链式调用
js 复制代码
console.log(chalk.blue.bgRed.bold('蓝字红底加粗'));
5.256色和RGB支持
js 复制代码
console.log(chalk.rgb(255, 136, 0).bold('橙色加粗'))
console.log(chalk.hex('#FF8800')('十六进制颜色'))
实际应用场景
  • 可自定义进行封装,方便调用 ,如下
js 复制代码
function showError(message) {
  console.log(chalk.red.bold('✖ 错误:'), chalk.red(message));
}
// 输出:✖ 错误: 文件不存在
function showSuccess(message) {
  console.log(chalk.green.bold('✓ 成功:'), message);
}
function showWarning(message) {
  console.log(chalk.yellow.bold('⚠ 警告:'), message);
}
  • 创建彩色表格,虽然这种方式不太完美但是也勉强能看出是表格了
js 复制代码
// 空格
const  space = '    ';

console.log(
  chalk.blue('名称') + space + 
  chalk.green('状态') + space + 
  chalk.yellow('进度')
);
console.log(
  chalk.blue('张三') + space +
  chalk.green('已支付') + space +
  chalk.yellow('100%')
)
console.log(
  chalk.blue('李四') + space +
  chalk.green('未支付') + space +
  chalk.yellow('0%')
)
高级用法
1.自定义主题函数
js 复制代码
const theme = {
  error: text=>{
    console.log(chalk.red.bold(text))
  },
  success: text=>{
    console.log(chalk.green(text))
  },
  warning: text=>{
    console.log(chalk.yellow.underline(text))
  }
};
theme.error('错误信息');
theme.success('成功信息');
theme.warning('警告信息');
2. 组合使用模板字符串
js 复制代码
const name = 'Alice';
console.log(chalk`{red 警告:} 用户 {blue.bold ${name}} 不存在`);

ora

  • ora 是 Node.js 中一个专门用于**命令行加载动画(Spinner)**的库,它可以在执行异步操作时显示旋转的加载指示器,为用户提供清晰的执行状态反馈。这个模块特别适合需要等待的操作(如文件下载、安装依赖等)。
核心功能
1. 基本用法
js 复制代码
const ora = require('ora');

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

// 模拟异步操作
setTimeout(() => {
  spinner.succeed('加载完成');
}, 2000);
2. 状态变化效果
方法 效果示例 使用场景
.start() ⠋ 正在加载... 开始加载
.succeed(text) ✓ 成功 操作成功
.fail(text) ✖ 失败 操作失败
.warn(text) ⚠ 警告 出现警告
.info(text) ℹ 信息 显示信息
.stop() 清除动画 手动停止
3.丰富的预设动画:包含超过30种动画效果
js 复制代码
spinner.spinner = 'dots'; // 可选:dots, line, pulse等
4.颜色支持:与chalk完美配合
js 复制代码
spinner.color = 'yellow'; // 改变颜色
5.速度控制:调整动画刷新率
js 复制代码
spinner.interval = 100; // 毫秒(默认80)
6.多行文本支持
js 复制代码
spinner.text = '第一行\n第二行';
实际应用场景
1. 文件下载进度
js 复制代码
async function downloadFile() {
  const spinner = ora('下载文件中...').start();
  try {
    await download('https://example.com/file.zip');
    spinner.succeed('下载完成');
  } catch (err) {
    spinner.fail('下载失败');
    console.error(err);
  }
}
2. 安装依赖
js 复制代码
async function installDependencies() {
  const spinner = ora('正在安装依赖...').start();
  try {
    await execa('npm', ['install']);
    spinner.succeed('依赖安装完成');
  } catch {
    spinner.fail('依赖安装失败');
  }
}
高级用法
1. 自定义动画
js 复制代码
const ora = require('ora');

const spinner = ora('正在加载...').start();
spinner.spinner = {
  interval: 80,
  frames: ['-', '+', '-']
};
// 模拟异步操作
setTimeout(() => {
  spinner.succeed('加载完成');
  // spinner.fail('加载完成');
}, 3000);
2. 组合进度条
js 复制代码
const ora = require('ora');

const spinner = ora({
  text: '处理中...',
  spinner: {
    frames: ['🕛', '🕒', '🕕', '🕘'],
    interval: 300
  }
}).start();
// 模拟异步操作
setTimeout(() => {
  spinner.succeed('加载完成');
  // spinner.fail('加载完成');
}, 3000);
3. 动态更新文本
js 复制代码
const ora = require('ora');
const spinner = ora({
  text: '处理中...',
  spinner: {
    frames: ['🕛', '🕒', '🕕', '🕘'],
    interval: 300
  }
}).start();
let i = 1
const time = setInterval(() => {
  spinner.text = `已处理 ${i}/${100} 文件`;
  i++
}, 1000);
// 模拟异步操作
setTimeout(() => {
  spinner.succeed('加载完成');
  clearInterval(time);
  // spinner.fail('加载完成');
}, 5000);

inquirer

  • 这是一个nodejs命令行交互工具,关于使用可查看我写的另外一篇文章

https://blog.csdn.net/weixin_43376417/article/details/135908186?spm=1011.2415.3001.5331

直接点击目录的 使用inquirer包来实现 查看

  • 整理不易,大家方便的话可帮忙点点赞点点收藏啥的,拜托啦,
相关推荐
Hashan18 小时前
elpis-core:基于 Koa 的轻量级 Web 应用框架
前端·javascript·node.js
w重名了10988219 小时前
记录一次gnvm切换node版本显示内存溢出的报错
前端·node.js
用户25191624271119 小时前
Node之EventEmitter
前端·javascript·node.js
狼爷1 天前
前端项目从 Windows 到 Linux:构建失败的陷阱
前端·node.js·angular.js
duangww1 天前
Linux设置定时作业执行node.js脚本
linux·node.js·sap fiori
源去_云走1 天前
npm 包构建与发布
前端·npm·node.js
码农欧文1 天前
关于npm和pnpm
前端·npm·node.js