230 lines
6.3 KiB
JavaScript
230 lines
6.3 KiB
JavaScript
|
|
#!/usr/bin / env node
|
|||
|
|
|
|||
|
|
/* eslint-disable */
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
function parseChangelog(content) {
|
|||
|
|
const lines = content.split('\n');
|
|||
|
|
const versions = [];
|
|||
|
|
let currentVersion = null;
|
|||
|
|
let currentSection = null;
|
|||
|
|
let inVersionContent = false;
|
|||
|
|
|
|||
|
|
for (const line of lines) {
|
|||
|
|
const trimmedLine = line.trim();
|
|||
|
|
|
|||
|
|
// 匹配版本行: ## [X.Y.Z] - YYYY-MM-DD
|
|||
|
|
const versionMatch = trimmedLine.match(
|
|||
|
|
/^## \[([\d.]+)\] - (\d{4}-\d{2}-\d{2})$/
|
|||
|
|
);
|
|||
|
|
if (versionMatch) {
|
|||
|
|
if (currentVersion) {
|
|||
|
|
versions.push(currentVersion);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
currentVersion = {
|
|||
|
|
version: versionMatch[1],
|
|||
|
|
date: versionMatch[2],
|
|||
|
|
added: [],
|
|||
|
|
changed: [],
|
|||
|
|
fixed: [],
|
|||
|
|
content: [], // 用于存储原始内容,当没有分类时使用
|
|||
|
|
};
|
|||
|
|
currentSection = null;
|
|||
|
|
inVersionContent = true;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果遇到下一个版本或到达文件末尾,停止处理当前版本
|
|||
|
|
if (inVersionContent && currentVersion) {
|
|||
|
|
// 匹配章节标题
|
|||
|
|
if (trimmedLine === '### Added') {
|
|||
|
|
currentSection = 'added';
|
|||
|
|
continue;
|
|||
|
|
} else if (trimmedLine === '### Changed') {
|
|||
|
|
currentSection = 'changed';
|
|||
|
|
continue;
|
|||
|
|
} else if (trimmedLine === '### Fixed') {
|
|||
|
|
currentSection = 'fixed';
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 匹配条目: - 内容
|
|||
|
|
if (trimmedLine.startsWith('- ') && currentSection) {
|
|||
|
|
const entry = trimmedLine.substring(2);
|
|||
|
|
currentVersion[currentSection].push(entry);
|
|||
|
|
} else if (
|
|||
|
|
trimmedLine &&
|
|||
|
|
!trimmedLine.startsWith('#') &&
|
|||
|
|
!trimmedLine.startsWith('###')
|
|||
|
|
) {
|
|||
|
|
currentVersion.content.push(trimmedLine);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 添加最后一个版本
|
|||
|
|
if (currentVersion) {
|
|||
|
|
versions.push(currentVersion);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 后处理:如果某个版本没有分类内容,但有 content,则将 content 放到 changed 中
|
|||
|
|
versions.forEach((version) => {
|
|||
|
|
const hasCategories =
|
|||
|
|
version.added.length > 0 ||
|
|||
|
|
version.changed.length > 0 ||
|
|||
|
|
version.fixed.length > 0;
|
|||
|
|
if (!hasCategories && version.content.length > 0) {
|
|||
|
|
version.changed = version.content;
|
|||
|
|
}
|
|||
|
|
// 清理 content 字段
|
|||
|
|
delete version.content;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return { versions };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function generateTypeScript(changelogData) {
|
|||
|
|
const entries = changelogData.versions
|
|||
|
|
.map((version) => {
|
|||
|
|
const addedEntries = version.added
|
|||
|
|
.map((entry) => ` "${entry}"`)
|
|||
|
|
.join(',\n');
|
|||
|
|
const changedEntries = version.changed
|
|||
|
|
.map((entry) => ` "${entry}"`)
|
|||
|
|
.join(',\n');
|
|||
|
|
const fixedEntries = version.fixed
|
|||
|
|
.map((entry) => ` "${entry}"`)
|
|||
|
|
.join(',\n');
|
|||
|
|
|
|||
|
|
return ` {
|
|||
|
|
version: "${version.version}",
|
|||
|
|
date: "${version.date}",
|
|||
|
|
added: [
|
|||
|
|
${addedEntries || ' // 无新增内容'}
|
|||
|
|
],
|
|||
|
|
changed: [
|
|||
|
|
${changedEntries || ' // 无变更内容'}
|
|||
|
|
],
|
|||
|
|
fixed: [
|
|||
|
|
${fixedEntries || ' // 无修复内容'}
|
|||
|
|
]
|
|||
|
|
}`;
|
|||
|
|
})
|
|||
|
|
.join(',\n');
|
|||
|
|
|
|||
|
|
return `// 此文件由 scripts/convert-changelog.js 自动生成
|
|||
|
|
// 请勿手动编辑
|
|||
|
|
|
|||
|
|
export interface ChangelogEntry {
|
|||
|
|
version: string;
|
|||
|
|
date: string;
|
|||
|
|
added: string[];
|
|||
|
|
changed: string[];
|
|||
|
|
fixed: string[];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const changelog: ChangelogEntry[] = [
|
|||
|
|
${entries}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
export default changelog;
|
|||
|
|
`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateVersionFile(version) {
|
|||
|
|
const versionTxtPath = path.join(process.cwd(), 'VERSION.txt');
|
|||
|
|
try {
|
|||
|
|
fs.writeFileSync(versionTxtPath, version, 'utf8');
|
|||
|
|
console.log(`✅ 已更新 VERSION.txt: ${version}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`❌ 无法更新 VERSION.txt:`, error.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function updateVersionTs(version) {
|
|||
|
|
const versionTsPath = path.join(process.cwd(), 'src/lib/version.ts');
|
|||
|
|
try {
|
|||
|
|
let content = fs.readFileSync(versionTsPath, 'utf8');
|
|||
|
|
|
|||
|
|
// 替换 CURRENT_VERSION 常量
|
|||
|
|
const updatedContent = content.replace(
|
|||
|
|
/const CURRENT_VERSION = ['"`][^'"`]+['"`];/,
|
|||
|
|
`const CURRENT_VERSION = '${version}';`
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
fs.writeFileSync(versionTsPath, updatedContent, 'utf8');
|
|||
|
|
console.log(`✅ 已更新 version.ts: ${version}`);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`❌ 无法更新 version.ts:`, error.message);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function main() {
|
|||
|
|
try {
|
|||
|
|
const changelogPath = path.join(process.cwd(), 'CHANGELOG');
|
|||
|
|
const outputPath = path.join(process.cwd(), 'src/lib/changelog.ts');
|
|||
|
|
|
|||
|
|
console.log('正在读取 CHANGELOG 文件...');
|
|||
|
|
const changelogContent = fs.readFileSync(changelogPath, 'utf-8');
|
|||
|
|
|
|||
|
|
console.log('正在解析 CHANGELOG 内容...');
|
|||
|
|
const changelogData = parseChangelog(changelogContent);
|
|||
|
|
|
|||
|
|
if (changelogData.versions.length === 0) {
|
|||
|
|
console.error('❌ 未在 CHANGELOG 中找到任何版本');
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取最新版本号(CHANGELOG中的第一个版本)
|
|||
|
|
const latestVersion = changelogData.versions[0].version;
|
|||
|
|
console.log(`🔢 最新版本: ${latestVersion}`);
|
|||
|
|
|
|||
|
|
console.log('正在生成 TypeScript 文件...');
|
|||
|
|
const tsContent = generateTypeScript(changelogData);
|
|||
|
|
|
|||
|
|
// 确保输出目录存在
|
|||
|
|
const outputDir = path.dirname(outputPath);
|
|||
|
|
if (!fs.existsSync(outputDir)) {
|
|||
|
|
fs.mkdirSync(outputDir, { recursive: true });
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
fs.writeFileSync(outputPath, tsContent, 'utf-8');
|
|||
|
|
|
|||
|
|
// 检查是否在 GitHub Actions 环境中运行
|
|||
|
|
const isGitHubActions = process.env.GITHUB_ACTIONS === 'true';
|
|||
|
|
|
|||
|
|
if (isGitHubActions) {
|
|||
|
|
// 在 GitHub Actions 中,更新版本文件
|
|||
|
|
console.log('正在更新版本文件...');
|
|||
|
|
updateVersionFile(latestVersion);
|
|||
|
|
updateVersionTs(latestVersion);
|
|||
|
|
} else {
|
|||
|
|
// 在本地运行时,只提示但不更新版本文件
|
|||
|
|
console.log('🔧 本地运行模式:跳过版本文件更新');
|
|||
|
|
console.log('💡 版本文件更新将在 git tag 触发的 release 工作流中完成');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log(`✅ 成功生成 ${outputPath}`);
|
|||
|
|
console.log(`📊 版本统计:`);
|
|||
|
|
changelogData.versions.forEach((version) => {
|
|||
|
|
console.log(
|
|||
|
|
` ${version.version} (${version.date}): +${version.added.length} ~${version.changed.length} !${version.fixed.length}`
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
console.log('\n🎉 转换完成!');
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('❌ 转换失败:', error);
|
|||
|
|
process.exit(1);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (require.main === module) {
|
|||
|
|
main();
|
|||
|
|
}
|