從零構建自己的CLI工具,提高效率

語言: CN / TW / HK

theme: cyanosis

前言

公司需要的新專案比較多,主管要求我準備搭建一個通用的框架,放在git上,以便於新專案直接套用。我不禁開始思考,如果每次要新建一個專案,做法的流程大概就是新建一個資料夾(或者在git倉庫上新建),然後拷貝通用框架的git地址,拉取到該資料夾,然後就可以開發的。這操作流程看起來比較繁瑣,效率也很低,需要拷貝時肯定要先開啟git倉庫,然後找通用框架的git地址,後面需要新建專案,git地址未必能記得,還需要取git倉庫。這樣可不行的,所以我就想到之前就很🔥的cli工具,之前看到好多大神寫的文章,感覺很酷也很崇拜,心想自己也要寫出一個,但久久沒機會,主要是沒需求用到,也就不知道怎麼寫。這不,現在機會來了嘛,就這麼開始躍躍欲試下手~😎

cli工具大多數需要外掛工具,也就意味著你需要看比較多的文件,所以需要一顆耐心的❤️。

Ready coffe,Let's start

START

cli需要用到的工具庫 🔧

| 名稱 | 簡介 | 文件地址 | | --- | --- |---| | commander| 命令列自定義指令,比如說 -v, -c | https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md | | chalk | 美化樣式,高亮字型 | https://github.com/chalk/chalk | | inquirer | 互動式回答 | https://github.com/SBoudrias/Inquirer.js/ | | figlet | 藝術字 | https://github.com/patorjk/figlet.js | | ora | 載入的動畫效果 | https://github.com/sindresorhus/ora | | download-git-repo | 下載遠端模板 | https://www.npmjs.com/package/download-git-repo | | handlebars | 可以替換模板中的動態字串 | https://handlebarsjs.com/zh/guide/#%E4%BB%80%E4%B9%88%E6%98%AF-handlebars |

新建一個專案 🆕

  • 新建資料夾 📁

JavaScript mkdir fency-cli //新建資料夾,名字隨意 cd fency-cli //進入資料夾裡面 npm init -y //快速生成package.json

  • 安裝剛才說到的所有工具庫 ⬇️

JavaScript yarn add commander chalk inquirer figlet ora download-git-repo handlebars -D

Tips: ora, 我用的是5.6版本,6以上用import

  • 新建命令列的入口檔案 /bin/cli.js 📃 ```

! /usr/bin/env node //用於解釋程式的指令碼

console.log('Hello fe-cli') //為了測試是否正常 ```

  • 在package.json檔案中增加入口檔案 bin欄位 🏠

json { "name": "fency-cli", "version": "1.0.0", "description": "腳手架工具", "bin": { "fe-cli": "./bin/cli.js" }, ... }

增加bin,是為了"npm link"正常使用,“fe-cli“作為命令列的名字,下面我們可以測試下~

  • 然後把這個命令對映到全域性 🔗

JavaScript npm link

  • 執行完成,效果是這樣的 ✅

image.png

  • 然後測試下,在命令列輸入fe-cli執行 ⚙️

image.png

Hello fency-cli 就是cli檔案的列印結果,然後只要你改動某個檔案,會同步更新到全域性的。

獲取版本號 🏷

  • 一般專案都會有版本號,而版本號代表功能的迭代,所以我們先做個cli的版本號,版本號都是與package.json裡的version有關,看下面的🌰

```JavaScript const { program } = require('commander') const package=require('../package.json')

//獲取package.json的版本號 program.version(package.version)

//解析命令列的指令,必須要加上,不然打印不出資訊 program.parse(process.argv) ```

  • 然後測試下,輸入 fe-cli -V 或者 fe-cli --version

image.png

program.version第二個引數沒自定義的話,預設就是-V或者--version,要是想支援-v, 就要加上第二個引數,比如program.version(package.version, '-v, --version')

新建業務的專案

一般我們新建業務的專案時,流程大概是這樣,新建一個資料夾和命名,然後拷貝通用框架到該專案,然後把該專案推送到git倉庫。現在我們要把這些流程改為自動化流程~👀

Tips: 克隆通用框架的git地址或者推送到git倉庫之前,要設定好ssh,不然沒法用。

廢話不多說,start~🙈

  • 準備互動式的回答 因為我們專案會有兩個型別,一個是前臺的框架,另一個是後臺的框架,所以我是這麼準備,先讓使用者用建立的命令列,然後輸入資料夾的名字和描述(輸入後要判斷是否有同名的,如果有同名就提醒使用者),然後選擇一個框架,選擇後就可以開始拷貝。

在src資料夾新建一個question.js

```JavaScript const fse=require('fs-extra')

const create = [ { name:'conf', type:'confirm', message:'🆕 是否建立新的專案?' },{ name:'name', message:'👉 請輸入專案名稱:', validate:function(val){ if(!val){ return '親,你忘了輸入專案的名稱哦~' } if(fse.existsSync(val)){ return '當前目錄已存在同名的專案,請更換專案名' } return true }, when: res => Boolean(res.conf) },{ name:'desc', message:'💬 請輸入專案的描述:', when:res=>Boolean(res.conf) },{ name:'template', type:'list', message:'🔜 請選擇一個框架?', choices:[ { key:'a', name:'普通通用框架', value:'', //前臺通用框架的git地址 }, { key:'b', name:'中後臺通用框架', value:'', //中後臺的通用框架git地址 } ], filter:function(val){ return val.toLowerCase() }, when: res =>{ Boolean(res.conf) } } ]

module.exports={ create } ```

  • 增加建立檔案的邏輯,在src資料夾下面新建一個create.js

