feat(changelog): generate per-language markdown from JSON

Add scripts/changelog-to-markdown.mjs that renders changelogs/{cn,en,tw,ja}.json
into readable Markdown files. Runs idempotently; output passes Prettier without
changes.

Wire the script into the sync-changelog skill (step 4, before the release commit)
and update commit_release_changelogs.sh to include the four .md files in the
release: v<version> commit, with a preflight guard that errors if the md files
are missing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
diguaandClaude Opus 4.8 committed 2026-06-03 00:46:34 +08:00
1 parent 2f8591fb8c
commit f09d40f436
7 files changed
+4352 -7

No files matched your search

+1055
View File
File diff suppressed because it is too large. Load diff
+1055
View File
File diff suppressed because it is too large. Load diff
+1055
View File
File diff suppressed because it is too large. Load diff
+1055
View File
File diff suppressed because it is too large. Load diff
+102
View File
@@ -0,0 +1,102 @@
/**
* changelog-to-markdown.mjs
*
* 将 changelogs/{cn,en,tw,ja}.json 全量渲染为同名 .md 文件。
* 幂等:每次全量重写,md 始终与 json 保持一致。
*
* 用法:
* node scripts/changelog-to-markdown.mjs
*/
import { readFileSync, writeFileSync } from 'fs'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = resolve(__dirname, '..')
// ── 本地化配置 ──────────────────────────────────────────────────────────────
const LOCALES = [
{ key: 'cn', h1: '更新日志' },
{ key: 'en', h1: 'Changelog' },
{ key: 'tw', h1: '更新日誌' },
{ key: 'ja', h1: '変更履歴' },
]
/** type → 各语言的标题(含 emoji)。缺失 type 回退为原始字符串,兼容未来扩展。 */
const TYPE_LABELS = {
feat: { cn: '✨ 新功能', en: '✨ Features', tw: '✨ 新功能', ja: '✨ 新機能' },
fix: { cn: '🐛 修复', en: '🐛 Bug Fixes', tw: '🐛 修復', ja: '🐛 バグ修正' },
refactor: { cn: '♻️ 重构', en: '♻️ Refactoring', tw: '♻️ 重構', ja: '♻️ リファクタリング' },
perf: { cn: '⚡ 性能', en: '⚡ Performance', tw: '⚡ 效能', ja: '⚡ パフォーマンス' },
docs: { cn: '📝 文档', en: '📝 Documentation', tw: '📝 文件', ja: '📝 ドキュメント' },
style: { cn: '💄 样式', en: '💄 Styles', tw: '💄 樣式', ja: '💄 スタイル' },
ci: { cn: '👷 CI', en: '👷 CI', tw: '👷 CI', ja: '👷 CI' },
chore: { cn: '🔧 杂项', en: '🔧 Chores', tw: '🔧 雜項', ja: '🔧 雑務' },
}
// ── 渲染函数 ────────────────────────────────────────────────────────────────
/**
* 将单个语言的 changelog 数组渲染为 Markdown 字符串。
* @param {string} h1 - H1 标题文字
* @param {string} langKey - 'cn' | 'en' | 'tw' | 'ja'(用于 type 标题查表)
* @param {Array} entries - changelog 数组
*/
function renderMarkdown(h1, langKey, entries) {
const lines = [`# ${h1}`, '']
for (const entry of entries) {
const { version, date, summary, changes } = entry
lines.push(`## v${version} (${date})`)
lines.push('')
if (summary) {
lines.push(`> ${summary}`)
lines.push('')
}
if (Array.isArray(changes)) {
for (const group of changes) {
const { type, items } = group
const typeMap = TYPE_LABELS[type]
const label = typeMap ? typeMap[langKey] : type
lines.push(`### ${label}`)
lines.push('')
for (const item of items) {
lines.push(`- ${item}`)
}
lines.push('')
}
}
}
// 确保文件以单个换行结尾
return lines.join('\n').trimEnd() + '\n'
}
// ── 主流程 ──────────────────────────────────────────────────────────────────
let hasError = false
for (const { key, h1 } of LOCALES) {
const jsonPath = resolve(ROOT, 'changelogs', `${key}.json`)
const mdPath = resolve(ROOT, 'changelogs', `${key}.md`)
let entries
try {
entries = JSON.parse(readFileSync(jsonPath, 'utf8'))
} catch (err) {
console.error(`✗ 读取 ${jsonPath} 失败:${err.message}`)
hasError = true
continue
}
const md = renderMarkdown(h1, key, entries)
writeFileSync(mdPath, md, 'utf8')
console.log(`✓ changelogs/${key}.md (${entries.length} 版本)`)
}
if (hasError) process.exit(1)
+16 -6
View File
@@ -54,20 +54,29 @@ scripts/preflight_main_clean.sh . "package.json,changelogs/cn.json"
5. 翻译时必须逐条对应当前中文条目:`summary` 与 `changes.items` 的语义都应来自最新中文文件内容,不得沿用旧版本措辞。
6. 写入后执行格式化(若项目有 Prettier,优先使用 Prettier)。
## 4. 在当前仓库创建发布提交
## 4. 生成多语言 Markdown
在提交前,将 4 个 JSON 渲染为可读的 Markdown 文件:
```bash
node scripts/changelog-to-markdown.mjs
prettier --write changelogs/*.md
```
生成结果:`changelogs/cn.md`、`en.md`、`tw.md`、`ja.md`(全量幂等,每次重写)。
## 5. 在当前仓库创建发布提交
1. 提交文件必须包含:
- `package.json`
- `changelogs/cn.json`
- `changelogs/en.json`
- `changelogs/tw.json`
- `changelogs/ja.json`
- `changelogs/cn.json`、`en.json`、`tw.json`、`ja.json`
- `changelogs/cn.md`、`en.md`、`tw.md`、`ja.md`
2. commit message:`release: v<version>`(示例:`release: v0.9.6`)。
3. 仅创建 commit,不 push。
可复用脚本:`scripts/commit_release_changelogs.sh`
## 5. 输出结果
## 6. 输出结果
输出以下信息给用户:
@@ -82,3 +91,4 @@ scripts/preflight_main_clean.sh . "package.json,changelogs/cn.json"
- `references/japanese-release-style.md`
- `scripts/preflight_main_clean.sh`
- `scripts/commit_release_changelogs.sh`
- `scripts/changelog-to-markdown.mjs`
@@ -13,6 +13,10 @@ CN_FILE="$REPO_PATH/changelogs/cn.json"
EN_FILE="$REPO_PATH/changelogs/en.json"
TW_FILE="$REPO_PATH/changelogs/tw.json"
JA_FILE="$REPO_PATH/changelogs/ja.json"
CN_MD="$REPO_PATH/changelogs/cn.md"
EN_MD="$REPO_PATH/changelogs/en.md"
TW_MD="$REPO_PATH/changelogs/tw.md"
JA_MD="$REPO_PATH/changelogs/ja.md"
PKG_FILE="$REPO_PATH/package.json"
if [[ ! -f "$CN_FILE" || ! -f "$EN_FILE" || ! -f "$TW_FILE" || ! -f "$JA_FILE" || ! -f "$PKG_FILE" ]]; then
@@ -20,6 +24,11 @@ if [[ ! -f "$CN_FILE" || ! -f "$EN_FILE" || ! -f "$TW_FILE" || ! -f "$JA_FILE" |
exit 1
fi
if [[ ! -f "$CN_MD" || ! -f "$EN_MD" || ! -f "$TW_MD" || ! -f "$JA_MD" ]]; then
echo "错误: Markdown 文件不完整,请先运行 node scripts/changelog-to-markdown.mjs" >&2
exit 1
fi
VERSION="$(node -e "const fs=require('fs');const arr=JSON.parse(fs.readFileSync(process.argv[1],'utf8'));process.stdout.write(arr?.[0]?.version||'');" "$CN_FILE")"
if [[ -z "$VERSION" ]]; then
echo "错误: 无法从 $CN_FILE 读取当前版本" >&2
@@ -33,7 +42,11 @@ git -C "$REPO_PATH" add \
changelogs/cn.json \
changelogs/en.json \
changelogs/tw.json \
changelogs/ja.json
changelogs/ja.json \
changelogs/cn.md \
changelogs/en.md \
changelogs/tw.md \
changelogs/ja.md
# 若没有差异则不提交,避免空提交失败。
if git -C "$REPO_PATH" diff --cached --quiet; then