Release Boop
Releases new versions of Boop using a Go CLI (cmd/boop-release), ported from
the Screendrop release tool. It runs fully automated and non-interactively,
so a release can be triggered directly from a chat session.
Two modes:
- Full auto (
-build) — archive, export (Developer ID), notarize, staple,
package, sign, and publish. Nothing in Xcode's GUI is required.
- Package-only (no
-build) — assumes a notarized ~/Downloads/Boop.app
already exists, then packages & publishes.
Prefer full auto unless the user says they've already exported the app.
Prerequisites
create-dmg (brew), gh (authenticated), git, plutil, go.
- Sparkle's
sign_update in DerivedData — produced by building/archiving once.
- A notarytool keychain profile. Defaults to
screendrop-notary, which
already exists on this machine — notarytool profiles are per Apple team, not
per app, so the same credentials notarize every one of Fayaz's apps. To use a
different one, pass -notary-profile <name>, or create it with:xcrun notarytool store-credentials "<name>" \
--key /path/to/AuthKey_XXXXXXXXXX.p8 --key-id XXXXXXXXXX --issuer <issuer-uuid>
Flags
-build — run archive → export → notarize → staple first.
-set-version <x.y.z> — set MARKETING_VERSION before archiving (and commit).
-set-build <n> — set CURRENT_PROJECT_VERSION before archiving (and commit).
-scheme <name> — Xcode scheme (default Boop).
-notes "<text>" — release notes, one bullet per line. Skips the prompt.
-notes-file <path> — read notes from a file instead.
-notary-profile <name> — notarytool keychain profile (default screendrop-notary).
-yes / -y — assume yes for all confirmations (non-interactive).
Cutting a release
- Decide version and build number.
CURRENT_PROJECT_VERSION must
increase every release or Sparkle won't offer the update:grep -E "MARKETING_VERSION|CURRENT_PROJECT_VERSION" \
Boop.xcodeproj/project.pbxproj | sort -u
Never regress MARKETING_VERSION (don't go 0.10 → 0.2).
- Commit and push code changes to
main first, so the tag points at the
released source.
- Run it (non-interactive, safe from a tool call — allow ~7 min, notarization
blocks for a few minutes and that is not a hang):
\
go run ./cmd/boop-release -build -yes \
-set-version <x.y.z> -set-build <n> \
-notes "First note
Second note"
What it does (in order)
With -build: set version/build (commit) → archive → export with Developer ID →
notarize (notarytool submit --wait, verifying status: Accepted) → staple →
place app at ~/Downloads/Boop.app.
Then always: preflight checks → validate version/build and Sparkle keys →
collect notes → create-dmg → sign the DMG with Sparkle sign_update (EdDSA) →
push local commits → gh release create vX.Y.Z with the DMG → prepend and push
the appcast.xml entry → regenerate the Homebrew cask (non-fatal).
Ordering & robustness: the GitHub release is created before the appcast is
pushed, so a published appcast never points at a missing release. Network calls
retry with backoff. Re-running is safe: the DMG is re-uploaded with --clobber
and the appcast entry for that build is replaced, not duplicated.
Sparkle configuration
- SUFeedURL:
https://raw.githubusercontent.com/fayazara/boop/main/appcast.xml
- SUPublicEDKey:
MA/6n0fqT0T2updDlkXr8BjhJKoHWik9uf6Lh5pUG7U=
- Keys live in the login keychain under service
https://sparkle-project.org,
account ed25519 — a single key shared by all of Fayaz's apps, Screendrop
included. Never run generate_keys without -p: regenerating would overwrite
that key and break updates for every previously shipped app. To read the public
key: generate_keys -p.
UpdaterManager.swift — singleton, started at launch in Release builds only,
wired to the menu bar and Settings.
After releasing
gh release view v<x.y.z> --repo fayazara/boop --json tagName,assets \
-q '{tag: .tagName, assets: [.assets[].name]}'
git pull --ff-only origin main # the CLI pushed the appcast commit itself
Troubleshooting
- Partial failure mid-release — re-run the exact same command; the pipeline is idempotent.
- notarytool credentials error — the keychain profile is missing/invalid; re-run
store-credentials or pass -notary-profile.
- Notarization "Invalid" —
xcrun notarytool log <submission-id> --keychain-profile <profile> (usually signing/entitlements).
sign_update not found — build/archive once so DerivedData has the Sparkle artifacts.
- SPM fails with
Couldn't get revision '<tag>^{commit}' — stale clone cache:
rm -rf ~/Library/Caches/org.swift.swiftpm/repositories/Sparkle-* and re-resolve.
- Build already in appcast — re-running is safe, but a new release needs a higher build number.
- Update never offered — check
CURRENT_PROJECT_VERSION actually increased; Sparkle compares builds, not marketing versions.
1---2name: release-boop3description: Release the Boop macOS app to GitHub using the boop-release CLI tool. Use this skill whenever the user wants to publish a new version, create a release, ship an update, cut a build, push a release to GitHub, or update the appcast. Also use when they mention archiving, notarization, DMG creation, Sparkle signing, bumping the version/build, or anything related to building and distributing a new Boop version.4---56# Release Boop78Releases new versions of Boop using a Go CLI (`cmd/boop-release`), ported from9the Screendrop release tool. It runs **fully automated and non-interactively**,10so a release can be triggered directly from a chat session.1112Two modes:1314- **Full auto (`-build`)** — archive, export (Developer ID), notarize, staple,15 package, sign, and publish. Nothing in Xcode's GUI is required.16- **Package-only** (no `-build`) — assumes a notarized `~/Downloads/Boop.app`17 already exists, then packages & publishes.1819Prefer **full auto** unless the user says they've already exported the app.2021## Prerequisites22231. `create-dmg` (brew), `gh` (authenticated), `git`, `plutil`, `go`.242. Sparkle's `sign_update` in DerivedData — produced by building/archiving once.253. A **notarytool keychain profile**. Defaults to `screendrop-notary`, which26 already exists on this machine — notarytool profiles are per Apple *team*, not27 per app, so the same credentials notarize every one of Fayaz's apps. To use a28 different one, pass `-notary-profile <name>`, or create it with:29 ```bash30 xcrun notarytool store-credentials "<name>" \31 --key /path/to/AuthKey_XXXXXXXXXX.p8 --key-id XXXXXXXXXX --issuer <issuer-uuid>32 ```3334## Flags3536- `-build` — run archive → export → notarize → staple first.37- `-set-version <x.y.z>` — set `MARKETING_VERSION` before archiving (and commit).38- `-set-build <n>` — set `CURRENT_PROJECT_VERSION` before archiving (and commit).39- `-scheme <name>` — Xcode scheme (default `Boop`).40- `-notes "<text>"` — release notes, one bullet per line. Skips the prompt.41- `-notes-file <path>` — read notes from a file instead.42- `-notary-profile <name>` — notarytool keychain profile (default `screendrop-notary`).43- `-yes` / `-y` — assume yes for all confirmations (non-interactive).4445## Cutting a release46471. **Decide version and build number.** `CURRENT_PROJECT_VERSION` **must48 increase** every release or Sparkle won't offer the update:49 ```bash50 grep -E "MARKETING_VERSION|CURRENT_PROJECT_VERSION" \51 Boop.xcodeproj/project.pbxproj | sort -u52 ```53 Never regress `MARKETING_VERSION` (don't go `0.10` → `0.2`).542. **Commit and push code changes to `main` first**, so the tag points at the55 released source.563. **Run it** (non-interactive, safe from a tool call — allow ~7 min, notarization57 blocks for a few minutes and that is not a hang):58 ```bash59 \60 go run ./cmd/boop-release -build -yes \61 -set-version <x.y.z> -set-build <n> \62 -notes "First note63 Second note"64 ```6566## What it does (in order)6768With `-build`: set version/build (commit) → archive → export with Developer ID →69notarize (`notarytool submit --wait`, verifying `status: Accepted`) → staple →70place app at `~/Downloads/Boop.app`.7172Then always: preflight checks → validate version/build and Sparkle keys →73collect notes → `create-dmg` → sign the DMG with Sparkle `sign_update` (EdDSA) →74push local commits → `gh release create vX.Y.Z` with the DMG → prepend and push75the `appcast.xml` entry → regenerate the Homebrew cask (non-fatal).7677**Ordering & robustness:** the GitHub release is created **before** the appcast is78pushed, so a published appcast never points at a missing release. Network calls79retry with backoff. Re-running is safe: the DMG is re-uploaded with `--clobber`80and the appcast entry for that build is replaced, not duplicated.8182## Sparkle configuration8384- **SUFeedURL**: `https://raw.githubusercontent.com/fayazara/boop/main/appcast.xml`85- **SUPublicEDKey**: `MA/6n0fqT0T2updDlkXr8BjhJKoHWik9uf6Lh5pUG7U=`86- **Keys live in the login keychain** under service `https://sparkle-project.org`,87 account `ed25519` — a **single key shared by all of Fayaz's apps**, Screendrop88 included. Never run `generate_keys` without `-p`: regenerating would overwrite89 that key and break updates for every previously shipped app. To read the public90 key: `generate_keys -p`.91- `UpdaterManager.swift` — singleton, started at launch in Release builds only,92 wired to the menu bar and Settings.9394## After releasing9596```bash97gh release view v<x.y.z> --repo fayazara/boop --json tagName,assets \98 -q '{tag: .tagName, assets: [.assets[].name]}'99git pull --ff-only origin main # the CLI pushed the appcast commit itself100```101102## Troubleshooting103104- **Partial failure mid-release** — re-run the exact same command; the pipeline is idempotent.105- **notarytool credentials error** — the keychain profile is missing/invalid; re-run `store-credentials` or pass `-notary-profile`.106- **Notarization "Invalid"** — `xcrun notarytool log <submission-id> --keychain-profile <profile>` (usually signing/entitlements).107- **`sign_update` not found** — build/archive once so DerivedData has the Sparkle artifacts.108- **SPM fails with `Couldn't get revision '<tag>^{commit}'`** — stale clone cache:109 `rm -rf ~/Library/Caches/org.swift.swiftpm/repositories/Sparkle-*` and re-resolve.110- **Build already in appcast** — re-running is safe, but a *new* release needs a higher build number.111- **Update never offered** — check `CURRENT_PROJECT_VERSION` actually increased; Sparkle compares builds, not marketing versions.