Releases
How sand releases are cut and published.
The pipeline
Releases run through .github/workflows/release-please.yml in two chained
jobs, release-please then goreleaser:
- release-please watches every push to
mainand maintains a standing release PR that collects the changelog from Conventional Commit messages. Its configuration (release-please-config.json) setsrelease-type: go,bump-minor-pre-major: true, and — the part that matters for the next step —draft: trueandforce-tag-creation: true. Merging the release PR creates thevX.Y.Ztag and a draft GitHub Release rather than a published one. - GoReleaser (
.goreleaser.yaml) then adopts that draft (use_existing_draft: true), cross-compilessandfor darwin+linux × amd64+arm64 withCGO_ENABLED=0, uploads the archives into the draft, publishes it, and pushes an updated formula to thelullabot/homebrew-sandbartap.
Both steps live in one workflow, chained with needs, for a specific
reason: this repository has GitHub's immutable-releases setting turned on,
which freezes a release the instant it is published — no further assets
can ever be attached. release-please therefore leaves its release as a
draft, and GoReleaser is the one that publishes it, only after every
archive has been uploaded. A separate tag-triggered workflow would race the
draft's creation and risk a GoReleaser run that finds no draft to adopt,
which creates and publishes a release of its own and burns that version
number permanently.
The Homebrew tap
GoReleaser's brews: publisher (not homebrew_casks:) pushes to
lullabot/homebrew-sandbar. A cask is deliberately not used: casks are
macOS-only, and sand needs to brew install on both macOS and Linux, so
a formula is the only cross-platform option.
Docs releases
A release tag also triggers the docs publishing workflow, which builds and
publishes that version of this site with mike and moves the latest
alias to point at it. See the docs workflow itself for the exact trigger
and versioning behaviour.
The one manual step
GitHub Pages must be pointed at the gh-pages branch once, by a repository
admin: Settings → Pages → Build and deployment → Deploy from a branch →
gh-pages / (root). The docs workflow creates the gh-pages branch on
its first run, but the site returns a 404 until this setting is switched —
automation cannot flip it, since it requires repository admin access. This
is the one thing every future maintainer needs to know to unblock a fresh
fork or a repository transfer.
One-time human prerequisites
Before the first release can run, a repository admin has to do a few things by hand — none of this is created by CI:
- Create the
lullabot/homebrew-sandbarrepository. It has to exist before GoReleaser's firstbrews:push. - Create a dedicated org GitHub App with
Contents: write, installed only onlullabot/homebrew-sandbar, and set its App ID as theHOMEBREW_TAP_APP_IDrepository variable and its private key as theHOMEBREW_TAP_APP_PRIVATE_KEYsecret on this repo. GoReleaser mints a short-lived, tap-scoped token from that App at release time — no long-lived PAT sits in the repo's secrets. - Install the org release-please App on this repo (
contents: write,pull-requests: write) and set itsRELEASE_PLEASE_APP_IDvariable andRELEASE_PLEASE_PRIVATE_KEYsecret. release-please authenticates as this App — not the defaultGITHUB_TOKEN— specifically so the release PR it opens is able to trigger the test workflow; events authored withGITHUB_TOKENdon't trigger other workflows.
See .github/workflows/release-please.yml for exactly where each
credential is consumed.