Skip to content

Commit 2798aaa

Browse files
TIANQIAN1238claude
andcommitted
chore: generate meaningful CHANGELOG and release notes from git log
- CHANGELOG entries now auto-categorize commits (feat→Added, fix→Fixed, etc.) - GitHub Release notes include actual commit messages instead of placeholder Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 9cfa2e7 commit 2798aaa

1 file changed

Lines changed: 57 additions & 9 deletions

File tree

‎packages/codeblog/script/release.ts‎

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,50 @@ if (fs.existsSync(readme)) {
5050
console.log(" ✓ README.md")
5151
}
5252

53-
// CHANGELOG.md — prepend new version section
53+
// CHANGELOG.md — prepend new version section with commit messages
5454
const changelog = path.join(root, "CHANGELOG.md")
5555
if (fs.existsSync(changelog)) {
5656
let text = await Bun.file(changelog).text()
57-
const entry = [
58-
`## [${version}] - ${date}`,
59-
"",
60-
"### Added",
61-
"- See release notes for details",
62-
"",
63-
].join("\n")
64-
text = text.replace("## [Unreleased]", `## [Unreleased]\n\n${entry}`)
57+
58+
// Collect commit messages since last tag for changelog content
59+
let changes: string[] = []
60+
try {
61+
const lastTag = (await $`git describe --tags --abbrev=0`.cwd(root).text()).trim()
62+
const log = (await $`git log ${lastTag}..HEAD --oneline --no-decorate`.cwd(root).text()).trim()
63+
changes = log.split("\n").filter(Boolean).map((line) => line.replace(/^[a-f0-9]+ /, ""))
64+
} catch {
65+
changes = []
66+
}
67+
68+
// Categorize commits by conventional commit prefix
69+
const added: string[] = []
70+
const fixed: string[] = []
71+
const changed: string[] = []
72+
73+
for (const msg of changes) {
74+
if (/^feat[:(]/i.test(msg)) added.push(msg.replace(/^feat[:(]\s*[^)]*\)?:?\s*/i, ""))
75+
else if (/^fix[:(]/i.test(msg)) fixed.push(msg.replace(/^fix[:(]\s*[^)]*\)?:?\s*/i, ""))
76+
else if (/^(chore|refactor|perf|docs|style|build|ci)[:(]/i.test(msg))
77+
changed.push(msg.replace(/^(chore|refactor|perf|docs|style|build|ci)[:(]\s*[^)]*\)?:?\s*/i, ""))
78+
else if (!/^release/i.test(msg)) changed.push(msg)
79+
}
80+
81+
const sections: string[] = [`## [${version}] - ${date}`]
82+
if (added.length > 0) {
83+
sections.push("", "### Added", ...added.map((m) => `- ${m}`))
84+
}
85+
if (fixed.length > 0) {
86+
sections.push("", "### Fixed", ...fixed.map((m) => `- ${m}`))
87+
}
88+
if (changed.length > 0) {
89+
sections.push("", "### Changed", ...changed.map((m) => `- ${m}`))
90+
}
91+
if (added.length === 0 && fixed.length === 0 && changed.length === 0) {
92+
sections.push("", "### Changed", "- Internal improvements and dependency updates")
93+
}
94+
95+
const entry = sections.join("\n")
96+
text = text.replace("## [Unreleased]", `## [Unreleased]\n\n${entry}\n`)
6597
await Bun.file(changelog).write(text)
6698
console.log(" ✓ CHANGELOG.md")
6799
}
@@ -112,9 +144,25 @@ console.log(` ✓ ${tag} pushed`)
112144

113145
// ─── Step 6: GitHub Release ──────────────────────
114146
console.log("\n6. Creating GitHub Release...")
147+
148+
// Collect commit messages since previous tag for release notes
149+
let releaseChanges: string[] = []
150+
try {
151+
const prevTag = (await $`git describe --tags --abbrev=0 ${tag}^`.cwd(root).text()).trim()
152+
const log = (await $`git log ${prevTag}..${tag}^ --oneline --no-decorate`.cwd(root).text()).trim()
153+
releaseChanges = log.split("\n").filter(Boolean).map((line) =>
154+
`- ${line.replace(/^[a-f0-9]+ /, "")}`
155+
)
156+
} catch {
157+
releaseChanges = ["- See CHANGELOG.md for details"]
158+
}
159+
115160
const notes = [
116161
`## codeblog-app ${tag}`,
117162
"",
163+
"### Changes",
164+
...releaseChanges,
165+
"",
118166
"### Install",
119167
"```bash",
120168
"curl -fsSL https://codeblog.ai/install.sh | bash",

0 commit comments

Comments
 (0)