Skip to main content

release-please owns versions

Versions and changelogs are derived from the conventional commits commitlint already enforces. googleapis/release-please-action opens and maintains the release pull request; merging it is what makes a version real, and creating the GitHub releases is what permits a publish.

The chain it replaces had a human in the middle. A workflow force-pushed a branch carrying the bump, and somebody was supposed to open a pull request from it. That stopped happening, and main then took more than a hundred commits under a version string the registry already held. Five release dispatches in one day all named that version, from four different commits, and only one of them published anything: the publisher skips a version already on the registry and exits 0, so three runs reported a successful release of code they had not sent. npm refusing to overwrite a version was the only thing standing between four different builds and one version string.

That is not a hygiene problem. compile stamps the version into every artifact as compiledWith, so an artifact is supposed to identify what produced it — and two materially different compilers stamped the same string. One of them rejects a registration the other accepts because the registration behavior changed between builds. A provenance field that does not identify its producer is worse than an absent one because it looks like an answer.

So the release pull request is opened by the tool that computes it, and the publish job is gated on releases_created, which is true only on the run that created the releases for a version. A version cannot be published twice because the gate is an output of the thing that assigns it, rather than a step someone performs.

Rejected: patching the incumbent. It needed a workflow that opened its own pull request, a publish that failed rather than skipped, a dist-tag rule that stopped sending release candidates to latest, and a rule about when to leave prerelease mode — four bespoke mechanisms in a repository being cured of bespoke mechanisms, and none of them the missing gate.

Rejected: semantic-release, which publishes on every merge to the default branch and would remove the choice of when to leave a prerelease line — a choice this project made deliberately, and exercised once, in the release that cut 0.1.0.

Rejected, within release-please: the node-workspace plugin, which rewrites internal dependency ranges. Internal dependencies here are written workspace:* and pnpm resolves them when it packs, so there is nothing for it to do and a version range where the protocol belongs is the failure it would produce. linked-versions moves the packages as one and leaves dependency ranges alone, which is what a workspace protocol needs from it.