Skip to content

Latest commit

 

History

History
175 lines (123 loc) · 8.31 KB

File metadata and controls

175 lines (123 loc) · 8.31 KB

Releasing Gas City

Distribution Channels

Channel Mechanism Automatic?
GitHub Release GoReleaser via release.yml on tag push Yes
GitHub draft prerelease GoReleaser via rc-release.yml on RC tag push or manual dispatch Yes
Homebrew tap (gastownhall/gascity) release.yml writes an asset-based formula after archives upload Yes
Homebrew core (Homebrew/homebrew-core) BrewTestBot autobump, once listed Yes (~3h delay)

The homebrew-core submission is in progress. Until it lands and is added to the autobump list, users install via brew install gascity.

How to Release

Recommended: bump script

./scripts/bump-version.sh X.Y.Z --commit --tag --push

This rewrites the [Unreleased] section of CHANGELOG.md into [X.Y.Z] - YYYY-MM-DD, commits, tags vX.Y.Z, and pushes. GitHub Actions takes it from there.

Release candidates

Use the RC Release workflow to produce downloadable release-candidate archives for manual verification without updating Homebrew or publishing the stable release.

Recommended path:

  1. Run the RC gate on the intended release branch or commit.
  2. Open Actions → RC Release → Run workflow.
  3. Set tag_name to an RC tag such as v1.2.0-rc1.
  4. Set target_ref to the exact RC-gate-passing commit SHA.

The workflow creates the annotated RC tag if needed, builds GoReleaser archives for linux/darwin × amd64/arm64, and creates a GitHub draft prerelease with generated GoReleaser notes and downloadable assets. It does not update the Homebrew tap, create attestations, or mark the release latest.

Tag creation on the dispatch path uses the gastownhall-release-tagger GitHub App (an Integration bypass actor on the "Protect release tags" ruleset), minted per-run from the RELEASE_TAGGER_APP_ID / RELEASE_TAGGER_APP_PRIVATE_KEY repository secrets — the workflow's default GITHUB_TOKEN is deliberately NOT a bypass actor, so arbitrary workflows cannot create, move, or delete v* tags. The tagger token is minted only when the dispatch must create a new tag; re-dispatching an already-existing RC tag re-drafts it without the tagger secrets. If the app secrets are missing on a create dispatch, the workflow fails with instructions instead of a bare GH013; the manual-tag path below always works for members of the release-maintainers bypass team.

Because the tagger App token is a bypass actor, pushing the newly created tag re-fires rc-release.yml on that tag push. To avoid drafting the same RC twice, a dispatch that creates the tag stops after pushing it, and the resulting tag-push run is the one that builds the GoReleaser draft. A dispatch for a tag that already exists — and every direct tag push — goes straight to the draft build.

You can also push an existing RC tag manually:

git tag -a vX.Y.Z-rcN <commit> -m "Release vX.Y.Z-rcN"
git push origin vX.Y.Z-rcN

Pushing an RC tag triggers rc-release.yml. The stable release.yml jobs skip tags containing a prerelease suffix, so RC tags do not run the Homebrew or stable publishing path.

Manual

If you want to do the steps by hand:

  1. Edit CHANGELOG.md: move [Unreleased] contents under a new ## [X.Y.Z] - YYYY-MM-DD section.
  2. Commit:
    git add CHANGELOG.md
    git commit -m "chore: release vX.Y.Z"
  3. Tag and push:
    git tag -a vX.Y.Z -m "Release vX.Y.Z"
    git push origin HEAD
    git push origin vX.Y.Z

Version numbers live only in the git tag — there is no Version constant in Go source to keep in sync. cmd/gc/cmd_version.go has var version = "dev" that the Makefile and .goreleaser.yml inject at build time via -X main.version=$(VERSION).

What Happens After Tag Push

