Skip to content

Commit 87f9e6d

Browse files
committed
feat: auto-release on main push with changelog and version injection
- Release workflow triggers on push to main instead of tags - Auto-detect version bump from conventional commits: feat: → minor, fix: → patch, breaking: → major - Auto-generate changelog grouped by Features/Fixes/Other - Inject version/commit/date into binary via ldflags - Skip release for chore commits and non-meaningful changes - Concurrency guard prevents parallel releases - Keep workflow_dispatch for manual trigger
1 parent a1db070 commit 87f9e6d

2 files changed

Lines changed: 148 additions & 26 deletions

File tree

.github/workflows/release.yml

Lines changed: 140 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,132 @@
1-
name: Build and Release
1+
name: Release
22

33
on:
44
push:
5-
tags:
6-
- 'v*.*.*'
5+
branches: [main]
76
workflow_dispatch:
87

8+
# Prevent concurrent releases
9+
concurrency:
10+
group: release
11+
cancel-in-progress: false
12+
913
jobs:
14+
check:
15+
runs-on: ubuntu-latest
16+
outputs:
17+
should_release: ${{ steps.check.outputs.should_release }}
18+
next_version: ${{ steps.version.outputs.next_version }}
19+
changelog: ${{ steps.changelog.outputs.changelog }}
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v4
23+
with:
24+
fetch-depth: 0
25+
26+
- name: Check if release needed
27+
id: check
28+
run: |
29+
# Skip if the last commit is a release commit (avoid infinite loop)
30+
LAST_MSG=$(git log -1 --pretty=%s)
31+
if [[ "$LAST_MSG" == chore:\ release* ]]; then
32+
echo "should_release=false" >> $GITHUB_OUTPUT
33+
exit 0
34+
fi
35+
36+
# Skip if no meaningful changes since last tag
37+
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
38+
if [ -z "$LAST_TAG" ]; then
39+
echo "should_release=true" >> $GITHUB_OUTPUT
40+
exit 0
41+
fi
42+
43+
# Check for feat/fix/refactor commits since last tag
44+
COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=%s)
45+
if echo "$COMMITS" | grep -qE '^(feat|fix|refactor|perf|breaking)'; then
46+
echo "should_release=true" >> $GITHUB_OUTPUT
47+
else
48+
echo "should_release=false" >> $GITHUB_OUTPUT
49+
fi
50+
51+
- name: Determine next version
52+
if: steps.check.outputs.should_release == 'true'
53+
id: version
54+
run: |
55+
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
56+
echo "Last tag: $LAST_TAG"
57+
58+
# Parse semver
59+
VERSION=${LAST_TAG#v}
60+
MAJOR=$(echo $VERSION | cut -d. -f1)
61+
MINOR=$(echo $VERSION | cut -d. -f2)
62+
PATCH=$(echo $VERSION | cut -d. -f3)
63+
64+
# Determine bump type from commits
65+
COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=%s 2>/dev/null || git log --pretty=%s)
66+
if echo "$COMMITS" | grep -qiE '^breaking|BREAKING CHANGE'; then
67+
MAJOR=$((MAJOR + 1))
68+
MINOR=0
69+
PATCH=0
70+
elif echo "$COMMITS" | grep -qE '^feat'; then
71+
MINOR=$((MINOR + 1))
72+
PATCH=0
73+
else
74+
PATCH=$((PATCH + 1))
75+
fi
76+
77+
NEXT="v${MAJOR}.${MINOR}.${PATCH}"
78+
echo "Next version: $NEXT"
79+
echo "next_version=$NEXT" >> $GITHUB_OUTPUT
80+
81+
- name: Generate changelog
82+
if: steps.check.outputs.should_release == 'true'
83+
id: changelog
84+
run: |
85+
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
86+
if [ -z "$LAST_TAG" ]; then
87+
RANGE="HEAD"
88+
else
89+
RANGE="${LAST_TAG}..HEAD"
90+
fi
91+
92+
{
93+
echo 'changelog<<CHANGELOG_EOF'
94+
95+
# Features
96+
FEATS=$(git log $RANGE --pretty="- %s (%h)" --grep="^feat" --extended-regexp 2>/dev/null)
97+
if [ -n "$FEATS" ]; then
98+
echo "### Features"
99+
echo "$FEATS"
100+
echo ""
101+
fi
102+
103+
# Fixes
104+
FIXES=$(git log $RANGE --pretty="- %s (%h)" --grep="^fix" --extended-regexp 2>/dev/null)
105+
if [ -n "$FIXES" ]; then
106+
echo "### Bug Fixes"
107+
echo "$FIXES"
108+
echo ""
109+
fi
110+
111+
# Other changes (refactor, perf, etc.)
112+
OTHERS=$(git log $RANGE --pretty="- %s (%h)" --grep="^refactor\|^perf\|^chore\|^ci\|^docs\|^test\|^build" --extended-regexp 2>/dev/null)
113+
if [ -n "$OTHERS" ]; then
114+
echo "### Other Changes"
115+
echo "$OTHERS"
116+
echo ""
117+
fi
118+
119+
echo "### Platforms"
120+
echo "- Linux (amd64, arm64)"
121+
echo "- macOS (amd64, arm64)"
122+
echo "- Windows (amd64, arm64)"
123+
124+
echo 'CHANGELOG_EOF'
125+
} >> $GITHUB_OUTPUT
126+
10127
build:
128+
needs: check
129+
if: needs.check.outputs.should_release == 'true'
11130
strategy:
12131
matrix:
13132
include:
@@ -51,17 +170,22 @@ jobs:
51170
- name: Install dependencies
52171
run: go mod download
53172

