Document the release workflow (validate/build/publish-npm/release), npm trusted publishing via OIDC, the canonical repository.url requirement, and the @routstr/sdk release-ordering rule in docs/CI.md, linked from the README release section and project structure.
5.1 KiB
CI and release automation
How continuous integration and releases work for routstrd. Everything runs on
GitHub Actions; there are no repository secrets — publishing uses OIDC
trusted publishing and the workflow's own GITHUB_TOKEN.
Workflow
.github/workflows/release.yml.
Triggers:
| Trigger | What runs |
|---|---|
pull_request |
validate + build (no publish, no release) |
workflow_dispatch (manual) |
validate + build only — publish and release jobs are tag-gated |
push of a v* tag |
the full pipeline: validate → build → publish npm → GitHub Release |
Jobs:
-
validate—bun install --frozen-lockfile, then (on a tag) asserts the tag equalsv+package.jsonversion, thenbun run lint(tsc --noEmit) andbun test. -
build— a 4-way matrix that compiles standalone executables withbun build --compileand smoke-tests each one (--version,--help):runner target platform-arch ubuntu-24.04bun-linux-x64linux-x64ubuntu-24.04-armbun-linux-arm64linux-arm64macos-15-intelbun-darwin-x64darwin-x64macos-15bun-darwin-arm64darwin-arm64Each binary is packed into
routstrd-v<version>-<platform>-<arch>.tar.gzand uploaded as a build artifact. -
publish-npm— tag-gated,needs: [validate, build]. Publishes to npm with provenance (see below). Skips gracefully if that version already exists. -
release— tag-gated,needs: build. Collects the artifacts, writesSHA256SUMS, smoke-testsinstall.shagainst the just-built Linux-x64 archive over a local HTTP server, then creates the GitHub Release with the four archives,SHA256SUMS, andinstall.sh.
Cutting a release
# 1. bump package.json version and commit it
# 2. push the tag
git tag v0.4.15
git push origin main v0.4.15
The tag push is the single action that triggers everything. The tag must
equal v + package.json version (the tag must not already exist — the release
job uses gh release create --verify-tag).
After it finishes, confirm routstrd@<version> is on npm (latest) and that
the GitHub Release has all four archives plus install.sh.
npm publishing (OIDC trusted publishing)
publish-npm publishes with npm publish --access public --provenance. There is
no NPM_TOKEN:
-
The job grants
id-token: write; npm exchanges the GitHub OIDC token for a short-lived publish token. -
A one-time trust relationship on npm binds the package to this repo/workflow:
npm trust github routstrd \ --file release.yml \ --repo Routstr/routstrd \ --allow-publish -
The workflow upgrades npm (
npm install -g npm@latest) because trusted publishing requires npm ≥ 11.5.1 while Node 22 ships npm 10.x. -
npm publishruns the package'sprepublishOnly(bun run build), sodist/is produced in the job.
Gotchas
repository.urlmust use the canonical casing. Provenance validates it case-sensitively against the repo (https://github.com/Routstr/routstrd). A lowercaseroutstrfails with422 … Failed to validate repository information.bun/npmpreserve path case when normalizing, so the fix is to spell it exactlyRoutstr.- npm has a post-publish processing delay. A successful publish reports
+ routstrd@x.y.zand "Your package is being processed and may take a few minutes to become available." The version can take several minutes to become fetchable. A green workflow does not mean it is instantly installable.
Why tag push, not release: published
publish-npm runs off the tag push, not a release: published event. The
release job creates the GitHub Release using the default GITHUB_TOKEN, and
GitHub suppresses workflow runs for events generated by that token (loop
prevention) — so a release: published workflow would never fire here.
Idempotency
npm publishis guarded: ifroutstrd@<version>already exists, the job logs a warning and skips instead of failing.gh release create --verify-tagfails if the tag is missing; if the release already exists the job errors, so do not create the release by hand.
Dependency ordering with @routstr/sdk
routstrd depends on the published @routstr/sdk (not a local file link),
and CI installs with bun install --frozen-lockfile from the npm registry. When
a release needs a newer SDK:
- Release
@routstr/sdkfirst (it has its own publish pipeline; see the SDK repo's.github/workflows/publish.yml). - In the same commit, bump
@routstr/sdkinpackage.jsonand refreshbun.lock(bun install). - Then tag and push
routstrd.
Failure modes if that order is broken:
- Version not yet on npm →
validatefails withNo version matching "^x.y.z" found for specifier "@routstr/sdk". package.jsonbumped butbun.lockstale →bun install --frozen-lockfilefails.
Manual runs
gh workflow run release.yml --ref main runs validate + build only (the
publish and release jobs are gated on a v* tag ref). Useful for exercising the
build matrix without releasing.