```JavaScript

const download = require('download-git-repo') const ora = require('ora') const fse = require('fs-extra') const handlebars = require('handlebars') const myChalk = require('../utils/chalk')

const { red, yellow, green } = myChalk

function createProject(project) { //獲取使用者輸入,選擇的資訊 const { template, name, desc } = project; const spinner = ora("正在拉取框架..."); spinner.start(); download(template, name, { clone: true }, async function (err) { if (err) { red(err); spinner.text = red(拉取失敗. ${err}) spinner.fail() process.exit(1); } else { spinner.text = green(拉取成功...) spinner.succeed() spinner.text = yellow('請稍等,. 正在替換package.json中的專案名稱、描述...') const multiMeta={ project_name: name, project_desc: desc } const multiFiles=[ ${name}/package.json ] // 用條件迴圈把模板字元替換到檔案去 for (var i = 0; i < multiFiles.length; i++) { // 這裡記得 try {} catch {} 哦,以便出錯時可以終止掉 Spinner try { // 等待讀取檔案 const multiFilesContent = await fse.readFile(multiFiles[i], 'utf8') // 等待替換檔案,handlebars.compile(原檔案內容)(模板字元) const multiFilesResult = await handlebars.compile(multiFilesContent)(multiMeta) // 等待輸出檔案 await fse.outputFile(multiFiles[i], multiFilesResult) } catch (err) { // 如果出錯,Spinner 就改變文字資訊 spinner.text = red(專案建立失敗. ${err}) // 終止等待動畫並顯示 X 標誌 spinner.fail() // 退出程序 process.exit(1) } } // 如果成功,Spinner 就改變文字資訊 spinner.text = yellow(專案已建立成功!) // 終止等待動畫並顯示 ✔ 標誌 spinner.succeed() } }); }

module.exports = createProject ```

  • 然後處理使用者輸入的create命令列 cli.js增加一段程式碼

```JavaScript const inquirer = require('inquirer') const package = require('../package.json') const question = require('../src/question') const myChalk = require('../utils/chalk') const createProject = require('../src/create')

const { red } = myChalk /* 建立專案 /

program .command('create') .description('建立一個專案') .action(function(){ inquirer.prompt(question.create).then(async answer => { if(answer.conf){ createProject(answer) }else{ red(🆘 您已經終止此操作 🆘) } }).catch(err=>{ red(❌ 程式出錯 ❌) process.exit(1); }) }) ```

  • 最後我們看看效果

image.png

Cool~

把新專案推送到git倉庫

一般公司都會有自己的git倉庫,以供多個小夥伴一起開發用,所以要實現新專案推送到git倉庫的自動流程,但前提要在遠端倉庫新建一個庫,然後複製git地址。(目前只能這樣,小夥伴如果有更好的辦法,不妨說下,在此非常感謝🙏)

  • question.js增加一個互動式答案 JavaScript const pushGit=[ { name:'url', type:'input', message:'🌲 請輸入遠端倉庫的地址:', } ]

  • 增加推送遠端倉庫的功能,在src資料夾新建一個git檔案 ```JavaScript const execa = require('execa') const ora = require('ora') const spinner = ora('git pushing...\n') const myChalk = require('../utils/chalk')

const { red, green } =myChalk

async function push(gitRemote) { const runCMD = (command, args) => { if (!args) { [command, ...args] = command.split(/\s+/); } return execa(command, args).catch((err) => { spinner.fail( red("推送失敗,請檢查遠端倉庫地址對不對") ); }); }; await runCMD("echo unicorns"); await runCMD("git init"); await runCMD(git remote add origin ${gitRemote}); await runCMD("git add ."); await runCMD("git commit -m init"); spinner.start(); await runCMD("git push origin master").then((res) => { if (res) { spinner.stop(); console.log(); console.log( green( " 🎉 推送成功辣~\n" + " \n" + " 😀 可以愉快開始打碼,願神獸保佑你,寫的程式碼永無bug\n" ) ); } }); }

module.exports = { push }

  • 最後新增推送的命令列 cli.js新增一段程式碼 ```JavaScript const git = require('../src/git')

/* 推送遠端倉庫 / program .command('pushGit') .description('推送到gitlab倉庫') .action(function(){ inquirer.prompt(question.pushGit).then(answer=>{ git.push(answer.url) }).catch(err => { red(❌ 程式出錯 ❌) process.exit(1); }) }) ```

這樣就能成功推送到遠端倉庫,沒出意外的話。

嗯,到此為止啦,如果有新功能的需求,可以自己拓展下。本人有時間的話,也會同步更新哈~

fency-cli的demo

如有不足的地方,請多多指教~🙏