diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 14c2c64..d5e04a5 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -1,9 +1,24 @@ name: Release Build -# Fires when release-please's PR is merged and it publishes a GitHub Release. +# Builds, signs and packages the DMG for a tag, attaches it to the matching +# GitHub Release, and asks maptic/homebrew-tap to bump its cask. +# +# Called by release-please.yml right after it cuts a release (a release created +# with the default GITHUB_TOKEN does not emit a `release: published` event), and +# runnable by hand for a tag that needs re-packaging. on: - release: - types: [published] + workflow_call: + inputs: + tag: + description: Release tag to build, e.g. 1.2.3 + required: true + type: string + workflow_dispatch: + inputs: + tag: + description: Release tag to build, e.g. 1.2.3 + required: true + type: string permissions: contents: write @@ -11,18 +26,23 @@ permissions: jobs: build-dmg: runs-on: macos-latest + outputs: + version: ${{ steps.ver.outputs.version }} env: - TAG: ${{ github.event.release.tag_name }} + TAG: ${{ inputs.tag }} # 'true' only when Developer-ID signing secrets are configured. HAS_SIGNING: ${{ secrets.MACOS_CERTIFICATE != '' }} steps: - uses: actions/checkout@v4 + with: + ref: ${{ inputs.tag }} - name: Select Xcode uses: maxim-lobanov/setup-xcode@v1 with: xcode-version: latest-stable + # Tags are bare semver ("1.2.3"); the strip only guards a hand-typed "v". - name: Derive version id: ver run: echo "version=${TAG#v}" >> "$GITHUB_OUTPUT" @@ -101,40 +121,27 @@ jobs: gh release upload "$TAG" \ "Mounty-$VERSION.dmg" "Mounty-$VERSION.dmg.sha256" --clobber - # -------- Homebrew cask -------- - - name: Update Homebrew cask + # -------- Homebrew cask -------- + # The tap owns the cask: it renders the download URL, re-computes the sha256 + # from the published asset and commits the bump itself. Requires + # HOMEBREW_TAP_TOKEN — a fine-grained PAT with `contents: write` on + # maptic/homebrew-tap. Without it the release still succeeds, only unbumped. + update-tap: + needs: build-dmg + runs-on: ubuntu-latest + permissions: {} + steps: + - name: Dispatch cask-release to maptic/homebrew-tap env: - TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} - VERSION: ${{ steps.ver.outputs.version }} + GH_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} + VERSION: ${{ needs.build-dmg.outputs.version }} run: | - if [ -z "${TAP_TOKEN:-}" ]; then - echo "HOMEBREW_TAP_TOKEN not set; skipping cask update." + set -euo pipefail + if [ -z "${GH_TOKEN:-}" ]; then + echo "::warning::HOMEBREW_TAP_TOKEN not set; skipping the Homebrew cask bump." exit 0 fi - SHA=$(shasum -a 256 "Mounty-$VERSION.dmg" | awk '{print $1}') - git clone "https://x-access-token:${TAP_TOKEN}@github.com/maptic/homebrew-tap.git" tap - mkdir -p tap/Casks - cat > tap/Casks/mounty.rb <= :tahoe" - - app "Mounty.app" - - zap trash: "~/Library/Preferences/ch.maptic.Mounty.plist" - end - EOF - - cd tap - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add Casks/mounty.rb - git commit -m "chore: update mounty to $VERSION" - git push + jq -n --arg version "$VERSION" \ + '{event_type: "cask-release", client_payload: {cask: "mounty", version: $version}}' \ + | gh api repos/maptic/homebrew-tap/dispatches --input - + echo "::notice::Requested cask bump to $VERSION in maptic/homebrew-tap." diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 151077c..6353f8f 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -11,10 +11,28 @@ permissions: jobs: release-please: runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.version }} steps: # Opens/updates a release PR based on Conventional Commits. Merging that PR - # creates the git tag + GitHub Release, which triggers release-build.yml. + # creates the git tag + GitHub Release. - uses: googleapis/release-please-action@v4 + id: release with: config-file: release-please-config.json manifest-file: .release-please-manifest.json + + # A release created with the default GITHUB_TOKEN does NOT emit a + # `release: published` event, so the build is chained explicitly here instead + # of relying on that trigger. + release-build: + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + permissions: + contents: write + uses: ./.github/workflows/release-build.yml + with: + tag: ${{ needs.release-please.outputs.tag_name }} + secrets: inherit diff --git a/AGENTS.md b/AGENTS.md index fc360d3..551ecd9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -86,6 +86,28 @@ Versioning and releases are **fully automated** by `release-please` from commit `feat:` → minor, `fix:` → patch, `feat!:`/`BREAKING CHANGE:` → major; `docs/chore/refactor/test/ci` → no release. +### Release pipeline + +``` +push to main → release-please PR → merge → tag X.Y.Z + GitHub Release + │ + ├─ release-build.yml: DMG (+ notarization) → release assets + └─ repository_dispatch "cask-release" → maptic/homebrew-tap + bumps Casks/mounty.rb +``` + +`release-build.yml` is **called by** `release-please.yml`, not triggered by the `release: published` +event — a release created with the default `GITHUB_TOKEN` does not emit that event. To re-package a +tag by hand: `gh workflow run release-build.yml -f tag=X.Y.Z`. + +Tags are bare semver — `1.2.3`, not `v1.2.3` or `mounty-v1.2.3`. The repository holds one app, so a +component prefix carries no information and only makes release URLs redundant. + +The cask bump needs `HOMEBREW_TAP_TOKEN` (a fine-grained PAT with `contents: write` on +`maptic/homebrew-tap`) in this repository's secrets; without it the release still succeeds and only +the tap bump is skipped. The tap re-computes the `sha256` from the published asset itself, so the +dispatch payload carries just the cask token and the version. + ### Mandatory model attribution for agent commits Any commit you create as an AI agent MUST include a `Generated-by:` git trailer naming the exact diff --git a/docs/stories/024-homebrew-tap-distribution.md b/docs/stories/024-homebrew-tap-distribution.md new file mode 100644 index 0000000..441c5fa --- /dev/null +++ b/docs/stories/024-homebrew-tap-distribution.md @@ -0,0 +1,28 @@ +# STORY-024: Homebrew tap distribution + +- Status: IN_PROGRESS +- Type: ci +- Date: 2026-08-10 +- Commit: _none_ + +## Intent + +Ship Mounty through `brew install --cask maptic/tap/mounty` and keep the cask current without manual +steps: cutting a release must build the DMG, attach it to the GitHub Release, and bump the cask in +`maptic/homebrew-tap`. + +## Acceptance criteria + +- [ ] Merging the release-please PR builds and attaches the DMG — previously the release build never + ran, because a release created with the default `GITHUB_TOKEN` emits no `release: published` + event. +- [ ] Release tags are bare semver (`1.2.1`), matching the download URL the cask interpolates, and + the existing `mounty-v1.2.0` tag and release are renamed to that scheme. +- [ ] The release build asks `maptic/homebrew-tap` to bump the cask via a `cask-release` + `repository_dispatch`, and skips that step (without failing) when `HOMEBREW_TAP_TOKEN` is absent. +- [ ] `release-build.yml` can be re-run by hand for an existing tag. + +## Validation + +Record the release run that produced the DMG assets and the resulting cask bump commit in +`maptic/homebrew-tap`. diff --git a/docs/stories/INDEX.md b/docs/stories/INDEX.md index a3b1c9c..0f7088a 100644 --- a/docs/stories/INDEX.md +++ b/docs/stories/INDEX.md @@ -4,6 +4,7 @@ Newest stories first. Statuses: `OPEN`, `IN_PROGRESS`, `CLOSED`. | ID | Type | Story | Status | Date | | --- | --- | --- | --- | --- | +| [024](./024-homebrew-tap-distribution.md) | ci | Homebrew tap distribution | IN_PROGRESS | 2026-08-10 | | [023](./023-readme-logo-visual-alignment.md) | fix | README logo visual alignment | CLOSED | 2026-08-10 | | [022](./022-documentation-source-artwork.md) | docs | Documentation source artwork | CLOSED | 2026-08-10 | | [021](./021-readme-logo-alignment.md) | fix | README logo alignment | CLOSED | 2026-08-10 | diff --git a/release-please-config.json b/release-please-config.json index 49b11e6..10881e9 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -8,7 +8,8 @@ "bump-minor-pre-major": false, "draft": false, "prerelease": false, - "include-v-in-tag": true, + "include-v-in-tag": false, + "include-component-in-tag": false, "extra-files": [ "version.txt" ]