1.什么是CLI
CLI(Command Line Interface的缩写面),即命令行界面,是指可在用户提示符下键入可执行指令的界面。常用的vue-cli, create-react-app,express-generator 等都是cli工具。
2.开始
本篇文章对应的项目地址: (github.com/DIVINER-onlys/…)
创建一个efox-cli目录,并进入该目录
mkdir efox-cli && cd efox-cli
执行
npm init
生成的package.json如下,新增bin,用于存放一个可执行文件,如下
// package.json
{
"name": "efox-cli",
"version": "1.0.0",
"description": "efox-cli",
"bin": {
"efox-cli": "bin/efox.js"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "sam",
"license": "ISC"
}
bin字段安装时,将可执行文件(bin/efox.js)链接到当前项目的./node_modules/.bin中
- 全局安装,npm将会使用指定符号链接(efox-cli)把这些文件链接到
/usr/local/bin/ - 本地安装,会链接到
./node_modules/.bin/
新建文件bin/efox.js,
// bin/efox.js
#!/usr/bin/env node
// 解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件
// https://github.com/jingzhiMo/jingzhiMo.github.io/issues/15
console.log('谢邀,人在家,刚下飞机')
接下来我们配置下efox-cli成为可执行命令,用于执行bin/efox.js文件,
执行npm install -g 或 npm link将当前项目安装到全局环境,这样就可以直接使用efox-cli来运行文件了
在package.json的script字段中添加脚本名:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"efox": "efox-cli"
},
通过输入npm run efox,也可以输出内容
3.实现
首先献上整个实现的流程图,我们看着流程图来一步一步实现
检查node版本
我们需要semver(语义化版本控制模块),chalk(支持修改控制台中字符串的样式 字体样式、字体颜色、背景颜色),执行
npm i semver chalk
在package.json加入
// package.json
"engines": {
"node": ">=8"
}
检查node版本, 在bin/efox.js加入
// bin/efox.js
const semver = require('semver') // 语义化版本控制模块
const chalk = require('chalk') // 支持修改控制台中字符串的样式 字体样式、字体颜色、背景颜色
const package = require('../package.json')
// 检查node版本
function checkNodeVersion(wanted, id) {
if(!semver.satisfies(process.version, wanted)) {
console.log(chalk.red.bold.bgBlack(
`You are using Node ${process.version}, but this version of ${id} requires Node ${wanted}.\nPlease upgrade your Node version.`
))
process.exit(1)
}
}
checkNodeVersion(package.engines.node, 'efox')
node版本低于8时
定义指令
明确我们的目标,我们的指令是 efox-cli c name -p efox -m nextjs -f
- c:创建指令
- -p: 指定项目组
- -m:指定模板
- -f:强制覆盖同名文件夹
定义指令我们需要用到
commander(命令行工具),ora(实现node.js命令行环境的loading效果,和显示各种状态的图标等)执行
npm i commander ora
由于commander各项使用npm上说明很详细,这里不多做介绍,在bin/efox.js加入
// bin/efox.js
const program = require('commander') // 命令行工具
const ora = require('ora') // 实现node.js命令行环境的loading效果,和显示各种状态的图标等
program
.version(package.version, '-v, --version')
.usage('<command> [options]')
program
.command('create [app-name]')
.alias('c')
.description('基于efox-cli创建一个项目')
.option('-p, --project <project>', '选择项目组')
.option('-m, --module <module>', '选择项目组内的目标目录')
// .option('-d, --dest', '直接在当前目录生成项目')
// .option('-g, --gitClone <repository>', '使用远程git项目作为模板')
.option('-f, --force', '覆写同名文件夹生成项目')
.action((name, cmd) => {
const options = cleanArgs(cmd)
if (process.argv.includes('-g') || process.argv.includes('--git')) {
options.forceGit = true
}
console.log('执行指令内容', name, options)
// 下面这个逻辑后面会说
// require('../lib/create')(name, options)
})
program
.parse(process.argv)
// commander passes the Command object itself as options,
// extract only actual options into a fresh object.
function cleanArgs(cmd) {
const args = {}
cmd.options.forEach(o => {
const key = camelize(o.long.replace(/^--/, ''))
// if an option is not present and Command has a method with the same name
// it should not be copied
if (typeof cmd[key] !== 'function' && typeof cmd[key] !== 'undefined') {
args[key] = cmd[key]
}
})
return args
}
// 小驼峰转换
function camelize(str) {
return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '')
}
执行 efox-cli c test -p efox -m nextjs -f
下期后面会处理通过这些参数来加载脚手架,也就是require('../lib/create')(name, options)这个逻辑
我们还可以定义其他指令,比如info
这里需要执行 npm i envinfo
// bin/efox.js
program
.command('info')
.description('查看当前运行环境')
.action((cmd) => {
console.log(chalk.green.bold('\nEnvironment Info:'))
const spinner = ora('Start loading system configuration...').start()
// eninfo 获取系统的信息,设备信息,浏览器,node版本等
require('envinfo').run(
{
System: ['OS', 'CPU'],
Binaries: ['Node', 'Yarn', 'npm'],
Browsers: ['Chrome', 'Edge', 'Firefox', 'Safari'],
npmPackages: '/**/{*efox*,@efox/*,*vue*,@vue/*/}',
npmGlobalPackages: ['@efox/efoxcli']
},
{
showNotFound: true,
duplicates: true,
fullTree: true
}
).then(res => {
console.log(chalk.green(res))
spinner.succeed('Loading system configuration is complete')
})
})
输入 efox-cli info查看系统信息
还可以定义参数问题,如只执行efox-cli输出help信息
// bin/efox.js
// output help information on unknown commands
program
.arguments('[command]')
.action((cmd) => {
if (!process.argv.slice(2).length) {
program.outputHelp()
return
}
program.outputHelp()
console.log(chalk.red(`\n未知命令:${chalk.yellow(cmd)}, 帮助请输入: ${chalk.green('efox-cli --help')}\n`))
})
// add some useful info on help
program.on('--help', () => {
console.log(` 使用 ${chalk.cyan.green.bold(`efox-cli <command> --help`)} 命令来查询对应的使用方式`)
})
program.commands.forEach(c => c.on('--help', () => console.log()))
4.最后
下期马上来