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