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 .')
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 .')
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 {
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)
}
}
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
|