feat(cli): 新增 commit-lint 提交信息校验
This commit is contained in:
@@ -638,6 +638,40 @@ function cmdVerify() {
|
|||||||
console.log('verify OK (manifest ↔ docs ↔ demo 一致,皮肤无硬编码颜色,README 组件清单同步)');
|
console.log('verify OK (manifest ↔ docs ↔ demo 一致,皮肤无硬编码颜色,README 组件清单同步)');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
|
||||||
|
function cmdCommitLint(args) {
|
||||||
|
// 输入来源:文件路径(commit-msg 钩子传参)或参数拼接
|
||||||
|
let msg = '';
|
||||||
|
if (args[0] && fs.existsSync(args[0])) msg = fs.readFileSync(args[0], 'utf8');
|
||||||
|
else msg = args.join(' ');
|
||||||
|
if (!msg.trim()) { console.error('commit-lint: 提交信息为空'); process.exit(1); }
|
||||||
|
|
||||||
|
const head = msg.split('\n')[0].trimEnd();
|
||||||
|
// git 自动生成的合并/回滚信息直接放行
|
||||||
|
if (/^(Merge |Revert |fixup!|squash!)/.test(head)) return console.log('commit-lint OK (git 自动信息,跳过校验)');
|
||||||
|
|
||||||
|
const TYPES = ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'iter'];
|
||||||
|
const m = head.match(/^(\w+)(?:\(([^)]*)\))?: (.+)$/);
|
||||||
|
if (!m) { console.error('commit-lint: 首行须为 <type>(<scope>): <subject>,规范见 COMMITTING.md'); process.exit(1); }
|
||||||
|
const [, type, scope, subject] = m;
|
||||||
|
const problems = [];
|
||||||
|
// type 枚举
|
||||||
|
if (!TYPES.includes(type)) problems.push(`type「${type}」不在枚举:${TYPES.join('/')}`);
|
||||||
|
// iter 提交 scope 必须是 backlog 条目 id
|
||||||
|
if (type === 'iter' && !/^[a-z]\d{2,}$/.test(scope ?? '')) problems.push('iter 提交的 scope 必须是 backlog 条目 id(如 b02/e03)');
|
||||||
|
// 非 iter 提交 scope 仅允许小写字母与连字符
|
||||||
|
if (type !== 'iter' && scope && !/^[a-z][a-z-]*$/.test(scope)) problems.push(`scope「${scope}」仅允许小写字母与连字符`);
|
||||||
|
// subject 长度与标点
|
||||||
|
if ([...subject].length > 50) problems.push(`subject 超过 50 字符(当前 ${[...subject].length})`);
|
||||||
|
if (/[。..!!??]$/.test(subject.trim())) problems.push('subject 结尾不加句号');
|
||||||
|
// body 行宽
|
||||||
|
for (const line of msg.split('\n').slice(1)) {
|
||||||
|
if (line.length > 80) problems.push(`body 行超过 80 字符:${line.slice(0, 20)}…`);
|
||||||
|
}
|
||||||
|
if (problems.length) { for (const p of problems) console.error('commit-lint: ' + p); process.exit(1); }
|
||||||
|
console.log(`commit-lint OK (${type}${scope ? `(${scope})` : ''})`);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 入口 ----
|
// ---- 入口 ----
|
||||||
|
|
||||||
const [cmd, ...args] = process.argv.slice(2);
|
const [cmd, ...args] = process.argv.slice(2);
|
||||||
@@ -664,10 +698,13 @@ switch (cmd) {
|
|||||||
case 'verify':
|
case 'verify':
|
||||||
cmdVerify();
|
cmdVerify();
|
||||||
break;
|
break;
|
||||||
|
case 'commit-lint':
|
||||||
|
cmdCommitLint(args);
|
||||||
|
break;
|
||||||
case 'mcp':
|
case 'mcp':
|
||||||
cmdMcp();
|
cmdMcp();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.log('usage: ua <index|list|info <name>|docs|style|verify|mcp>');
|
console.log('usage: ua <index|list|info <name>|docs|style|verify|commit-lint|mcp>');
|
||||||
process.exit(cmd ? 1 : 0);
|
process.exit(cmd ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user