提交Bug修復時容易忘記同步調整版本號,如果遺漏了很容易造成漏更新等問題,所以打算將版本號修改做成硬性的檢測規範。
實現效果跟ESlint
的檢測差不多,Git
提交後自動執行程式碼檢測,不符合規範就報錯撤回提交,符合規範就通過提交。
分析
主要要解決以下幾個問題:
觸發檢測的方式
既然想到
ESlint
,那第一個念頭是給ESlint
增加自定義外掛。但仔細又想了想,因為檢測的是非JavaScript檔案,而且也不是程式碼那種邏輯檢測,只是在提交前做一下相應的檔案是否有修改,實際上並不是很適合的場景。最適合的還是直接用
Git
的鉤子,ESlint
就是利用husky
在相關鉤子中呼叫檢測。之前寫了篇husky7 + commitlint + lint-staged 記錄,所以流程比較熟悉,只需要在
.husky
資料夾下新增相關鉤子名稱的檔案,然後直接寫shell
指令碼就能出發。單純的指令碼並不利於維護,所以打算跟
ESlint
一樣,寫成命令列式的,也方便後期增加功能。怎樣知曉檔案變動
這個就是
Git
自帶的功能了,只需要利用git diff
,對比package.json
檔案,就會輸出對比資訊。但文字的不好分析,一下子又沒找到相關可用的外掛,於是用peggy自己寫了個相關的解析器,將文字轉換成方便解析的json資料。
提交失敗後終止提交
shell
指令碼成功返回0
、失敗返回非0
,Node.js
提供了相關方法process.exit(1)
。如果有不懂的方式實際上翻一翻
lint-staged
都能找到所需的答案,畢竟現成的例子在那裡。提供可以主動繞過提交的方式
既然是加入鉤子中,不需要的時候肯定不能刪了鉤子再提交。那隻能在提交邏輯中做跳過檢測。
最合適的是在提交訊息的
body
中攜帶指定的關鍵字,當程式識別到關鍵字就跳過檢測邏輯即可。
關鍵邏輯
提供命令列呼叫命令
建立一個空專案,新增bin
欄位,為我們的程式增加相關的呼叫命令
"bin":{
"check": "./dist/index.js"
}
對應的check
是命令列的呼叫名稱,對應的值是要執行的JavaScript檔案:
#!/usr/bin/env node
console.log('hello')
這樣就能列印出hello
了。
當然這樣還不夠,命令列程式還可能攜帶引數,或是其他功能,所以可以配合Commander.js
輔助處理命令列命令。
由於和業務程式碼寫在一起,只能提供大概的示例:
import { Command } from "commander";
const program = new Command();
program
.command("change")
.option(
"--commitMsgPath <filePath>",
"當 commit message 中包含指定字串時跳過當前命令檢測"
)
.description("檢測版本號是否有變動")
.action(async ({ commitMsgPath }: { commitMsgPath: string }) => {
try {
let msg: string[];
if (commitMsgPath) msg = readGitMessage(commitMsgPath);
const flag = await checkVersion(msg);
if (!flag) throw new Error("請修改版本號再提交");
} catch (e) {
console.error(`${DATA.NAME}: ${e.name} ${e.message}`);
process.exit(1);
}
});
program.parse(process.argv);
呼叫git diff判斷資料
執行git命令可以使用simple-git
這個庫,功能很豐富,但可惜diff的返回資料是純文字,並不方便處理。利用之前寫好的簡單的diff格式的解析器,處理成可讀的json資料。
剩下的就很簡單了,將返回的diff資料傳入解析器,然後判斷解析器中是否有相應的關鍵字version
,有即代表版號是有修改過的,有需要可以做更細緻的版本號校驗。
import simpleGit, { SimpleGit } from "simple-git";
const GitDiffParser = require("../lib/gitDiffParser");
/**
* 檢測版本號是否變動
* @param msg
*/
export const checkVersion = async (msg?: string[]) => {
let flag = false;
const git: SimpleGit = simpleGit({
baseDir: process.cwd(),
binary: "git",
});
// 檢測 git message 中是否有指定文字 有則跳過版本檢測
if (msg && msg.some((item) => item === DATA.SKIP_MESSAGE_KEY)) return true;
// 判斷版本號是否有變化
const diff = await git.diff(["--cached", "package.json"]);
if (diff) {
const result = <GitDiffParserResult>(
GitDiffParser.parse(diff, { REMOVE_INDENT: true })
);
result.change.forEach((item) => {
item.content.forEach((content) => {
if (content.type === "+" && content.text.includes(`"version"`))
flag = true;
});
});
return flag;
}
return flag;
};
檢測message提供跳過檢測的方式
本來是寫在pre-commit
鉤子中的,但查了幾個獲取訊息的git命令,都沒辦法獲取到當前的message
。
所以只能換到commit-msg
鉤子中,通過$1
變數來獲取到訊息。而且傳回的並不是訊息的文字,而是訊息的檔案路徑,所以需要自行讀取檔案內容。
/**
* 讀取git message訊息檔案
*/
export const readGitMessage = (filePath: string) => {
try {
const msg: string = fs.readFileSync(
path.join(process.cwd(), filePath),
"utf-8"
);
return msg.split(/\s/).filter((item) => item);
} catch (e) {
return undefined;
}
};
這樣在checkVersion
流程中呼叫,跟預定的key對比,如果相同就直接返回true
,這樣就跳過了版本檢測邏輯了。
commit-msg指令碼
新增進專案,就能直接通過npx
調起命令,執行檢測邏輯。而訊息地址作為引數傳入命令中(之前的.option("--commitMsgPath <filePath>", "當 commit message 中包含指定字串時跳過當前命令檢測")
配置的引數)。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx commitlint --edit "$1"
npx check change --commitMsgPath "$1"