提交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 ",
"当 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 = (
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
配置的参数)。
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx commitlint --edit "$1"
npx check change --commitMsgPath "$1"