All checks were successful
Push & PR Check / check (push) Successful in 54s
lefthook 在 agent 沙箱(DSH workspace-write)内静默失效的机器层硬补偿, dsh-takeover-readiness T2。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.5 KiB
JavaScript
68 lines
2.5 KiB
JavaScript
// CI 侧 commit message 校验:Conventional Commits + feat 须带 Feature-Confirmed trailer。
|
||
// 与 lefthook commit-msg 同一套规则的服务端硬补偿——agent 沙箱(如 DSH workspace-write)
|
||
// 内 lefthook 会静默失效,本脚本保证机器校验层不缺位(dsh-takeover-readiness T2)。
|
||
// 环境变量:EVENT_NAME / BEFORE_SHA / BASE_SHA / HEAD_SHA(由 workflow 从 github context 注入)
|
||
import { execSync } from 'node:child_process';
|
||
|
||
const git = (cmd) => execSync(`git ${cmd}`, { encoding: 'utf8' }).trim();
|
||
|
||
const CONVENTIONAL =
|
||
/^(feat|fix|docs|chore|refactor|ci|perf|style|test|build|revert)(\(.+\))?!?: .+/;
|
||
const ZERO_SHA = /^0+$/;
|
||
|
||
const { EVENT_NAME, BEFORE_SHA, BASE_SHA, HEAD_SHA } = process.env;
|
||
|
||
let range;
|
||
if (EVENT_NAME === 'pull_request' && BASE_SHA) {
|
||
range = `${BASE_SHA}..${HEAD_SHA}`;
|
||
} else if (BEFORE_SHA && !ZERO_SHA.test(BEFORE_SHA)) {
|
||
range = `${BEFORE_SHA}..${HEAD_SHA}`;
|
||
} else {
|
||
// 新分支首推等场景拿不到 before:与 master 比对兜底,再退化为仅查 HEAD
|
||
try {
|
||
git('fetch origin master --quiet');
|
||
range = `origin/master..${HEAD_SHA || 'HEAD'}`;
|
||
} catch {
|
||
range = `${HEAD_SHA || 'HEAD'} -1`;
|
||
}
|
||
}
|
||
|
||
let shas;
|
||
try {
|
||
shas = git(`rev-list --no-merges ${range}`).split('\n').filter(Boolean);
|
||
} catch {
|
||
// force push 后旧 before 在 clone 里不存在等场景:退化为与 master 比对,再退化为仅查 HEAD
|
||
try {
|
||
git('fetch origin master --quiet');
|
||
range = `origin/master..${HEAD_SHA || 'HEAD'}`;
|
||
shas = git(`rev-list --no-merges ${range}`).split('\n').filter(Boolean);
|
||
} catch {
|
||
range = 'HEAD -1';
|
||
shas = [git('rev-parse HEAD')];
|
||
}
|
||
}
|
||
console.log(`commit message 校验:范围 ${range},共 ${shas.length} 个 commit`);
|
||
|
||
const errors = [];
|
||
for (const sha of shas) {
|
||
const subject = git(`log -1 --format=%s ${sha}`);
|
||
if (!CONVENTIONAL.test(subject)) {
|
||
errors.push(`${sha.slice(0, 8)} 首行不符合 Conventional Commits:${subject}`);
|
||
continue;
|
||
}
|
||
if (/^feat/.test(subject)) {
|
||
const body = git(`log -1 --format=%B ${sha}`);
|
||
if (!body.includes('Feature-Confirmed: true')) {
|
||
errors.push(`${sha.slice(0, 8)} feat 提交缺少 "Feature-Confirmed: true" trailer:${subject}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (errors.length > 0) {
|
||
console.error('\n❌ commit message 校验失败:');
|
||
for (const e of errors) console.error(` ${e}`);
|
||
console.error('\n 规则:首行须为 <type>(<scope>): <subject>;feat 提交末尾须有 Feature-Confirmed: true');
|
||
process.exit(1);
|
||
}
|
||
console.log('✅ commit message 校验通过');
|