iter(g13): 新增 ua changelog 生成变更日志
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user