Step-by-step for cutting releases — both beta (every merge to next) and stable (manual promotion). Complements the system-level Release & deploy pipeline architecture page.
Beta releases — automatic
Every merge to next becomes a beta release. As a contributor you do not run anything by hand:
Open a PR from your feat/... (or fix/... etc.) branch into next.
Bump the beta version on your branch:
cd pluginnpm run bump:beta # X.Y.Z-beta.N → X.Y.Z-beta.(N+1)
Use npm run bump:beta:start only when starting a fresh cycle right after a stable promotion (when next carries a plain X.Y.Z and needs a -beta.0).
Commit, push, get review, merge.
release.yml fires on the next push and publishes vX.Y.Z-beta.N as a GitHub prerelease with cosign-signed daemon binaries. BRAT consumers pick it up on next launch.
Stable releases — promotion via release branch
When next has accumulated enough verified work to ship as the next stable, the promotion flow is:
git checkout -b release/X.Y.Z nextcd pluginnpm run bump:stable # 1.0.44-beta.5 → 1.0.44 (drops -beta suffix)git commit -am "release: prepare X.Y.Z promotion"git push origin release/X.Y.Zgh pr create --base main --head release/X.Y.Z \ --title "release: promote next → main (X.Y.Z)"
The release branch is critical — it gives the bump commit a place to be CI-validated (commitlint, version-check, lint, type-check, tests, integration) before it reaches main. No admin override on next or main is needed.
After the PR is reviewed and merged into main:
release.yml fires on the main push → publishes vX.Y.Z stable + cosign-signed binaries + GitHub Release marked latest.
sync-main-to-next.yml fires on the same main push → opens an auto-merging PR main → next so the merge commit rejoins.
The next beta cycle starts on a normal feat/... branch with npm run bump:beta:start (X.Y.Z → X.Y.(Z+1)-beta.0).
The script (plugin/scripts/bump-stable.mjs) reads the current -beta.N suffix, strips it, runs npm version <stripped>. The version lifecycle hook calls bump-version.mjs which detects the plain version and updates all five manifest files (vs. a beta bump which only touches the bundled manifest + the BRAT mirror — see Release pipeline for why).
Hot-fixing main without going through next
If main has a critical bug that can’t wait for the next promotion:
Branch off main directly: git checkout -b hotfix/X.Y.Z main
Make the fix + bump: npm version patch --no-git-tag-version to go 1.0.44 → 1.0.45. (bump:stable won’t help here — there’s no -beta suffix to strip.)
PR into main. version-check requires the head version to be plain X.Y.Z strictly greater than base — same rule as a normal promotion.
After merge, the sync-main-to-next.yml workflow back-syncs to next automatically.
Pre-release sanity checks
Before opening the promotion PR, on the release branch:
# Plugin builds cleancd pluginnpm run build # esbuild production bundle# Tests pass locallynpm test # vitest unit tests# Server cross-buildsmake -C ../server cross # 4 binaries land in server/dist/# Optional: verify the daemon binary you'll ship matches the# previous release's signature chaincosign verify-blob \ --bundle ../server/dist/obsidian-remote-server-linux-amd64.bundle \ --certificate-identity-regexp \ 'https://github.com/sotashimozono/obsidian-remote-ssh/.github/workflows/release.yml@.*' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com \ ../server/dist/obsidian-remote-server-linux-amd64
CI runs the same checks on the release branch’s PR, so this is paranoia, not gating — useful when you’d rather find a problem before the PR is open.
What the release pipeline does NOT cover
Documentation site deploy — handled by docs.yml (separate workflow, triggers on changes to docs/** and docs-site/**).
Container image builds — deploy/docker/ is built smoke-test in CI but not pushed anywhere.
Notifications — no Slack/Discord/email hooks; subscribe to GitHub Releases (Watch → Custom → Releases).