Make update commands fail clearly on invalid arguments while helping stable users discover newer beta releases. - Parse update and upgrade options strictly, rejecting unknown, duplicate, and malformed flags before running an update - Show a live-registry beta availability notice for stable human-readable output without affecting JSON or cached results - Add CLI coverage, beta bootstrap documentation, and a patch changeset Files changed: .changeset/fn-8452-update-unknown-flags.md | 7 + RELEASING.md | 2 +- docs/cli-reference.md | 4 + docs/getting-started.md | 2 + packages/cli/src/__tests__/bin-update-args.test.ts | 75 ++++++++++ packages/cli/src/bin.ts | 25 +--- packages/cli/src/commands/__tests__/update.test.ts | 44 ++++++ packages/cli/src/commands/update.ts | 162 +++++++++++++++++++-- 8 files changed, 285 insertions(+), 36 deletions(-) Fusion-Task-Id: FN-8452 Fusion-Task-Lineage: b29a1ce5-5a40-40ec-ac18-07107fa18344 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
12 KiB
Releasing
This project uses changesets for automated versioning and release management. Releases are distributed through two channels:
- npm packages — published automatically via
version.ymlusing changesets - GitHub Release with platform binaries — built and uploaded via
release.ymlwhen a version tag is pushed
How it works
1. Add a changeset
When you make a change that should be included in a release, add a changeset:
pnpm changeset
This will prompt you to:
- Select which packages are affected
- Choose the semver bump type (patch, minor, major)
- Write a summary of the change
Then edit the created changeset file to use the structured body format:
---
"@runfusion/fusion": minor
---
summary: Add a Command Center productivity control for LOC backfills.
category: feature
dev: Uses the new `fn_backfill_loc` tool; settings key `commandCenter.locBackfill`.
Fields:
summary(required) — one line, user-facing, max 120 chars.category(required) — one of:feature,fix,breaking,security,performance,internal.dev(optional) — developer/migration detail.
A markdown file will be created in the .changeset/ directory. Commit this file along with your code changes. Validate with pnpm check:changesets.
2. Version PR is created automatically
When changesets are merged to main, the version.yml workflow automatically opens (or updates) a "Version Packages" pull request. This PR:
- Consumes all pending changeset files
- Bumps package versions according to the changeset declarations
- Generates/updates
CHANGELOG.mdfiles for affected packages - Distills the version's changeset summaries into grouped, end-user-facing release notes in the root
CHANGELOG.mdvia Claude (claude -p --model sonnet): a Highlights section (top 3–5) plus category groups, and prints an engagement-oriented X draft (≤280 chars) after a local release
3. Merge the Version PR to release
When you merge the Version Packages PR:
- The
version.ymlworkflow detects that all changesets have been consumed - It builds all packages and publishes them to npm with provenance attestation
- It creates a git tag
v{version}based on thekbCLI package version - The tag push triggers
release.yml, which:- Builds platform-specific binaries for Linux x64, macOS x64, macOS arm64, and Windows x64
- Builds Android release assets: signed
fusion-android-release.apk+fusion-android-release.aabwhen Android signing secrets are configured, otherwise unsigned debugfusion-android.apk - Signs macOS binaries (codesign + notarization), Windows binaries (Authenticode), and Android release artifacts when keystore secrets are available
- Generates SHA256 checksums for all binaries and Android artifacts
- Creates a GitHub Release with all binaries, Android artifacts, and checksums attached
Release tracks: beta and stable
Fusion ships on two tracks. Users pick theirs with the updateChannel global setting (Settings → General → Release channel) or fn update --channel <stable|beta>. Older global installs that predate --channel can bootstrap with npm install -g @runfusion/fusion@beta.
| Track | Cut from | Version shape | npm dist-tag | GitHub Release | Homebrew |
|---|---|---|---|---|---|
| beta | main |
X.Y.Z-beta.N |
beta |
prerelease | — |
| stable | release branch |
X.Y.Z |
latest |
latest | bumped |
Beta release (from main)
pnpm release # prompts for the channel; beta is the default answer
pnpm release --channel beta # explicit, no prompt
The script auto-enters changesets pre-mode (.changeset/pre.json, tag beta) the first time, versions to the next -beta.N, publishes with an explicit --tag beta, tags vX.Y.Z-beta.N (the tag push builds binaries and marks the GitHub Release as a prerelease), and skips the Homebrew tap and X draft. Changeset .md files are preserved through beta versioning — pre-mode records them in pre.json so the eventual stable release aggregates everything.
Promoting to stable
The easy path — run from main and let the script do the promotion:
pnpm release --channel stable # or answer "stable" at the channel prompt
When run from main with the stable channel, the script starts assisted promotion:
- It proposes the newest
v*-beta*tag reachable from HEAD as the promotion target (promote a tested beta, not main's tip); you can accept or type another tag/commit. - It verifies
releasefast-forwards cleanly to that commit (a diverged release branch — e.g. unmerged hotfixes — fails with instructions instead of guessing). - It creates a temporary git worktree on
releaseat the target (bootstrapping the branch if it doesn't exist yet), installs dependencies there, and re-runs the whole stable release inside it — your checkout never leavesmain. The Homebrew tap path is handed into the worktree viaFUSION_HOMEBREW_TAP_DIR. - On success the temp worktree is removed; on failure it is kept for inspection.
The stable release itself exits pre-mode, versions to the clean X.Y.Z with the aggregated changelog, publishes to latest, marks the GitHub Release latest, and bumps the Homebrew tap.
Afterwards, back-merge release into main (the script prints the exact commands). This carries the consumed changesets, changelogs, version bump, and pre.json removal back — without it, the next beta double-releases old changesets.
Manual alternative: check out release in a worktree yourself, git merge --ff-only vX.Y.Z-beta.N, and run pnpm release --channel stable there.
Hotfixes
Commit on release (or a worktree branched from it), add a changeset, run pnpm release, then cherry-pick the fix back to main.
Channel semantics for users
stablefollows the npmlatestdist-tag only — betas are invisible.betafollows the semver-max oflatestandbeta, so beta users are offered each promoted stable once it overtakes their prerelease.- Switching beta → stable never downgrades; the user stays on the installed beta until the next stable passes it.
fn update --channel stable --forceis the explicit downgrade. - Desktop beta builds emit
beta*.ymlelectron-updater manifests; the desktop app selects them whenupdateChannelisbeta(allowPrerelease+ channel).
Distribution channels
| Channel | Workflow | Trigger | Output |
|---|---|---|---|
| npm | version.yml (stable only) or pnpm release |
Manual | npm packages with provenance (CI) |
| GitHub Release | release.yml |
Version tag (v*; v*-beta.N → prerelease) |
Signed platform binaries, Android APK/AAB + checksums |
Platform binaries
| Platform | Binary name | Signed |
|---|---|---|
| Linux x64 | fusion-linux-x64 |
— |
| macOS arm64 | fusion-darwin-arm64 |
✓ (codesign + notarization) |
| Windows x64 | fusion-windows-x64.exe |
✓ (Authenticode) |
| Android | fusion-android-release.apk, fusion-android-release.aab |
✓ when Android keystore secrets are configured |
| Android fallback | fusion-android.apk |
— (debug/unsigned APK when Android keystore secrets are absent) |
macOS Intel (
darwin-x64) is intentionally not shipped: the CLI is Apple-Silicon-only becausemacos-13GitHub runners are too scarce to build reliably. The desktop macOS DMG/ZIP remains universal.
Android release signing
release.yml and the tag-less test-release.yml rehearsal workflow publish signed Android release artifacts when all Android signing secrets are configured:
ANDROID_KEYSTORE_BASE64— base64-encoded.jks/.keystorefileANDROID_KEYSTORE_PASSWORDANDROID_KEY_ALIASANDROID_KEY_PASSWORD
Encode the keystore before saving it as a GitHub Actions secret:
base64 -w0 release.keystore
The Android native project under packages/mobile/android/ is generated and gitignored, so CI does not commit signing configuration into Gradle files. Instead, the release job injects signing at build time with Android Gradle Plugin android.injected.signing.* properties, builds assembleRelease and bundleRelease, verifies the APK signature, and uploads fusion-android-release.apk, fusion-android-release.aab, and matching .sha256 files. If ANDROID_KEYSTORE_BASE64 is absent, the workflow preserves the secret-free path by building the unsigned debug APK as fusion-android.apk with fusion-android.apk.sha256.
Automated Play Store / Play Console upload is intentionally out of scope for the release pipeline right now. It needs a Google service-account JSON secret, a published Play listing, and fastlane or r0adkll/upload-google-play wiring; that work is tracked separately in FN-7043 while Fusion remains sideload-first for pre-1.0 Android distribution.
Testing binary builds
Use the Test Release workflow (test-release.yml) to manually test binary builds without creating a real release:
- Go to Actions → Test Release → Run workflow
- The workflow builds all 4 platform binaries plus the Android APK/AAB path (signed when Android signing secrets are available, unsigned debug APK otherwise), runs smoke tests, and uploads artifacts
- Download the
all-binariesartifact to inspect the output
Manual release (fallback)
If you need to release manually, you can still push a version tag directly:
git tag v0.2.0
git push origin v0.2.0
This will trigger release.yml to build binaries and create a GitHub Release. Note: npm publishing is handled separately by version.yml and won't be triggered by a manual tag push.
Available scripts
| Script | Description |
|---|---|
pnpm changeset |
Add a new changeset |
pnpm changeset status |
Check pending changesets |
pnpm release |
Local interactive release: previews changesets, lets you accept or override the proposed version, then bumps + builds + publishes + tags; Claude authors Highlights + a ≤280-char engagement X draft (soft deterministic fallback if Claude is offline) |
pnpm release --yes |
Same, but auto-accepts the proposed version and skips the final confirmation |
pnpm release --dry-run |
Preview only — show changesets, proposed version, and Claude-authored X draft preview, then exit before any file/git/npm changes |
pnpm release --channel beta |
Beta release from main: pre-mode version X.Y.Z-beta.N, npm dist-tag beta, GitHub prerelease; no Homebrew/X draft |
pnpm release --channel stable |
Stable release: requires the release branch, publishes latest, marks the GitHub Release latest |
Without --channel, pnpm release prompts for the channel and defaults to beta (also the silent default with --yes or a non-interactive dry-run). Stable releases are always an explicit choice.
| pnpm release:version | Apply changesets and bump versions (used by CI) |
| pnpm --filter @runfusion/fusion build:exe | Build binary for current platform |
| pnpm --filter @runfusion/fusion build:exe -- --target <target> | Cross-compile for a specific platform |
| pnpm --filter @runfusion/fusion build:exe:all | Build binaries for all platforms |
Tips
- Every user-facing change should have a changeset — CI will remind you if one is missing
- You can add multiple changesets per PR if you're making changes to multiple packages
- Changeset files are automatically deleted when versions are bumped
- CI verifies binary compilation on every push/PR to catch build regressions early
- If your project enables
completionDocumentationMode: "changeset", triage specs will explicitly require.changeset/*.mdcompletion artifacts for relevant tasks; keep this aligned with your repo's release convention.
Internal packages
The following packages are internal and are not published to npm:
@fusion/core— Core domain model and task store@fusion/dashboard— Web UI and API server@fusion/engine— AI agents and orchestration@fusion/plugin-sdk— Plugin development SDK@fusion-plugin-examples/*— Example plugins
These packages have private: true in their package.json and are listed in the .changeset/config.json ignore array to prevent accidental publishing. Only the @runfusion/fusion package is published to npm.