mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
docs(ci): describe the two release pipelines
Claude-Session: https://claude.ai/code/session_01ShuAy8q8ZfPi8KHxwc8JpQ
This commit is contained in:
1 parent
f7465eed16
commit
29ba1d34be
1 file changed
+56
-35
+56
-35
@@ -253,50 +253,71 @@ window looked like and not only that the conviction happened.
|
|||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Every merge to master cuts a release. `release.yml` watches ci with a
|
Two pipelines, and they are independent of each other.
|
||||||
`workflow_run` trigger, and when a push to master finishes green it advances the
|
|
||||||
patch: `0.1.3` becomes `0.1.4`. That publishes `@sanderling/spec` to npm and a
|
|
||||||
GitHub Release carrying the CLI binaries, both from the commit ci went green on
|
|
||||||
rather than from whatever master has drifted to since.
|
|
||||||
|
|
||||||
For a minor or major release, Actions -> release -> Run workflow. The `bump`
|
**Every merge to master cuts a patch.** The `Release` jobs sit in `ci.yml`
|
||||||
dropdown picks which part of `MAJOR.MINOR.PATCH` to advance, and `version`
|
alongside everything else, waiting on `Checks`, `Folio` and `Replay UI`, so
|
||||||
overrides it with a version named outright, which is how a pre-release like
|
nothing reaches a registry that the emulators and the simulator have not agreed
|
||||||
`1.0.0-rc1` gets cut. A pre-release publishes under npm's `next` tag so
|
on. `0.1.4` becomes `0.1.5`: published to npm, and to GitHub Releases with the
|
||||||
`npm install @sanderling/spec` keeps resolving the latest stable.
|
CLI binaries. It releases the commit that triggered the run rather than master's
|
||||||
|
head, because master can move in the hour the device legs take.
|
||||||
|
|
||||||
**The tags are the version.** Nothing in the tree holds it: `pkg/spec/package.json`
|
**`release.yml` promotes that to a milestone.** Actions -> release -> Run
|
||||||
stays at `0.0.0-dev` and CI stamps the real version in before it publishes. So
|
workflow, pick `minor` or `major`, and the version you have been running as
|
||||||
there is no version-bump commit to land on master, nothing to conflict on, and
|
`0.1.6` is republished as `0.2.0`. The `version` box overrides the dropdown with
|
||||||
no second record to hold in step with the tags. `.github/scripts/next-version.sh`
|
a version named outright, which is how a pre-release like `1.0.0-rc1` gets cut;
|
||||||
is the whole rule, and it counts off stable tags only, because `v0.0.1-rc4` is a
|
a pre-release publishes under npm's `next` tag so `npm install @sanderling/spec`
|
||||||
candidate for `0.0.1` and a patch counted off it would skip the version it was a
|
keeps resolving the latest stable.
|
||||||
candidate for. Run it anywhere to see what the next release would be:
|
|
||||||
|
It runs no checks and needs none. The commit it releases is the one the last
|
||||||
|
release was cut from, and that commit only carries a tag because a whole ci run
|
||||||
|
went green on it. Re-running the device legs to republish bytes that already
|
||||||
|
passed them would prove nothing. Afterwards the patch line continues from the
|
||||||
|
milestone: the next merge counts off `0.2.0` and cuts `0.2.1`.
|
||||||
|
|
||||||
|
Both call `release-publish.yml`, which is where the tagging, the npm publish and
|
||||||
|
GoReleaser actually live. Two copies of a publish drift, and the drift only
|
||||||
|
shows up on a release.
|
||||||
|
|
||||||
|
**The tags are the version.** Nothing in the tree holds it:
|
||||||
|
`pkg/spec/package.json` stays at `0.0.0-dev` and CI stamps the real version in
|
||||||
|
before it publishes. So there is no version-bump commit to land on master,
|
||||||
|
nothing to conflict on, and no second record to hold in step with the tags.
|
||||||
|
`.github/scripts/next-version.sh` is the whole rule, and it counts off stable
|
||||||
|
tags only, because `v0.0.1-rc4` is a candidate for `0.0.1` and a patch counted
|
||||||
|
off it would skip the version it was a candidate for. Run it anywhere to see
|
||||||
|
what the next release would be:
|
||||||
|
|
||||||
```
|
```
|
||||||
BUMP=minor .github/scripts/next-version.sh
|
BUMP=minor .github/scripts/next-version.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
The tag is pushed before anything is published, because npm is the half of a
|
The tag is pushed before anything is published, because npm is the half of a
|
||||||
release that cannot be taken back and a tag is the half that can.
|
release that cannot be taken back and a tag is the half that can. Both pipelines
|
||||||
|
resolve their version under one `release-tag` concurrency group, so a promotion
|
||||||
|
and a merge can never count off the same tag at once.
|
||||||
|
|
||||||
### Why the release is its own workflow
|
### The npm credential
|
||||||
|
|
||||||
npm authenticates the publish over OIDC, against a trusted publisher configured
|
`NPM_TOKEN` is a classic automation token. Those do not expire, which is the
|
||||||
for `@sanderling/spec` on npmjs.com. There is no token in the repo and none to
|
whole point: what took the pipeline down in August 2026 was a *granular* token,
|
||||||
expire, which is what took the pipeline down in August 2026: the `NPM_TOKEN`
|
and granular tokens default to a 30-day life. npm answers a publish it will not
|
||||||
secret behind the old tag-driven release had expired, and npm answers an
|
authorise with `404`, so the failure read as "package does not exist" while
|
||||||
unauthorised publish with a 404 that reads as "no such package".
|
`@sanderling/spec` was sitting in the registry the whole time.
|
||||||
|
|
||||||
npm matches that trusted publisher against the filename of the workflow that
|
Trusted publishing over OIDC would remove the token, and it is the better
|
||||||
*starts* the run, and a package carries only one. So the publish has to live in
|
mechanism, but it cannot express this shape: a package carries exactly one
|
||||||
one entry-point workflow: a job inside ci.yml would present `ci.yml`, and a
|
trusted publisher, matched against the filename of the workflow that *starts*
|
||||||
reusable workflow called from ci.yml would present the caller's name too. That
|
the run, and there are two workflows here that publish. A reusable workflow does
|
||||||
is why the merge path arrives at `release.yml` as a `workflow_run` rather than
|
not help, because npm sees the caller's name. Collapsing the two pipelines into
|
||||||
as a job at the end of ci.
|
one is the price of OIDC, and it is not worth paying.
|
||||||
|
|
||||||
Setting that up again, or moving the package, means npmjs.com -> the package ->
|
### A promoted release has thin release notes
|
||||||
Settings -> Trusted publisher: repository `priyanshujain/sanderling`, workflow
|
|
||||||
`release.yml`. It needs npm 11.5.1 or newer, which the job installs, because
|
GoReleaser builds its changelog from the commits between the previous tag and
|
||||||
`actions/setup-node` writes an empty `_authToken` line into `.npmrc` and an
|
this one. A promotion tags a commit that is already tagged, so `v0.2.0` and
|
||||||
older npm reads that as "auth is configured" and never asks for an OIDC token.
|
`v0.1.6` sit on the same commit and there is nothing between them to list. The
|
||||||
|
binaries and the npm tarball are correct; only the generated notes are empty.
|
||||||
|
Setting `GORELEASER_PREVIOUS_TAG` to the previous milestone would make the notes
|
||||||
|
span the patches being consolidated, and is the obvious thing to add if those
|
||||||
|
notes start mattering.
|
||||||
Reference in new issue
Block a user