ci(release): compose the release page — download button first, BREAKING, highlights, grouped changelog - #2248
Conversation
Renders the GitHub Release body the workflow will pass to --notes-file: the download button first (shields.io for-the-badge image linked to the deterministic asset URL), the SHA-256, the in-zip quick start link and an RC banner, then Breaking changes from the tag's UPGRADING.md section, Highlights from docs/releases/notes/<tag>.md, then the generated, grouped changelog. Curated sources are required for a stable tag (a missing UPGRADING section or notes file fails the run) and advisory for a release candidate (warning plus a documented fallback). Refs #2234
…e-notes A new compose-notes job renders the page body from the resolved tag, the built checksum and the tag's own docs, and uploads it as the release-page-notes artifact — so a no-publish rehearsal dispatch previews the exact page before any tag is cut. create-release downloads that artifact, refuses an empty or button-less body, passes it to gh release create --notes-file, and re-asserts it in the same gh release edit that clears the draft flag. That re-assertion is what keeps the resumable adopt path (#1806) idempotent: a draft left by an earlier attempt is brought to this run's body instead of keeping a stale one. .github/release.yml groups the generated changelog into bug fixes, features, docs, CI and dependencies, with a catch-all so no merged PR disappears. Refs #2234
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9bf8797a7f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Review record (global law 2) — round 1Fresh-context independent review (Opus, read-only + Blocking (HIGH): the MEDIUM (taken in the same round — wrong page content, cheap): LOW, taken (one-liner): 120,000-character body guard (GitHub caps at 125,000). LOW, recorded on #2234 (not fix-cycled): a Verified by the reviewer (no finding): no After the fix push: aging resets; one scoped verification of the fix diff; Codex triaged by the same bar. |
… changelog base Review round 1 on #2248. HIGH — the preview artifact was named release-page-notes, which minimatch's release-* pattern in create-release DOES match: with merge-multiple the rendered Markdown would have landed in release-assets/ and been published as a stray asset beside the ZIP. The PR's claim that the name sat outside the pattern was false. Renamed to composed-page-body, and the decorative string assertion is replaced by a property test that parses the workflow's own pattern: and name: values and evaluates them with node:path matchesGlob - including a self-check that the old name really did match. MEDIUM — generate-notes was POSTed with only tag_name, so GitHub inferred the changelog base. Once v0.3.0-rc.1 exists the stable v0.3.0 page would have covered only rc.1 to final. The base is now the newest published, non-draft, non-prerelease release, passed as previous_tag_name; a first release omits the field and says so. The call gets the same bounded 3-try 10/20s retry as the asset upload and fails closed when exhausted. LOW — the composer errors when the composed body exceeds 120,000 characters, naming the overflow, instead of letting gh reject the publish at GitHub's 125,000 cap after the assets are already built. Refs #2234
Fix round 1 —
|
| Command | Result |
|---|---|
node --test scripts/ci/compose-release-notes.test.mjs |
33 pass / 0 fail (was 31) |
node --test scripts/ci/release-desktop-dispatch.test.mjs (Git for Windows bash first on PATH) |
76 pass / 0 fail (was 75) |
node scripts/check-docs-governance.mjs |
Docs governance check passed. |
yaml.safe_load on the workflow |
jobs [resolve-source, build-frontend, build-backend, compose-notes, create-release] |
Mutation checks on the new tests
Each mutation applied, suite run, source restored, suite re-run green.
| Mutation | Went red |
|---|---|
artifact renamed back to release-page-notes |
the composed-body artifact name cannot be swept into the release asset download + the release body is the composed file on create AND re-asserted on publish |
generate_args+=(-f "previous_tag_name=…") → generate_args+=() |
the changelog base is the newest published stable release, with bounded retries |
--exclude-pre-releases dropped from the base lookup |
same test |
if (body.length > MAX_RELEASE_BODY_LENGTH) → if (false) |
a body over the GitHub release-body limit fails, naming the overflow |
The requested rename-back mutation is the first row: it goes red, so the property test is load-bearing.
Not changed, by instruction
The rehearsal-always-prerelease behaviour (a v0.0.0-dryrun+<sha7> tag contains -, so the preview always shows the RC banner) and the fenced-code heading truncation are recorded on #2234 and deliberately untouched here.
Still NOT verified
Unchanged from the PR body: the real publish path, the live generate-notes call and its new retry/base resolution, and the download-artifact handoff can only be proven by a real tag push or a rehearsal dispatch. Neither has been run. actionlint / shellcheck were not run locally — the new bash adds an array, a for retry loop and a sleep, all mirroring the existing asset-upload block, and avoids literal backticks in single-quoted printf formats (the SC2016 trap this workflow has hit before).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3d3b5fb44
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Fix-diff verification (fresh context, read-only + node --test in a scratch worktree) on |
|
Round 2 (ceiling). Codex's three new P2s are real edge cases outside the |
|
Base re-proof, not a fix. This PR's CI merge ref was computed against |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a62d07019
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Closes #2234
The release page stops being GitHub's flat auto-changelog with the download buried under the asset table.
scripts/ci/compose-release-notes.mjsrenders the body the workflow passes to--notes-file: download button first, then## Breaking changesfrom the tag's ownUPGRADING.mdsection,## Highlightsfromdocs/releases/notes/<tag>.md, then## What's changedfromreleases/generate-notesgrouped through a new.github/release.yml.What changed
scripts/ci/compose-release-notes.mjscomposeReleaseNotes()+ CLI. Renders the body; decides the missing-source policy.scripts/ci/compose-release-notes.test.mjsnode:testcases, including four CLI round trips through the real filesystem..github/release.yml"*"catch-all so no merged PR disappears..github/workflows/release-desktop.ymlcompose-notesjob;create-releaseconsumes its artifact,--notes-fileon create, re-asserted on publish.scripts/ci/release-desktop-dispatch.test.mjscompose-notes.docs/ops/RELEASE_TRUST_AND_DISTRIBUTION.mdDesign notes worth a reviewer's attention
resolve-source; andcreate-releaseis gated onpublish == 'true', so putting it there would leave the rehearsal path with nothing to preview.compose-notesneeds[resolve-source, build-backend]and has noif:guard — ano-publishdispatch renders the page and uploads it.The preview artifact isThis was false and is fixed in fix round 1 (release-page-notes, deliberately outside therelease-*pattern…c3d3b5fb4): minimatch'srelease-*DOES matchrelease-page-notes, so withmerge-multiple: truethe rendered Markdown would have landed inrelease-assets/and been published as a stray asset beside the ZIP. The artifact is nowcomposed-page-body, downloaded by exact name, and the string assertion is replaced by a property test that parses the workflow's ownpattern:andname:values and evaluates them withnode:path'smatchesGlob— including a self-check that the old name really did match. See the Fix round 1 comment.gh release edit --draft=false … --notes-filere-asserts the body in the same edit that clears the draft, so an adopted draft from an earlier attempt (possibly created before this composer existed) is brought to this run's body. The--prereleasearray and the notes file travel together, so the [Release] RC tags publish as a full release: no --prerelease on the GitHub Release and GHCR latest moves to the RC #2217 "never briefly a full release" property is unchanged.taskdeck-${RELEASE_TAG}-win-x64.zip— the tag with its leadingv, matching whatbuild-backendactually packages (Compress-Archive … "taskdeck-${env:RELEASE_TAG}-${env:RELEASE_RID}.zip"). The issue text said "tag without the leadingv"; the workflow disagrees and the workflow wins.UPGRADING.mdsection or missing highlights file ⇒ exit 1, nothing written, nothing published. Release candidate ⇒::warning::plus a documented fallback. A missing or filename-mismatched checksum fails either way — publishing a digest for a different file is worse than publishing none.${{ }}reaches bash:RELEASE_TAG/RELEASE_PRERELEASE/RELEASE_PUBLISHcome throughenv:fromresolve-source(already grammar-checked there); the repo comes from${GITHUB_REPOSITORY}.compose-notesneedscontents: writeonly becausereleases/generate-notesis a POST that a read-only token is refused — it performs no writes.Rendered sample —
v0.3.0-rc.1Rendered locally by the real CLI against the real
UPGRADING.mdfromdocs/v030-rc1-pretag(PR #2245) and a realPOST /releases/generate-notes(previous_tag_name=v0.2.0). 10,800 bytes, exit 0. The SHA-256 and the highlights bullets are stand-ins — the digest comes from the build and the highlights file is #2245's deliverable.Composed release body (abridged in the changelog section only)
Note on the sample's changelog: it is ungrouped because
.github/release.ymlis read from the repository's default branch, and this PR is what puts it onmain. Once merged,generate-notesreturns the same PR list under### Bug fixes/### Features/ … headings. The composer passes that grouping through unchanged (the generated changelog is passed through with its compare linkcovers it against a grouped fixture).Verification
All run in the worktree
C:\Users\jekyt\source\wt-2234at head9bf8797a7.node --test scripts/ci/compose-release-notes.test.mjsnode --test scripts/ci/release-desktop-dispatch.test.mjs(Git for Windows bash first on PATH)node scripts/check-docs-governance.mjsDocs governance check passed.python -c "yaml.safe_load(...)"on both YAML files[resolve-source, build-frontend, build-backend, compose-notes, create-release]; 9 changelog categories parsedv0.3.0-rc.1against realUPGRADING.md+ realgenerate-notesMutation checks
Every new test was proven to fail against a deliberately broken source, then the source was restored and re-run green.
Composer (
compose-release-notes.mjs):sections.join→sections.reverse().join(button no longer first)the download link is the first line of the body,sections run download, breaking changes, highlights, changelog, and 2 CLI round tripsif (requireCuratedSources) errors.push→if (false)(stable-tag policy downgraded to a warning)… is an ERROR for a stable tagcases +the CLI exits non-zero and writes NOTHING …'--'→'-'the badge escapes shields.io separators so an RC tag renders in fullif (name !== assetName)→if (false)(checksum may name another file)a checksum naming a different file is refused …text.startsWith(tag)a version heading matches on a tag boundary, never a bare prefix+a missing UPGRADING section is an ERROR for a stable tagWorkflow (
release-desktop.yml):release-notes/other.mdthe release body is the composed file on create AND re-asserted on publishrelease-page-notes→release-notes(inside therelease-*asset glob)the rendered notes upload as a run artifact outside the release-* asset patternif [ "${RELEASE_PUBLISH}" = "true" ]→if truegenerate-notes is attempted only when a tag actually existsRestored source:
git diffclean against the committed files, both suites green.NOT verified
gh release create --notes-file/gh release edit --notes-file, thedownload-artifacthandoff betweencompose-notesandcreate-release, the livegenerate-notescall inside Actions, and the adopt-path re-assertion can only be proven by an actual tag push. The structural tests pin the strings; they do not execute the workflow.release-page-notesartifact (and the job summary, which prints the body). That has not been run here.actionlint/shellcheckwere not run locally (the Workflow Lint CI job covers them). The new bash avoids literal backticks in single-quotedprintfformats, which is the SC2016 trap this workflow has hit before.·in the badge message was not loaded in a browser.powershellfence, the liftedUPGRADING.mdanchors such as#automatic-pre-migration-backups, which will not resolve on a release page) was not eyeballed.Residuals
docs/releases/notes/v0.3.0-rc.1.mddoes not exist yet — onmainor ondocs/v030-rc1-pretag(docs: v0.3.0-rc.1 pre-tag docs — apply the RC deck rulings #2245). The RC path degrades to a warning and omits## Highlights, so the tag is not blocked, but the RC page will have no curated highlights until that file is written. That is docs: v0.3.0-rc.1 pre-tag docs — apply the RC deck rulings #2245's deliverable, not this PR's.UPGRADING.mdonmainstill has no## v0.3.0-rc.1section (it is on the docs: v0.3.0-rc.1 pre-tag docs — apply the RC deck rulings #2245 branch). Same degrade-to-warning path; the tag must be cut from a commit that has it, or the RC page falls back to a pointer..github/release.ymlcategories — they describe scheduling, not what changed.Human-action file
OUTSTANDING_TASKS.mdis untouched by this PR. Its open[ ]items are unchanged; nothing here closes one.