148 lines
7.7 KiB
Bash
Executable File
148 lines
7.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# One-command macOS release for a channel: build → sign (Developer ID) → notarize +
|
||
# staple the .app → build + sign the DMG → notarize + staple the DMG → (optionally)
|
||
# regenerate the Sparkle appcast.
|
||
#
|
||
# scripts/release-macos.sh {canary|beta|rc|stable} # dev isn't a distribution channel
|
||
#
|
||
# Prerequisites (one-time — see signing/README.md):
|
||
# • A "Developer ID Application" certificate in the keychain (auto-detected, or set
|
||
# NUCLEIC_SIGN_ID to its identity string / hash).
|
||
# • Notary credentials: a stored profile (NUCLEIC_NOTARY_PROFILE, default
|
||
# 'nucleic-notary') OR the NUCLEIC_ASC_* API-key env vars (see scripts/notarize.sh).
|
||
#
|
||
# Versioning: every release bumps the build number (CFBundleVersion) by 1 in ./VERSION, and
|
||
# bumps a semver component (CFBundleShortVersionString) — default minor (build-only on the
|
||
# fast-moving canary and dev channels), since we're pre-1.0. The new ./VERSION is committed on success (so versions/build
|
||
# numbers stay monotonic). Both the Makefile shortcut `make release-beta BUMP=major` and a direct
|
||
# `NUCLEIC_BUMP=build` work.
|
||
#
|
||
# Env:
|
||
# NUCLEIC_SIGN_ID signing identity (default: auto-detect Developer ID Application)
|
||
# NUCLEIC_BUMP semver bump: minor (default; build on canary/dev) | major | patch | build (build = build number only)
|
||
# NUCLEIC_VERSION_COMMIT commit the ./VERSION bump on success (default 1; 0 leaves it unstaged)
|
||
# NUCLEIC_VERSION pin the marketing version (overrides ./VERSION; forwarded to package-app.sh)
|
||
# NUCLEIC_NOTARIZE_APP notarize+staple the .app too (default 1; the DMG is always done)
|
||
# NUCLEIC_APPCAST regenerate the Sparkle appcast afterwards (default 1)
|
||
# NUCLEIC_R2_UPLOAD publish the DMG + appcast to R2 afterwards (default 1; needs wrangler)
|
||
# NUCLEIC_SYNC_LOWER after committing the bump, fast-forward VERSION on less-stable
|
||
# branches that now trail this release (default 1; see scripts/sync-version.sh)
|
||
|
||
set -euo pipefail
|
||
|
||
CHANNEL="${1:?usage: release-macos.sh (canary|beta|rc|stable)}"
|
||
case "$CHANNEL" in
|
||
canary) APP_NAME="Nucleic Canary" ;;
|
||
beta) APP_NAME="Nucleic Beta" ;;
|
||
rc) APP_NAME="Nucleic RC" ;;
|
||
stable) APP_NAME="Nucleic" ;;
|
||
dev) echo "release-macos: 'dev' is a local channel, not for distribution. Use make app-dev." >&2; exit 2 ;;
|
||
*) echo "usage: $0 {canary|beta|rc|stable}" >&2; exit 2 ;;
|
||
esac
|
||
|
||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||
cd "$ROOT"
|
||
|
||
# Resolve a Developer ID Application identity; refuse to "release" an ad-hoc build.
|
||
SIGN_ID="${NUCLEIC_SIGN_ID:-}"
|
||
if [ -z "$SIGN_ID" ]; then
|
||
SIGN_ID="$(security find-identity -v -p codesigning 2>/dev/null \
|
||
| grep 'Developer ID Application' | head -1 | sed -E 's/.*"([^"]+)".*/\1/')"
|
||
fi
|
||
if [ -z "$SIGN_ID" ] || [ "$SIGN_ID" = "-" ]; then
|
||
cat >&2 <<EOF
|
||
release-macos: no "Developer ID Application" identity found.
|
||
|
||
Create one in Xcode → Settings → Accounts → (your paid team) → Manage Certificates
|
||
→ + → Developer ID Application, then re-run. Or set NUCLEIC_SIGN_ID explicitly.
|
||
Installed identities:
|
||
$(security find-identity -v -p codesigning 2>/dev/null | sed 's/^/ /')
|
||
EOF
|
||
exit 1
|
||
fi
|
||
export NUCLEIC_SIGN_ID="$SIGN_ID"
|
||
echo "▸ Releasing $CHANNEL with identity: $SIGN_ID"
|
||
|
||
# 0. Bump the version: build number always +1, plus a semver bump (NUCLEIC_BUMP, default minor —
|
||
# we're pre-1.0, so each release is a minor, except the canary/dev channels which default to
|
||
# build-only (no marketing-version change); major stays 0 until an explicit BUMP=major).
|
||
# bump-version.sh rewrites ./VERSION; package-app.sh reads it on the very next line. If the
|
||
# release fails before the artifact exists, an EXIT trap restores ./VERSION to its committed
|
||
# value so a retry reuses the same number instead of skipping one. On success the bump is
|
||
# committed (step 7); SUCCEEDED gates the trap so the bump survives.
|
||
SUCCEEDED=0
|
||
trap '[ "$SUCCEEDED" = 1 ] || git checkout -- VERSION 2>/dev/null || true' EXIT
|
||
# Default bump is channel-aware: the fast-moving canary and dev channels iterate constantly, so
|
||
# they default to build-only (build number +1, marketing version unchanged); the promotion
|
||
# channels (beta/rc/stable) default to a minor (we're pre-1.0). NUCLEIC_BUMP overrides either —
|
||
# e.g. `make release-canary BUMP=patch`.
|
||
DEFAULT_BUMP="minor"; case "$CHANNEL" in canary|dev) DEFAULT_BUMP="build" ;; esac
|
||
BUMP="${NUCLEIC_BUMP:-$DEFAULT_BUMP}"
|
||
read -r REL_VERSION REL_BUILD <<EOF
|
||
$(scripts/bump-version.sh "$BUMP")
|
||
EOF
|
||
echo "▸ Version: $REL_VERSION (build $REL_BUILD) [bump=$BUMP]"
|
||
|
||
# 1. Build + assemble + Developer-ID-sign the .app (picks up the bumped ./VERSION).
|
||
scripts/package-app.sh "$CHANNEL"
|
||
|
||
# 2. Notarize + staple the .app (so a copied-out app is trusted offline; default on).
|
||
APP="$ROOT/dist/$APP_NAME.app"
|
||
if [ "${NUCLEIC_NOTARIZE_APP:-1}" = "1" ]; then
|
||
scripts/notarize.sh "$APP"
|
||
fi
|
||
|
||
# 3. Build + sign the DMG (from the now-stapled app).
|
||
scripts/make-dmg.sh "$CHANNEL"
|
||
plist="$APP/Contents/Info.plist"
|
||
SHORT_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$plist")"
|
||
BUILD_NUMBER="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$plist")"
|
||
case "$CHANNEL" in canary) SLUG="Nucleic-Canary";; beta) SLUG="Nucleic-Beta";; rc) SLUG="Nucleic-RC";; stable) SLUG="Nucleic";; esac
|
||
DMG="$ROOT/dist/${SLUG}-${SHORT_VERSION}.${BUILD_NUMBER}.dmg"
|
||
|
||
# 4. Notarize + staple the DMG.
|
||
scripts/notarize.sh "$DMG"
|
||
|
||
# The distributable artifact now exists and is notarized — the version is "spent". Keep the bump
|
||
# (the EXIT trap will no longer revert ./VERSION); steps 5–7 are post-processing.
|
||
SUCCEEDED=1
|
||
|
||
# 5. Regenerate the Sparkle appcast for this channel (default on; skipped if absent).
|
||
if [ "${NUCLEIC_APPCAST:-1}" = "1" ] && [ -x scripts/generate-appcast.sh ]; then
|
||
scripts/generate-appcast.sh "$CHANNEL"
|
||
fi
|
||
|
||
# 6. Publish the DMG + appcast to R2 (updates.nucleic.blakeslee.xyz), the Sparkle feed host.
|
||
# Default on; needs wrangler + R2 auth. Set NUCLEIC_R2_UPLOAD=0 to upload manually instead.
|
||
if [ "${NUCLEIC_R2_UPLOAD:-1}" = "1" ] && [ -x scripts/upload-r2.sh ]; then
|
||
scripts/upload-r2.sh "$CHANNEL" \
|
||
|| echo " (R2 upload skipped/failed — upload dist/ to the bucket manually; see signing/README.md)"
|
||
fi
|
||
|
||
# 7. Record the version bump (the release succeeded) so versions/build numbers stay monotonic
|
||
# across releases. Off with NUCLEIC_VERSION_COMMIT=0 (then ./VERSION is left as a working
|
||
# change for you to commit). A release that failed before step 4 had its ./VERSION restored by
|
||
# the EXIT trap, so the next run reuses the same number rather than skipping one.
|
||
if [ "${NUCLEIC_VERSION_COMMIT:-1}" = "1" ] && ! git diff --quiet -- VERSION 2>/dev/null; then
|
||
git add VERSION
|
||
git commit -q -m "release($CHANNEL): v$REL_VERSION (build $REL_BUILD)" \
|
||
&& echo "▸ Committed version bump → v$REL_VERSION (build $REL_BUILD)"
|
||
fi
|
||
|
||
# 8. Propagate this version DOWN the channel chain: fast-forward any less-stable branch
|
||
# (dev/canary/…) whose VERSION now trails this release, so lower channels never lag a
|
||
# version a more-stable one already shipped. VERSION-only commits on local branches; push
|
||
# them with your usual pushes. Off with NUCLEIC_SYNC_LOWER=0; runs only when we committed
|
||
# the bump above (so the branch tip reflects the new version). Non-fatal — the release itself
|
||
# already succeeded.
|
||
if [ "${NUCLEIC_SYNC_LOWER:-1}" = "1" ] && [ "${NUCLEIC_VERSION_COMMIT:-1}" = "1" ] && [ -x scripts/sync-version.sh ]; then
|
||
scripts/sync-version.sh --apply \
|
||
|| echo " (version sync to lower branches skipped/failed — run 'make sync-versions APPLY=1' by hand)"
|
||
fi
|
||
|
||
echo
|
||
echo "✓ Release artifact ready:"
|
||
echo " $DMG"
|
||
echo " Hosted via R2 at ${NUCLEIC_FEED_BASE:-https://updates.nucleic.blakeslee.xyz} (GitHub release is a fallback) — see signing/README.md."
|