diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44c8578..4155f17 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,8 @@ jobs: release: name: Build & publish release runs-on: macos-15 + outputs: + version: ${{ steps.version.outputs.version }} env: DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer steps: @@ -68,3 +70,37 @@ jobs: dist/NotchBar.dmg \ --title "NotchBar $GITHUB_REF_NAME" \ --generate-notes + + bump-cask: + name: Bump the Homebrew cask + needs: release + runs-on: ubuntu-latest + # Only needs to read this repo; every write goes to the tap through TAP_TOKEN. + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + + - name: Require the tap token + env: + TAP_TOKEN: ${{ secrets.TAP_TOKEN }} + run: | + [ -n "$TAP_TOKEN" ] || { + echo "::error::TAP_TOKEN is unset. The cask bump needs a fine-grained PAT with contents, pull-requests and issues write access on Periicles/homebrew-tap." + exit 1 + } + + # The tap lives in its own repository, so GITHUB_TOKEN cannot write to it. + - name: Check out the tap + uses: actions/checkout@v5 + with: + repository: Periicles/homebrew-tap + token: ${{ secrets.TAP_TOKEN }} + path: tap + + # A PAT-authored PR triggers the tap's brew test-bot; a GITHUB_TOKEN one + # would not, and auto-merge would then have no check to wait for. + - name: Open the cask bump pull request + env: + GH_TOKEN: ${{ secrets.TAP_TOKEN }} + run: scripts/bump-cask.sh "${{ needs.release.outputs.version }}" tap diff --git a/CHANGELOG.md b/CHANGELOG.md index d6fcf85..cad6bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ All notable changes to NotchBar are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and NotchBar adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Changed + +- **Publishing a release now bumps the Homebrew cask on its own.** A `bump-cask` + job hashes the released `.dmg`, opens a pull request on `Periicles/homebrew-tap` + and turns on auto-merge, so it lands as soon as the tap's `brew test-bot` is + green. `brew upgrade --cask notchbar` no longer waits on a manual bump. +- README documents how to update an existing install, per install route. + ## [0.3.0] - 2026-08-15 ### Added diff --git a/README.md b/README.md index 7db229a..9ed18e3 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,18 @@ Nothing is piped into a shell — every step is visible above. `curl` does not s On first launch, grant Calendar access when prompted, then hover over the notch and click the settings icon to choose which calendars to track. +## Updating + +NotchBar has no network access and does not check for new versions on its own — take the route matching how you installed it: + +| Installed with | Update with | +|---|---| +| Homebrew | `brew upgrade --cask notchbar` | +| The one-command install | Quit NotchBar, then re-run the exact same command — it overwrites the copy in `/Applications` | +| The `.dmg` | Quit NotchBar, download the latest `.dmg` and drag the app over the old one | + +The cask is bumped automatically whenever a release is published, so Homebrew is the route that asks the least of you. Your settings and calendar selection survive an update — they live in the app's container, not in the bundle — but macOS may ask for Calendar access again after the app bundle is replaced. + ## Uninstall 1. If you turned on **Launch at login**, hover the notch → open settings → toggle it **off** first (this removes the login item cleanly). You can also remove it later under **System Settings → General → Login Items**. @@ -190,6 +202,14 @@ git push origin v0.3.0 The version comes from the tag (`vX.Y.Z` → `X.Y.Z`) and is injected into the app at build time — no need to edit `Info.plist`. The `CFBundleShortVersionString` checked into `Supporting/Info.plist` is only a placeholder for local `swift run` builds; every packaged build overwrites it. Releases are published as stable, which is what keeps `/releases/latest/download/NotchBar.dmg` resolving — that URL skips pre-releases. +Publishing a release also bumps the [Homebrew cask](https://github.com/Periicles/homebrew-tap). The `bump-cask` job computes the sha256 of the released `.dmg`, opens a pull request on the tap and turns on auto-merge, so the bump lands as soon as the tap's `brew test-bot` is green and `brew upgrade --cask notchbar` serves the new version with no manual step. It needs one repository secret: + +| Secret | For | +|---|---| +| `TAP_TOKEN` | fine-grained PAT with **contents**, **pull requests** and **issues** write access on `Periicles/homebrew-tap` | + +To rehearse the rewrite without pushing anything: `scripts/bump-cask.sh 0.3.0 "$(brew --repository periicles/tap)" --dry-run`. + **Notarization is not planned** — it needs a paid Apple Developer ID, and the command-line install routes already avoid the Gatekeeper prompt. Kept here in case that ever changes: add these repository secrets and follow the commented hooks in `release.yml` / `scripts/package.sh`. | Secret | For | diff --git a/scripts/bump-cask.sh b/scripts/bump-cask.sh new file mode 100755 index 0000000..aaadc11 --- /dev/null +++ b/scripts/bump-cask.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# Bumps the notchbar cask in the Homebrew tap to a published release, as an +# auto-merging pull request. +# +# Usage: bump-cask.sh VERSION TAP_DIR [--dry-run] +# VERSION the released version, without the leading v (e.g. 0.4.0) +# TAP_DIR a checkout of Periicles/homebrew-tap +# --dry-run rewrite the cask and print the diff, then stop before any push +# +# The sha256 is computed from the .dmg attached to the GitHub release, not from +# a local build: it has to match the exact bytes Homebrew will download. +# +# Pushing needs a token with contents, pull-requests and issues write access on +# the tap (CI passes TAP_TOKEN); `gh` reads it from GH_TOKEN. +set -euo pipefail + +TAP_REPO="Periicles/homebrew-tap" +CASK_PATH="Casks/notchbar.rb" +DMG_URL_BASE="https://github.com/Periicles/Notchapp/releases/download" + +VERSION="${1:-}" +TAP_DIR="${2:-}" +DRY_RUN="" +[ "${3:-}" = "--dry-run" ] && DRY_RUN=1 + +if [ -z "$VERSION" ] || [ -z "$TAP_DIR" ]; then + sed -n '2,12p' "$0" >&2 + exit 2 +fi +if ! [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+)+$ ]]; then + echo "error: '$VERSION' is not a version like 0.4.0" >&2 + exit 2 +fi + +CASK="$TAP_DIR/$CASK_PATH" +test -f "$CASK" || { echo "error: $CASK not found — is $TAP_DIR a tap checkout?" >&2; exit 2; } + +sha256_of() { + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$1" | awk '{print $1}' + else + sha256sum "$1" | awk '{print $1}' + fi +} + +DMG_URL="$DMG_URL_BASE/v$VERSION/NotchBar.dmg" +DMG="$(mktemp -t notchbar-dmg)" +trap 'rm -f "$DMG"' EXIT + +echo "==> Downloading $DMG_URL" +curl -fsSL -o "$DMG" "$DMG_URL" +SHA="$(sha256_of "$DMG")" +echo "==> sha256 $SHA" + +CURRENT_VERSION="$(sed -n 's/^ version "\(.*\)"$/\1/p' "$CASK")" +CURRENT_SHA="$(sed -n 's/^ sha256 "\(.*\)"$/\1/p' "$CASK")" +if [ "$CURRENT_VERSION" = "$VERSION" ] && [ "$CURRENT_SHA" = "$SHA" ]; then + echo "==> Cask is already on $VERSION with a matching sha256 — nothing to do" + exit 0 +fi + +echo "==> Rewriting the cask: $CURRENT_VERSION -> $VERSION" +sed -i.bak \ + -e "s|^ version \".*\"$| version \"$VERSION\"|" \ + -e "s|^ sha256 \".*\"$| sha256 \"$SHA\"|" \ + "$CASK" +rm -f "$CASK.bak" + +# A silent no-op sed here would open an empty PR that auto-merges into nothing. +grep -q "^ version \"$VERSION\"$" "$CASK" || { echo "error: version line not rewritten" >&2; exit 1; } +grep -q "^ sha256 \"$SHA\"$" "$CASK" || { echo "error: sha256 line not rewritten" >&2; exit 1; } + +git -C "$TAP_DIR" --no-pager diff -- "$CASK_PATH" + +if [ -n "$DRY_RUN" ]; then + echo "==> Dry run: stopping before commit" + exit 0 +fi + +BRANCH="chore/notchbar-$VERSION" +echo "==> Pushing $BRANCH to $TAP_REPO" +git -C "$TAP_DIR" config user.name "github-actions[bot]" +git -C "$TAP_DIR" config user.email "41898282+github-actions[bot]@users.noreply.github.com" +git -C "$TAP_DIR" checkout -B "$BRANCH" +git -C "$TAP_DIR" add "$CASK_PATH" +git -C "$TAP_DIR" commit -m "chore(cask): notchbar $VERSION" +# Force-push so re-running a failed release job reuses the same branch and PR. +git -C "$TAP_DIR" push --force origin "HEAD:refs/heads/$BRANCH" + +PR_URL="$(gh pr list --repo "$TAP_REPO" --head "$BRANCH" --state open --json url --jq '.[0].url // empty')" +if [ -n "$PR_URL" ]; then + echo "==> Reusing the open PR $PR_URL" +else + echo "==> Opening the pull request" + PR_URL="$(gh pr create --repo "$TAP_REPO" --base main --head "$BRANCH" --assignee @me \ + --title "chore(cask): notchbar $VERSION" \ + --body "NotchBar $VERSION is published, so the cask has to follow — until it does, \`brew upgrade --cask notchbar\` is a no-op and Homebrew users stay on $CURRENT_VERSION. + +Version and sha256 come from the .dmg attached to the [v$VERSION release]($DMG_URL_BASE/v$VERSION/NotchBar.dmg), computed on the exact bytes Homebrew downloads. + +Verification: \`brew test-bot --only-tap-syntax\` on this PR, then \`brew upgrade --cask notchbar\`.")" +fi + +echo "==> Enabling auto-merge on $PR_URL" +gh pr merge --repo "$TAP_REPO" --auto --squash --delete-branch "$PR_URL" +echo "==> Done: $PR_URL"