用一次就会爱上的cli工具开发
本文转载自用一次就会爱上的cli工具开发
写在前面
最近接手任务——使用nodejs开发一个公司内部使用的cli工具,简而言之就是输入一行命令快速搭建好项目结构,也可以通过不同的命令引入不同的文件。
了解
首先要基于node环境,然后我们需要知道cli是什么?cli是command-line interface的缩写,即命令行工具,常用的vue-cli
, create-react-app
,express-generator
等都是cli工具。
回顾
创建一个exercise-cli
目录,并使用cmd进入该目录:
mkdir exercise-cli && cd exercise-cli
复制代码
在该目录下新建index.js
:
//index.js
console.log('谢邀,人在美国,刚下飞机。');
复制代码
使用node运行index.js:
这是node的基本用法,那么如何使用自定义命令行输出这句话呢?
点火
使用npm init
创建package.json
,一路回车,当然你也可以配置相关信息,有兴趣可自己选择:
现在目录中自动生成一个package.json
文件:
{
"name": "exercise-cli",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
复制代码
现在在package.json
中添加字段bin
,用来存放一个可执行的文件,我们此处的可执行文件就是index.js,因此配置如下:
"bin":{
"exercise-cli":"./index.js"
},
复制代码
此时我们配置exercise-cli
命令来执行index.js
文件,需要在index.js
文件头部添加#!/usr/bin/env node
, 让系统自己去找node的执行程序。至于这玩意具体什么,百度出这么个东西,可自行参考。
//index.js
#!/usr/bin/env node
console.log('谢邀,人在美国,刚下飞机。');
复制代码
然后在cmd输入npn link
或npm install -g
将当前项目安装到全局环境,这样就可以直接使用exercise-cli
来运行文件了:
再学一点,在package.json的scripts字段里添加脚本名:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"exercise":"exercise-cli"
}
复制代码
命令行输入npm run exercise
,同样输出了index.js
里的内容,联想起vue-cli
的npm run dev
、npm run build
等会不会若有所思呢?
起飞
有点样子了,接下来让我们看看它是如何生成项目模板的,一个思路是用一个templates文件夹保存项目模板,然后通过fs.mkdir()来创建项目目录,最后把文件从templates文件夹拷贝到项目中去。本地templates目录如下图所示:
1.拷贝文件
模拟场景:将本地templates中的vue.min.js
拷贝到新生成的项目模板中。在新生成的项目模板中新建public目录,该目录下新建js目录,将vue.min.js
通过copyTemplate()
方法从templates里拷贝到新建的js目录下面:
//index.js
#!/usr/bin/env node
var fs = require('fs');
var path = require('path');
// 复制文件
function copyTemplate (from, to) {
from = path.join(__dirname, 'templates', from);
console.log(from);
write(to, fs.readFileSync(from, 'utf-8'))
}
function write (path, str, mode) {
fs.writeFileSync(path, str)
}
// 新建目录
function mkdir (path, fn) {
fs.mkdir(path, function (err) {
fn && fn()
})
}
var PATH = ".";
mkdir(PATH+'/public',function(){
mkdir(PATH + '/public/js',function () {
copyTemplate("/js/vue.min.js", PATH + '/public/js/vue.min.js');
})
})
复制代码
用cmd打开任意文件夹输入exercise-cli
,该文件夹下会public\js\vue.min.js
:
![img](data:image/svg+xml;utf8,)
2.拷贝文件夹
我们学会了拷贝文件,那么如何拷贝整个文件夹呢,例如我想将templates下的整个js目录全部拷贝到新生成的项目模板中,又该如何?有需求就有方案,我们可以遍历整个文件夹,对遍历到的path进行判断,如果是文件则直接拷贝,如果是文件夹则递归:
//index.js
// 复制目录
var copy=function(src,dst){
let paths = fs.readdirSync(src); //同步读取当前目录(只能读取绝对路径,相对路径无法获取)
paths.forEach(function(path){
var _src=src+'/'+path;
var _dst=dst+'/'+path;
fs.stat(_src,function(err,stats){ //stats 该对象 包含文件属性
if(err)throw err;
if(stats.isFile()){ //如果是个文件则拷贝
let readable=fs.createReadStream(_src);//创建读取流
let writable=fs.createWriteStream(_dst);//创建写入流
readable.pipe(writable);
}else if(stats.isDirectory()){ //是目录则 递归
checkDirectory(_src,_dst,copy);
}
});
});
}
var checkDirectory=function(src,dst,callback){
fs.access(dst, fs.constants.F_OK, (err) => {
if(err){
fs.mkdirSync(dst);
callback(src,dst);
}else{
callback(src,dst);
}
});
};
mkdir(PATH+'/public',function(){
mkdir(PATH + '/public/js',function () {
checkDirectory('C:/Users/Administrator/Desktop/vue-3.0/nodeTest/exercise/templates/js',PATH+'/public/js',copy);
})
})
复制代码
依然在找一个文件夹打开cmd输入exercise-cli
,该文件夹下会生成public目录,该目录下面会生成templates下的整个js文件:
3.接收命令行参数
平常我们使用命令行工具时都会用到参数,如webpack -p
, express -e
等,在此我们为exercise-cli
配置-l
,当使用exercise-cli -l
时,添加layerJS。
我们可以使用process.argv
来获取命令行参数,process.argv
是一个参数数组,第一项为node.exe的绝对路径,第二项为执行该js的绝对路径,使用process.argv.slice(2)
即可获取输入的参数数组。
//index.js
console.log(process.argv);
复制代码
通过遍历参数数组来检查命令中输入了哪些参数。如果输入了预设的参数,就为config对象添加对应的属性,在生成文件时根据config判断是否将模板文件拷贝到项目中。
var config = {};
process.argv.slice(2).forEach(item=>{
if(item=="-l"){
config.layer = true;
}
})
var PATH = ".";
mkdir(PATH+'/public',function(){
mkdir(PATH + '/public/js',function () {
// copyTemplate("/js/vue.min.js", PATH + '/public/js/vue.min.js');
checkDirectory('C:/Users/Administrator/Desktop/vue-3.0/nodeTest/exercise/templates/js',PATH+'/public/js',copy);
if(config.layer){
checkDirectory('C:/Users/Administrator/Desktop/exercise-cli/templates/layer',PATH+'/public/js',copy);
//此处注意layerJS存放在templates中的路径。
}
})
})
console.log('拷贝成功');
复制代码
在任意文件夹打开cmd输入exercise-cli -l
,执行成功,js目录中多出了layerJS目录:
加速
初始commander.js
其实node中有一款工具包可以快速开发命令行工具,它就是commander.js。
首先全局安装一下:
npm install commander -g
复制代码
看个例子:
var program = require('commander');
program
.version('1.0.0','-v, --version')
.command('check [checkname]')
.alias('c')
.description('yo yo check now')
.option('-a, --name [moduleName]', '模块名称')
.action((checkname,option) => {
console.log('指令 install 后面跟的参数值 checkname: ' + checkname);
console.log(option);
// 获得了参数,可以在这里做响应的业务处理
})
//自定义帮助信息
.on('--help', function() {
console.log(' 下面我随便说两句:')
console.log('')
console.log('$ 人有多大胆,母猪多大产,i love xx')
console.log('$ 广阔天地,大有所为,呱~')
})
program.parse(process.argv)
复制代码
命令行执行:
看完输出一脸懵逼,别急,这就带您瞧瞧这都是些什么东西:
- version - 定义命令程序的版本号,.version('0.0.1', '-v, --version'),第一个参数版本号必须,第二个参数可省略,默认为 -V 和 --version
- command – 定义命令行指令,后面可跟上一个name,用空格隔开,如 .command('app [name]')
- alias – 定义一个更短的命令行指令 ,如执行命令$ exercise-cli c 与之是等价的
- description – 描述,它会在help里面展示
- option – 定义参数。它接受四个参数,在第一个参数中,它可输入短名字 -a和长名字–name ,使用 | 或者,分隔,在命令行里使用时,这两个是等价的,区别是后者可以在程序里通过回调获取到;第二个为描述, 会在 help 信息里展示出来;第三个参数为回调函数,他接收的参数为一个string,有时候我们需要一个命令行创建多个模块,就需要一个回调来处理;第四个参数为默认值
- action – 注册一个callback函数,这里需注意目前回调不支持let声明变量
- parse – 用于解析process.argv,设置options以及触发commands,用法示例:.parse(process.argv)
看到这,多多少少对如何编写命令行工具有个大体的认知了,光说不练嘴把式,自我实践:用commander.js完成上个段落3.接收命令行参数
中的例子。
分割线(以下深入和浅出部分于2019.4.30 更)
深入inquirer.js
创建脚手架的时候我们会发现很多脚手架都需要我们和命令行频繁交互,就像我们开始使用npm init
的时候一样,那么是如何实现和命令行交互的呢?此时inquirer.js闪亮登场。
//命令行安装
npm install inquirer
//index.js引入
var inquirer = require('inquirer');
复制代码
- 基本语法
var inquirer = require('inquirer');
inquirer.prompt([/* Pass your questions in here */]).then(function (answers) {
// Use user feedback for... whatever!!
})
复制代码
- 参数详解
- type:表示提问的类型,包括:input, confirm, list, rawlist, expand, checkbox, password, editor;
- name: 存储当前问题回答的变量;
- message:问题的描述;
- default:默认值;
- choices:列表选项,在某些type下可用,并且包含一个分隔符(separator);
- validate:对用户的回答进行校验;
- filter:对用户的回答进行过滤处理,返回处理后的值;
- transformer:对用户回答的显示效果进行处理(如:修改回答的字体或背景颜色),但不会影响最终的答案的内容;
- when:根据前面问题的回答,判断当前问题是否需要被回答;
- pageSize:修改某些type类型下的渲染行数;
- prefix:修改message默认前缀;
- suffix:修改message默认后缀。
- 实例分析
在.action
的回调函数里输入以下内容:
// 获得了参数,可以在这里做响应的业务处理
var prompList = [
{
type:'input',
message:'姓名',
name:'name'
},{
type:'input',
message:'手机号',
name:'phone',
validate:val=>{
if(val.match(/\d{11}/g)){
return true
}
return '请输入11位数字'
}
},{
type:'confirm',
message:'是否参加本次考核?',
name:'assess',
prefix:'前缀'
},{
type:'confirm',
message:'是否同意本次考核须知?',
name:'notice',
suffix:'后缀',
when:answers=>{
return answers.assess
}
},{
type:'list',
message:'欢迎来到本次考核,请选择学历:',
name:'eductionBg',
choices:[
"大专",
"本科",
"本科以上"
],
filter:val=>{//将选择的内容后面加学历
return val+'学历'
}
},{
type:'rawlist',
message:'请选择你爱玩的游戏:',
name:'game',
choices:[
"LOL",
"DOTA",
"PUBG"
]
},{
type:'expand',
message:'请选择你喜欢的水果:',
name:'fruit',
choices: [
{
key: "a",
name: "Apple",
value: "apple"
},
{
key: "O",
name: "Orange",
value: "orange"
},
{
key: "p",
name: "Pear",
value: "pear"
}
]
},{
type:'checkbox',
message:'请选择你喜欢的颜色:',
name:'color',
choices:[
{
name: "red"
},
new inquirer.Separator(), // 添加分隔符
{
name: "blur",
checked: true // 默认选中
},
{
name: "green"
},
new inquirer.Separator("--- 分隔符 ---"), // 自定义分隔符
{
name: "yellow"
}
]
},{
type:'password',
message:'请输入你的游戏密码:',
name:'pwd'
}
]
inquirer.prompt(prompList).then(answers=>{
console.log(answers);
})
复制代码
命令行交互如下:
浅出chalk.js
最后我们引入chalk这个美化命令行的模块,它具有轻量级、高性能、学习成本低等特点。继续在以上例子中引入chalk进行输出:
//命令行安装
npm install chalk
//index.js引入
var chalk = require('chalk');
复制代码
在inquirer里打印如下:
inquirer.prompt(prompList).then(answers=>{
console.log(answers);
console.log(chalk.green('考核完成'))//字体绿色
console.log(chalk.blue('你最棒了'))//字体蓝色
console.log(chalk.blue.bgRed('五一放假喽')) //支持设置背景
console.log(chalk.blue(answers))
})
复制代码
命令行最终显示如下:
感兴趣的话还是自己敲一下吧。
着陆
想让别人来安装你的cli工具,你需要把它发布到npm上,先在npm官网创个账号(注意需要邮件验证),在命令行输入npm adduser
,依次填上你注册的username、password、email。接着输入npm publish
即可:
输入npm install -g exercise-cli
或npm install exercise-cli
安装一下你的cli感受它的魅力吧。
代码已上传至我的GitHub,欢迎Fork。
感谢
用一次就会爱上的cli工具开发的更多相关文章
- 『.NET Core CLI工具文档』(十四)dotnet-install 脚本参考
说明:本文是个人翻译文章,由于个人水平有限,有不对的地方请大家帮忙更正. 原文:dotnet-install scripts reference 翻译:dotnet-install 脚本参考 名称 d ...
- 『.NET Core CLI工具文档』(八)dotnet-restore
说明:本文是个人翻译文章,由于个人水平有限,有不对的地方请大家帮忙更正. 原文:dotnet-restore 翻译:dotnet-restore 名称 dotnet-restore - 还原一个项目的 ...
- 『.NET Core CLI工具文档』(六)dotnet 命令
说明:本文是个人翻译文章,由于个人水平有限,有不对的地方请大家帮忙更正. 原文:dotnet command 翻译:dotnet 命令 名称 dotnet -- 运行命令行命令的一般驱动程序 概要 d ...
- 『.NET Core CLI工具文档』(一).NET Core 命令行工具(CLI)
说明:本文是个人翻译文章,由于个人水平有限,有不对的地方请大家帮忙更正. 原文:.NET Core Command Line Tools 翻译:.NET Core命令行工具 什么是 .NET Core ...
- Angular2 CLI 快速开发
Angular2 CLI 快速开发 http://www.tuicool.com/articles/z6V3Ubz 解决npm 的 shasum check failed for错误(npm注册国内镜 ...
- PHP CLI模式开发
PHP CLI模式开发不需要任何一种Web服务器(包括Apache或MS IIS等),这样,CLI可以运行在各种场合.有两种方法可以运行PHP CLI脚本. 第一种方法是使用 # php /path/ ...
- PHP CLI模式开发(转)
PHP CLI模式开发不需要任何一种Web服务器(包括Apache或MS IIS等),这样,CLI可以运行在各种场合. 有两种方法可以运行PHP CLI脚本. 第一种方法是使用php -f /path ...
- 用node编写自己的cli工具
工作中接到新项目,开发前都需要先规划项目目录,然后一个个创建文件,搭建sass编译环境,下载jquery,Swiper等类库... 这些准备工作都要花上不少时间.每做一个项目,都会遇到同样的问题,再重 ...
- ubuntu 18.04安装clojure工程的cli工具lein
官网的安装过程https://leiningen.org/#install 是文字描述,并不够lazy. 我仿照code,chrome nodejs的方式,给出下面的命令行安装过程 wget http ...
随机推荐
- HaspMap源码分析(JDK 1.8)
底层结构分析 上面这两张图分别画出了JDK 1.7.1.8底层数据结构,在JDK 1.7.1.8中都使用 了散列算法,但是在JDK 1.8中引入了红黑树,在链表的长度大于等于8并且hash桶的长度大于 ...
- hbase 集群(完全分布式)方式安装
一,环境 1, 主节点一台: ubuntu desktop 16.04 zhoujun 172.16.12.1 从节点(slave)两台:ubuntu server 16.04 hadoo ...
- python爬虫selenium相关
首先上很好用的selenium中文文档,基本上所有问题都能通过阅读此文档解决.可惜好像没找到翻译者名称. https://python-selenium-zh.readthedocs.io/zh_CN ...
- TypeScript中 typeof ArrayInstance[number] 剖析
假设这样一个场景,目前业务上仅对接了三方支付 'Alipay', 'Wxpay', 'PayPal', 实际业务 getPaymentMode 会根据不同支付方式进行不同的付款/结算流程. const ...
- Jenkins(6)测试报告邮件发送
前言 前面已经实现在jenkins上展示html的测试报告,接下来只差最后一步,把报告发给你的领导,展示你的劳动成果了. 安装 Email Extension Plugin 插件 jenkins首页- ...
- Jenkins(2)docker容器中安装python3
前言 使用docker安装jenkins环境,jenkins构建的workspace目录默认是在容器里面构建的,如果我们想执行python3的代码,需进容器内部安装python3的环境. 进jenki ...
- MDK中用C++开发STM32
作者:良知犹存 转载授权以及围观:欢迎添加微信:Allen-Iverson-me-LYN 前言 最近想开发一段单片机的代码,代码本身有很多的重复元素,这重复定义的一些结构体使用起来有些繁琐, ...
- Educational Codeforces Round 2 E. Lomsat gelral(dsu)
题目链接 题意:给你一棵以1为根n个点的树,问你以i为根的子树的众数和是多少 思路:dsu是一种优化暴力的手段 首先进行轻重链剖分 然后只记录重链的信息 轻链的信息就直接暴力查找 经过证明这样复杂度可 ...
- 【bzoj2429】[HAOI2006]聪明的猴子(图论--最小瓶颈生成树 模版题)
题意:有M只猴子,他们的最大跳跃距离为Ai.树林中有N棵树露出了水面,给出了它们的坐标.问有多少只猴子能在这个地区露出水面的所有树冠上觅食. 解法:由于要尽量多的猴子能到达所有树冠,便用Kruskal ...
- BSGS及其扩展
目录 定义 原理 朴素算法 数论分块 例题 Luogu2485 [SDOI2011]计算器 题解 代码 扩展 例题 Luogu4195 [模板]exBSGS/Spoj3105 Mod 代码 之前写了一 ...