Node.js实现自定义命令行工具保姆级教学
认识 cli
工具
Node.js 命令行工具(CLI
)是一种常用于开发和部署应用程序的工具。与图形化界面不同,命令行工具通常以文本模式进行操作,通过输入命令和参数来完成特定的任务。
下面是 Node.js
命令行工具的一些特点:
-
命令行工具基于
Node.js
平台,因此具有跨平台的优势,可以在Windows
、Mac OS
或Linux
上运行。 -
命令行工具通常使用命令行参数来接收输入,并且执行效率比图形化界面高。
-
命令行工具通常可以很容易地集成到工作流程中,并可以通过自定义脚本轻松批量处理任务。
-
命令行工具可以通过标准输出(
STDOUT
)将输出信息传递给用户,例如成功或错误消息、进度说明等,并可以轻松将输出内容重定向到文件或其他程序中进行后续处理。 -
命令行工具通常可以与其他工具或服务集成,例如版本控制系统、云平台服务等,以提高生产力。
总之,Node.js
命令行工具是一种强大而灵活的工具,可以帮助开发人员进行自动化配置和任务处理,并提供了更高效、更灵活的工作方式。
初始化 cli
项目
-
在项目根目录下执行
npm init
命令,创建package.json
文件 -
在项目根目录下创建
bin
文件夹,内部创建index.js
文件
mycli/
├── bin/
│ └── index.js
└── package.json
- 在
package.json
文件中添加如下配置:
"bin": {
"mycli": "./bin/index.js"
},
- 在
bin/index.js
中添加如下代码:
// 告诉操作系统用 Node 来运行此文件
#!/usr/bin/env node
console.log('hello CLI');
- 在项目根目录下执行
npm link
命令,将当前cli
挂载到全局中,此后,在任意命令行窗口执行mycli
命令,即可打开该cli
,并执行其中代码
commander
工具使用
修改 bin/index.js
中内容如下:
#!/usr/bin/env node
console.log(process.argv);
在任意命令行窗口执行 mycli
命令结果如下:
其中第一个元素值为执行当前 cli
文件的环境位置,第二个元素值为当前 cli
文件所在位置
可以带参数执行,例如执行 mycli --help
,结果如下:
可以看出,多了第三个元素,元素值即为执行 cli
是所带的参数,于是我们可以通过判断参数值来使得 cli
执行不同的任务,但是如果仅通过 if~else
或者 swicth~case
来控制的话,分支未免太多,太复杂,所以就出现了Commander
工具
介绍
commander
是一个基于 Node.js
的命令行界面,用于快速开发命令行工具。通过使用 commander
,开发者可以更加方便地实现命令行程序的处理和交互。
使用 commander
可以轻松定义命令及选项,并为每个命令添加自定义回调函数,命令行工具的参数解析等都可以通过 commander
来完成。同时,commander
还提供了丰富的 API,可以用于输出帮助信息、管理版本号等。
具体用法参考官方文档:github.com/tj/commande…
基础使用
首先在项目根目录下执行 npm install commander
命令安装 commander
,然后修改bin/index.js
如下:
#!/usr/bin/env node
const program = require('commander');
program.parse(process.argv); // 表示使用 Commander 来处理命令行参数
有了上面代码后,commander
会默认配置 --help
参数,执行 mycli --help
,结果如下:
自定义命令
然后修改bin/index.js
如下:
#!/usr/bin/env node
const program = require('commander');
program.command('create <project> [other...]') // 自定义 create 命令,接收一个必填参数 project,[other...] 表示其他参数
.alias('crt') // 别名,之后运行 mycli create ... 和 mycli crt ... 效果是一样的
.description('创建项目') // 描述
.action((project, args) => { // 命令行的执行逻辑代码
console.log(project)
console.log(args)
})
program.parse(process.argv);
执行效果如下:
同时,commander
默认会将该命令添加到 --help
的显示结果中:
逻辑封装
接下来可以按照下面方式将代码封装一下,从而将 cli
对 --help
的处理、对命令的定义和命令所要执行的方法拆分开来,更符合模块化的开发思想:
mycli/
├── node_modules/
├── bin/
│ └── index.js
├── lib/
│ └── core/
│ ├── action.js(内部定义cli为命令提供的处理逻辑)
│ ├── commander.js(内部定义cli支持的命令)
│ └── help.js(内部定义cli对--help命令的处理逻辑)
├── package.json
└── package-lock.json
#!/usr/bin/env node
const program = require('commander');
const myhelp = require('../lib/core/help')
const myCommander = require('../lib/core/commander')
myhelp(program)
myCommander(program)
program.parse(process.argv);
const myhelp = function (program) {
program.option('-f --framework <framework>', '设置框架')
}
module.exports = myhelp
const myAction = require('./action')
const myCommander = function (program) {
program.command('create <project> [other...]') // 自定义 create 命令,接收一个必填参数 project,[other...] 表示其他参数
.alias('crt') // 别名,之后运行 mycli create ... 和 mycli crt ... 效果是一样的
.description('创建项目') // 描述
.action(myAction)
}
module.exports = myCommander
const myAction = (project, args) => { // 命令行的执行逻辑代码
console.log(project)
console.log(args)
}
module.exports = myAction
inquirer
工具使用
我们知道,很多 cli
工具是可以与用户进行交互的,最基本的例子就是 npm
在执行 npm init
之后,会在命令行窗口显示很多问题,而只有用户在回答了这些问题之后,npm
才会根据用户回答,来创建 package.json
文件。而要实现这种效果,要借助一个工具:inquirer
介绍
inquirer
是一个基于 Node.js
的交互式命令行工具,可以用于收集用户的输入信息。用户可以通过选择列表、输入文本、确认等方式来提供信息,使得命令行交互的过程更加友好和高效。具体用法可参考其官方文档:github.com/SBoudrias/I…
基础使用
首先我们需要执行 npm install inquirer
将其安装到项目中,然后我们修改 lib/action.js
如下:
const inquirer = require('inquirer')
const myAction = (project, args) => {
// 命令行的执行逻辑代码
inquirer.prompt([{ // 数组中每一个对象表示一个问题
type: 'list', // 问题类型,list表示选择列表
name: 'framework', // 用于接收答案的键值
choices: ['express', 'koa', 'egg'], // 选项
message: '请选择你所使用的框架' // 问题
}]).then(answer => {
console.log(answer)
})
}
module.exports = myAction
接下来在命令行中输入 mycli create projectName
就会出现这种效果:
选择 koa
后:
逻辑封装
为了更好的模块化,可以将答案封装在一个单独的配置文件中,在项目根目录下新增 config.js
文件:
mycli/
├── node_modules/
├── bin/
│ └── index.js
├── lib/
│ └── core/
│ ├── action.js(内部定义cli为命令提供的处理逻辑)
│ ├── commander.js(内部定义cli支持的命令)
│ └── help.js(内部定义cli对--help命令的处理逻辑)
├── config.js
├── package.json
└── package-lock.json
module.exports = {
framework: ['express', 'koa', 'egg']
}
const inquirer = require('inquirer')
const config = require('../../config')
const myAction = (project, args) => {
// 命令行的执行逻辑代码
inquirer.prompt([{ // 数组中每一个对象表示一个问题
type: 'list', // 问题类型,list表示选择列表
name: 'framework', // 用于接收答案的键值
choices: config.framework, // 选项
message: '请选择你所使用的框架' // 问题
}]).then(answer => {
console.log(answer)
})
}
module.exports = myAction
download-git-repo
工具使用
接下来我们需要根据用户选择去为用户下载对应的框架模板项目,那么下载这个动作改怎么做呢?这就引出了远程仓库下载工具 download-git-repo
介绍
download-git-repo
是一个 Node.js 模块,用于从 Git 仓库中下载代码或文件。该模块可以下载 GitHub、GitLab 和 Bitbucket 等平台上的 Git 仓库,支持使用 HTTPS 或 SSH 协议进行下载。具体用法可参考官方文档:gitlab.com/flippidippi…
基础使用 + 逻辑封装
修改 lib/core/action.js
和 config.js
如下:
module.exports = {
// 可选择的框架
framework: ['express', 'koa', 'egg'],
// 框架对应的仓库地址
frameworkUrls: {
express: 'git@gitee.com:gao-shunpeng/express-template.git',
koa: 'git@gitee.com:gao-shunpeng/koa-template.git',
egg: 'git@gitee.com:gao-shunpeng/egg-template.git'
}
}
const inquirer = require('inquirer')
const config = require('../../config')
const download = require('download-git-repo')
const myAction = async (project, args) => {
// 命令行的执行逻辑代码
const answer = await inquirer.prompt([{ // 数组中每一个对象表示一个问题
type: 'list', // 问题类型,list表示选择列表
name: 'framework', // 用于接收答案的键值
choices: config.framework, // 选项
message: '请选择你所使用的框架' // 问题
}])
// console.log(answer)
// 下载代码模板
/* 参数一是要下载的仓库对应的远程地址
* 参数二是下载后保存的目录路径,可以是相对路径或绝对路径。如果目录不存在,则会自动创建。这里使用用户执行
* mycli create projectName 时输入的 projectName 值作为保存目录名
* 参数三是下载选项,可以不传递,默认为 { clone: true },表示使用 Git 命令进行克隆。如果设置为 { clone:
* false },则表示直接从 Git 仓库中下载。
* 参数四是一个回调函数,
*/
download(`direct:${config.frameworkUrls[answer.framework]}`, project, { clone: true }, err => {
console.log(err)
})
}
module.exports = myAction
这样,在命令行中执行 mycli create projectName
后,就会自动在命令行坐在目录创建名为 ${ projectName }
的目录,并在其中下载用户选择的对应模板项目
ora
工具使用
这时候我们希望在仓库代码下载过程中,给使用者一些提示信息,如 loading
状态等,这时候我们就可以通过加载交互工具 ora
来实现
介绍
ora
是一个命令行加载动画效果的工具,可以在命令行中展示加载动画,增强用户体验,它的特点是易使用,配置简单,支持多种加载动画效果如显示进度条、旋转动画和状态文本等等。具体用法参考官方文档:github.com/sindresorhu…
基础使用
首先我们需要下载 ora
,因为其在 6.x.x
版本之后不再使用 CommonJS
模块化规范而转为使用的 ES6
的模块化规范,所以我们要在项目根目录执行 npm install ora@5
来下载对应的最新 5.x.x
版本
然后修改 lib/core/action.js
如下:
const inquirer = require('inquirer')
const config = require('../../config')
const download = require('download-git-repo')
const ora = require('ora')
const myAction = async (project, args) => {
// 命令行的执行逻辑代码
const answer = await inquirer.prompt([{ // 数组中每一个对象表示一个问题
type: 'list', // 问题类型,list表示选择列表
name: 'framework', // 用于接收答案的键值
choices: config.framework, // 选项
message: '请选择你所使用的框架' // 问题
}])
// console.log(answer)
// 下载代码模板
const spinner = ora().start() // 创建实例并启动加载指示器
spinner.text = '代码正在下载……' // 下载过程中在命令行展示的 loading 信息
download(`direct:${config.frameworkUrls[answer.framework]}`, project, { clone: true }, err => {
if (!err) {
spinner.succeed('代码下载成功')
// 下载成功后,提示使用者j接下来可执行的操作
console.log('Done! you run:')
console.log('cd ' + project);
console.log('npm install ');
console.log('npm run dev ');
} else {
spinner.fail('代码下载失败')
}
})
}
module.exports = myAction
这样就可以在下载模板的过程中,展示一个动态的加载状态如下了:
chalk
工具使用
现在如果我们想搞得再花里胡哨点,比如使用各种颜色来展示之前的那些提示信息,我们可以使用命令行渲染工具 chalk
来实现
介绍
chalk
是一个 Node.js 模块,用于在控制台输出带有颜色的文本。它可以使控制台输出更加美观和易读。
使用 chalk
可以很方便地设置输出的文本的颜色、背景色等样式。常见的样式包括:
-
字体颜色:
.red
、.green
、.yellow
、.blue
、.magenta
、.cyan
、.gray
等; -
背景颜色:
.bgRed
、.bgGreen
、.bgYellow
、.bgBlue
、.bgMagenta
、.bgCyan
、.bgGray
等; -
其他样式:
.bold
、.underline
、.italic
等。
例如,以下代码可以将输出的文本设置为黄色加粗字体:
const chalk = require('chalk');
console.log(chalk.yellow.bold('Hello, world!'));
需要注意的是,使用 chalk
时应该避免过于花哨的样式,否则会影响阅读体验。具体用法参考官方文档:github.com/chalk/chalk
基础使用
首先我们需要下载 chalk
,因为其在 5.x.x
版本之后不再使用 CommonJS
模块化规范而转为使用的 ES6
的模块化规范,所以我们要在项目根目录执行 npm install chalk@4
来下载对应的最新 4.x.x
版本
然后修改 lib/core/action.js
如下:
const inquirer = require('inquirer')
const config = require('../../config')
const download = require('download-git-repo')
const ora = require('ora')
const chalk = require('chalk')
const myAction = async (project, args) => {
// 命令行的执行逻辑代码
const answer = await inquirer.prompt([{ // 数组中每一个对象表示一个问题
type: 'list', // 问题类型,list表示选择列表
name: 'framework', // 用于接收答案的键值
choices: config.framework, // 选项
message: '请选择你所使用的框架' // 问题
}])
// console.log(answer)
// 下载代码模板
const spinner = ora({ color: 'yellow' }).start()
spinner.text = '代码正在下载……'
download(`direct:${config.frameworkUrls[answer.framework]}`, project, { clone: true }, err => {
if (!err) {
spinner.succeed('代码下载成功')
console.log(chalk.green.bold('Done!'), chalk.yellow('you run:'));
console.log(chalk.blue.bold('cd ') + chalk.yellow(project));
console.log(chalk.blue.bold('npm install'));
console.log(chalk.blue.bold('npm run dev '));
} else {
spinner.fail('代码下载失败')
}
})
}
module.exports = myAction
这样就可以在命令行中展示五颜六色的提示信息如下:
逻辑封装
我们可以把下载远程库的具体操作单独封装起来,封装后的项目文件目录结构如下:
mycli/
├── node_modules/
├── bin/
│ └── index.js
├── lib/
│ └── core/
│ ├── action.js(内部定义cli为命令提供的处理逻辑)
│ ├── commander.js(内部定义cli支持的命令)
│ ├── download.js(内部定义下载远程代码库的具体处理逻辑)
│ └── help.js(内部定义cli对--help命令的处理逻辑)
├── config.js
├── package.json
└── package-lock.json
lib/core
下新建 download.js
文件,内容如下:
const download = require('download-git-repo')
const ora = require('ora')
const chalk = require('chalk')
const downloadFun = function (url, project) {
const spinner = ora({ color: 'yellow' }).start()
spinner.text = '代码正在下载……'
download(`direct:${config.frameworkUrls[answer.framework]}`, project, { clone: true }, err => {
if (!err) {
spinner.succeed('代码下载成功')
console.log(chalk.green.bold('Done!'), chalk.yellow('you run:'));
console.log(chalk.blue.bold('cd ') + chalk.yellow(project));
console.log(chalk.blue.bold('npm install'));
console.log(chalk.blue.bold('npm run dev '));
} else {
spinner.fail('代码下载失败')
}
})
}
module.exports = downloadFun
修改 lib/core/action.js
如下:
const inquirer = require('inquirer')
const config = require('../../config')
const downloadFun = require('./download')
const myAction = async (project, args) => {
// 命令行的执行逻辑代码
const answer = await inquirer.prompt([{ // 数组中每一个对象表示一个问题
type: 'list', // 问题类型,list表示选择列表
name: 'framework', // 用于接收答案的键值
choices: config.framework, // 选项
message: '请选择你所使用的框架' // 问题
}])
// 下载代码模板
downloadFun(config.foramworkUrl[answer.framwork], project)
}
module.exports = myAction
转载自:https://juejin.cn/post/7233391595306647609