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 # After .99 roll over to the next minor (e.g. 0.0.99 -> 0.1.0,
64 # 0.1.99 -> 0.2.0). Keep major fixed at 0 for now.
65 ver="${latest#v}"
66 IFS=. read -r major minor patch <<<"$ver"
67 if [ "$patch" -ge 99 ]; then
68 minor=$((minor + 1))
69 patch=0
70 else
71 patch=$((patch + 1))
72 fi
73 next="v${major}.${minor}.${patch}"
74 echo "next tag: $next"
75
76 echo "next=$next" >> "$GITHUB_OUTPUT"
77 echo "version=${next#v}" >> "$GITHUB_OUTPUT"
78
79 - name: create and push tag
80 env:
81 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
82 run: |
83 git config user.name "github-actions[bot]"
84 git config user.email "github-actions[bot]@users.noreply.github.com"
85 git tag -a "${{ steps.bump.outputs.next }}" \
86 -m "release ${{ steps.bump.outputs.next }}"
87 git push origin "${{ steps.bump.outputs.next }}"
88
89 - name: set up go
90 uses: actions/setup-go@v5
91 with:
92 go-version: "1.23"
93 cache: true
94
95 - name: run goreleaser
96 uses: goreleaser/goreleaser-action@v6
97 with:
98 distribution: goreleaser
99 version: latest
100 args: release --clean
101 env:
102 # goreleaser reads the tag we just pushed and builds binaries
103 # for every (goos, goarch) combination in .goreleaser.yaml.
104 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}