54-
- name: Build codes
173+
- name: Build
55174
shell: bash
56175
env:
57176
GOOS: ${{ matrix.goos }}
58177
GOARCH: ${{ matrix.goarch }}
59178
CGO_ENABLED: '0'
60179
run: |
180+
VERSION=${{ needs.check.outputs.next_version }}
181+
COMMIT=$(git rev-parse --short HEAD)
182+
DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
183+
LDFLAGS="-s -w -X codes/internal/commands.Version=${VERSION} -X codes/internal/commands.Commit=${COMMIT} -X codes/internal/commands.Date=${DATE}"
184+
61185
if [ "${{ matrix.goos }}" = "windows" ]; then
62-
go build -o codes.exe ./cmd/codes
186+
go build -ldflags "$LDFLAGS" -o codes.exe ./cmd/codes
63187
else
64-
go build -o codes ./cmd/codes
188+
go build -ldflags "$LDFLAGS" -o codes ./cmd/codes
65189
fi
66190
67191
- name: Test
@@ -74,30 +198,22 @@ jobs:
74198
./codes version
75199
fi
76200
77-
- name: Set binary name
78-
id: binary
79-
shell: bash
80-
run: |
81-
if [ "${{ matrix.goos }}" = "windows" ]; then
82-
echo "name=codes.exe" >> $GITHUB_OUTPUT
83-
else
84-
echo "name=codes" >> $GITHUB_OUTPUT
85-
fi
86-
87201
- name: Upload artifact
88202
uses: actions/upload-artifact@v4
89203
with:
90204
name: codes-${{ matrix.goos }}-${{ matrix.goarch }}
91-
path: ${{ steps.binary.outputs.name }}
205+
path: codes${{ matrix.goos == 'windows' && '.exe' || '' }}
92206
retention-days: 7
93207

94208
release:
95-
needs: build
209+
needs: [check, build]
96210
runs-on: ubuntu-latest
97-
if: startsWith(github.ref, 'refs/tags/v')
98211
permissions:
99212
contents: write
100213
steps:
214+
- name: Checkout code
215+
uses: actions/checkout@v4
216+
101217
- name: Download all artifacts
102218
uses: actions/download-artifact@v4
103219
with:
@@ -112,19 +228,18 @@ jobs:
112228
mv "$dir/codes.exe" "release/codes-$platform.exe"
113229
elif [ -f "$dir/codes" ]; then
114230
mv "$dir/codes" "release/codes-$platform"
231+
chmod +x "release/codes-$platform"
115232
fi
116233
done
117234
ls -la release/
118235
119236
- name: Create release
120237
uses: softprops/action-gh-release@v2
121238
with:
239+
tag_name: ${{ needs.check.outputs.next_version }}
240+
name: ${{ needs.check.outputs.next_version }}
241+
body: ${{ needs.check.outputs.changelog }}
122242
files: release/*
123243
draft: false
124244
prerelease: false
125-
body: |
126-
Cross-platform builds for codes CLI tool
127-
128-
- Linux (amd64, arm64)
129-
- Windows (amd64, arm64)
130-
- macOS (amd64, arm64)
245+
make_latest: true

internal/commands/commands.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ import (
1919
"codes/internal/ui"
2020
)
2121

22+
// Version information, set via ldflags at build time.
23+
var (
24+
Version = "dev"
25+
Commit = "unknown"
26+
Date = "unknown"
27+
)
28+
2229
// min returns the minimum of two integers
2330
func min(a, b int) int {
2431
if a < b {
@@ -28,7 +35,7 @@ func min(a, b int) int {
2835
}
2936

3037
func RunVersion() {
31-
fmt.Printf("codes version dev (built unknown)\n")
38+
fmt.Printf("codes version %s (commit %s, built %s)\n", Version, Commit, Date)
3239
}
3340

3441
func RunSelect() {

0 commit comments

Comments
 (0)