Generate release notes from merged pull requests - #986
Conversation
Cutting a release required hand-writing a CHANGELOG.md section first, and release.sh hard-failed without one. That is a whole pull request per release whose only job is narrating the previous ones, written after the fact by whoever remembers, and free to drift from what actually merged. release.sh now asks GitHub for the notes covering every pull request since the previous tag, each credited to its author. The tag does not exist yet at that point in the run, so target_commitish anchors the range end at the commit being released — verified against the live API. The runtime keeps working unchanged. The generated body is written back as a ## [X.Y.Z] section, so CHANGELOG.md still ships in DOC_FILES and /changelog still reads it offline. A hand-written section still wins when a release deserves narration, and --notes still overrides both. Generated bodies open with their own h2s. parseChangelogText ends a version section at the next line starting with "## ", so those are demoted to h3 on write — without that the entry truncates to its header and /changelog renders an empty release. CL-7888
38a4d51 to
9702ef0
Compare
Two bugs, both fatal to the generate-notes path. write_changelog_section ran during preflight, so it dirtied a tracked file before the release-commit step, which refuses to run on an unclean tree. Every generated-notes release would have died there. And nothing staged CHANGELOG.md anyway, so the section would never have reached main. The write now happens in the release-commit step and is staged with the version bump, which also puts it in DOC_FILES before the binaries build. The insertion point matched the first `## [` of any kind, which is the live `## [Unreleased]` heading — the new release sorted above it and stranded its contents below every future release. It now targets the first versioned header, and warns when Unreleased still has content, since nothing renames it any more. previous_tag is gone. The script fetches with --no-tags and never refreshes tags, so deriving the previous tag locally would replay already-released pull requests from a stale clone. GitHub derives it from release history instead. That also removes a set -e trap: the pipeline returned 1 on a first release and only survived because command substitution under `||` suspends errexit. CL-7888
|
Audit: Approve. Fit: generated release notes from merged PRs is the right source of truth going forward — it removes hand-written notes drift and matches how GitHub already credits authors. Scope stays tight to the release path. Fix note: one verified dead branch fixed on this branch (b409727) — in |
Closes CL-7888.
Summary
Cutting a release required hand-writing a
## [X.Y.Z]section inCHANGELOG.mdfirst, andrelease.shhard-failed without one (no ## [$VERSION] section in CHANGELOG.md — rename [Unreleased] first). That is a whole pull request per release whose only job is narrating the previous ones — written after the fact by whoever remembers, and free to drift from what actually merged.release.shnow asks GitHub for the notes covering every pull request merged since the previous tag, each credited to its author.faremeter/interchangereleases this way.The runtime feature stays
This is not "delete the changelog" — it is "stop writing it by hand".
src/changelog/index.tsstill parses the file,/changelogstill works, the post-upgrade what's-new still works, andCHANGELOG.mdstill ships inDOC_FILESso offline installs carry their notes. Only the source of the text changes.Precedence:
--notes <file>, then an existing hand-written section, then generated. A release that deserves narration can still have it.Two things I got wrong first, and verified
The tag does not exist yet when notes are resolved. I flagged this as the likely awkward part on the issue.
target_commitishanchors the range end at the commit being released — confirmed against the live API, which returned the full 0.3.24..HEAD PR list for an unbornv0.3.26.Generated bodies open with their own h2s (
## What's Changed,## New Contributors).parseChangelogTextends a version section at the next line starting with##, so writing the body in verbatim truncated the entry to its header —/changelogwould have rendered an empty release. Caught by round-tripping real generated output through the actual parser, not by reading it. Headings are demoted to h3 on write.After the fix, that round-trip gives:
Also
The
CHANGELOG.mdpreamble claimed the file is the hand-maintained source and told you to rename## [Unreleased]at cut time. Both are now false; it describes generation instead, and marks that everything below predates it.Note on grouping
GitHub's
.github/release.ymlgroups by pull-request labels, not commit-message type. The Conventional Commits work in #985 makes the generated list read better but does not by itself produce grouped sections. Interchange's notes are ungrouped and read fine, so grouping is optional polish — not added here.Testing
bash -n scripts/release.sh, changelog parser tests (32 pass),tsc --noEmit,bun run lint. The generate-notes call and the parser round-trip were both exercised against the real repo. The fullrelease.shrun is not exercised — it tags and publishes.