From 868e61979e1045f7496318ac96946604e8dc2426 Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Thu, 16 Apr 2026 19:04:27 -0700 Subject: [PATCH] chore: release script supports --draft + RELEASE_NOTES.md Drafts skip the appcast push and main tag, so a draft release won't show up in users' Sparkle update feed and v1.6.0 stays "latest" until explicitly promoted. The signed appcast entry is saved to the release dir for later manual promotion. Also adds release notes file convention: releases/v/RELEASE_NOTES.md is auto-included in the version-bump commit and used as the GitHub release body. Co-Authored-By: Claude Opus 4.6 --- releases/v1.6.1/RELEASE_NOTES.md | 25 +++++++ scripts/release.sh | 108 +++++++++++++++++++++++-------- 2 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 releases/v1.6.1/RELEASE_NOTES.md diff --git a/releases/v1.6.1/RELEASE_NOTES.md b/releases/v1.6.1/RELEASE_NOTES.md new file mode 100644 index 0000000..7bd7795 --- /dev/null +++ b/releases/v1.6.1/RELEASE_NOTES.md @@ -0,0 +1,25 @@ +## What's New in 1.6.1 + +### Auto-updates + +Scarf now ships with [Sparkle](https://sparkle-project.org). On launch (and daily thereafter) it checks an EdDSA-signed appcast at [awizemann.github.io/scarf/appcast.xml](https://awizemann.github.io/scarf/appcast.xml). When a new version is available you'll get an in-app update prompt — no more manually downloading zips and dragging into Applications. + +You can disable automatic checks or trigger a manual one from **Settings → General → Updates**, the menu bar icon, or the **Scarf → Check for Updates…** menu item. + +### Notarized & Developer ID signed + +This is the first release that's properly Developer ID signed and notarized by Apple. Gatekeeper accepts it on first launch — no more right-click → Open dance, no more "Scarf cannot be opened because the developer cannot be verified" warnings. + +### Fixes + +- Chat works correctly when no terminal hermes session is running, and surfaces the real error when it can't reach the agent (b6df…) + +### Under the hood + +- Tracked `Info.plist` (replacing auto-generation) so signing-relevant keys live in version control +- New `UpdaterService` wraps Sparkle and is injected via SwiftUI `.environment()` +- One-command release pipeline at [scripts/release.sh](https://github.com/awizemann/scarf/blob/main/scripts/release.sh) handles archive → sign → notarize → staple → appcast → GitHub release → tag + +--- + +**Migrating from 1.6.0:** unzip and replace your existing `Scarf.app` in `/Applications`. After this release, future updates install in-place via Sparkle. diff --git a/scripts/release.sh b/scripts/release.sh index c2d1e47..8bdf6d2 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -2,7 +2,18 @@ # # Scarf release pipeline — local, manual, repeatable. # -# Usage: ./scripts/release.sh 1.7.0 +# Usage: +# ./scripts/release.sh 1.7.0 # full release: build, sign, notarize, +# # appcast push, GitHub release, tag +# ./scripts/release.sh 1.7.0 --draft # everything builds + notarizes, but the +# # GitHub release is created as draft, the +# # appcast is NOT updated, and main is NOT +# # tagged. Promote later with --promote. +# +# Release notes: +# If `releases/v/RELEASE_NOTES.md` exists, it is committed alongside the +# version bump and used as the GitHub release body. Otherwise a minimal autogenerated +# note is used. # # Prerequisites (one-time setup): # 1. Developer ID Application cert installed in login Keychain. @@ -19,8 +30,20 @@ # set -euo pipefail +# ---------- arg parsing ---------- +VERSION="" +DRAFT=0 +for arg in "$@"; do + case "$arg" in + --draft) DRAFT=1 ;; + -h|--help) sed -n '2,30p' "$0"; exit 0 ;; + -*) printf '[ERR] unknown flag: %s\n' "$arg" >&2; exit 1 ;; + *) [[ -z "$VERSION" ]] && VERSION="$arg" || { printf '[ERR] unexpected arg: %s\n' "$arg" >&2; exit 1; } ;; + esac +done +[[ -n "$VERSION" ]] || { printf 'usage: ./scripts/release.sh [--draft]\n' >&2; exit 1; } + # ---------- config ---------- -VERSION="${1:?usage: ./scripts/release.sh e.g. 1.7.0}" TEAM_ID="3Q6X2L86C4" BUNDLE_ID="com.scarf.app" SCHEME="scarf" @@ -81,6 +104,11 @@ NEW_BUILD=$((CUR_BUILD + 1)) sed -i '' -E "s/MARKETING_VERSION = [0-9]+\.[0-9]+\.[0-9]+;/MARKETING_VERSION = ${VERSION};/g" "$PBXPROJ" sed -i '' -E "s/CURRENT_PROJECT_VERSION = [0-9]+;/CURRENT_PROJECT_VERSION = ${NEW_BUILD};/g" "$PBXPROJ" git add "$PBXPROJ" +# Include release notes in the bump commit if user prepared them ahead of time. +NOTES_FILE="$RELEASE_DIR/RELEASE_NOTES.md" +if [[ -f "$NOTES_FILE" ]]; then + git add "$NOTES_FILE" +fi git commit -m "chore: Bump version to ${VERSION}" # ---------- build ---------- @@ -154,17 +182,9 @@ ED_SIGNATURE="$(echo "$SIG_OUTPUT" | sed -nE 's/.*sparkle:edSignature="([^"]+)". FILE_LENGTH="$(echo "$SIG_OUTPUT" | sed -nE 's/.*length="([^"]+)".*/\1/p')" [[ -n "$ED_SIGNATURE" && -n "$FILE_LENGTH" ]] || die "sign_update did not produce signature: $SIG_OUTPUT" -# ---------- update appcast on gh-pages ---------- -log "Update appcast.xml on gh-pages worktree" -if [[ ! -d "$GH_PAGES_WORKTREE" ]]; then - git worktree add "$GH_PAGES_WORKTREE" gh-pages -fi -( - cd "$GH_PAGES_WORKTREE" - git pull --ff-only origin gh-pages - PUB_DATE="$(LC_TIME=en_US.UTF-8 date -u +"%a, %d %b %Y %H:%M:%S +0000")" - DOWNLOAD_URL="$DOWNLOAD_URL_BASE/v${VERSION}/Scarf-v${VERSION}-Universal.zip" - NEW_ITEM=$(cat < Version ${VERSION} ${NEW_BUILD} @@ -178,8 +198,18 @@ fi EOF ) - # Insert new item after en line - python3 - "$NEW_ITEM" <<'PY' + +# ---------- update appcast on gh-pages (skipped for drafts) ---------- +if [[ $DRAFT -eq 0 ]]; then + log "Update appcast.xml on gh-pages worktree" + if [[ ! -d "$GH_PAGES_WORKTREE" ]]; then + git worktree add "$GH_PAGES_WORKTREE" gh-pages + fi + ( + cd "$GH_PAGES_WORKTREE" + git pull --ff-only origin gh-pages + # Insert new item after en line + python3 - "$APPCAST_ITEM" <<'PY' import sys, pathlib new_item = sys.argv[1] p = pathlib.Path("appcast.xml") @@ -190,22 +220,48 @@ if marker not in xml: xml = xml.replace(marker, marker + "\n" + new_item, 1) p.write_text(xml) PY - git add appcast.xml - git commit -m "release: v${VERSION}" - git push origin gh-pages -) + git add appcast.xml + git commit -m "release: v${VERSION}" + git push origin gh-pages + ) +else + log "Draft mode — skipping appcast push. Saving entry to $RELEASE_DIR/appcast-entry.xml for later promotion." + printf '%s\n' "$APPCAST_ITEM" > "$RELEASE_DIR/appcast-entry.xml" +fi # ---------- github release ---------- log "Create GitHub release and upload artifacts" +GH_FLAGS=() +[[ $DRAFT -eq 1 ]] && GH_FLAGS+=(--draft) +if [[ -f "$NOTES_FILE" ]]; then + GH_FLAGS+=(--notes-file "$NOTES_FILE") +else + GH_FLAGS+=(--notes "Release v${VERSION}. See commit history for details.") +fi gh release create "v${VERSION}" \ --title "Scarf v${VERSION}" \ - --notes "Release notes: https://github.com/awizemann/scarf/releases/tag/v${VERSION}" \ + "${GH_FLAGS[@]}" \ "$UNIVERSAL_ZIP" -log "Tag main and push" -git tag "v${VERSION}" -git push origin main --tags +# ---------- tag main (skipped for drafts) ---------- +if [[ $DRAFT -eq 0 ]]; then + log "Tag main and push" + git tag "v${VERSION}" + git push origin main --tags +else + log "Draft mode — skipping tag. Bump commit is local only; push manually with: git push origin main" +fi -log "Release v${VERSION} complete" -log " Download: $DOWNLOAD_URL_BASE/v${VERSION}/Scarf-v${VERSION}-Universal.zip" -log " Appcast: $APPCAST_URL" +if [[ $DRAFT -eq 1 ]]; then + log "Draft release v${VERSION} ready" + log " Review: https://github.com/awizemann/scarf/releases" + log " Promote: in GitHub UI, edit the draft and uncheck 'Set as a pre-release / draft' → Publish." + log " Then commit + push appcast-entry.xml to gh-pages, and tag main:" + log " git push origin main" + log " git tag v${VERSION} && git push origin v${VERSION}" + log " (manually merge $RELEASE_DIR/appcast-entry.xml into gh-pages branch's appcast.xml after en)" +else + log "Release v${VERSION} complete" + log " Download: $DOWNLOAD_URL" + log " Appcast: $APPCAST_URL" +fi