用一次就會愛上的cli工具開發

寫在前面

最近接手任務——使用nodejs開發一個公司內部使用的cli工具,簡而言之就是輸入一行命令快速搭建好項目結構,也能夠經過不一樣的命令引入不一樣的文件。html

瞭解

首先要基於node環境,而後咱們須要知道cli是什麼?cli是command-line interface的縮寫,即命令行工具,經常使用的vue-clicreate-react-appexpress-generator 等都是cli工具。vue

回顧

建立一個exercise-cli目錄,並使用cmd進入該目錄:node

mkdir exercise-cli && cd exercise-cli
複製代碼

在該目錄下新建index.js:react

//index.js
console.log('謝邀,人在美國,剛下飛機。');
複製代碼

使用node運行index.js:webpack

這是node的基本用法,那麼如何使用自定義命令行輸出這句話呢?

點火

使用npm init建立package.json,一路回車,固然你也能夠配置相關信息,有興趣可本身選擇:git

如今目錄中自動生成一個 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,所以配置以下:github

"bin":{
    "exercise-cli":"./index.js"
},
複製代碼

此時咱們配置exercise-cli命令來執行index.js文件,須要在index.js文件頭部添加#!/usr/bin/env node, 讓系統本身去找node的執行程序。至於這玩意具體什麼,百度出這麼個東西,可自行參考。web

//index.js
#!/usr/bin/env node
console.log('謝邀,人在美國,剛下飛機。');
複製代碼

而後在cmd輸入npn linknpm install -g將當前項目安裝到全局環境,這樣就能夠直接使用exercise-cli來運行文件了:vue-cli

再學一點,在package.json的scripts字段裏添加腳本名:

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "exercise":"exercise-cli"
 }
複製代碼

命令行輸入npm run exercise,一樣輸出了index.js裏的內容,聯想起vue-clinpm run devnpm run build等會不會如有所思呢?express

起飛

有點樣子了,接下來讓咱們看看它是如何生成項目模板的,一個思路是用一個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:

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');
複製代碼
  1. 基本語法
var inquirer = require('inquirer');
inquirer.prompt([/* Pass your questions in here */]).then(function (answers) {
    // Use user feedback for... whatever!! 
})
複製代碼
  1. 參數詳解
  • 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默認後綴。
  1. 實例分析

.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-clinpm install exercise-cli安裝一下你的cli感覺它的魅力吧。

代碼已上傳至個人GitHub,歡迎Fork。

感謝

相關文章
相關標籤/搜索