iter(g13): 新增 ua changelog 生成变更日志

This commit is contained in:
HIsheR
2026-10-26 10:31:03 +08:00
parent 3161340852
commit 90d3b64fa0
4 changed files with 166 additions and 3 deletions
+48 -1
View File
@@ -3,6 +3,7 @@
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawnSync } from 'node:child_process';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -635,6 +636,49 @@ function checkLlmsFull(pkgs, issues) {
}
}
/*** 生成 CHANGELOG.md:按提交类型前缀分组渲染 git 史(输出不含时间戳,可幂等比对);--check 只校验不写盘 ***/
function cmdChangelog(args) {
const check = args.includes('--check');
const res = spawnSync('git', ['log', '-n', '400', '--pretty=format:%h\t%s'], { cwd: ROOT, encoding: 'utf8' });
if (res.status !== 0) { console.error('changelog: 读取 git log 失败(是否在 git 仓库内?)'); process.exit(1); }
const rows = res.stdout.split(/\r?\n/).filter(Boolean).map((line) => {
const [hash, ...rest] = line.split('\t'); // 第一段为短哈希,其余为标题(标题可能含制表符)
return { hash, subject: rest.join('\t') };
});
const groups = [
{ title: '迭代', test: /^iter\(/ }, // 自动化迭代提交
{ title: '功能', test: /^feat[(:]/ }, // 新功能
{ title: '修复', test: /^fix[(:]/ }, // 缺陷修复
{ title: '文档', test: /^docs[(:]/ }, // 文档变更
{ title: '维护', test: /^(chore|refactor|test|build|ci)[(:]/ }, // 杂项维护
];
const buckets = new Map(groups.map((g) => [g.title, []]));
buckets.set('其他', []); // 未匹配前缀的提交兜底
for (const row of rows) {
const hit = groups.find((g) => g.test.test(row.subject));
buckets.get(hit ? hit.title : '其他').push(row);
}
const parts = ['# 变更日志', '', '> 由 `ua changelog` 依据 git 提交史生成,请勿手改;条目按提交时间倒序。', ''];
for (const [title, list] of buckets) {
if (!list.length) continue; // 空分组不渲染
parts.push(`## ${title}`, '');
for (const row of list) parts.push(`- \`${row.hash}\` ${row.subject}`);
parts.push('');
}
const next = parts.join('\n');
const file = path.join(ROOT, 'CHANGELOG.md');
const prev = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
const same = prev.replace(/\r\n/g, '\n') === next;
if (check) {
if (!same) { console.error('changelog: CHANGELOG.md 与提交史不一致,请运行 ua changelog 重新生成'); process.exit(1); }
console.log('changelog OK (与提交史一致)');
return;
}
if (same) { console.log('changelog unchanged, skip'); return; } // 无变化不写盘,避免空提交
fs.writeFileSync(file, next);
console.log(`changelog written -> CHANGELOG.md (${rows.length} commits)`);
}
/*** 交叉校验留痕与条目:done 条目必须有一条 done 留痕,留痕 id 必须存在于 backlog(防「标完成无记录 / 记录指向幽灵条目」) ***/
function checkIterations() {
const issues = [];
@@ -804,6 +848,9 @@ switch (cmd) {
case 'commit-lint':
cmdCommitLint(args);
break;
case 'changelog':
cmdChangelog(args);
break;
case 'lint-iterations':
cmdLintIterations();
break;
@@ -811,6 +858,6 @@ switch (cmd) {
cmdMcp();
break;
default:
console.log('usage: ua <index|list|info <name>|docs|style|verify|commit-lint|lint-iterations|mcp>');
console.log('usage: ua <index|list|info <name>|docs|style|verify|commit-lint|lint-iterations|changelog|mcp>');
process.exit(cmd ? 1 : 0);
}