Skip to content

feat(release_library): aggregate prerelease notes into graduated Lerna releases - #158

Merged
mathieudi merged 5 commits into
mainfrom
feat/graduation-notes-lerna
Jul 31, 2026
Merged

feat(release_library): aggregate prerelease notes into graduated Lerna releases#158
mathieudi merged 5 commits into
mainfrom
feat/graduation-notes-lerna

Conversation

@mathieudi

Copy link
Copy Markdown
Contributor

Adapter B of the graduation release-notes mechanism (design: github-repo-alignment/12-graduation-notes-design.md §3). Adapter A covers the 11 semantic-release libraries via @side/semantic-config-base; this covers the Lerna path.

The Lerna path is worse than semantic-release, not better. --conventional-graduate starts conventional-changelog's range at the last prerelease tag, so a graduation's range is empty and lerna writes **Note:** Version bump only for package <pkg> — the stable release body isn't collapsed, it's empty. That already shipped a real major with its BREAKING CHANGES note missing (@side-eng/test-monorepo-hello@10.0.0).

What this does

A composite action run once after publish, gated on main && IS_MONOREPO && ENABLE_GRADUATION_NOTES, using the App token the job already mints (contents:write — no permission change). It reads the tags at HEAD, finds the ones whose release body is the bump-only sentinel, aggregates that package's in-range prerelease releases via the shared engine, and PATCHes the release body.

Release bodies only — no commit, no push, no bypass actor, no re-trigger loop. Lerna's CHANGELOG.md is a committed file, so the prerelease entries already survive the squash there; only the release body needed repair.

It re-reads every release it patched and asserts the marker, the entry count and a byte-identical round-trip (design §4 guard 2), failing loudly otherwise. Idempotent twice over: the sentinel is gone after the first patch, and the engine's marker would no-op anyway.

Engine delivery

Installed from the registry at release time (@side/graduation-notes@^1), deliberately not vendored — the design made the engine a package precisely so the two adapters can't drift. Until reside-eng/release-config#205 publishes it, the step warns and skips; verified live (npm returns E404 today, adapter exits 0, releases untouched). Same for a missing token, no tags at HEAD, or an API failure — the engine's policy is never to block a release on an unknown.

Validation (local only, no mutations)

Mock releases API seeded from read-only dumps of all four Lerna repos, plus a throwaway git repo carrying the tags lerna would have created, run against the real adapter and real engine.

  • Sweep of 133 real releases across platform-tools / lint-config / test-monorepo / release-config: 0 crashes, 8 real graduations repaired, everything else a clean no-op.
  • Exercised: multi-package single run (4 patched in one pass), ordinary release no-op, dependency-bump-only no-op, all-bump-only-prereleases no-op, mixed noise, idempotency (3 runs → 1 PATCH), engine-unpublished degrade, no tags at HEAD, no token, transport failure, unrenderable sources → exit 1, tampered published body → assertion fails loudly.

⚠️ Design correction found in real data

The design specified detection as "sentinel AND compare-from is a prerelease". That second condition produces false negatives on real losses and is now advisory only: @side/fastify-pubsub@0.12.1 carries the sentinel with a stable compare-from, yet 0.12.1-alpha.0 in that range published a real Bug Fix (#501). The broader rule — sentinel plus at least one in-range prerelease that isn't itself bump-only — is a strict superset and cannot false-positive, since a sentinel body states nothing and the sources are in-range ancestors of that exact package version by construction.

Coverage

Covers 3 of 4 Lerna repos (platform-tools, lint-config, test-monorepo). release-config is NOT covered — it runs an inline release.yml rather than this template. Cost of leaving it is currently zero (all its prerelease bodies are themselves bump-only, so there's nothing to aggregate), but migrating it onto the template is worth doing for its four other drifts, including the missing --conventional-graduate.

🤖 Generated with Claude Code

…a releases

`lerna publish --conventional-graduate` resolves conventional-changelog's `from` to the
package's last PRERELEASE tag, so the commit range is empty and the GitHub Release body
becomes `**Note:** Version bump only for package <pkg>` — every itemised change of the
prerelease line is missing from the stable release. Confirmed on real graduations, incl.
`@side-eng/test-monorepo-hello@10.0.0`, a major whose BREAKING CHANGES note is absent.

`packages/<pkg>/CHANGELOG.md` is committed, so the detail survives; only the release body
is empty. This adds a post-publish step that patches release BODIES only — no commit, no
push, no bypass actor, no workflow re-trigger — keyed per package (all Lerna repos use
`"version": "independent"`).

The aggregation engine ships as `@side/graduation-notes`, the same package Adapter A
resolves through `@side/semantic-config-base`, installed at release time. Until it is
published the step warns and skips, so today's releases are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mathieudi
mathieudi requested a review from a team as a code owner July 30, 2026 16:25
@mathieudi
mathieudi requested review from alexsupp and prescottprue and removed request for a team July 30, 2026 16:25
@side-review-request
side-review-request Bot requested a review from a team July 30, 2026 16:26
mathieudi and others added 4 commits July 30, 2026 18:34
Lets a repo point at a vendored engine instead of the registry install — needed
to validate before @side/graduation-notes publishes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…before merge)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A graduation whose changelog range includes the squash commit publishes a
one-entry body rather than the sentinel, losing the prerelease detail all the
same. Detection now defers to the engine's nothing-new-vs-stable no-op.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@mathieudi
mathieudi merged commit 5ac7ebb into main Jul 31, 2026
4 checks passed
@mathieudi
mathieudi deleted the feat/graduation-notes-lerna branch July 31, 2026 09:41
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 1.18.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants