手把手教你使用nodejs编写一个【使用远程仓库模板,快速创建项目模块】的cli(命令行)
目錄
- 實現(xiàn)步驟
- 初始化cli項目
- 項目目錄
- 創(chuàng)建交互式命令
- 拉取遠程倉庫代碼,讀取倉庫中的模板
- 拉取遠程倉庫代碼
- ora 終端 loading
- 讀取倉庫中的模板
- 將選擇的模板復制寫入目標項目
- Commands
- 完成
系列文章
- 手把手教你使用nodejs編寫cli(命令行)
- 手把手教你使用nodejs編寫cli(命令行)——拉取遠程倉庫作為代碼模板
- 手把手教你使用nodejs編寫一個【使用遠程倉庫模板,快速創(chuàng)建項目模塊】的cli(命令行)
在工作過程中,很多時候我們會遇到一些很相似的需求,這時候我們會進行【搬磚】。這時候我們經(jīng)常會復制一份相似的代碼,改一改就成了。
但是這樣有兩個問題:
首先,從其他業(yè)務(wù)模塊復制過來的代碼中需要刪刪減減,有些繁瑣,效率較低;
其次,即便復制的是一個基礎(chǔ)模板代碼,也會面臨手動 copy 的低效問題;
還有,一般如果同事之間用一個代碼模板庫,需要將之 git clone 至本地磁盤,一般手動 copy 很少會 git pull 代碼,這樣就會造成代碼模塊版本滯后。
我們要實現(xiàn)的就是實現(xiàn)一個能【讀取遠程倉庫模板,快速創(chuàng)建項目模塊的腳手架工具】,以下為流程圖與操作動態(tài)圖:
實現(xiàn)步驟
1. 交互式命令,獲取用戶選擇;
2. 拉取遠程倉庫代碼,讀取倉庫中的模板;
3. 根據(jù)用戶選擇,將選擇的模板復制寫入目標項目。
初始化cli項目
創(chuàng)建一個新項目 create-modules-tools,并且使用 Node.js 的 esm 模塊,并把 Node.js 升至 13.2.0 以上版本以支持 esm 模塊。
esm 模塊不支持文件擴展名的自動解析以及導入具有索引文件的目錄的能力,后面需要注意不要丟掉 index.js、.js
package.json
{..."type": "module","bin": {"create-module": "bin/index.js"}... }項目目錄
create-modules-tools ├── bin │ └── index.js # CLI執(zhí)行入口文件 ├── commands # 其他命令,如 --help ├── questions # 交互式命令行 ├── utils # 工具函數(shù),主要是獲取腳手架項目的物理路徑函數(shù) ├── .gitignore ├── package.json ├── package-lock.json ├── README.md # 使用文檔 └── CHANGELOG.md # 版本記錄文件創(chuàng)建交互式命令
這里我們使用 inquirer 文檔,安裝 inquirer
npm i inquirer1. 設(shè)置遠程倉庫地址 questions/remoteUrl.js
import fs from "fs";export default (templatesDirRootPath) => {let remoteUrl = ''if (fs.existsSync(`${templatesDirRootPath}/defaultRemoteUrl.txt`)) {remoteUrl = fs.readFileSync(`${templatesDirRootPath}/defaultRemoteUrl.txt`, "utf-8")}return {type: 'input',name: 'remoteUrl',default: remoteUrl || undefined,message: '請設(shè)置遠程倉庫地址',validate(val) {// git倉庫的正則表達式 http://cn.voidcc.com/question/p-qlprjeax-kd.htmlconst gitRemoteUrlReg = /(\w+:\/\/)([email protected])*([\w\d\.]+)(:[\d]+){0,1}\/*(.*)/if (!val) {return '請設(shè)置遠程倉庫地址'} else if (!gitRemoteUrlReg.test(val)) {return '遠程倉庫地址格式錯誤,請重新輸入'} else {return true;}}} }2. 要創(chuàng)建的目錄名稱 questions/createDir.js
export default () => ({type: 'input',name: 'createDir',message: '請輸入要創(chuàng)建的目錄名稱',validate(val) {if (val) return true;return '請輸入要創(chuàng)建的目錄名稱'} })3. 選擇模板 questions/selectModules.js
import inquirer from "inquirer";export default async (choices = []) => inquirer.prompt([{type: 'list',name: 'tplModule',message: '請選擇模板',choices} ])4. 交互問答入口文件 questions/index.js
import inquirer from 'inquirer'import remoteUrl from "./remoteUrl.js"; import createDir from "./createDir.js";export default (templatesDirRootPath) => inquirer.prompt([remoteUrl(templatesDirRootPath), // 設(shè)置遠程倉庫地址createDir(), // 要創(chuàng)建的目錄名稱 ])5. 腳手架入口文件 bin/index.js
#!/usr/bin/env node// chalk 美化輸出 import chalk from 'chalk'import questions from '../questions/index.js' import { getTemplatesDirRootPath } from '../utils/index.js' import selectModules from '../questions/selectModules.js'// 存放模板文件的目錄路徑 const templatesDirRootPath = getTemplatesDirRootPath()const config = await questions(templatesDirRootPath)console.log(chalk.blue('config:'), config);// 先創(chuàng)建目標目錄,檢查用戶輸入的目錄是否已存在 fs.mkdirSync(`./${config.createDir}`) if (!fs.existsSync(templatesDirRootPath)) {fs.mkdirSync(templatesDirRootPath) }執(zhí)行 npm link 將該模塊鏈接到全局npm模塊中
執(zhí)行 create-module,可以得到用戶輸入的結(jié)果
拉取遠程倉庫代碼,讀取倉庫中的模板
上面我們已經(jīng)得到了遠程倉庫的地址,但是 Node.js 是沒有能力讀取遠程 git 倉庫中的目錄的,將其clone 至本地,然后在本地讀取。
這里我們應該考慮用戶一般不會經(jīng)常切換模板倉庫,因此我們設(shè)計為:
在用戶初次 git clone 后不將該本地模板倉庫刪除,而是保留。在用戶下次使用腳手架時,我們?nèi)z查該模板是否存在于本地,如果存在,則執(zhí)行 git pull,這樣會更快的拉新讀取。如果不存在則執(zhí)行 git clone
同時支持用戶主動清空腳手架中的本地模板,create-module --empty
拉取遠程倉庫代碼
utils/index.js
import path from 'path' import { fileURLToPath } from 'url' import process from "process";// 獲取絕對路徑 export const getRootPath = (pathUrl) => {const __dirname = fileURLToPath(import.meta.url)return path.resolve(__dirname, `../${pathUrl}`) }// 設(shè)置模板緩存目錄 export const getTemplatesDirRootPath = () => {// 存放模板文件的文件夾名稱const templatesDirPath = 'CreateModulesProjects'const processCwd = process.cwd()const processCwdArr = processCwd.split('/')// 存放模板文件的目錄路徑return `/${processCwdArr[1]}/${processCwdArr[2]}/${templatesDirPath}` }bin/index.js
// ...// 獲取遠程倉庫目錄名稱 const getGitRemoteFilename = () => {const arr = config.remoteUrl.split('/')return arr[arr.length - 1].split('.')[0] }// 遠程倉庫目錄名稱 const gitRemoteFilename = getGitRemoteFilename() console.log(chalk.blue('gitRemoteFilename:'), gitRemoteFilename);let getGitRemoteResult = {} // 拉取遠程倉庫結(jié)果// 獲取遠程倉庫代碼 const getGitRemote = () => {// 該遠程倉庫是否已經(jīng)存在于本地const exist = fs.existsSync(`${templatesDirRootPath}/${gitRemoteFilename}/.git`)const spinners = [ora("讀取中...")];spinners[0].start();if (exist) { // 存在,則 git pullgetGitRemoteResult = execaSync(`git`, ['config', 'pull.rebase', 'false'], {cwd: `${templatesDirRootPath}/${gitRemoteFilename}`,})getGitRemoteResult = execaSync(`git`, ['pull'], {cwd: `${templatesDirRootPath}/${gitRemoteFilename}`,})}else { // 不存在,則 git clonetry {getGitRemoteResult = execaSync(`git`, ['clone', '-b', 'master', config.remoteUrl], {cwd: templatesDirRootPath,})} catch (err) {fs.rmdirSync(`./${config.createDir}`)console.error(err)}}fs.writeFile(`${templatesDirRootPath}/defaultRemoteUrl.txt`, config.remoteUrl, err => {if (err) console.log(err);})// console.log(chalk.blue('getGitRemoteResult:'), getGitRemoteResult);// failed 一定返回,沒有failed字段也代表失敗if (getGitRemoteResult.failed === true || getGitRemoteResult.failed === undefined || getGitRemoteResult.failed === null) {spinners[0].fail("讀取遠程倉庫失敗!");} else {spinners[0].succeed("讀取遠程倉庫成功!");} }getGitRemote()ora 終端 loading
上面拉取遠程倉庫代碼子進程使用了 ora 來實現(xiàn) loading 效果,如下圖:
讀取倉庫中的模板
// ...import selectModules from '../questions/selectModules.js'// 讀取并選擇模板 const getAndSelectModule = async () => {// 獲取遠程倉庫中的目錄const tplDirs = fs.readdirSync(`${templatesDirRootPath}/${gitRemoteFilename}`)console.log('tplDirs', tplDirs);// 可選的模板const tplModules = []for (const item of tplDirs) {// 篩選目錄并將 .git 排除if (fs.statSync(`${templatesDirRootPath}/${gitRemoteFilename}/${item}`).isDirectory() && item !== '.git'){tplModules.push({value: item,name: item,})}}// 選擇模板const selectedModule = await selectModules(tplModules)// 已選擇的模板 selectedModule.tplModuleconsole.log(selectedModule);return selectedModule }const selectedModule = await getAndSelectModule() console.log('selectedModule', selectedModule);將選擇的模板復制寫入目標項目
這里使用 fs-extra 復制文件
import fse from 'fs-extra'/*** 進行copy* @param selectedModule 選擇的模塊*/ const fsCopy = (selectedModule) => {try {fse.copy(`${templatesDirRootPath}/${gitRemoteFilename}/${selectedModule.tplModule}`,`./${config.createDir}`,(err) => {if (err) {console.error(err);} else {// git add 新創(chuàng)建的文件execa(`git`, ['add', './'], { cwd: './', }, err => {if (err) console.log(err);})console.log(chalk.green('創(chuàng)建模塊成功!'))}})} catch (err) {fs.rmdir(`./${config.createDir}`)console.error(err)} }fsCopy(selectedModule)至此,主體功能已完成。
Commands
另外,如果需要添加其他命令,如上文說的 create-modlue --help、create-module --empty 可以在使用 process.argv 獲取命令行參數(shù)進行相關(guān)邏輯,這里不再贅述了。
import fs from 'fs' import { program } from 'commander'; import empty from "./empty.js"; const { version } = JSON.parse(await fs.readFileSync(new URL('../package.json', import.meta.url)))program.version(version, '-v, --version').option('-e, --empty', '清空本地模板緩存')program.parse(process.argv);const options = program.opts();switch (true) {case options.empty:empty()break; }commands/empty.js
import fs from "fs"; import { getTemplatesDirRootPath } from '../utils/index.js'export default () => {const templatesDirRootPath = getTemplatesDirRootPath()if (fs.existsSync(templatesDirRootPath)) {// recursive <boolean> 如果為 true,則執(zhí)行遞歸刪除。 在遞歸模式下,操作將在失敗時重試。 默認值: false。fs.rmSync(templatesDirRootPath, { recursive: true })// 重新創(chuàng)建一個空目錄fs.mkdirSync(templatesDirRootPath)console.log('清空本地模板緩存成功!');process.exit(1)} else {console.log('清空本地模板緩存成功!');} }完成
將其發(fā)布至 npm,執(zhí)行 npm i create-modlues-tools -g 安裝,執(zhí)行 create-module 即可使用啦
源碼:https://gitee.com/yanhuakang/create-modules-tools
總結(jié)
以上是生活随笔為你收集整理的手把手教你使用nodejs编写一个【使用远程仓库模板,快速创建项目模块】的cli(命令行)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Google翻译API(B/S调用和C/
- 下一篇: 智能钢琴小提琴吉他教育曲谱识别(5线谱/