release #24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: release | |
| # Two ways in, one pipeline. Pushing a v* tag releases that tag. The manual | |
| # dispatch takes a version, creates the tag on main's head in-run, and | |
| # releases it — the tag still exists afterwards, because goreleaser derives | |
| # the version, the changelog range, and the GitHub Release from the tag | |
| # itself. (The in-run tag push uses GITHUB_TOKEN, which deliberately does | |
| # not re-trigger the tag event — the same run carries on to goreleaser.) | |
| on: | |
| push: | |
| tags: ["v*"] | |
| workflow_dispatch: | |
| inputs: | |
| bump: | |
| description: "Which part of the version to bump" | |
| type: choice | |
| # No default, on purpose: with zero tags the base is v0.0.0, so a | |
| # patch default would have minted v0.0.1 as the first-ever release. | |
| # The dispatcher has to say what kind of release this is. (The tag | |
| # step below refuses v0.0.x regardless, in case the UI ever | |
| # preselects an option anyway.) | |
| required: true | |
| options: [patch, minor, major] | |
| permissions: | |
| contents: write | |
| jobs: | |
| goreleaser: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| # Required for the changelog: goreleaser walks the history | |
| # between tags. | |
| fetch-depth: 0 | |
| # Fail here, not mid-goreleaser: the cask publish to karnstack/tap is | |
| # the last thing goreleaser does, so a missing or powerless | |
| # TAP_GITHUB_TOKEN would only surface after the GitHub release and | |
| # artifacts are already live — a stranded half-release where the | |
| # release page looks fine and `brew install karnstack/tap/flue` is | |
| # broken. Prove the token exists and can see the tap before anything | |
| # is tagged or published. | |
| - name: Preflight the tap token | |
| env: | |
| GH_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} | |
| run: | | |
| if [ -z "$GH_TOKEN" ]; then | |
| echo "::error::TAP_GITHUB_TOKEN is not set. Add the repo secret (a token that can push to karnstack/tap) before releasing." | |
| exit 1 | |
| fi | |
| if ! gh api repos/karnstack/tap --silent; then | |
| echo "::error::karnstack/tap is not reachable with TAP_GITHUB_TOKEN. Check that the repo exists and the token grants access to it." | |
| exit 1 | |
| fi | |
| # go, node, and pnpm from mise.toml — goreleaser's before hook runs | |
| # `make web`, which needs all three, and so does the test gate below. | |
| - name: Install the toolchain from mise.toml | |
| uses: jdx/mise-action@v4 | |
| # mise-action caches the tools it installs, but not the pnpm store; | |
| # cache it separately, keyed on the lockfile. | |
| - name: Locate the pnpm store | |
| id: pnpm-store | |
| run: echo "path=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" | |
| - name: Cache the pnpm store | |
| uses: actions/cache@v6 | |
| with: | |
| path: ${{ steps.pnpm-store.outputs.path }} | |
| key: pnpm-store-${{ runner.os }}-${{ hashFiles('web/pnpm-lock.yaml', 'relay/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| pnpm-store-${{ runner.os }}- | |
| # The same test surface ci.yml runs, and it runs before the tag step | |
| # on purpose: a tag is forever, so a broken main head has to fail the | |
| # run while it is still untagged and unpublished — after tagging is | |
| # too late. | |
| - name: Lint and test | |
| run: make lint test | |
| # ubuntu-latest images ship shellcheck, so this step normally does | |
| # nothing. The guard is what keeps it honest: the unconditional | |
| # apt-get it replaces once sat on a bad mirror for 40+ minutes, and a | |
| # step that exists only for the day the image drops the tool must not | |
| # be able to hold CI hostage — hence also the timeout. | |
| - name: Install shellcheck if the image lost it | |
| timeout-minutes: 3 | |
| run: command -v shellcheck || (sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck) | |
| - name: Shellcheck the install script | |
| run: shellcheck scripts/install.sh scripts/install_test.sh | |
| - name: Install-script tests | |
| run: bash scripts/install_test.sh | |
| - name: Tag the next ${{ inputs.bump }} version | |
| if: github.event_name == 'workflow_dispatch' | |
| env: | |
| BUMP: ${{ inputs.bump }} | |
| run: | | |
| # The next version comes from the latest existing v* tag. With no | |
| # tags yet, the base is v0.0.0 — so the first minor-bump release | |
| # is v0.1.0. | |
| latest=$(git tag --list 'v*' --sort=-v:refname | head -n 1) | |
| latest=${latest:-v0.0.0} | |
| IFS=. read -r major minor patch <<< "${latest#v}" | |
| case "$BUMP" in | |
| major) version="v$((major + 1)).0.0" ;; | |
| minor) version="v${major}.$((minor + 1)).0" ;; | |
| patch) version="v${major}.${minor}.$((patch + 1))" ;; | |
| esac | |
| echo "releasing ${version} (latest was ${latest})" | tee -a "$GITHUB_STEP_SUMMARY" | |
| # A v0.0.x result can only mean a patch bump off the tagless | |
| # repo — nothing has shipped yet, so the first release must be at | |
| # least v0.1.0. Refuse to mint it rather than publish it. | |
| case "$version" in | |
| v0.0.*) | |
| echo "::error::refusing to tag ${version}: no release exists yet, so the first one must be at least v0.1.0. Re-run with bump=minor (or major)." | |
| exit 1 | |
| ;; | |
| esac | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$version" -m "$version" | |
| git push origin "$version" | |
| - name: Release | |
| uses: goreleaser/goreleaser-action@v7 | |
| with: | |
| distribution: goreleaser | |
| version: "~> v2" | |
| args: release --clean | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # User-provided repo secret — the one manual setup step. It | |
| # authorizes the cask push to karnstack/tap; the default | |
| # GITHUB_TOKEN cannot write to another repository. The preflight | |
| # step has already proven it non-empty and able to see the tap. | |
| TAP_GITHUB_TOKEN: ${{ secrets.TAP_GITHUB_TOKEN }} |