diff --git a/RELEASING.md b/RELEASING.md index 6a58e1e..c55e41b 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -2,30 +2,24 @@ Releases are automated with [GoReleaser](https://goreleaser.com) via `.github/workflows/release.yml`. Pushing a `vX.Y.Z` tag builds the binaries -(macOS + Linux, amd64 + arm64), publishes a GitHub release with checksums, and -updates the Homebrew tap formula. - -## One-time setup - -1. **Create the tap repo** `RevylAI/homebrew-tap` (public, empty is fine). This - is what backs `brew install revylai/tap/greenlight`. -2. **Add a secret** `HOMEBREW_TAP_TOKEN` to this repo - (Settings → Secrets and variables → Actions): a fine-grained or classic PAT - with **contents: write** on `RevylAI/homebrew-tap`. The default `GITHUB_TOKEN` - can't push to another repo, which is why this is needed. +(macOS + Linux, amd64 + arm64) and publishes a GitHub release with checksums. ## Cutting a release ```bash -git tag v0.1.0 -git push origin v0.1.0 +git tag -a v0.2.0 -m "v0.2.0 — what changed" +git push origin v0.2.0 ``` -The workflow does the rest. To dry-run locally first: +Bump `version` in `metadata.json` and `.claude-plugin/plugin.json` first, in a +normal PR. The tag should point at a commit where those already read the new +number. + +To dry-run locally: ```bash +goreleaser check # validate the config goreleaser release --snapshot --clean # builds into ./dist, publishes nothing -goreleaser check # validate the config ``` After a release, both install paths work: @@ -34,3 +28,46 @@ After a release, both install paths work: brew install revylai/tap/greenlight go install github.com/RevylAI/greenlight/cmd/greenlight@latest ``` + +## How the Homebrew tap is updated + +`brew install revylai/tap/greenlight` is backed by +[`RevylAI/homebrew-tap`](https://github.com/RevylAI/homebrew-tap). + +The `homebrew_casks` block in `.goreleaser.yml` wants to push the cask there +during the release, which needs a `HOMEBREW_TAP_TOKEN` secret, because a +workflow's built-in `GITHUB_TOKEN` cannot write to a different repository. That +secret has never been set, and creating it needs **admin** on this repo. The +v0.2.0 release published its binaries fine and then failed on that step with +`401 Bad credentials`. + +So the tap updates itself instead. `.github/workflows/update-cask.yml` in the +tap repo polls this repo's releases hourly, regenerates `Casks/greenlight.rb`, +and commits with its own `GITHUB_TOKEN`. Reading a public repo's releases needs +no credentials and a workflow can always write to its own repo, so that path +needs no secret and no admin. It can also be run on demand from the tap's +Actions tab if you don't want to wait for the next hour. + +Nothing needs to be done during a release. Confirm the tap picked it up: + +```bash +brew update && brew info --cask revylai/tap/greenlight +``` + +If `HOMEBREW_TAP_TOKEN` is ever configured, both paths can coexist: the tap +workflow generates output byte-identical to GoReleaser's template, so whichever +runs second is a no-op rather than a revert. + +### Gotchas worth knowing + +- **The tap serves a cask, not a formula.** GoReleaser's `brews` output is + deprecated and now fails `goreleaser check`. Homebrew prefers a formula over a + cask of the same name, so a leftover `Formula/greenlight.rb` will silently keep + winning and serving an old version. There must only be one. +- **Don't hash locally built archives.** The builds are not byte-reproducible, so + a cask generated from a local `goreleaser release --snapshot` carries hashes + that do not match the published artifacts, and every install fails checksum + verification. Always take hashes from the release's `checksums.txt`. +- **GoReleaser infers the repo from the git remote.** Running a dry-run on a + fork bakes the fork's download URLs into the generated cask. `.goreleaser.yml` + pins `release.github` to `RevylAI/greenlight` to prevent that.