拒绝“忘了写 Changelog”:手写 Git Hook 强制校验 Changeset
在现代的前端 Monorepo 架构(如使用 pnpm workspaces)中,Changesets 已经成为了自动化版本管理和生成 Changelog 的事实标准。然而,团队协作中经常会出现一个尴尬的情况:开发者写完了完美的代码,提交并推送了 PR,结果 CI 流水线报错——“检测到代码变更但缺少 Changeset 文件”。
为了避免这种低级错误浪费 CI 资源和开发时间,最优雅的解决方案是将校验逻辑前置到本地。本文将教你如何手写一个 Git Hook,在 git commit 时强制检查是否遗漏了 Changeset。
1. 核心逻辑拆解
我们要实现的 pre-commit 钩子需要完成以下逻辑:
- 识别变更:获取当前暂存区(Staged)中的所有文件。
- 过滤路径:判断是否有实际的代码逻辑变更(排除掉文档、配置文件等)。
- 匹配校验:检查暂存区中是否存在
.changeset/*.md格式的新文件。 - 拦截或放行:如果检测到代码变更但无 changeset,则以非零状态码退出,中断提交。
2. 方案一:原生 Shell 脚本实现
如果你不想引入额外的依赖,可以直接在项目的 .git/hooks/pre-commit(或者自定义的 hooks 目录)中编写以下脚本:
#!/bin/bash
# 获取当前暂存区的文件列表
STAGED_FILES=$(git diff --cached --name-only)
# 如果暂存区为空,直接放行
if [ -z "$STAGED_FILES" ]; then
exit 0
fi
# 检查是否存在 .changeset 目录下的新 markdown 文件
HAS_CHANGESET=$(echo "$STAGED_FILES" | grep -E "^\.changeset/.*\.md$")
# 定义需要强制 changeset 的特征文件(可根据项目调整)
# 比如:只有 src 或 packages 目录下的变更才需要 changeset
NEEDS_CHANGESET=$(echo "$STAGED_FILES" | grep -E "^(packages|src)/")
if [ -n "$NEEDS_CHANGESET" ] && [ -z "$HAS_CHANGESET" ]; then
echo "❌ 错误: 检测到代码变更,但未发现相应的 Changeset 文件。"
echo "请运行 'npx changeset' 生成变更描述,然后再尝试提交。"
echo "如果本次提交确实不需要 changeset(如纯文档修复),请使用 --no-verify 跳过(不推荐)。"
exit 1
fi
exit 0
3. 方案二:Node.js + Husky 环境下的工程化实现
在大多数前端项目中,我们会使用 Husky 来管理 Hooks。你可以创建一个独立的脚本文件 scripts/check-changeset.mjs,利用 Node.js 提供更强大的逻辑控制。
脚本代码:
// scripts/check-changeset.mjs
import { execSync } from 'child_process';
const getStagedFiles = () => {
return execSync('git diff --cached --name-only', { encoding: 'utf8' })
.split('\n')
.filter(Boolean);
};
const files = getStagedFiles();
// 1. 检查是否存在 changeset 文件
const hasChangeset = files.some(file => file.startsWith('.changeset/') && file.endsWith('.md'));
// 2. 检查是否存在核心代码变更
const hasCodeChanges = files.some(file => {
return file.startsWith('packages/') || file.startsWith('src/') || file.endsWith('.ts') || file.endsWith('.js');
});
// 3. 逻辑判定
if (hasCodeChanges && !hasChangeset) {
console.error('\x1b[31m%s\x1b[0m', '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.error('\x1b[31m%s\x1b[0m', ' 🚨 提交拒绝: 缺少 Changeset 文件!');
console.error('\x1b[31m%s\x1b[0m', ' 检测到 packages 或 src 下的代码变更,必须提供变更描述。');
console.error('\x1b[31m%s\x1b[0m', ' 请执行: npx changeset');
console.error('\x1b[31m%s\x1b[0m', '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
process.exit(1);
}
process.exit(0);
在 Husky 中配置:
执行以下命令添加钩子:
npx husky add .husky/pre-commit "node scripts/check-changeset.mjs"
4. 进阶优化:支持“逃生舱”
有时候我们确实需要提交一些琐碎的改动(比如修复了一个注释、更新了 README),这时候强制要求 changeset 会显得很冗余。我们可以通过环境变量来增加灵活性:
在脚本中添加判断:
if (process.env.SKIP_CHANGESET) {
process.exit(0);
}
之后,开发者可以通过 SKIP_CHANGESET=1 git commit -m "docs: update readme" 来临时绕过校验。
5. 为什么不在 CI 中做?
虽然 GitHub Actions 等 CI 工具也可以拦截缺失 Changeset 的 PR,但 反馈周期太长。开发者推送代码 -> 等待 CI 启动 -> 等待安装依赖 -> 报错,这个过程通常需要 2-5 分钟。而在本地 Hook 中校验,反馈是毫秒级的。
总结
通过简单的几行脚本,我们可以将团队的规范从“口头约定”转变为“自动化约束”。这不仅保护了 master 分支的健康,也让版本发布流程变得更加顺滑。如果你的项目正在使用 Changesets,强烈建议立刻加上这个 Hook。