From 5490d718ad8abf8b12667a8dcc192cd71bd2f09f Mon Sep 17 00:00:00 2001 From: Wells Riley <884715+wr@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:00:48 -0400 Subject: [PATCH] Guard release.sh on a missing CHANGELOG entry before building MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit release.sh extracts the GitHub + Sparkle release notes from the `## v` section of CHANGELOG.md, but only checked for it right before creating the GitHub release — after build, notarize, and staple. A missing entry burned ~4 min of notarization before failing. Move the check to pre-flight (before the version bump / build) so it fails in seconds. Also fix the /release command doc to match how the script actually sources notes (CHANGELOG.md, not a passed-in file), drop the stale placeholder-notes step, and fix the DMG path + a signature-grep that could match an adjacent appcast entry. --- .claude/commands/release.md | 41 ++++++++++++++++++++++--------------- scripts/release.sh | 16 +++++++++++++++ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/.claude/commands/release.md b/.claude/commands/release.md index b377802..2e37c40 100644 --- a/.claude/commands/release.md +++ b/.claude/commands/release.md @@ -27,7 +27,8 @@ git fetch origin main --quiet && git rev-list --left-right --count main...origin # Required tools. command -v xcodebuild xcodegen create-dmg gh python3 -xcrun --find notarytool stapler +xcrun --find notarytool +xcrun --find stapler # Required env vars (mirror scripts/release.sh). printenv APPLE_TEAM_ID GITHUB_REPO @@ -52,11 +53,16 @@ grep -E "MARKETING_VERSION|CURRENT_PROJECT_VERSION" project.yml `xcrun notarytool store-credentials AC_PASSWORD --apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_SPECIFIC_PWD"` - `SUPublicEDKey` is missing or null — instruct the user to run `./bin/generate_keys`, paste the public key into `project.yml`, then `xcodegen generate`. - Requested version is not strictly greater than the current `MARKETING_VERSION` (semver compare). If it isn't, ask the user to confirm a downgrade or correction. +- No `## v` section in `CHANGELOG.md`. The script itself guards on this before building (added after a release once burned ~4 min of notarization before failing on a missing entry), but check it here too so you catch it during pre-flight, not after. You'll write the entry in Step 2. - Sanity-check that a Debug build compiles before kicking off the release: `xcodebuild -project Mojito.xcodeproj -scheme Mojito -configuration Debug -destination 'platform=macOS' build` (tail the output, only proceed on `BUILD SUCCEEDED`). -## Step 2 — Collect release notes +## Step 2 — Write the CHANGELOG.md entry -Ask the user for release notes in markdown. Suggest a starter template based on `git log --oneline` since the last tag: +**The release notes come from `CHANGELOG.md`, not a file you pass in.** `release.sh` extracts the `## v` section (everything up to the next `## vX.Y.Z`) and uses it verbatim for *both* the GitHub release body and the Sparkle in-app notes — so the two can never drift. If that section is missing, the script aborts. + +So the job here is to add a `## v` section to the top of `CHANGELOG.md` (just above the previous version's heading). Match the existing house style: a `## New` and/or `## Fixed` subsection, each bullet ending with a `([#NN](https://github.com/wr/mojito/pull/NN))` PR link. Write for end users — describe the visible behavior change, not the internal ticket. Skip internal-only commits (test harnesses, CI, docs, cosmetic link tweaks). + +Draft from the commits since the last tag: ```bash LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") @@ -65,7 +71,7 @@ if [[ -n "$LAST_TAG" ]]; then fi ``` -Show the user the commit list and ask them to either (a) write notes directly, or (b) tell you to draft from the commits and have them approve. Save the final notes to a temp file path you'll reuse in Step 4. Don't proceed until the user approves. +Show the user your drafted section and get approval before proceeding. (Easter-egg rule still applies — a bare "added easter eggs" and nothing more.) The entry gets committed to `main` in Step 5 alongside the version bump. ## Step 3 — Run release.sh @@ -82,18 +88,16 @@ This script: 5. Wraps `Mojito.app` in a DMG via `create-dmg`. 6. Submits to `notarytool` (waits ~2–10 min), then staples. 7. Calls `./bin/sign_update` to produce an EdDSA signature for the DMG. -8. `gh release create v` with the DMG attached. Release notes are placeholder "TODO" — you will replace them in Step 4. -9. Clones the `gh-pages` branch to a tempdir, runs `scripts/update_appcast.py` to prepend the new release entry, commits, and pushes. +8. `gh release create v` with the DMG attached, using the `## v` section of `CHANGELOG.md` as the release body (the notes are already correct — there is no placeholder to replace). +9. Clones the `gh-pages` branch to a tempdir, renders the changelog fragment to HTML release notes, runs `scripts/update_appcast.py` to prepend the new entry (build number, length, EdDSA signature), commits, and pushes. Do **not** suppress output — surface the script's progress so the user can see notarization timing. If the script exits non-zero at any step, stop the skill and report the failure with the last 30 lines of output. -## Step 4 — Replace the placeholder release notes +Because notarization can take minutes, run the script in the background and let the task-completion notification tell you when it's done, rather than polling. -```bash -gh release edit "v" --repo "$GITHUB_REPO" --notes-file -``` +## Step 4 — Confirm the release notes rendered -Verify with: +The GitHub release body already carries the real notes (from `CHANGELOG.md`), so there's nothing to replace — just confirm: ```bash gh release view "v" --repo "$GITHUB_REPO" @@ -119,17 +123,20 @@ Run in parallel and report results: # Release exists with the DMG attached. gh release view "v" --repo "$GITHUB_REPO" --json assets,name,tagName -# Appcast contains the new entry. -curl -sf "https://mojito.wells.ee/appcast.xml" | head -40 - -# Sparkle EdDSA signature on the appcast matches what was uploaded. -curl -sf "https://mojito.wells.ee/appcast.xml" | grep -A1 "v" | grep -o 'sparkle:edSignature="[^"]*"' +# Appcast contains the new entry, and its enclosure line carries the right +# build number + EdDSA signature. Grab the for THIS version's DMG — +# grepping near "v" alone is unreliable, it can catch an adjacent +# item's signature (the appcast lists every past release). +curl -sf "https://mojito.wells.ee/appcast.xml" \ + | grep -o "https://github.com/wr/mojito/releases/download/v/Mojito.dmg[^/]*sparkle:edSignature=\"[^\"]*\"" ``` +The signature it prints must match the `sparkle:edSignature=...` line the script echoed under `→ Signing for Sparkle`. (CDN caching can briefly serve the old appcast; if it doesn't match, re-check against `raw.githubusercontent.com/wr/mojito/gh-pages/appcast.xml`.) + Confirm to the user: - Release URL: `https://github.com/$GITHUB_REPO/releases/tag/v` -- DMG path on disk: `dist/Mojito-.dmg` +- DMG path on disk: `dist/Mojito.dmg` (single file, no version suffix — the version lives in the release-tag URL) - Reminder: to test the update flow, launch a previously-installed older copy of Mojito and use **Mojito → Check for Updates** — Sparkle should offer the new version. --- diff --git a/scripts/release.sh b/scripts/release.sh index 7352122..7b536bf 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -82,6 +82,22 @@ if [[ -z "$SU_PUBKEY" || "$SU_PUBKEY" == "" ]]; then exit 1 fi +# Refuse to release without a changelog entry for this version. The +# `## v$VERSION` section in CHANGELOG.md is the single source of truth for the +# GitHub release body and the Sparkle notes (re-extracted below). This is +# checked here — before the ~4-minute build + notarize — so a missing entry +# fails in seconds instead of after all the expensive, irreversible work. +CHANGELOG_CHECK=$(awk -v header="## v$VERSION" ' + $0 == header { capture = 1; next } + capture && /^## v[0-9]/ { exit } + capture { print } +' "$REPO_ROOT/CHANGELOG.md") +if [[ -z "$CHANGELOG_CHECK" ]]; then + echo "error: no '## v$VERSION' section found in CHANGELOG.md." >&2 + echo " Add a '## v$VERSION' entry before releasing." >&2 + exit 1 +fi + BUILD_DIR="$REPO_ROOT/build/release" APP_NAME="Mojito" APP_PATH="$BUILD_DIR/Build/Products/Release/$APP_NAME.app"