詳解前端腳手架開發排坑全指南【前端提效必須上乾貨】

咱們業務中能夠經過Vue-cli腳手架快速生成vue項目,一樣咱們也能夠開發一款cli腳手架用於快速生成咱們平常提煉出來的業務基礎模型/架構。本文將詳細講解腳手架如何開發,所涉及到的技術細節和坑以及各類第三方包的講解,確保即便是小白同窗也能夠照着作出來本身的cli。html

裝逼大法!提高逼格!!升職加薪!!!贏娶白富美!!!!你還在等什麼?快快開始吧~~~前端

感受像作夢同樣
感受像作夢同樣

言歸正傳,首先思考一下咱們的腳手架要幫助咱們作什麼事情?好比,這裏咱們就實現一個vta-cli,經過在終端運行一個vta create my-app就能夠初始化咱們日出提煉出來的一套Vue+Ts+ElementUi的RBAC系統的基礎項目架構。有了這個目標,咱們就能夠拆解要實現的步驟了:vue

  • 支持終端命令vta
  • vta create my-app命令運行後,檢查當前文件名的存在與否狀況
  • 拉取們git上的模板項目到本地
  • 拷貝當前下載的資源到咱們目標地址
  • 更新package.json等文件內容(好比name、author、version字段更改等)
  • 在項目中初始化Git來管理項目
  • 自動安裝當前項目所須要的依賴
  • 運行app

👇下面咱們就一步一步講解具體實現過程,跟上隊別掉隊哈~~~node

✨✨初始化項目基礎架構

  • 首先建立文件夾,手動點擊建立也行,終端運行命令也行:
# 終端建立項目根文件夾並進入到根文件夾
mkdir vta-cli && cd vta-cli  # 建立bin和src文件夾 mkdir bin src  # bin下建立init.js做爲腳本的入口文件 cd bin && touch init.js  # 並在init.js中鍵入以下內容: #!/usr/bin/env node console.log('Hello,my bin!')  # 初始化npm的包管理文件, 根目錄下執行 # 該命令會詢問你不少配置參數,若是不想詢問直接在後面加-y參數便可 npm init 複製代碼

項目基本的目錄文件夾出來了,說下具體的目錄做用,bin文件夾用於存放咱們的命令入口文件,init.js做爲入口文件(命名隨你),src做爲咱們真正實現腳本命令邏輯的地方:linux

vta-cli項目目錄文件夾
vta-cli項目目錄文件夾
  • 緊接着咱們須要配置package.json文件,在裏面添加咱們的腳本命令。打開咱們的package.json文件,在裏面添加bin字段:
{
 "bin": {  "vta": "bin/init.js"  }, } 複製代碼

這就是咱們定義了vta這個能夠在終端運行的腳本命令,即運行vta這個命令的時候,程序會去運行咱們配置的bin/init.js這個腳本文件。其實,根據npm的機制,當install一個包的時候,會自動去查詢其定義的bin命令,並把他添加到node_modules/.bin文件中去,做爲shell的命令能夠去執行。所以當你的包安裝到局部的項目中,那麼其bin中的命令就是局部可運行的,安裝到全局中則變成了全局能夠運行的命令。ios

說明一下,並非必定要是js文件,其實在linux系統中一切皆文件,是沒有後綴名的規定的,至少爲了讓「人」好識別而已。git

重點強調一下:init.js文件的第一行,必定是第一行,咱們添加了#!/usr/bin/env node代碼,是指定了咱們腳本的運行環境,和自定在咱們運行vta命令的時候添加了node命令做爲前綴,即實際運行的是node vtagithub

  • 接下來,爲了方便咱們測試,咱們須要將這個包發不到本地的全局環境。咱們能夠經過以下命令:
# 終端運行命令(需在當前項目根目錄下)
npm link 複製代碼

注意,npm link是當咱們當前包link到本地的全局中,就比如如咱們安裝依賴時使用了-g參數把一些包裝到了全局環境同樣,是用來方便咱們本地開發時測試的,他可讓咱們開發的時候自動熱更新。若是不清楚npm link的小夥伴,能夠去npm官網查查npm link的用法再繼續往下學習。vue-cli

