| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- /**
- * generate-changelog.js — 从 git log 生成 CHANGELOG.md
- *
- * 逻辑:
- * 1. 读取 LAST_BUILD 文件(记录上次构建的 commit hash)
- * 2. 从 LAST_BUILD 到 HEAD 收集 commit messages
- * 3. 按 conventional-commit 分类 (Feat/Fix/Refactor/Chore/Docs/…)
- * 4. 追加到 CHANGELOG.md 末尾并更新 LAST_BUILD
- *
- * 用法: node scripts/generate-changelog.js <version>
- */
- const fs = require('fs');
- const path = require('path');
- const { execSync } = require('child_process');
- const rootDir = path.resolve(__dirname, '..');
- const version = process.argv[2];
- if (!version) {
- console.error('[generate-changelog] 用法: node scripts/generate-changelog.js <version>');
- process.exit(1);
- }
- const lastBuildFile = path.join(rootDir, '.last_build_commit');
- const changelogFile = path.join(rootDir, 'public', 'CHANGELOG.md');
- // 获取上次构建 commit hash
- let fromHash = '';
- try {
- fromHash = fs.readFileSync(lastBuildFile, 'utf-8').trim();
- } catch {
- // 首次构建:从第一个 commit 开始
- try {
- fromHash = execSync('git rev-list --max-parents=0 HEAD', { cwd: rootDir, encoding: 'utf-8' }).trim();
- } catch {
- fromHash = '';
- }
- }
- // 获取当前 HEAD
- const headHash = execSync('git rev-parse HEAD', { cwd: rootDir, encoding: 'utf-8' }).trim();
- // 获取 commit 日志
- let log = '';
- try {
- const range = fromHash ? `${fromHash}..HEAD` : 'HEAD';
- log = execSync(
- `git log --no-merges --pretty=format:"%s|||%b|||%an|||%ai" ${range}`,
- { cwd: rootDir, encoding: 'utf-8' }
- ).trim();
- } catch {
- log = '';
- }
- // 解析 commit
- const commits = log ? log.split('\n').filter(Boolean).map(line => {
- const [subject, body, author, date] = line.split('|||');
- return { subject: subject || '', body: body || '', author: author || '', date: date || '' };
- }) : [];
- if (commits.length === 0) {
- console.log('[generate-changelog] 没有新 commit,跳过 changelog 生成');
- // 仍然更新 LAST_BUILD
- fs.writeFileSync(lastBuildFile, headHash, 'utf-8');
- return;
- }
- const hasMeaningful = commits.some(c => {
- const cat = categorize(c.subject);
- return cat !== '工程化';
- });
- if (!hasMeaningful) {
- console.log('[generate-changelog] 仅有工程化变更,跳过 changelog 生成');
- fs.writeFileSync(lastBuildFile, headHash, 'utf-8');
- return;
- }
- // 分类
- function categorize(subject) {
- const s = subject.toLowerCase();
- if (s.startsWith('feat') || s.startsWith('feature')) return '新功能';
- if (s.startsWith('fix') || s.startsWith('bug')) return 'Bug 修复';
- if (s.startsWith('refactor')) return '重构';
- if (s.startsWith('docs') || s.startsWith('doc')) return '文档';
- if (s.startsWith('chore') || s.startsWith('build') || s.startsWith('ci')) return '工程化';
- if (s.startsWith('perf') || s.startsWith('optimize')) return '性能优化';
- if (s.startsWith('style')) return '样式';
- if (s.startsWith('test')) return '测试';
- return '其他';
- }
- const grouped = {};
- commits.forEach(c => {
- const cat = categorize(c.subject);
- if (!grouped[cat]) grouped[cat] = [];
- // 格式化 subject:去掉 feat/fix: 前缀
- let desc = c.subject.replace(/^(feat|feature|fix|refactor|docs?|chore|build|ci|perf|style|test)(\([^)]*\))?[:\s]*/i, '');
- if (!desc) desc = c.subject;
- grouped[cat].push(`- ${desc}`);
- });
- // 构建新版本 changelog 块
- const now = new Date().toISOString().slice(0, 10);
- let block = `\n## v${version} (${now})\n\n`;
- for (const [cat, items] of Object.entries(grouped)) {
- if (cat === '工程化') continue;
- block += `### ${cat}\n${items.join('\n')}\n\n`;
- }
- // 追加或新建 CHANGELOG.md
- let existing = '';
- try {
- existing = fs.readFileSync(changelogFile, 'utf-8');
- } catch {
- existing = '# 更新日志 (Changelog)\n\n此文件记录所有构建版本的变更。\n';
- }
- // 新版本插入到标题之后、旧内容之前
- const headerEnd = existing.indexOf('\n## ');
- if (headerEnd === -1) {
- existing = existing.trimEnd() + block;
- } else {
- existing = existing.slice(0, headerEnd) + block + existing.slice(headerEnd);
- }
- fs.writeFileSync(changelogFile, existing, 'utf-8');
- // 更新 LAST_BUILD
- fs.writeFileSync(lastBuildFile, headHash, 'utf-8');
- console.log(`[generate-changelog] CHANGELOG.md 已更新,新增 ${commits.length} 条记录`);
|