avatar

使用nodejs编写cli(命令行)

使用nodejs编写cli(命令行)

背景

在之前的开发中,使用过vue-cli等命令行工具生成项目模板,极大地方便了日常工作的展开。但是有时候一些十分特别的需求,我们是找不到适合的cli工具去做的。比如说,在同一个后端鉴权逻辑下,登录等功能是一样的,前端项目可能采用Vant、ElementUI、AntDesign、AntDesign Mobile等,目前npm仓库中现有的cli工具基本都不会涉及业务逻辑,即使对业务很熟悉,每次新建项目要处理起登录逻辑来也是要改很多文件的。
所以呢,何不为自己项目写一个cli?就专门做这些繁琐的活?

项目实战

声明入口,安装依赖

  1. 修改package.json,添加一个bin字段,bin字段里面写上这个命令行的名字,也就是vite-helper-cli,它告诉node,里面的js脚本可以通过命令行的方式执行,以vite-helper-cli的命令调用。
  2. chalk.js可以为console.log()的输出信息配置不同的显示颜色和样式
  3. execa 相对于内置的 Node.js child_process 模块来说,execa 公开了一个基于 promise 的 API。这意味着我们可以在代码中使用 async/await,而不需要像使用异步 child_process 模块方法那样创建回调函数。如果我们需要它,还有一个 execaSync 方法可以同步运行命令。
  4. fs-extra是fs的一个扩展,提供了非常多的便利API,并且继承了fs所有方法和为fs方法添加了promise的支持。
  5. Inquirer 是常规交互式命令行用户接口的集合,提供给 Node.js 一个方便嵌入,漂亮的命令行接口。
  6. ora实现node.js 命令行环境的 loading效果, 和显示各种状态的图标等
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    {
    "name": "@pengweifu/vite-helper-cli",
    "version": "1.0.0",
    "description": "vite helper command",
    "bin": {
    "vite-helper-cli": "bin/index.js"
    },
    "scripts": {
    "test": "node bin/index.js"
    },
    "keywords": [
    "vite"
    ],
    "author": "pengweifu",
    "license": "MIT",
    "type": "module",
    "dependencies": {
    "chalk": "^5.0.1",
    "execa": "^6.1.0",
    "fs-extra": "^10.1.0",
    "inquirer": "^9.0.0",
    "ora": "^6.1.2"
    }
    }

入口文件顶部声明执行环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// bin/index.js
#!/usr/bin/env node

import chalk from 'chalk'
import question from './question/index.js'
import fetchTemplate from './fetchTemplate/index.js'

question().then((answer) => {
fetchTemplate(`./${answer.dirName}`, answer.platform)
.then(() => {
console.log(chalk.green('done!'))
})
.catch(() => {
console.log(chalk.red('failed!'))
})
})

通过inquirer收集用户输入变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//  bin/question/index.js
import inquirer from 'inquirer'
import config from '../config.js'

export default function () {
return inquirer.prompt([
{
type: 'input',
name: 'dirName',
message: '请输入要创建的目录名称:',
validate(val) {
if (!val) {
return '请输入要创建的目录名称'
}
return true
},
},
{
type: 'list',
name: 'platform',
message: '请选择模板:',
default: 'vue3 desktop',
choices: config.map(item => item.platform),
},
])
}

通过execa拉取git仓库的项目模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// bin/fetchTemplate/index.js
import { execaSync } from 'execa'
import ora from 'ora'
import chalk from 'chalk'
import fse from 'fs-extra'
import config from '../config.js'

export default function (dirName, platform) {
return new Promise((resolve, reject) => {
let getGitRemoteResult = {
failed: true,
}
const tmpPath = `${dirName}/temp`
const gitUrl = config.find((item) => item.platform === platform).git
const gitDir = gitUrl.replace('http://git.cn:3000/master/', '').replace('.git', '')
try {
fse.mkdirSync(dirName)
fse.mkdirSync(tmpPath)
} catch (error) {
console.log(chalk.red('mkdir failed!'))
reject()
return
}
const spinners = [ora('loading...')]
spinners[0].start()

try {
getGitRemoteResult = execaSync('git', ['clone', '-b', 'master', gitUrl], {
cwd: tmpPath,
})
} catch (err) {
console.error(err)
fse.rmdirSync(dirName)
console.log(chalk.red('git clone failed!'))
reject()
return
}
if (getGitRemoteResult.failed === true) {
spinners[0].fail('git clone failed!')
reject()
return
}
try {
fse.copySync(`${tmpPath}/${gitDir}`, `${dirName}`)
execaSync('rm', ['-rf', tmpPath], {
cwd: './',
})
execaSync('rm', ['-rf', `${dirName}/.git`], {
cwd: './',
})
execaSync('rm', ['-rf', `${dirName}/package-lock.json`], {
cwd: './',
})
execaSync('rm', ['-rf', `${dirName}/yarn.lock`], {
cwd: './',
})
} catch (err) {
console.error(err)
fse.rmdirSync(dirName)
console.log(chalk.red('copy failed!'))
reject()
return
}
spinners[0].succeed('git clone success!')
resolve()
})
}

// bin/config.js
export default [
{
platform: 'vite2.x + vue3.x + pinia + element-plus',
git: 'http://git.cn:3000/master/vite-vue.git',
},
{
platform: 'vite2.x + vue3.x + pinia + vant',
git: 'http://git.cn:3000/master/vite-vue-mobile.git',
},
{
platform: 'vite2.x + react18 + mobx + antd',
git: 'http://git.cn:3000/master/vite-react.git',
},
{
platform: 'vite2.x + react18 + mobx + antd-mobile',
git: 'http://git.cn:3000/master/vite-react-mobile.git',
},
]

调试,发布,使用

  1. 在编写以上cli时,可以通过npm run test来测试效果
  2. 当测试没问题以后,可以通过npm publish来发布
  3. 发布到npm仓库以后,可以npm i -g @pengweifu/vite-helper-cli 全局安装
  4. 安装成功以后,就可以像vue-cli那样来使用,在终端执行vite-helper-cli,根据提示生成项目模板了
文章作者: pengweifu
文章链接: https://www.pengwf.com/2022/07/07/web/JS-npm-cli/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 麦子的博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论