gitlink-cli/skills/gitlink-community-ops/scripts/release-analyze.js

122 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const fs = require('fs');
const path = require('path');
const dir = process.argv[2] || '_output';
const lastTag = process.argv[3] || 'v0.0.0';
// 读提交日志(优先 git log后备 API JSON
const logFile = path.join(dir, `commits-since-${lastTag}.log`);
const statsFile = path.join(dir, `stats-since-${lastTag}.log`);
const apiFile = path.join(dir, 'commits-api.json');
let commits = [];
if (fs.existsSync(logFile)) {
const content = fs.readFileSync(logFile, 'utf8');
commits = content.trim().split('\n').filter(Boolean).map(line => {
const parts = line.split('|');
return { sha: parts[0] || '', msg: parts[1] || '', author: parts[2] || '', date: parts[3] || '' };
});
console.log('✓ 从 git log 读取 ' + commits.length + ' 条提交');
} else if (fs.existsSync(apiFile)) {
try {
const raw = JSON.parse(fs.readFileSync(apiFile, 'utf8'));
const list = raw.data && raw.data.commits ? raw.data.commits : (raw.data || []);
commits = list.map(c => ({
sha: c.sha || '',
msg: (c.commit_message || '').replace(/\n.*$/, '').trim(),
author: (c.author && c.author.login) || (c.committer && c.committer.login) || '',
date: c.commit_time ? new Date(c.commit_time * 1000).toISOString().slice(0, 10) : ''
}));
console.log('✓ 从 API 读取 ' + commits.length + ' 条提交');
} catch(e) {
console.log('⚠ API 数据解析失败: ' + e.message);
}
}
// 按 Conventional Commits 分类
const categories = { feat: [], fix: [], docs: [], refactor: [], perf: [], test: [], chore: [], revert: [], other: [] };
const typeLabels = {
feat: '✨ 新功能', fix: '🐛 Bug 修复', docs: '📝 文档',
refactor: '♻️ 代码重构', perf: '⚡ 性能优化', test: '✅ 测试',
chore: '🔧 工程配置', revert: '⏪ 回退', other: '📦 其他变更'
};
commits.forEach(c => {
const m = c.msg.match(/^(\w+)(\([^)]+\))?:/);
const type = m ? m[1] : 'other';
if (categories[type]) categories[type].push(c);
else categories.other.push(c);
});
const totalCommits = commits.length;
const authors = new Set(commits.map(c => c.author).filter(Boolean));
const contributorCount = authors.size;
// 统计各类型数量
const counts = {};
Object.keys(categories).forEach(k => counts[k] = categories[k].length);
const classifiedTotal = Object.values(counts).reduce((a, b) => a + b, 0);
// 统计文件变更(兼容中英文 Git 输出)
let totalFiles = 0, additions = 0, deletions = 0;
if (fs.existsSync(statsFile)) {
const stats = fs.readFileSync(statsFile, 'utf8');
const lines = stats.split('\n');
lines.forEach(line => {
// 匹配 "X file(s) changed" 或 "X 个文件已更改"
const fm = line.match(/(\d+)\s*(file|个文件)/i);
if (fm) totalFiles += parseInt(fm[1]) || 0;
// 匹配 "Y insertion" 或 "Y 行插入"
const am = line.match(/(\d+)\s*(insertion|行插入)/i);
if (am) additions += parseInt(am[1]) || 0;
// 匹配 "Z deletion" 或 "Z 行删除"
const dm = line.match(/(\d+)\s*(deletion|行删除)/i);
if (dm) deletions += parseInt(dm[1]) || 0;
});
}
// 语义化版本推荐
const hasBreaking = commits.some(c => c.msg.includes('BREAKING CHANGE') || c.msg.includes('!'));
const hasFeat = counts.feat > 0;
const oldVer = lastTag.match(/v?(\d+)\.(\d+)\.(\d+)/);
let newVer = 'v0.1.0';
if (oldVer) {
const [_, major, minor, patch] = oldVer.map(Number);
if (hasBreaking) newVer = `v${major + 1}.0.0`;
else if (hasFeat) newVer = `v${major}.${minor + 1}.0`;
else newVer = `v${major}.${minor}.${patch + 1}`;
}
// 生成 Release Notes
const sections = ['feat', 'fix', 'docs', 'refactor', 'perf', 'test', 'chore', 'revert', 'other']
.filter(t => counts[t] > 0)
.map(t => {
const items = categories[t].map(c => `- ${c.msg}@${c.author || 'unknown'}`).join('\n');
const pct = totalCommits > 0 ? Math.round(counts[t] / totalCommits * 100) : 0;
return `### ${typeLabels[t]}${counts[t]}\n\n${items}\n\n**占比:** ${pct}%`;
}).join('\n\n');
const notes = `## ${newVer} (${new Date().toISOString().slice(0, 10)})
### 📊 版本概览
- 基于上一版本:${lastTag}
- 包含 **${totalCommits}** 次提交
- 涉及 **${totalFiles || '—'}** 个文件
- 新增 **${additions || '—'}** 行 / 删除 **${deletions || '—'}** 行
- 贡献者:**${contributorCount}** 人
${totalCommits > 0 ? sections : '(暂无提交记录,请确认 tag 名称正确)'}
### 📈 统计汇总
| 类别 | 数量 | 占比 |
|------|------|------|
${Object.entries(typeLabels).map(([k, label]) => `| ${label} | ${counts[k]} | ${totalCommits > 0 ? Math.round(counts[k] / totalCommits * 100) : 0}% |`).join('\n')}
---
*自动生成时间:${new Date().toISOString().slice(0, 19).replace('T', ' ')}*
`;
fs.writeFileSync(path.join(dir, `release-notes-${newVer}.md`), notes, 'utf8');
console.log('Release Notes 已生成: ' + path.join(dir, `release-notes-${newVer}.md`));
console.log(`版本推荐: ${lastTag}${newVer}${hasBreaking ? 'BREAKING': hasFeat ? '有新功能': '修复'}`);