.github/workflows/release.yml fires on stable vMAJOR.MINOR.PATCH tags and runs, in order:

  1. Reject replace directives in go.mod — they break go install ...@latest and bottle builds in homebrew-core.
  2. make check-version-tag — asserts the tag is a clean vMAJOR.MINOR.PATCH with no pre-release suffix. RC/beta tags are handled by rc-release.yml instead.
  3. GoReleaser — builds binaries for linux/darwin × amd64/arm64 and creates the GitHub Release with grouped changelog (feat: → Features, fix: → Bug Fixes, others → Others).
  4. Release attestations — downloads the published checksum manifest, uploads an SPDX SBOM asset, and creates GitHub artifact attestations for the release archives.
  5. Homebrew tap update — downloads the published checksums and writes an asset-based formula to gastownhall/homebrew-gascity.

Forks skip publish/announce steps automatically via the --skip=publish --skip=announce flag (the workflow checks github.repository != 'gastownhall/gascity').

Running checks locally before pushing the tag

make check-version-tag    # no-op unless HEAD is a release tag
grep '^replace' go.mod    # should print nothing
goreleaser check          # also enforced by CI

Homebrew tap (gastownhall/gascity)

The release workflow automatically overwrites Formula/gascity.rb in the gastownhall/homebrew-gascity repo on every tag push. Publishing is GitHub App only: HOMEBREW_TAP_APP_ID and HOMEBREW_TAP_APP_PRIVATE_KEY must be configured in repository secrets for an app installed on gastownhall/homebrew-gascity with contents write.

The tap formula installs prebuilt release assets, so users do not need Go or a source build:

brew install gascity

The intended long-term user-facing Homebrew path is homebrew-core:

brew install gascity

Until the core formula lands, the tap is the public install path. After core lands, keep the tap available for emergency updates while normal releases flow through homebrew-core.

Homebrew core (planned)

Once the formula is merged to Homebrew/homebrew-core and added to the autobump list, the flow becomes:

  1. Tag push → GoReleaser creates GitHub Release (as today).
  2. BrewTestBot polls releases every ~3h, opens a PR to homebrew-core bumping the formula's url and sha256.
  3. Homebrew maintainers merge; bottles are built for macOS (arm64 + x86_64) and Linux.
  4. brew upgrade gascity picks up the new version worldwide.

Manual brew bump-formula-pr is refused for autobump formulae. If the bot stalls, check https://github.com/Homebrew/homebrew-core/pulls?q=gascity for stuck PRs.

Files Updated During a Release

File What changes Updated by
CHANGELOG.md [Unreleased][X.Y.Z] - DATE scripts/bump-version.sh
Git tag vX.Y.Z Created and pushed scripts/bump-version.sh
GitHub Release page Created with binaries + grouped changelog GoReleaser in release.yml
Release SBOM + attestations SPDX SBOM uploaded and release archives attested attest-release in release.yml
gastownhall/homebrew-gascity/Formula/gascity.rb asset URLs + sha256 updated update-homebrew-formula in release.yml

Troubleshooting

Stable release workflow skipped an RC tag

This is expected. Tags with suffixes such as -rc1, -beta, or -alpha.1 are intentionally excluded from the stable release.yml publishing jobs. Use the RC Release workflow for release candidates.

GoReleaser fails with "replace directives"

go.mod contains a replace directive. These break go install ...@latest and homebrew-core bottle builds. Remove the directive and commit before tagging.

Release tag pushed but workflow didn't run

Check .github/workflows/release.yml still matches tags: v*. Verify the tag was pushed to origin (git ls-remote origin refs/tags/vX.Y.Z).

Tap formula not updated

Check the Homebrew tap GitHub App credentials in repo secrets: HOMEBREW_TAP_APP_ID and HOMEBREW_TAP_APP_PRIVATE_KEY. The app must be installed on gastownhall/homebrew-gascity with contents write. The workflow intentionally fails instead of falling back to a long-lived token.

Homebrew shows old version after a release

While on the tap: a tag push updates the tap directly; users pick it up on the next brew update && brew upgrade gascity. If the formula wasn't updated, see above.

After homebrew-core lands: the autobump bot runs every ~3h. After ~6h without a PR, check https://github.com/Homebrew/homebrew-core/pulls?q=gascity.