chore(mobile): lighter-weight release process (#2144)

Signed-off-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1re830n24qhgstulznk5dxdluccspxkxxdq643lm4q3zwwwjgsuqqmcmdrm <1e4f17cd5505d105f3e29da8d337fcc6201358c6683558ff750444e73a488700@buzz.block.builderlab.xyz>
Signed-off-by: npub102wg7q285p64ch2fjvstmf2ntn2sz3c4u5hmwatalc76mhsuauysftjtfj <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
Co-authored-by: npub1sv749mw8zcmld4ygjx2mx2aqcn3zvtuj2nlmgatxgad3t9uweu9q5marze <833d52edc71637f6d4889195b32ba0c4e2262f9254ffb47566475b15978ecf0a@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1ux8n2yfs8qfvgd75s7kyhar2mztac355v6vmrz4juc9l3msw4pgstums9e <e18f3511303812c437d487ac4bf46ad897dc46946699b18ab2e60bf8ee0ea851@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1re830n24qhgstulznk5dxdluccspxkxxdq643lm4q3zwwwjgsuqqmcmdrm <1e4f17cd5505d105f3e29da8d337fcc6201358c6683558ff750444e73a488700@buzz.block.builderlab.xyz>
Co-authored-by: 7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz <7a9c8f0147a0755c5d499320bda5535cd5014715e52fb7757dfe3dadde1cef09@buzz.block.builderlab.xyz>
This commit is contained in:
Tom Brow
2026-07-23 15:05:59 -07:00
committed by GitHub
parent e67303f603
commit 21573b6cb9
13 changed files with 1014 additions and 190 deletions

View File

@@ -1,25 +1,18 @@
# Releasing Buzz
Buzz has three independent release lanes, each driven by a release PR — no human
ever pushes a git tag:
Buzz has three independent release lanes. Desktop and relay use release PRs.
Mobile uses immutable release-candidate tags cut directly from remote `main`:
| Lane | Recipe | Artifact |
|------|--------|----------|
| Lane | Entry point | Artifact |
|------|-------------|----------|
| Desktop | `just release-desktop` | Signed desktop app (macOS/Linux) |
| Relay | `just release-relay` | `ghcr.io/block/buzz` container image |
| Mobile | `just release-mobile` | Buzz mobile app (tag is the `sprout_ref` for the internal build) |
| Mobile | `scripts/mobile-release.sh candidate X.Y.Z` | Exact `mobile-vX.Y.Z-rc.N` source identity |
The three lanes version independently: the desktop version lives in
`desktop/package.json`, the relay version in `crates/buzz-relay/Cargo.toml`, and
the mobile version in `mobile/pubspec.yaml`.
The mobile lane publishes a `mobile-v<version>` tag that is consumed
**manually**, cross-repo, as the `sprout_ref` input to the internal
`buzz-releases` Buildkite pipeline (iOS dogfood → Block Comp Portal, App Store →
TestFlight — see [Internal Releases](#internal-releases)). The OSS lane is
tag-only **by design**: OSS `block/buzz` CI cannot trigger CI in the private
`buzz-releases` repo (infosec), so a human cuts the internal build from the tag
rather than auto-dispatching across that boundary.
The lanes version independently. Desktop reads its manifests, relay reads its
crate manifest, and mobile derives both source and marketing version from the
exact candidate tag. The mobile handoff to the private `buzz-releases` pipeline
remains manual because OSS CI cannot trigger private CI.
## Quick Start
@@ -27,126 +20,97 @@ rather than auto-dispatching across that boundary.
# Desktop release (next patch version)
just release-desktop
# Desktop patch / minor / explicit
just release-desktop patch
# Desktop explicit version
just release-desktop 0.4.0
just release-desktop 1.0.0
# Relay release (same argument forms)
# Relay release
just release-relay
just release-relay 0.4.0
# Mobile release (same argument forms)
just release-mobile
just release-mobile 0.4.0
# Publish the next mobile candidate from the exact current remote main commit
scripts/mobile-release.sh candidate 0.5.0
```
`just release-desktop` creates a `version-bump/<version>` PR; `just
release-relay` creates a `relay-release/<version>` PR; `just release-mobile`
creates a `mobile-release/<version>` PR. Each bumps its own version manifest,
regenerates lockfiles, and appends a changelog entry. Merge the PR to trigger
the build automatically (the mobile tag is instead the `sprout_ref` a human
feeds the internal build — see above).
Re-running any of these recipes with the same version is safe — it detects the
existing branch and PR, resets to current `main`, regenerates the changelog
with any new commits, and updates the PR in place.
Desktop and relay releases use metadata PRs. Mobile does not. Each
`mobile-vX.Y.Z-rc.N` tag is an immutable candidate and the artifact of record.
There is no mobile release branch, stable mobile tag alias, finalization step,
or mobile GitHub Release.
---
## How It Works
All three lanes share one engine; they differ only in which version manifest
they bump, which branch prefix they use, and what the merge triggers.
The merge workflow creates tags with a short-lived installation token from the
dedicated `buzz-release-bot` GitHub App. Release-tag rules allow that App to
create matching tags and prevent other actors from creating, moving, or
deleting them. The workflow's default `GITHUB_TOKEN` is read-only.
### Desktop
1. **`just release-desktop`** runs locally on `main` — computes the next
version, creates (or reuses) a `version-bump/<version>` branch, bumps the
desktop manifests, regenerates lockfiles, generates a changelog
entry in `CHANGELOG.md`, commits, pushes, and opens (or updates) a PR.
2. **Merge the PR** — the `auto-tag-on-release-pr-merge` workflow detects the
`version-bump/*` branch merge and pushes a `v<version>` tag.
3. **Tag triggers `release.yml`** — builds, signs, notarizes, and publishes the
desktop app for macOS and Linux.
1. **`just release-desktop`** runs locally on `main`, creates or updates a
`version-bump/<version>` PR, bumps the desktop manifests, regenerates
lockfiles, and updates `CHANGELOG.md`.
2. **Merge the PR.** `auto-tag-on-release-pr-merge` pushes `v<version>`.
3. **The tag triggers `release.yml`.** It builds, signs, notarizes, and
publishes the desktop app for macOS and Linux.
### Relay
1. **`just release-relay`** runs locally on `main` — computes the next relay
version, creates (or reuses) a `relay-release/<version>` branch, bumps
`crates/buzz-relay/Cargo.toml`, regenerates `Cargo.lock`, generates a
changelog entry in `crates/buzz-relay/CHANGELOG.md`, commits, pushes, and
opens (or updates) a PR.
2. **Merge the PR** — the `auto-tag-on-release-pr-merge` workflow detects the
`relay-release/*` branch merge and pushes a `relay-v<version>` tag.
3. **Tag triggers `docker.yml`** — the `relay-v<version>` push triggers
`docker.yml`, which builds the multi-arch relay
image and publishes `ghcr.io/block/buzz:<version>` (plus `:<major>.<minor>`,
`:<major>`, and `:latest` for stable releases). Prereleases
(`relay-v<version>-rc.1`) publish only the prerelease tag and do **not**
move `:latest`. GitHub runs the tag trigger because the tag is created by
the dedicated GitHub App rather than the workflow's `GITHUB_TOKEN`.
1. **`just release-relay`** runs locally on `main`, creates or updates a
`relay-release/<version>` PR, bumps `crates/buzz-relay/Cargo.toml`,
regenerates `Cargo.lock`, and updates the relay changelog.
2. **Merge the PR.** `auto-tag-on-release-pr-merge` pushes
`relay-v<version>`.
3. **The tag triggers `docker.yml`.** Stable releases update the version
aliases and `latest`; prereleases do not.
Every push to `main` continues to build and publish `:main` + `:sha-<7>` tags
(the rolling development image). The `:latest` tag tracks the latest **stable**
relay release only — it does not move on main pushes or prereleases.
Every push to `main` continues to publish the rolling relay `:main` and
`:sha-<7>` tags.
### Mobile
1. **`just release-mobile`** runs locally on `main` — computes the next mobile
version, creates (or reuses) a `mobile-release/<version>` branch, bumps
`mobile/pubspec.yaml` (preserving the `+build` number), regenerates
`mobile/pubspec.lock`, generates a changelog entry in `mobile/CHANGELOG.md`,
commits, pushes, and opens (or updates) a PR.
2. **Merge the PR** — the `auto-tag-on-release-pr-merge` workflow detects the
`mobile-release/*` branch merge and pushes a `mobile-v<version>` tag.
3. **The tag is consumed manually, cross-repo** — nothing in OSS `block/buzz`
builds on the tag (OSS CI must not trigger CI in the private `buzz-releases`
repo — infosec). A human feeds the `mobile-v<version>` tag as the
`sprout_ref` input to the internal `buzz-releases` Buildkite pipeline, which
builds and ships iOS to Block Comp Portal (dogfood) and TestFlight (App
Store, opt-in). See [Internal Releases](#internal-releases).
1. **Publish a candidate.** From a clean checkout whose `origin` is the
canonical `block/buzz` repository, run
`scripts/mobile-release.sh candidate X.Y.Z`. The script resolves and fetches
the exact current `origin/main` commit, derives the next number from exact
remote tags for that marketing version, and publishes an annotated
`mobile-vX.Y.Z-rc.N` tag there through the dedicated `buzz-release-bot`
GitHub App. It never uses the operator's checked-out commit and never moves
an existing candidate.
2. **Build the exact tag.** Enter the candidate tag as `mobile_ref` in the
private Buzz mobile Buildkite pipeline. OSS CI deliberately cannot trigger
that private pipeline. The tag supplies both source commit and release
version. Flutter receives clean marketing version `X.Y.Z`; Buildkite's
monotonically increasing build number supplies the platform build number.
3. **Promote tested artifacts.** Promote the already-built signed artifact for
each platform through its store workflow. Record the exact tag with the
build or rollout record. No source ref is changed and no final build is cut.
The iOS and Android artifacts for one marketing version may come from different
RC tags. For example, iOS can ship `mobile-v0.5.0-rc.2` while Android ships
`mobile-v0.5.0-rc.3`. Each platform's exact candidate tag is its source record.
There is intentionally no single selected or final candidate for the marketing
version.
The simplification trades away a separate stabilization line. Unrelated commits
that reach `main` become part of every later candidate, and there is no retained
hotfix branch or branch-ancestry history. Add a dedicated hotfix flow later if a
release actually needs isolation from `main`.
`mobile/pubspec.yaml` keeps `0.0.0+1` only as a valid, visibly non-release
fallback for local development and validation builds. Release jobs always
inject both version fields. `mobile/CHANGELOG.md` is retained as historical
release data. It is not a release ledger for this flow.
---
## Release Types
## Version Sources
The argument forms below apply to `release-desktop`, `release-relay`, and
`release-mobile`:
| Lane | Release version authority |
|------|---------------------------|
| Desktop | `desktop/package.json` and synchronized desktop manifests |
| Relay | `crates/buzz-relay/Cargo.toml` |
| Mobile | Exact `mobile-vX.Y.Z-rc.N` remote tag |
| Command | Version | Example |
|---------|---------|---------|
| `just release-desktop` | Next patch | `0.3.0` → `0.3.1` |
| `just release-desktop patch` | Next patch | `0.3.0` → `0.3.1` |
| `just release-desktop 0.4.0` | Explicit minor | `0.3.1` → `0.4.0` |
| `just release-desktop 1.0.0` | Explicit | `1.0.0` |
---
## Version Files
`just bump-desktop-version <version>` (desktop lane) updates these files:
| File | Field |
|------|-------|
| `desktop/package.json` | `"version"` |
| `desktop/src-tauri/tauri.conf.json` | `"version"` |
| `desktop/src-tauri/Cargo.toml` | `version` (under `[package]`) |
It also regenerates `pnpm-lock.yaml` and `desktop/src-tauri/Cargo.lock`.
`just bump-relay-version <version>` (relay lane) updates
`crates/buzz-relay/Cargo.toml` (`version` under `[package]`) and regenerates the
workspace `Cargo.lock`.
`just bump-mobile-version <version>` (mobile lane) updates
`mobile/pubspec.yaml` (`version:`, preserving the `+build` number) and
regenerates `mobile/pubspec.lock`.
`just bump-desktop-version <version>` updates the desktop manifests and
regenerates their lockfiles. `just bump-relay-version <version>` updates the
relay crate and regenerates `Cargo.lock`. Mobile has no bump recipe or
release-metadata PR.
---
@@ -184,42 +148,46 @@ existing immutable `v<version>` tag. Select that tag in the ref picker and
provide the matching semver version without the `v` prefix. It cannot build
from `main` or another caller-selected source ref.
Mobile intentionally has no branch or arbitrary-ref fallback. The private
Buildkite pipeline accepts only an exact candidate tag.
---
## Internal Releases
After the OSS release ships, trigger an internal build via the
[sprout-releases Buildkite pipeline](https://buildkite.com/runway/sprout-releases).
See the [buzz-releases README](https://github.com/squareup/buzz-releases#cutting-a-release)
for the full step-by-step instructions and input field reference.
For mobile, trigger the private
[Release Mobile pipeline](https://buildkite.com/runway/buzz-mobile-releases) with
an exact RC tag for the platform build being cut. For desktop, use
[Release Desktop](https://buildkite.com/runway/sprout-releases). See the
[buzz-releases README](https://github.com/squareup/buzz-releases#cutting-a-release)
for the private pipeline contract.
---
## What Gets Published
Each release produces two GitHub releases:
Desktop publishes two GitHub releases:
1. **`v<version>`** — the user-facing release with the `.dmg` installer
(macOS).
1. **`v<version>`**: the user-facing release with installers.
2. **`buzz-desktop-latest`**: the rolling auto-updater release.
2. **`buzz-desktop-latest`** — a rolling pre-release for the Tauri
auto-updater containing `latest.json` and each platform's signed
updater artifact plus its `.sig` signature (`.tar.gz` on macOS,
`.AppImage` on Linux, and `_alpha-unsigned.exe` on Windows).
Mobile publishes only annotated `mobile-vX.Y.Z-rc.N` git tags. Store artifacts
and rollout records retain the exact tag they used. Mobile does not publish a
GitHub Release or a stable `mobile-vX.Y.Z` alias.
---
## Platform Support
The release workflow builds **two separate macOS DMGs** — Apple
The release workflow builds **two separate macOS DMGs**: Apple
Silicon (`darwin-aarch64`, the `release` job) and Intel
(`darwin-x86_64`, the `release-macos-x64` job) — plus Linux `.deb` and
(`darwin-x86_64`, the `release-macos-x64` job), plus Linux `.deb` and
`.AppImage`. Both macOS DMGs are codesigned, notarized, and attached to
the same `v<version>` release. Intel users download the `_x64.dmg`.
The Linux AppImage is post-processed by `desktop/scripts/fix-appimage.sh`,
which strips infra libraries over-bundled by linuxdeploy (they crash on
Mesa 25+ / GLib 2.88 distros — see
Mesa 25+ / GLib 2.88 distros; see
[tauri-apps/tauri#15665](https://github.com/tauri-apps/tauri/issues/15665))
and re-signs the artifact. As a result the AppImage relies on the
host's Wayland/GStreamer/graphics stack and requires GLib >= 2.72
@@ -231,8 +199,16 @@ host's Wayland/GStreamer/graphics stack and requires GLib >= 2.72
## Prerequisites
- **Write access** to the `block/buzz` GitHub repository
- **`gh` CLI** authenticated (`gh auth status`)
- The following **GitHub Actions secrets** must be configured:
- An `origin` remote whose configured URL is the canonical `block/buzz`
repository
- `gh` CLI version 2.87.0 or newer, authenticated with permission to dispatch
the candidate workflow
- Release tag ruleset [`14378754`](https://github.com/block/buzz/rules/14378754)
active for `mobile-v*`, with creation, update, deletion, and non-fast-forward
protections and `buzz-release-bot` as its sole always-bypass actor
- The `buzz-release-bot` App credentials configured for GitHub Actions
- The following **GitHub Actions secrets** must also be configured for the
desktop release lane:
| Secret | Purpose |
|--------|---------|
@@ -240,6 +216,14 @@ host's Wayland/GStreamer/graphics stack and requires GLib >= 2.72
| `TAURI_SIGNING_PRIVATE_KEY` | Tauri updater private key |
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password for the private key |
Mobile candidate publication requires workflow-dispatch access and the existing
release App because strict tag protection denies direct human creation. The App
must be installed on `block/buzz`, have Contents write and Metadata read, and
retain an `always` bypass on the immutable `mobile-v*` tag rules. It does not
require GitHub Releases permissions, repository Administration permission, or a
mobile release-branch ruleset. The publisher validates both the App token's
effective `current_user_can_bypass` value and the exact ruleset bypass actor set.
---
## Troubleshooting
@@ -250,15 +234,38 @@ Switch to `main` and pull latest before running the release recipe.
### `just release-desktop` fails with "working tree is dirty"
Commit or stash your changes before running the release recipe.
### New commits merged after creating the release PR
Re-run the release recipe (`just release-desktop`, `just release-relay`, or `just release-mobile`) from an up-to-date `main`. It resets the branch to current `main`, regenerates the changelog and PR body to include the new commits, and force-pushes the updated branch.
### New commits land after publishing a mobile candidate
### Build fails at "Validate version"
The version string must be valid semver: `MAJOR.MINOR.PATCH` with an optional pre-release suffix. Do not include a `v` prefix.
Run `scripts/mobile-release.sh candidate <version>` again after the intended
fix reaches remote `main`. It publishes a new immutable RC tag at the new exact
remote commit. Continue referring to each tested or shipped platform artifact by
its own exact tag.
### `scripts/mobile-release.sh candidate` fails because `main` moved during publication
The App-backed workflow may already have published the requested immutable RC
at the prior `main` tip before the operator command detects the race. Do not
move or delete that tag, and do not treat it as the candidate for current
`main`. Inspect the run URL from the command output, then rerun
`scripts/mobile-release.sh candidate <version>` to publish the next RC from the
new current `main` tip.
### A mobile candidate command selects the wrong RC number
Do not retry by moving or deleting a tag. Inspect the exact remote `mobile-v*`
tags and resolve the unexpected state. Candidate numbers are monotonically
increasing remote identities.
### A mobile candidate publication is rejected by repository rules
Confirm `buzz-release-bot` remains the sole always-bypass actor for the active
`mobile-v*` ruleset and that its Actions credentials are available. Do not grant
direct human creation or weaken update or deletion protection. Existing
candidate tags must remain immutable.
### Auto-updater reports "no update available"
Verify that the `buzz-desktop-latest` release exists and contains a
valid `latest.json`. The manifest covers all four platform keys
(`darwin-aarch64`, `darwin-x86_64`, `linux-x86_64`,
`windows-x86_64`); a missing entry usually means that platform's
release job failed — check the workflow run.
release job failed. Check the workflow run.