提交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 nodeconsole.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"