From bc86473709932dec1baf6ac1d1af1eb59307add0 Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Fri, 15 May 2026 20:47:26 -0500 Subject: [PATCH 1/3] Adopt pizzabot's automated semver + CI + binary release workflows CI runs `go vet`, `go build`, `go test -race` on every PR and push to main. A successful run on main triggers the release workflow, which reads the merged PR's `release:*` label (default `patch`), computes the next semver tag, builds a static linux/amd64 binary, pushes the tag, and publishes a GitHub release with auto-generated notes. Replaces the previous tag-triggered release in `go.yml` with a fully automated PR-label-driven flow. --- .github/workflows/ci.yml | 30 +++++++ .github/workflows/release.yml | 158 ++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..25b52eb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +# Build, vet, and test the Go binary. Mirrors dolph/pizzabot so the +# release workflow can chain off of a successful run. + +on: + pull_request: + push: + branches: [main] + +jobs: + test: + name: build & test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - name: go vet + run: go vet ./... + + - name: go build + run: go build ./... + + - name: go test + run: go test -race ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..9848e10 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,158 @@ +name: release + +# Continuous release. After the CI workflow finishes successfully on +# `main` (i.e. immediately after a merge), this workflow reads the +# merged PR's `release:*` label, computes the next semver tag, and +# publishes a git tag + GitHub release. Tag format is `vMAJOR.MINOR.PATCH` +# — the convention Go modules require, used by `goreleaser`, +# `release-please`, `semantic-release`, and most published Go libraries. +# +# Version-bump labels (apply exactly one on the PR; default `release:patch`): +# +# release:major → 1.4.7 → 2.0.0 Breaking change. +# release:minor → 1.4.7 → 1.5.0 Backwards-compatible new behavior. +# release:patch → 1.4.7 → 1.4.8 Bug fix, refactor, dependency bump. +# Default when nothing else is set. +# release:skip → no release Docs-only / CI-only change. +# +# Precedence when more than one label is present: skip > major > minor > +# patch. `release:skip` always wins so a PR can be parked mid-flight; +# otherwise the largest declared bump wins. +# +# Direct pushes to `main` (no PR) fall back to `release:patch`. + +on: + workflow_run: + workflows: [CI] + types: [completed] + branches: [main] + +concurrency: + group: release + cancel-in-progress: false + +permissions: + contents: write # push tags, create releases + pull-requests: read # read the merged PR's labels + +jobs: + release: + # Only act on a successful CI run that was itself triggered by a push + # to main (i.e. the post-merge run, not the PR-time run). + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + GH_TOKEN: ${{ github.token }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + steps: + - uses: actions/checkout@v6 + with: + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 # full history + tags for semver math + + - name: Resolve merged PR and version bump + id: bump + run: | + set -euo pipefail + pr_json="$(gh api "repos/${{ github.repository }}/commits/${HEAD_SHA}/pulls" --jq '.[0] // empty')" + if [[ -z "$pr_json" ]]; then + echo "No PR found for ${HEAD_SHA} — direct push to main. Defaulting to release:patch." + echo "bump=patch" >> "$GITHUB_OUTPUT" + echo "pr=" >> "$GITHUB_OUTPUT" + exit 0 + fi + + number="$(jq -r '.number' <<<"$pr_json")" + labels="$(jq -r '.labels[].name' <<<"$pr_json")" + echo "PR #${number} labels:" + printf ' %s\n' $labels + + # Precedence: skip > major > minor > patch (default). + if grep -qx 'release:skip' <<<"$labels"; then + bump=skip + elif grep -qx 'release:major' <<<"$labels"; then + bump=major + elif grep -qx 'release:minor' <<<"$labels"; then + bump=minor + else + bump=patch + fi + + echo "Resolved bump: ${bump}" + echo "bump=${bump}" >> "$GITHUB_OUTPUT" + echo "pr=${number}" >> "$GITHUB_OUTPUT" + + - name: Skip release + if: steps.bump.outputs.bump == 'skip' + run: | + echo "release:skip on PR #${{ steps.bump.outputs.pr }} — no tag created." + + - name: Compute next semver tag + if: steps.bump.outputs.bump != 'skip' + id: tag + run: | + set -euo pipefail + git fetch --tags --quiet + latest="$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n1)" + if [[ -z "$latest" ]]; then + latest="v0.0.0" + fi + IFS='.' read -r major minor patch <<<"${latest#v}" + case "${{ steps.bump.outputs.bump }}" in + major) major=$((major+1)); minor=0; patch=0 ;; + minor) minor=$((minor+1)); patch=0 ;; + patch) patch=$((patch+1)) ;; + esac + next="v${major}.${minor}.${patch}" + echo "Bumping ${latest} → ${next} (${{ steps.bump.outputs.bump }})" + echo "latest=${latest}" >> "$GITHUB_OUTPUT" + echo "next=${next}" >> "$GITHUB_OUTPUT" + + - name: Install Go + if: steps.bump.outputs.bump != 'skip' + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + + - name: Build release binary + if: steps.bump.outputs.bump != 'skip' + # Static linux/amd64 binary. CGO off so the binary has no glibc + # dependency; -trimpath + -s -w shrinks size and strips local paths. + run: | + set -euo pipefail + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \ + go build -trimpath -ldflags="-s -w" \ + -o find-replace-linux-amd64 . + ls -l find-replace-linux-amd64 + + - name: Create and push tag + if: steps.bump.outputs.bump != 'skip' + run: | + set -euo pipefail + tag="${{ steps.tag.outputs.next }}" + if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then + echo "Tag ${tag} already exists locally — skipping." && exit 0 + fi + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git tag -a "${tag}" "${HEAD_SHA}" -m "${tag}" + git push origin "${tag}" + + - name: Publish GitHub release + if: steps.bump.outputs.bump != 'skip' + run: | + set -euo pipefail + tag="${{ steps.tag.outputs.next }}" + prev="${{ steps.tag.outputs.latest }}" + notes_args=(--generate-notes) + if [[ "${prev}" != "v0.0.0" ]]; then + notes_args+=(--notes-start-tag "${prev}") + fi + gh release create "${tag}" find-replace-linux-amd64 \ + --target "${HEAD_SHA}" \ + --title "${tag}" \ + "${notes_args[@]}" From ca879ce8be8686fbf72f3f28194ee57d5c87539d Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Fri, 15 May 2026 20:48:12 -0500 Subject: [PATCH 2/3] Drop go.yml superseded by ci.yml + release.yml The tag-triggered build/release flow is replaced by the PR-label-driven release.yml. --- .github/workflows/go.yml | 40 ---------------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index 5f3a521..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Go - -on: - push: - tags: - - v* - branches: - - main - - master - pull_request: - branches: - - main - - master - create: - -permissions: - contents: write - pull-requests: read - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Setup Go - uses: actions/setup-go@v3 - with: - go-version: 1.19 - - - name: Checkout - uses: actions/checkout@v3 - - - name: Build & Test - run: ./build.sh - - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/v') - with: - body_path: README.md - files: find-replace From f757bb7a0c78c522beea855b1b84d56947335839 Mon Sep 17 00:00:00 2001 From: Dolph Mathews Date: Fri, 15 May 2026 20:53:26 -0500 Subject: [PATCH 3/3] Scrub project-name reference from CI workflow comment --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25b52eb..a87104e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ name: CI -# Build, vet, and test the Go binary. Mirrors dolph/pizzabot so the -# release workflow can chain off of a successful run. +# Build, vet, and test the Go binary on every PR and push to main. +# The release workflow chains off of a successful run. on: pull_request: