node cli 命令行工具实现

node cli 命令行工具

初始化项目

mkdir tl && cd tl
npm init

修改package.json文件,添加bin字段,表明tl 是可执行文件,执行的文件是index.js

{
  "name": "y",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "tl": "./index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "license": "ISC",
}

创建index.js

#!/usr/bin/env node

console.log("你好")

安装

node i -g

此时就可以在命令行中执行tl命令了

交互

安装commander、inquirer、chalk(可选)

commander: 是完整的 node.js 命令行解决方案,能够帮助我们快速的实现cli命令;
inquirer: 是交互式命令行用户接口的集合,能够帮助我们快速的实现类似vue creat项目时的各种选择功能;
chalk: 是命令行美化工具;5.x版本只支持ES Module;

因为使用了chalk并且它只支持ES Module,所以需要修改package.json文件,添加type字段;也可以安装chalk4.x版本;

{
  ...
  "type": "module",
  ...
}

创建命令

首行的#!/usr/bin/env node是不可省略的,它表明了这是个node的可执行文件

#!/usr/bin/env node

import { Command } from 'commander'
import { readFile } from 'fs/promises'
import fs from 'fs'
import path from 'path'

const program = new Command()

const packageJson = JSON.parse(
  await readFile(new URL('./package.json', import.meta.url))
)

program.version(packageJson.version, '-v, --version', 'cli的最新版本')

program
  .command('gitreset')
    .description('执行回退到上一个版本')
    .action(async () => {
      try {
        await exec('git add .') // 解决未add的情况下不能reset
        handleExeRes(await exec('git reset --hard HEAD'))
      } catch (err) {
        console.log(chalk.red(`err: ${err}`))
      }
    })

program.parse(process.argv)

此时执行

tl -h

就可以看到已经有git reset命令了

创建交互式命令

使用inquirer完成用户交互式命令,最后拿到用户选择的结果做对应的操作;
如提前创建各种项目模板,如果用户选择的是vue3+ts,那么git clone vue3+ts的项目

import inquirer from 'inquirer'

const prompList = [
  ...
]
export default function (program) {
  program
    .command('create [projectName]')
    .description('创建新项目')
    .action((projectName) => {
      if (!projectName) {
        prompList.unshift({
          type: 'input',
          message: '项目名称:',
          name: 'projectName',
          default: 'newProject',
        })
      }

      inquirer.prompt(prompList).then((answers) => {
        console.log({ projectName, ...answers }) // 返回的结果,做处理
      })
    })
}

优化

当command多了后全写在index.js中就很麻烦,于是将gitreset命令放到command目录中

如图:

import chalk from 'chalk'
import child_process from 'child_process'
import util from 'util'
import { handleExeRes } from '../utils.js'

const exec = util.promisify(child_process.exec)

export default function (program) {
  program
    .command('gitreset')
    .description('执行回退到上一个版本')
    .action(async () => {
      try {
        await exec('git add .') // 解决未add的情况下不能reset
        handleExeRes(await exec('git reset --hard HEAD'))
      } catch (err) {
        console.log(chalk.red(`err: ${err}`))
      }
    })
}

这样index.js中就很清爽了,只需要import,然后执行就好了;

但是每添加一个命令都要引入一次有点麻烦,再优化一下,让它能够自动的引入commond下的命令

#!/usr/bin/env node

import { Command } from 'commander'
import { readFile } from 'fs/promises'
import fs from 'fs'
import { dirname, resolve } from 'path'
import { fileURLToPath } from 'url'

const program = new Command()
const __dirname = dirname(fileURLToPath(import.meta.url))

const packageJson = JSON.parse(
  await readFile(new URL('./package.json', import.meta.url))
)

program.version(packageJson.version, '-v, --version', 'cli的最新版本')

try {
  // 自动导入添加command
  const commandFiles = await fs.promises.readdir(resolve(__dirname, 'command'))
  for (let i = 0; i < commandFiles.length; i++) {
    const fileName = commandFiles[i]
    let model = await import(`./command/${fileName}`)
    if (typeof model?.default === 'function') {
      model.default(program)
    }
  }

  // 自动导入添加options
  const optionFiles = await fs.promises.readdir(resolve(__dirname, 'options'))
  for (let i = 0; i < optionFiles.length; i++) {
    const fileName = optionFiles[i]
    let model = await import(`./options/${fileName}`)
    model.default(program)
  }
} catch (err) {
  console.log('import err: ?>>>> ', err)
}
program.parse(process.argv)


完整项目地址: https://github.com/bfclouds/tl

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值