| name | release |
| description | Cut an orrerix release — version bump across all five files (including Cargo.lock and package-lock.json), bump PR, human-gated tag, CI publish, release notes, and npm trusted-publishing verification. Covers stable and beta/RC (pre-release) tags. |
Cutting an orrerix release
Releases are tag-driven: pushing a v* tag runs .github/workflows/release.yml,
which builds installers for Windows / macOS (arm64 + x64) / Linux, creates the
GitHub release, and then publishes the orrerix npm launcher.
The workflow runs from the tag's commit, not from main. Any fix to
release.yml only takes effect for a tag that points at (or after) the fixed
commit — re-running a failed job re-runs the old workflow. If a fix has to
take effect, the tag itself has to move (see step 5).
1. Bump the version — five files, in one PR
The version lives in five places that must stay in lockstep:
| File | Field |
|---|
package.json | version |
package-lock.json | version (both top-level and packages[""].version) |
src-tauri/tauri.conf.json | version |
src-tauri/Cargo.toml | [package] version |
Cargo.lock | the orrerix package entry (workspace root — not src-tauri/) |
The lockfiles are what get missed (#90, #224). After editing Cargo.toml,
run cargo update --workspace at the repo root (the Cargo workspace root)
to regenerate Cargo.lock — this is dependency resolution scoped to
the workspace's own members, not a build: it doesn't invoke rustc, so it's
the one exception the ci-validate skill carves out for agent workers (see
that skill's "The Cargo.lock exception" section). Commit the lock
change and let the bump PR's own CI run (below) prove cargo check --locked
is consistent — don't also run cargo check --locked locally. After editing
the root package.json, run npm install --package-lock-only to regenerate
package-lock.json — verify the diff touches only the version fields (no
dependency churn) before committing it.
npm/package.json also carries the version, but the publish job overwrites it
from the tag (npm version "${GITHUB_REF_NAME#v}") — keep it in lockstep
anyway so the tree reads consistently.
CI has a mechanical backstop for this (#274): the "Check version
consistency" step (node scripts/check-versions.js) checks all seven
version fields across these six files (the five above plus
npm/package.json) and fails the build if any disagree, so a missed
lockfile bump can't merge silently. Run npm run check:versions
locally before opening the bump PR if you want the same check without
waiting on CI.
Commit as chore(release): bump version to X.Y.Z, PR to main, wait for CI,
and stop — the human merges (as always in this repo).
PowerShell note: multi-line PR/issue bodies via gh break on inline quoting —
pipe a single-quoted here-string into --body-file - instead.
2. Tag (human-gated)
After the bump PR is merged, tagging is the human's call — confirm before
pushing a tag, since it publishes immediately:
git checkout main && git pull
git tag vX.Y.Z
git push origin vX.Y.Z
Ask for the release grant once. The grant the human issues for vX.Y.Z
covers that tag's whole pipeline — this tag push, gh release create|edit vX.Y.Z, and the release-notes write in step 4 — for ~90 minutes. It is not
spent by the tag push, so do not go back to the human for a second grant
mid-release; if a step is refused, the reason is one of: the window expired
(ask for a fresh grant, saying so), the call names a different tag or
release, or a release id orrerix could not resolve (the refusal message says
which). The version-bump PR's merge in step 1 is not covered — that is
still the human's merge, as always.
3. Watch the workflow
gh run list --workflow release.yml then watch the run — create-release,
four build matrix legs, promote, and publish-npm, in that dependency
order.
create-release creates the draft release once and hands its id to every
build leg (releaseId input) and to promote, so no leg ever looks a
release up or creates one for itself — legs starting near-simultaneously
otherwise race (~3% per upstream tauri-apps/tauri-action#914) into two
drafts for one tag, and the assets split across them: 5 of 9 public, 4
stranded on the draft, is what that looks like in practice (#282).
create-release is also idempotent: if a release for the tag already
exists (e.g. a "Re-run all jobs" after a partial failure), it reuses that
release's id instead of spawning a second draft.
promote verifies the release's own asset count (expects 10) before
flipping it public, and refuses — leaving the release in draft — if short.
If promote fails with "Only N/10 assets" in the logs, don't just re-run
it: check gh api repos/OWNER/REPO/releases for a stray duplicate release
on the same tag first. If it's a genuinely missing/failed matrix leg
instead, re-run that leg, then re-run promote.
- npm auth is trusted publishing (OIDC) — no
NPM_TOKEN secret exists; if
publish fails with an auth error, the fix is in npm's trusted-publisher
config for the repo, not in secrets.
- Trusted publishing cannot create a package, and
orrerix does not exist on the
registry yet (loomux-desktop was fully unpublished, so nothing is installable under
either name). The first publish is a human npm publish by hand, done after the
next stable bump PR merges so the hand-published version IS that release's version —
publish-npm's already-published skip then makes that release's automatic publish a
deliberate no-op, and the release after it is the first real OIDC publish. Until that
runbook is carried out — it is written and merged, at
doc/design/rebrand-external.md section 2, which also carries the ordering and the
quoted npm prerequisites — expect publish-npm to fail on a stable tag (#1153, #1297).
Delete this bullet once npm view orrerix version resolves: it asserts a
transient registry state and goes false the moment the hand-publish happens.
- The publish step installs a pinned npm version (see the comment in
release.yml). Do not switch it back to @latest casually: npm 12.0.0
shipped missing its own bundle, and trusted publishing
auto-enables provenance, so every publish died with (upstream npm/cli#9722; our un-pin tracker is #186). If publish
fails with a MODULE_NOT_FOUND inside npm's own tree, suspect the npm
version before suspecting the repo.
4. Release notes
The workflow creates the GitHub release with a generic download blurb. Write
real notes after the assets are up:
5. Verify — the release isn't done until all of these pass
npm view orrerix version → X.Y.Z.
- The GitHub release has 10 assets:
-setup.exe + .msi, both .dmgs,
.AppImage + .deb + .rpm, the two .app.tar.gz bundles, and
Orrerix_X.Y.Z_x64.pdb.zip — the Windows debug symbols, which a crash
dump from a released build needs to symbolicate (#1218).
- The release run's conclusion is
success (not just "the assets exist" —
publish-npm is the last job and can fail after the assets upload).
If publish-npm fails after the assets are up
Re-running the failed job re-uses the tag's workflow. If the fix needs a
workflow change:
- PR the
release.yml fix; human merges.
- Move the tag — this deletes a published tag, so it needs the human's
explicit go-ahead (permission rules will rightly block it otherwise):
git push origin :refs/tags/vX.Y.Z
git tag -f vX.Y.Z origin/main
git push origin vX.Y.Z
- The workflow re-runs from the fixed commit; installers rebuild identically
and re-attach to the existing release, and hand-written notes survive.
6. Beta / RC (pre-release) tags
A tag with a hyphenated suffix — vX.Y.Z-beta, vX.Y.Z-rc1, anything
contains(tag, '-') — is a pre-release. release.yml detects this from the
tag string alone (no separate input); every job downstream re-checks the
same contains(github.ref_name, '-') condition, so nothing extra needs
setting up beyond pushing the right tag.
What's different from a stable release:
- No MSI. WiX rejects a non-numeric pre-release version identifier
("optional pre-release identifier in app version must be numeric-only ...
for msi target"), so the Windows build leg passes
--bundles nsis to
tauri build for these tags — NSIS only, MSI skipped. The asset count
promote checks for is 9, not 10 (stable minus the .msi; the
.pdb.zip IS still produced — the same cargo build emits it either way).
prerelease: true on the GitHub release, set at creation and never
flipped back.
make_latest: false when promote publishes it — a beta must never
become the release GitHub's latest API resolves, since the README's
install.sh / install.ps1 one-liners resolve latest and must keep
landing users on the newest stable build.
publish-npm doesn't run at all (job-level if) — the npm launcher
(npx orrerix) stays pinned to the latest stable version; there's
no npm-side "prerelease" concept to keep a beta installable-but-not-default,
so the simplest correct behavior is not publishing it.
- Release notes still apply to the canonical
release_id exactly as in step
4 — pre-release doesn't change how or where notes get written, only the
content (skip mentioning the .msi download).
Stable tags (no hyphen) are unaffected by any of the above — same asset
count (10), same npm publish. promote's "Publish the draft release" step
never sends make_latest in the same API call as draft=false, in either
direction (a make_latest=true sent that way can be silently dropped —
see the comment above the gh api calls for the evidence and why the
mechanism is inferred, not documented). It flips draft=false alone
first, then — once that call has returned 2xx — a second, separate call
sets make_latest explicitly for the tag kind: true for stable,
false for beta/RC (#341, #543).