feat: add beta/stable release tracks with switchable update channel (#2345)
## Summary Fusion can now ship on two release tracks. Betas are cut from `main` as `vX.Y.Z-beta.N` (npm dist-tag `beta`, GitHub prerelease), stable releases are promoted to a long-lived `release` branch and published to `latest`, and users pick their track with the new `updateChannel` global setting — via **Settings → General → Release channel** or `fn update --channel <stable|beta>`. Previously everything was single-track: every publish landed on `latest` and every update surface could only see it. | | beta | stable | |---|---|---| | Cut from | `main` | `release` branch | | Version | `X.Y.Z-beta.N` (changesets pre-mode) | `X.Y.Z` | | npm dist-tag | `beta` | `latest` | | GitHub Release | prerelease | latest | | Homebrew tap / X draft | skipped | bumped / printed | ## How releasing works now `pnpm release` prompts for the channel and **defaults to beta**, so day-to-day releases are betas; stable is always an explicit choice. Choosing stable from `main` triggers assisted promotion: the script proposes the newest beta tag reachable from HEAD, verifies `release` fast-forwards to it, then runs the whole stable release inside a temporary git worktree on `release` — the primary checkout never leaves `main`. Changesets pre-mode preserves changeset files across betas, so the promoted stable release aggregates every changeset since the last stable into one clean changelog entry. ## Design decisions - **Every publish path names an explicit `--tag`.** A beta accidentally landing on `latest` is the one unrecoverable failure of a dual-track scheme, so nothing relies on npm's implicit default (`release.mjs`, `version.yml`). - **Beta channel resolves to semver-max of `latest` and `beta`**, so beta users are offered each promoted stable once it overtakes their prerelease. Switching beta → stable never downgrades; `fn update --channel stable --force` is the explicit escape hatch. - **One comparator instead of three.** CLI, dashboard, and desktop each had their own `isRemoteNewer` that ignored prerelease identifiers — `0.73.0-beta.2`, `-beta.3`, and `0.73.0` all compared equal, which breaks the moment any beta exists. They now share full SemVer-precedence helpers (`compareVersions`, `resolveUpdateTargetVersion`) from `@fusion/core`. - **Installs pin exact versions** (`@runfusion/fusion@0.73.0-beta.2`), never a dist-tag, so an install can't silently land on the wrong track. - **Desktop channels via electron-updater manifests.** Beta tags build desktop artifacts with `publish.channel=beta` (emitting `beta*.yml`); the app sets `channel`/`allowPrerelease` from the shared setting, re-read on every manual check. - **Update caches are channel-stamped** — a cache written for one channel is never served to the other, so switching tracks takes effect on the next check instead of after TTL. ## Test plan - New unit coverage: SemVer precedence + channel resolution in `@fusion/core` (30), channel behavior of the dashboard update check (28, incl. 9 new) and `fn update` (16, incl. 8 new: persist `--channel`, no-downgrade, `--force`, cache channel mismatch). - `pnpm verify:fast` green (scoped typecheck, builds, CLI build, boot smoke); desktop + settings-section suites green. - `release.mjs` dry-run matrix exercised by hand: channel prompt (default/override/invalid), branch preflights per channel, assisted-promotion target selection, fast-forward guard against a diverged `release` branch, and bootstrap when no `release` branch exists. - Not exercised live: an end-to-end publish (needs TTY authorization + real npm publish). First real run is the first `pnpm release --channel beta`. --- [](https://github.com/EveryInc/compound-engineering-plugin)  <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added beta and stable release channels across CLI, dashboard, and desktop updates. * Users can select a channel via Settings or `fn update --channel <stable|beta>` (stored as a global default). * Desktop beta releases now generate beta update manifests and publish as prereleases. * **Documentation** * Expanded release-track, settings, and CLI references to explain channel semantics and workflows. * **Bug Fixes** * Updates now pin the resolved version per channel, improve version comparison, and prevent unintended cross-channel downgrades unless `--force` is used. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -126,23 +126,34 @@ onboarding does not auto-launch.
|
||||
|
||||
## `fn update`
|
||||
|
||||
Check for and install the latest `@runfusion/fusion` CLI release from npm.
|
||||
<!--
|
||||
FNXC:UpdateChannels 2026-07-19-16:20:
|
||||
User-facing update-channel contract: `--channel` persists the chosen track to the shared `updateChannel` global setting; stable resolves the npm `latest` dist-tag only while beta resolves the newer of `latest` and `beta`; switching beta → stable never downgrades and `--force` is the sole explicit downgrade path; installs always pin the exact resolved version, never a dist-tag.
|
||||
Keep this comment in sync with packages/cli/src/commands/update.ts when the contract changes.
|
||||
-->
|
||||
|
||||
Check for and install the latest `@runfusion/fusion` CLI release from npm, on the configured release channel.
|
||||
|
||||
```bash
|
||||
fn update
|
||||
fn update --check
|
||||
fn update --global
|
||||
fn update --json
|
||||
fn update --channel beta # switch to the beta track and update onto it
|
||||
fn update --channel stable # switch back to stable (no downgrade; see --force)
|
||||
fn update --channel stable --force # explicit downgrade onto the current stable
|
||||
fn upgrade
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|---|---|
|
||||
| `--check` | Check only. Does not install. Exit code `1` when an update is available. |
|
||||
| `--global` | Explicitly install globally (`npm install -g @runfusion/fusion@latest`). This is the default behavior. |
|
||||
| `--json` | Output machine-readable status: `currentVersion`, `latestVersion`, `updateAvailable`, `updated`. |
|
||||
| `--global` | Explicitly install globally (`npm install -g @runfusion/fusion@<version>`). This is the default behavior. |
|
||||
| `--json` | Output machine-readable status: `currentVersion`, `latestVersion`, `updateAvailable`, `updated`, `channel`. |
|
||||
| `--channel <stable\|beta>` | Select the release track and persist it to global settings (`updateChannel`), shared with the dashboard and desktop updater. `stable` follows the npm `latest` dist-tag; `beta` follows the newer of `latest` and `beta`. |
|
||||
| `--force` | Install the resolved channel target even when it is not newer than the current version — the explicit beta → stable downgrade path. |
|
||||
|
||||
`fn upgrade` is an alias for `fn update`.
|
||||
`fn upgrade` is an alias for `fn update`. Installs always pin the exact resolved version rather than a dist-tag, so a beta-channel install can never silently land on stable (or vice versa).
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user