release.yml RawHistoryFolder
1name: release
2
3# Every push to main that passes CI cuts a new release. The workflow:
4# 1. waits for the ci workflow on the same commit to succeed
5# 2. looks up the latest existing vX.Y.Z tag
6# 3. bumps the patch number (vX.Y.Z+1) — or starts at v0.0.1 if none
7# 4. creates and pushes that tag
8# 5. runs goreleaser, which builds binaries for linux/darwin/windows
9# (amd64 + arm64) and attaches them plus checksums.txt to a fresh
10# GitHub Release page
11#
12# Skip a release by putting [release=skip] anywhere in the commit
13# message — useful for docs-only or ci-only commits. We use a rare
14# equals-sign form so commits that merely *mention* the feature
15# ("make the release step optional") don't accidentally opt out.
16on:
17 workflow_run:
18 workflows: [ci]
19 types: [completed]
20 branches: [main]
21
22permissions:
23 # Needed to create tags and to upload release archives.
24 contents: write
25
26# Only one release job at a time. If two pushes land in quick
27# succession, queue them so they don't race on the tag.
28concurrency:
29 group: release
30 cancel-in-progress: false
31
32jobs:
33 release:
34 # Only run when ci succeeded and we're on main, and when the head
35 # commit doesn't opt out via [skip-release].
36 if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main' && !contains(github.event.workflow_run.head_commit.message, '[release=skip]') }}
37 runs-on: ubuntu-latest
38 steps:
39 - name: checkout
40 uses: actions/checkout@v4
41 with:
42 # Full history so we can read existing tags.
43 fetch-depth: 0
44 # Check out the exact sha that ci verified, not whatever the
45 # branch tip happens to be at this moment (which may have
46 # advanced since ci finished).
47 ref: ${{ github.event.workflow_run.head_sha }}
48
49 - name: compute next version
50 id: bump
51 shell: bash
52 run: |
53 set -euo pipefail
54 # Find the newest vX.Y.Z tag. If the repo has none yet, start
55 # at v0.0.0 so the first release becomes v0.0.1.
56 latest=$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1)
57 if [ -z "$latest" ]; then
58 latest="v0.0.0"
59 fi
60 echo "latest tag: $latest"
61
62 # Strip the v prefix, split on dots, bump the patch.
63 ver="${latest#v}"
64 IFS=. read -r major minor patch <<<"$ver"
65 patch=$((patch + 1))
66 next="v${major}.${minor}.${patch}"
67 echo "next tag: $next"
68
69 echo "next=$next" >> "$GITHUB_OUTPUT"
70 echo "version=${next#v}" >> "$GITHUB_OUTPUT"
71
72 - name: create and push tag
73 env:
74 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
75 run: |
76 git config user.name "github-actions[bot]"
77 git config user.email "github-actions[bot]@users.noreply.github.com"
78 git tag -a "${{ steps.bump.outputs.next }}" \
79 -m "release ${{ steps.bump.outputs.next }}"
80 git push origin "${{ steps.bump.outputs.next }}"
81
82 - name: set up go
83 uses: actions/setup-go@v5
84 with:
85 go-version: "1.23"
86 cache: true
87
88 - name: run goreleaser
89 uses: goreleaser/goreleaser-action@v6
90 with:
91 distribution: goreleaser
92 version: latest
93 args: release --clean
94 env:
95 # goreleaser reads the tag we just pushed and builds binaries
96 # for every (goos, goarch) combination in .goreleaser.yaml.
97 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}