可是,我想說的時候,不少小夥伴在這塊可能會踩坑:typescript

  • 首先,最好把你的npm的鏡像源改成npm自己的鏡像源(若是你指定了爲淘寶等其餘的話);特別是你須要發佈npm倉庫的時候會失敗。
  • 其次,必定要在package.json的配置中把node_modules等無關的文件夾去掉(或者指定咱們須要的),也能夠經過.gitignore等配置文件忽略掉也能夠,或者.npmrc等。在哪裏設置均可以,由於npm配置取值是有一套前後順序的規則,有興趣的話能夠移步npm文檔查閱。這裏演示一下如何在package.json文件的配置:
{
 "files": [  "./bin",  "./src"  ], } 複製代碼

咱們經過在package.json文件中指定files文件夾目錄,即告訴npm咱們實際應該包含的真正文件有哪些,好比咱們只須要bin和src文件夾,一些默認的文件像package.json啊,其餘的一些基礎配置文件啊,即便你不添加,也會被默認包含進來的。這也是當咱們把這個包發佈到npm所須要配置的,也就是須要哪些文件發佈到npm倉庫上。

注意,也能夠經過排除的字段,exclude。可是,不少時候指定咱們須要哪些文件,可能更爲方便哦!再強調一遍,node_modules必定要排除掉,否則npm link會巨慢並且會失敗的機率大,當心踩坑~~

說的彷佛頗有道理表情包
說的彷佛頗有道理表情包
  • 測試命令
# 終端運行
vta  # 那麼腳本執行後,便會看到終端的輸出 # 說明腳本執行成功了 複製代碼

再次強調,init.js首行必定要添加沙棒,以下:

#!/usr/bin/env node
console.log('運行測試') 複製代碼

❤️❤️命令行界面的解決方案

commander.js是nodejs命令行界面的一個完整解決方案。能夠幫助咱們定義各類命令行命令/參數等等。好比咱們想定義create命令啊,或者-v做爲版本號查詢的參數等等。那就先看下怎麼使用吧:

  • 安裝
cnpm install commander -S
複製代碼
  • 引入使用
// 在init.js中引入
const { Command } = require('commander'); // 導入當前根目錄下的package.json文件, // 爲了獲取對應的字段值,好比版本version const package = require('../package'); // 初始化 const program = new Command(); 複製代碼
  • 定義版本命令和help命令的說明信息
// 
// 如此, program  .version(package.version, '-v, --version', 'display version for vta-cli')  .usage('<command> [options]'); //  複製代碼

經過調用version方法,定義命令行命令版本的功能,咱們即可以在命令行輸入vta -v獲得當前的版本信息。

調用usage方法,是定義的咱們的輔助命令(help)的提示的文案標題,相似於定義table的表頭的感受,以下圖,當咱們輸入vta -h時,就是定義的藍色框框內展現的部分:

版本信息演示代碼
版本信息演示代碼

注意,這裏version方法的第三個參數,是咱們定義的說明內容,如上圖的紅色部分。help默認也是這個值

  • 定義命令行參數
/**  * 定義vta的參數  */ program  .option('-y, --yes', 'run default action')  .option('-f, --force', 'force all the question');  /**  * 能夠經過判斷,當用戶輸入了對應的這些參數時,  * 咱們能夠作一些操做:  */ if (program.force) {  // do something.. } 複製代碼

經過option方法,定義咱們的命令行參數,比如vta -f,等同於vta --force。注意,第一個參數是定義命令行參數,包含一個短的名稱(1個字符)和一個長的名稱,不能多了。第二個參數,就是定義的說明內容。注意,判斷部分的代碼,只能使用長的名稱,不能判斷短的,例如program.f

  • 建立一個子命令

建立子命令是重要的一部分,好比咱們使用vue create my-app建立項目時, create就是vue命令的子命令,my-app是命令參數。這裏咱們也定義一個子命令:

/**  * 調用command方法,建立一個create命令,  * 同時create命令後面必須跟一個命令參數  * 若是你在終端運行vta create不加名稱,則會報錯提示用戶  */ program.command('create <name>')  // 定義該命令的描述  .description('create a vta template project')  // 爲該命令指定一些參數  // 最後咱們均可以解析到這些參數,而後根據參數實現對應邏輯  .option('-f, --force', '忽略文件夾檢查,若是已存在則直接覆蓋')  /**  * 最後定義咱們的實現邏輯  * source表示當前定義的name參數  * destination則是終端的cmd對象,能夠從中解析到咱們須要的內容  */  .action((source, destination) => {  /**  * 好比咱們這裏把實現邏輯放在了另外一個文件中去實現,  * 方便代碼解耦,  * 由於destination參數比較雜亂,其實仍是在此處先解析該參數對應再傳入使用吧  * 能夠定義一個解析的工具函數  */  new CreateCommand(source, destination)  }); 複製代碼

如圖,看下destination對象究竟是什麼?仍是滿多的內容。咱們須要關注的就是紅色框框的這部分,這裏就是咱們定義的該命令的全部參數的列表,咱們變量該列表,取圖中藍色的部分的值,解決--後面的部分,而後做爲key到整個cmd對象中取匹配,其值就是用戶輸入的參數的值。

cmd對象展現
cmd對象展現

好比,可能會定義一個解析的工具函數:

/**  * parseCmdParams  * @description 解析用戶輸入的參數  * @param {} cmd Cammander.action解析出的cmd對象  * @returns { Object } 返回一個用戶參數的鍵值對象  */ exports.parseCmdParams = (cmd) => {  if (!cmd) return {}  const resOps = {}  cmd.options.forEach(option => {  const key = option.long.replace(/^--/, '');  if (cmd[key] && !isFunction(cmd[key])) {  resOps[key] = cmd[key]  }  })  return resOps } 複製代碼

上述的解析方法實現方式和咱們vue-cli的差很少。

  • 完成解析
/**  * 切記parse方法的調用,必定要program.parse()方式,  * 而不是直接在上面的鏈式調用以後直接xxx.parse()調用,  * 否則就會做爲當前command的parse去處理了,從而help命令等都與你的預期不符合了  */ try {  program.parse(process.argv); } catch (error) {  console.log('err: ', error) } 複製代碼

最後必定要解析,不解析是拿不到對應參數program.parse(process.argv),也就是不會執行對應的命令等行爲的。切記!切記!切記!!!更詳細的命令請查詢commander文檔

🌞對目標路徑進行檢查

從上面的步驟咱們能夠看出,咱們已經定義好了vta create <name>的命令了,即當咱們運行vta create my-app命令的時候,就會初始化咱們定義的CreateCommand類了。下面咱們看看入如何實現這個邏輯:咱們首先建立src/command/CreateCommand.js這個文件來實現咱們的邏輯:

/**  * class 項目建立命令  *  * @description  * @param {} source 用戶提供的文件夾名稱  * @param {} destination 用戶輸入的create命令的參數  */ class Creator {  constructor(source, destination, ops = {}) {  this.source = source  this.cmdParams = parseCmdParams(destination)  this.RepoMaps = Object.assign({  repo: RepoPath, // 配置文件中放置的遠程地址常量  temp: path.join(__dirname, '../../__temp__'),  target: this.genTargetPath(this.source)  }, ops);  this.gitUser = {};  this.spinner = ora();  this.init();  }   // 其餘實例方法  // ... }  // 最終導出這個class module.exports = Creator; 複製代碼

咱們看下這個構造函數咱們用來作了什麼事情,首先就是把實例化時傳進來的參數賦值給this對象,供後面其餘實例方法中去使用。而後定義了RepoMaps屬性設置咱們的一些基礎參數,像項目模板的地址repo、咱們本地cli項目內部臨時存放的項目模板的地址temp、和最終咱們須要把項目安裝到的目標地址taregt。由於項目最終會安裝到終端運行的地址下的位置,而你的腳手架包是被安裝在其餘地址的。

而後定義了gitUser用於存放用戶的git信息,後面會經過自動執行命令獲取相關的信息,而後最後咱們會把信息塞到package.json文件中。

this.spinner = ora();就是實例化一個菊花圖,當咱們在執行命令的時候能夠調用this.spinner方法進行菊花轉呀轉!

下面咱們來實現這個init初始化的方法吧:

// 初始化函數
async init() {  try {  // 檢查目標路徑文件是否正確  await this.checkFolderExist();  // 拉取git上的vue+ts+ele的項目模板  // 存放在臨時文件夾中  await this.downloadRepo();  // 把下載下來的資源文件,拷貝到目標文件夾  await this.copyRepoFiles();  // 根據用戶git信息等,修改項目模板中package.json的一些信息  await this.updatePkgFile();  // 對咱們的項目進行git初始化  await this.initGit();  // 最後安裝依賴、啓動項目等!  await this.runApp();  } catch (error) {  console.log('')  log.error(error);  exit(1)  } finally {  this.spinner.stop();  } } 複製代碼

從上面代碼註釋能夠看到,咱們的init方法,就是把一系列操做一次調用執行便可。最後先看一下配置文件吧:

exports.InquirerConfig = {
 // 文件夾已存在的名稱的詢問參數  folderExist: [{  type: 'list',  name: 'recover',  message: '當前文件夾已存在,請選擇操做:',  choices: [  { name: '建立一個新的文件夾', value: 'newFolder' },  { name: '覆蓋', value: 'cover' },  { name: '退出', value: 'exit' },  ]  }],  // 重命名的詢問參數  rename: [{  name: 'inputNewName',  type: 'input',  message: '請輸入新的項目名稱: '  }] }  // 遠程Repo地址 // 你們開發階段,若是沒有本身的項目,能夠先調用個人這個地址練習 // 也能夠隨便一個地址練習均可以 exports.RepoPath = 'github:chinaBerg/vue-typescript-admin' 複製代碼

後面咱們將看看這一系列方法該如何實現?

🌞終端的菊花圖工具

首先介紹一下咱們的小菊花吧!咱們在執行各類操做的時候,好比拉模板數據等等,都是會有必定等待實際的,那麼這個等待過程,咱們能夠在終端有個小菊花轉轉轉,這樣 會給用戶更好的體驗,讓用戶知道當前腳本在執行加載,如圖(最左側有個小菊花在轉轉轉~~~):

轉動的ora菊花圖
轉動的ora菊花圖

ora就是這一款終端使用的菊花圖工具,下面看看如何使用吧!

  • 安裝
cnpm install ora -S
複製代碼
  • 使用
const ora = require('ora');
 // ora參數建立spinner文字內容 // 也能夠傳遞一個對象,設置spinner的週期、顏色等 // 調用start方法啓動,最終返回一個實例 const spinner = ora('Loading start')  // 開啓菊花轉轉 spinner.start();  // 中止 spinner.stop()  // 設置文案,後者菊花的color spinner.text = '正在安裝項目依賴文件,請稍後...'; spinner.color = 'green';  // 顯示轉成功的狀態 spinner.succeed('package.json更新完成'); 複製代碼

注意,文案的顏色,仍是得靠chalk輔助。後面會介紹chalk。上個圖片演示一下實際的運用:

更多詳細的用戶請查閱ora文檔

🌛五彩斑斕的控制檯

chalk是一款可讓咱們的控制檯打印出各類顏色/背景的內容的工具,由此咱們能夠鮮明的區分各類提示內容,以下圖(就問你騷不騷???):

chalk效果圖
chalk效果圖
  • 安裝
# 終端運行
cnpm i chalk -S 複製代碼
  • 使用
const chalk = require('chalk');
 // 好比,這裏定義一個log對象 exports.log = {  warning(msg = '') {  console.warning(chalk.yellow(`${msg}`));  },  error(msg = '') {  console.error(chalk.red(`${msg}`));  },  success(msg = '') {  console.log(chalk.green(`${msg}`));  } } 複製代碼

好比,上面咱們封裝了最簡單的log方法,用於打印各類類型的信息時展現帶顏色的內容。還有一點,咱們說一下上面提到了的如何配合ora使用吧:

const chalk = require('chalk');
const ora = require('ora');  const spinner = ora('Loading start')  // 開啓菊花轉轉 spinner.start(chalk.yellow('打印一個yellow色的文字')); 複製代碼

用法比較簡單,很少說了,更多用法仍是查閱文檔吧!

✨fs-extra文件操做

在詳細說明各個步驟實現的方式以前,咱們先說一下在cli中使用的文件操做的庫。node自己有fs操做,那麼咱們爲何還要引入fs-extra庫呢?是由於他徹底能夠用來取代fs的庫,省去了mkdirp``rimraf``ncp等庫等安裝引入。用於拷貝、讀取、刪除等文件操做,並且提供了更多的功能等等。

  • 安裝
cnpm install fs-extra
複製代碼

具體的api的方法,請查閱文檔fs-extra,後面講解各個步驟具體實現的時候也會說起到。

✨檢查文件夾是否合法

到了咱們運行vta create my-app的時候了,這時候咱們就要考慮了,若是當前位置已經存在了同名的文件夾,那麼咱們確定是不能直接覆蓋的,而是要給用戶選擇,好比覆蓋、從新建立一個新的文件夾、退出,以下圖:

而後根據用戶的不一樣選擇做出對於的操做。下面咱們看這個文件夾檢查的具體實現:

checkFolderExist() {
 return new Promise(async (resolve, reject) => {  const { target } = this.RepoMaps  // 若是create附加了--force或-f參數,則直接執行覆蓋操做  if (this.cmdParams.force) {  await fs.removeSync(target)  return resolve()  }  try {  // 不然進行文件夾檢查  const isTarget = await fs.pathExistsSync(target)  if (!isTarget) return resolve()   const { recover } = await inquirer.prompt(InquirerConfig.folderExist);  if (recover === 'cover') {  await fs.removeSync(target);  return resolve();  } else if (recover === 'newFolder') {  const { inputNewName } = await inquirer.prompt(InquirerConfig.rename);  this.source = inputNewName;  this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`);  return resolve();  } else {  exit(1);  }  } catch (error) {  log.error(`[vta]Error:${error}`)  exit(1);  }  })  } 複製代碼

具體講解:

  1. 咱們定義了這個方法,返回的是一個Promise對象。
  2. 咱們判斷用戶在輸入vta create my-app的時候有沒有在後面加-f的參數,若是添加了參數則是告訴咱們忽略檢查直接日後走,就是默認覆蓋的操做。經過調用fs.removeSync(target);方法進行移除須要覆蓋的文件;
  3. 不然的話,咱們則須要進行文件夾檢查的實現邏輯了。經過await fs.pathExistsSync(target)邏輯進行判斷當前文件夾名稱是否已經存在,若是不存在則resolve告訴程序執行文件夾檢查成功以後的程序。
  4. 若是同名的則給用戶提示,讓用戶選擇操做。下面將講解如何在命令行進行交互。

❤️命令行交互

說到命令行交互,就要提到一個比較程序的庫inquirer,這是一個用於node環境下進行命令行交互的庫,支持單選、多選、用戶輸入、confirm詢問等等操做。

  • 安裝
cnpm i inquirer -S
複製代碼
  • 使用
const inquirer = require('inquirer');
 // 定義詢問的參數 // type表示詢問的類型,是單選、多選、確認等等 // name能夠理解爲當前交互的標識符,其值爲交互的結果 const InquirerConfig = {  // 文件夾已存在的名稱的詢問參數  folderExist: [{  type: 'list',  name: 'recover',  message: '當前文件夾已存在,請選擇操做:',  choices: [  { name: '覆蓋', value: 'cover' },  { name: '建立一個新的文件夾', value: 'newFolder' },  { name: '退出', value: 'exit' },  ]  }],  // 重命名的詢問參數  rename: [{  name: 'inputNewName',  type: 'input',  message: '請輸入新的項目名稱: '  }] }  // 使用 // 經過當前標識符獲取交互的結果 // 好比,以下是一個單選的演示 const { recover } = await inquirer.prompt(InquirerConfig.folderExist);  // 若是用戶選中的是「覆蓋」選項 if (recover === 'cover') {  await fs.removeSync(target);  return resolve(); // 若是用戶選中的是「建立新文件夾」選中 } else if (recover === 'newFolder') {  // 再次建立一個用戶輸入的交互操做  // 讓用戶輸入新的文件夾名稱  const { inputNewName } = await inquirer.prompt(InquirerConfig.rename);  this.RepoMaps.target = this.genTargetPath(`./${inputNewName}`);  return resolve(); // 若是用戶選的是「退出」選項 } else {  exit(1); } 複製代碼
  1. 若是用戶選擇了覆蓋,咱們就移除文件夾而後reolve
  2. 若是用戶選擇了建立新的文件夾,那麼咱們就再給出一個用於輸入的終端,讓用戶輸入新的文件夾名稱。在用戶輸入完成後,咱們把target的目標地址更新掉。
  3. 若是用戶選擇退出,咱們則調用process.exit方法進行退出當前node程序便可。

❤️拉取git等遠程倉庫代碼

在進行了文件夾監測完成以後,就應該是要下載咱們在git上的項目資源了。下載資源咱們是經過download-git-repo這個庫來實現的。

  • 安裝
cnpm install download-git-repo -S
複製代碼
  • 使用
const path = require('path');
const downloadRepo = require('download-git-repo');   // 下載repo資源  downloadRepo() {  // 菊花轉起來~  this.spinner.start('正在拉取項目模板...');  const { repo, temp } = this.RepoMaps  return new Promise(async (resolve, reject) => {  // 若是本地臨時文件夾存在,則先刪除臨時文件夾  await fs.removeSync(temp);  /**  * 第一個參數爲遠程倉庫地址,注意是類型:做者/庫  * 第二個參數爲下載到的本地地址,  * 後面還能夠繼續加一個配置參數對象,最後一個是回調函數,  */  download(repo, temp, async err => {  if (err) return reject(err);  // 菊花變成對勾  this.spinner.succeed('模版下載成功');  return resolve()  })  })  } 複製代碼

主要邏輯就是把資源下載到咱們當前的臨時文件夾位置,若是臨時文件夾已經存在了那麼就先刪除臨時文件夾。

👍把資源拷貝到目標地址,並移除無關文件

上面經過git上資源的下載,咱們是下載到了cli目錄內的臨時文件內,那麼咱們還須要把資源移動到咱們指定的位置,而且刪除沒必要要的資源。因此咱們這邊會在utlis裏面封裝一個公共函數,用於資源的拷貝:

  • 拷貝函數的封裝
/**  * copyFiles 拷貝下載的repo資源  * @param { string } tempPath 待拷貝的資源路徑(絕對路徑)  * @param { string } targetPath 資源放置路徑(絕對路徑)  * @param { Array<string> } excludes 須要排除的資源名稱(會自動移除其全部子文件)  */ exports.copyFiles = async (tempPath, targetPath, excludes = []) => {  const removeFiles = ['./git', './changelogs']  // 資源拷貝  await fs.copySync(tempPath, targetPath)   // 刪除額外的資源文件  if (excludes && excludes.length) {  await Promise.all(excludes.map(file => async () =>  await fs.removeSync(path.resolve(targetPath, file))  ));  } } 複製代碼
  • 調用
// 拷貝repo資源
async copyRepoFiles() {  const { temp, target } = this.RepoMaps  await copyFiles(temp, target, ['./git', './changelogs']); } 複製代碼

這裏,咱們移除了項目中自己含有的./git./changelogs等文件,由於這些是該git項目須要的內容,而咱們實際是不須要的。

👍自動更新package.json文件

經過上面的操做,咱們已經把資源拷貝到咱們的目標地址了。那麼咱們還想自動把package.json中的name、version、author等字段更新成咱們須要的,應該怎麼作呢?

/**  * updatePkgFile  * @description 更新package.json文件  */ async updatePkgFile() {  // 菊花轉起來!  this.spinner.start('正在更新package.json...');  // 獲取當前的項目內的package.json文件的據對路徑  const pkgPath = path.resolve(this.RepoMaps.target, 'package.json');  // 定義須要移除的字段  // 這些字段自己只是git項目配置的內容,而咱們業務項目是不須要的  const unnecessaryKey = ['keywords', 'license', 'files']  // 調用方法獲取用戶的git信息  const { name = '', email = '' } = await getGitUser();   // 讀取package.json文件內容  const jsonData = fs.readJsonSync(pkgPath);  // 移除不須要的字段  unnecessaryKey.forEach(key => delete jsonData[key]);  // 合併咱們須要的信息  Object.assign(jsonData, {  // 以初始化的項目名稱做爲name  name: this.source,  // author字段更新成咱們git上的name  author: name && email ? `${name} ${email}` : '',  // 設置非私有  provide: true,  // 默認設置版本號1.0.0  version: "1.0.0"  });  // 將更新後的package.json數據寫入到package.json文件中去  await fs.writeJsonSync(pkgPath, jsonData, { spaces: '\t' });  // 中止菊花  this.spinner.succeed('package.json更新完成!'); } 複製代碼

這一塊,上面代碼註釋已經寫的很是清晰了,看一遍應該就曉得過程邏輯了吧!!!至於其中獲取用戶git信息的邏輯,後面立刻會講解到!!!

🌟獲取Git信息

如今咱們看下如何獲取git信息的,咱們定義了一個公共的方法getGitUser:

/**  * getGitUser  * @description 獲取git用戶信息  */ exports.getGitUser = () => {  return new Promise(async (resolve) => {  const user = {}  try {  const [name] = await runCmd('git config user.name')  const [email] = await runCmd('git config user.email')  // 移除結尾的換行符  if (name) user.name = name.replace(/\n/g, '');  if (email) user.email = `<${email || ''}>`.replace(/\n/g, '')  } catch (error) {  log.error('獲取用戶Git信息失敗')  reject(error)  } finally {  resolve(user)  }  }); } 複製代碼

咱們都知道,在終端想查看用戶的git信息,那麼只須要鍵入git config user.name便可,git config user.email能夠獲取用戶的郵箱。那麼咱們一樣的在腳本中也執行這樣的命令不就能夠獲取到了嗎?

那麼剩下的就是如何在終端執行shell命令呢?

✨✨node腳本中,執行指定的shell命令

node是經過開啓一個子進程來執行腳本命令的,child_process說明是node提供的一個開啓子進程的方法。因而咱們能夠封裝一個方法用於執行子進程:

// node的child_process能夠開啓一個進程執行任務
const childProcess = require('child_process');   /**  * runCmd  * @description 運行cmd命令  * @param { string } 待運行的cmd命令  */ const runCmd = (cmd) => {  return new Promise((resolve, reject) => {  childProcess.exec(cmd, (err, ...arg) => {  if (err) return reject(err)  return resolve(...arg)  })  }) } 複製代碼

因此上述獲取git詳情的操做其實就是調用的這個方法,讓node開啓一個子進程去運行咱們的git命令,而後將結果返回出來。

❤️初始化git文件

// 初始化git文件
 async initGit() {  // 菊花轉起來  this.spinner.start('正在初始化Git管理項目...');  // 調用子進程,運行cd xxx的命令進入到咱們目標文件目錄  await runCmd(`cd ${this.RepoMaps.target}`);   // 調用process.chdir方法,把node進程的執行位置變動到目標目錄  // 這步很重要,否則會執行失敗(由於執行位置不對)  process.chdir(this.RepoMaps.target);   // 調用子進程執行git init命令,輔助咱們進行git初始化  await runCmd(`git init`);  // 菊花停下來  this.spinner.succeed('Git初始化完成!'); } 複製代碼

這一塊也是調用的咱們封裝的方法執行git命令而已。可是必定要注意、process.chdir(this.RepoMaps.target);變動進程的執行位置,若是變動目錄失敗會拋出異常(例如,若是指定的 directory 不存在)。這步操做很是重要,切記!!切記!!!詳細能夠查閱process.chdir說明

🌟安裝依賴

最後咱們就須要自動暗轉項目依賴了。本質也是調用子進程執行npm命令就能夠了。這裏咱們直接指定了使用淘寶的鏡像源,小夥伴們也能夠擴展,根據用戶的選擇指定npm、yarn和其餘鏡像源等等,盡情發揮吧!!!

// 安裝依賴
 async runApp() {  try {  this.spinner.start('正在安裝項目依賴文件,請稍後...');  await runCmd(`npm install --registry=https://registry.npm.taobao.org`);  await runCmd(`git add . && git commit -m"init: 初始化項目基本框架"`);  this.spinner.succeed('依賴安裝完成!');   console.log('請運行以下命令啓動項目吧:\n');  log.success(` cd ${this.source}`);  log.success(` npm run serve`);  } catch (error) {  console.log('項目安裝失敗,請運行以下命令手動安裝:\n');  log.success(` cd ${this.source}`);  log.success(` npm run install`);  }  } 複製代碼

最後👍👍👍

vta-cli腳手架git源碼地址,有興趣的小夥伴能夠查閱代碼實現。也可使用vta-cli快速初始化Vue+Ts+ElementUi的RBAC後臺管理系統的基礎架構。安裝vta-cli的方法:

# 安裝cli
npm i vta-cli -g  # 初始化項目 vta create my-app 複製代碼

vue-typescript-admin項目模板將會很快完善起來!!!也歡迎小夥伴們一塊兒貢獻代碼哦~~

關於cli開發的講解,到這就基本結束了!!!上面涵蓋了常見的技術實現方案和注意細節,項目能夠無痛上手的~~~有興趣的小夥伴們能夠照着封裝本身的cli,把業務通用的場景解決方案抽離處理,提高本身的開發效率吧!最後,我是大家的老朋友愣錘,歡迎👏👏點贊👍👍收藏💗💗哦~~~

點贊👍、收藏👋、分享防走丟哦!!!須要的時候能夠拿出來對着開發~~~

❤️❤️❤️更新留白

此處將留做後續更多和腳手架開發相關的優秀庫的展現地址,後續會繼續更新~~~

👍👍👍做者其餘文章推薦

本文使用 mdnice 排版

相關文章
相關標籤/搜索