ci: release the CLI from a tag - #76
Merged
Merged
Conversation
Cutting v0.0.10 by hand took eleven steps: cross-compile four targets with a recipe recovered from the previous release's BUILDINFO, tar them, hash the archives and the binaries inside them, create the release, upload six assets, hand-write the eight hashes into npm/release.js, bump four files that name the version, publish, and move the dist-tags. Every one of them is a place to get a hash wrong or forget a file, and the whole sequence lives in whoever last did it. Pushing `v<semver>` now does it. The ordering the release depends on is enforced rather than remembered: the archives are uploaded before the wrapper that pins them is published, and the pins are computed from the uploaded artifacts, so the wrapper can only ever describe bytes that are actually downloadable. Gates, each guarding a failure the manual path allowed: - `make verify` and the race checks run against the tagged tree; an unverified tree is never published. - A version already on the registry stops the run before the GitHub release exists, rather than after, since npm forbids overwriting one. - The built binary must report the tag's version and keep its help within one screen, so correct hashes cannot be pinned to the wrong build. - The package must contain exactly the six allowlisted files. - The published package is installed from the registry and run, exercising the download and both checksum gates a user hits. Two things the manual path got wrong are fixed here. `latest` is set explicitly on every release: left alone it stays wherever npm first put it, which is how it came to sit on 0.0.3 while beta moved to 0.0.9. And the outgoing version is rewritten literally rather than by a `0.0.x` pattern that would silently stop matching at 0.1.0. Archives are built with deterministic tar metadata, so identical source produces identical hashes and a pin can be reproduced. Publishing uses npm Trusted Publishing over OIDC. npm is restricting tokens that bypass 2FA for direct publishing, and the manual publish of 0.0.10 failed on EOTP; OIDC has no one-time password and stores no secret. `NPM_TOKEN` is still honored as a fallback. Not yet exercised end to end: the workflow cannot run until it is on main and a tag is pushed. The release.js generator was verified separately by reproducing v0.0.10's published manifest byte-for-byte, and the version rewrite, package-contents check, and tag parsing were each run against the real 0.0.9 to 0.0.10 case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SDSieWT6mnHB1EYXUyz7H
The final step opens a PR recording the published pins on main. Without pull-requests: write the token is read-only for that API and gh pr create fails with 403 — after the release and the npm publish have already succeeded, which is the worst place to lose the record. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019SDSieWT6mnHB1EYXUyz7H
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.
Push
v<semver>, get a release. Replaces the eleven-step manual sequence that produced v0.0.10.What it does
Build four platform archives → publish the GitHub release → compute the pins from the uploaded artifacts → publish the npm wrapper → set
betaandlatest→ open a PR recording the pins onmain.The ordering the release depends on is enforced rather than remembered: archives are uploaded before the wrapper that pins them is published, so the wrapper can only ever describe bytes that are actually downloadable.
Gates
Each one guards a failure the manual path allowed:
make verify+ race checks on the tagged treeTwo bugs from the manual path, fixed
latestis set explicitly every release. Left alone it stays wherever npm first put it — which is how it came to sit on 0.0.3 whilebetamoved to 0.0.9. Anyone running a barenpm install @tokencanopy/rainiertoday gets a build from before the hosted v0 surface existed.The version rewrite matches the outgoing version literally, not a
0.0.xpattern that would silently stop matching at 0.1.0 — the same quiet drift this workflow exists to end.Archives use deterministic tar metadata, so identical source yields identical hashes and a pin can be reproduced.
Publishing: OIDC, not a token
npm is restricting tokens that bypass 2FA for direct publishing, and the manual publish of 0.0.10 failed on
EOTP. Trusted Publishing has no one-time password and stores no secret, and adds provenance attestation.NPM_TOKENis still honored as a fallback.One-time setup required before the first automated release: add this repository and
release-cli.ymlas a trusted publisher for@tokencanopy/rainieron npmjs.com.Verification
The workflow cannot run until it is on
mainand a tag is pushed, so it is not yet exercised end to end. What was verified:release.jsgenerator reproduces v0.0.10's published manifest byte-for-byte.v0.0.10,v1.2.3,v0.0.11-rc.1and rejects0.0.10andvfoo.🤖 Generated with Claude Code
https://claude.ai/code/session_019SDSieWT6mnHB1EYXUyz7H