Build the release page from the tag's CHANGELOG section - #54
Merged
Merged
Conversation
v2.5.3 was tagged, changelogged and announced without anyone running gh release create, so the Releases list kept showing v2.5.2 as Latest. The answer is a gate rather than a reminder: pushing vX.Y.Z now renders the release body from the `## vX.Y.Z` section of CHANGELOG.md, builds the wheel, the sdist and SHA256SUMS, and publishes the page. release_notes.py owns the body. No section for the tag, an empty section, or an explicit docs/assets/*.gif that is not in the checkout all fail before anything is published. Link targets become raw URLs because a release body is not rendered relative to the repository; a path written in a sentence stays a path. A bare <name>.gif is attached when docs/assets holds it, which is how the CHANGELOG names continuation clips: the v2.5.3 section writes one full path and four bare names, and all five of those clips are on that release page. release_publish.py owns the decision and has three outcomes — released is a no-op, missing is a create, unreadable is a stop. Only gh's own "release not found" counts as missing, so a 503 cannot be answered by creating a page, and re-running a tag changes nothing. The workflow is a wrapper around that script so the decision can be run outside Actions: workflow_dispatch runs it with --dry-run against a branch, and only a tag push creates anything. tests/release covers the section rules, the asset rules and the three outcomes against a stub gh. One test renders this repository's own current version, so a release commit whose CHANGELOG section is missing or names an uncommitted clip fails CI while the tag can still be fixed.
… its rc Two holes found by reviewing the notes renderer against its own stated contract, both reachable only from a CHANGELOG this repository does not have yet — which is exactly when they are cheap to close. _heading_lines documents "a `## …` inside a fenced block is code, not a section", but it toggled on backticks alone. Markdown fences both ways, and quoted markdown is written with `~~~` precisely so the backticks inside it stay readable — the CHANGELOG entry most likely to contain a `## vX.Y.Z` line was the one spelling the rule did not cover. A fence is now closed only by the character that opened it, so a ``` line inside a `~~~` block is content and vice versa. version_of accepts `vX.Y.Z` with a suffix, so v1.0.0-rc1 is a legal tag; the heading guard `(?![\w.])` did not refuse `-`, so the tag v1.0.0 matched the `## v1.0.0-rc1` heading and a release candidate's notes would have been published as the release's. The guard is `(?![\w.-])` now. The "what does exist" hint had the same character missing from its own pattern, which made the new failure contradict itself: it refused `## v1.0.0` and then reported "Newest sections: v1.0.0". One test each. Reverting either fix fails its test.
The release procedure lived only in an untracked local file, so the one rule that is not obvious from the workflow had nowhere to be read: the gate attaches a showcase GIF only when the CHANGELOG section names it as docs/assets/<name>.gif *and* that file is committed here. A clip produced outside the repository — v2.5.4's, for one — cannot be attached by the workflow at all, so that case is an explicit gh release upload after the page exists, not a silently missing clip. The rest is the chain a release is: release commit, PR to main, tag push, the workflow builds the page, and `gh release view` is what says it is done. Also written down: an existing page is left untouched, what makes the job fail instead of publishing something wrong, and that gh answers "release not found" for a repository it cannot see, so that sentence is not proof a release is missing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A
vX.Y.Ztag can be pushed, changelogged and announced while the release page is never created — v2.5.3 was, and the Releases list kept showing v2.5.2 as Latest. This makes the page a consequence of the tag instead of something to remember.What it adds
.github/workflows/release.yml—push: tags: v*builds the wheel, the sdist andSHA256SUMSfrom the tag's commit and publishes the release page.workflow_dispatchruns the same decision with--dry-runagainst a branch, so the body and the asset list can be read before a tag exists.scripts/release_notes.py— the body is the## vX.Y.Zsection ofCHANGELOG.md. No section, an empty section, or an explicitdocs/assets/*.gifthat is not in the checkout all fail before anything is published. Link targets become raw URLs, because a release body is not rendered relative to the repository; a path written in a sentence stays a path.scripts/release_publish.py— three outcomes and no fourth: released is a no-op, missing is a create, unreadable is a stop. Onlygh's ownrelease not foundcounts as missing, so a 503 cannot be answered by creating a page, and re-running a tag changes nothing.tests/release/— 28 tests over the section rules, the asset rules and the three outcomes against a stubgh. One of them renders this repository's own current version, so a release commit whose CHANGELOG section is missing or names an uncommitted clip fails CI while the tag can still be fixed.docs/release.md— the procedure, indexed indocs/README.md.Two parser fixes from review
Both were reachable only from a CHANGELOG this repository does not have yet, and both cost one character plus one test.
_heading_linessays "a## …inside a fenced block is code, not a section" but toggled on backticks alone, so a~~~block's## vX.Y.Zline cut the section short. A fence is now closed only by the character that opened it.version_ofaccepts a suffixed tag, sov1.0.0-rc1is legal, but the heading guard(?![\w.])did not refuse-— the tagv1.0.0matched a## v1.0.0-rc1heading and would have published the release candidate's notes as the release's. The guard is(?![\w.-]), and the "what does exist" hint carried the same gap, which made the new failure contradict itself ("no## v1.0.0section. Newest sections: v1.0.0").The rule worth knowing
The workflow attaches a showcase GIF only when the CHANGELOG section names it as
docs/assets/<name>.gifand that file is committed here. A clip produced outside the repository — v2.5.4's — cannot be attached by the gate at all; that case stays a manualgh release uploadafter the page exists.docs/release.mdsays so.Verification
actionlint 1.7.12onrelease.ymlandci.yml: no findings.ghwith a disk-backed release store: second run reports "already has a release page … body and assets left untouched", store diff 0.