Skip to content

Commit 1056784

Browse files
committed
Add label-driven semver versioning for automated releases
1 parent d34318e commit 1056784

4 files changed

Lines changed: 94 additions & 7 deletions

File tree

.github/actions/create-release-tag/action.yml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ description: Compute the next version tag, create it, and push it.
33

44
inputs:
55
bump:
6-
description: Version bump type (patch or minor)
6+
description: Version bump type (patch, minor, or major)
77
required: true
88

99
outputs:
@@ -28,23 +28,31 @@ runs:
2828
run: |
2929
set -euo pipefail
3030
31-
latest="$(git tag --list "v0.*.*" --sort=-v:refname | grep -E "^v0\.[0-9]+\.[0-9]+$" | head -n 1 || true)"
31+
latest="$(git tag --list "v*.*.*" --sort=-v:refname | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | head -n 1 || true)"
3232
if [[ -z "${latest}" ]]; then
33+
major=0
3334
minor=1
3435
patch=0
3536
else
37+
current_major="$(echo "${latest}" | cut -d. -f1 | tr -d 'v')"
3638
current_minor="$(echo "${latest}" | cut -d. -f2)"
3739
current_patch="$(echo "${latest}" | cut -d. -f3)"
38-
if [[ "${BUMP}" == "minor" ]]; then
40+
if [[ "${BUMP}" == "major" ]]; then
41+
major="$(( current_major + 1 ))"
42+
minor=0
43+
patch=0
44+
elif [[ "${BUMP}" == "minor" ]]; then
45+
major="${current_major}"
3946
minor="$(( current_minor + 1 ))"
4047
patch=0
4148
else
49+
major="${current_major}"
4250
minor="${current_minor}"
4351
patch="$(( current_patch + 1 ))"
4452
fi
4553
fi
4654
47-
tag="v0.${minor}.${patch}"
55+
tag="v${major}.${minor}.${patch}"
4856
if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
4957
echo "Tag ${tag} already exists"
5058
exit 1

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ updates:
77
time: "09:00"
88
timezone: "Etc/UTC"
99
open-pull-requests-limit: 10
10+
labels:
11+
- "semver: patch"
1012

1113
- package-ecosystem: "github-actions"
1214
directory: "/"
@@ -15,3 +17,5 @@ updates:
1517
time: "10:00"
1618
timezone: "Etc/UTC"
1719
open-pull-requests-limit: 10
20+
labels:
21+
- "semver: patch"

.github/workflows/automated-release.yml

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
run: |
3737
set -euo pipefail
3838
39-
latest_tag="$(git tag --list "v0.*.*" --sort=-v:refname | grep -E "^v0\.[0-9]+\.[0-9]+$" | head -n 1 || true)"
39+
latest_tag="$(git tag --list "v*.*.*" --sort=-v:refname | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | head -n 1 || true)"
4040
if [[ -z "${latest_tag}" ]]; then
4141
echo "No previous tag found, proceeding with release"
4242
echo "has_changes=true" >> "${GITHUB_OUTPUT}"
@@ -51,6 +51,62 @@ jobs:
5151
fi
5252
fi
5353
54+
determine-bump:
55+
name: Determine version bump
56+
needs: check-changes
57+
if: needs.check-changes.outputs.has_changes == 'true'
58+
runs-on: ubuntu-latest
59+
outputs:
60+
bump: ${{ steps.bump.outputs.bump }}
61+
steps:
62+
- name: Checkout code
63+
uses: actions/checkout@v6
64+
with:
65+
ref: main
66+
fetch-depth: 0
67+
token: ${{ secrets.PRO_ACCESS_TOKEN }}
68+
69+
- name: Fetch tags
70+
run: git fetch --tags --force
71+
72+
- name: Determine bump type from PR labels
73+
id: bump
74+
env:
75+
GH_TOKEN: ${{ secrets.PRO_ACCESS_TOKEN }}
76+
run: |
77+
set -euo pipefail
78+
79+
latest_tag="$(git tag --list "v*.*.*" --sort=-v:refname | grep -E "^v[0-9]+\.[0-9]+\.[0-9]+$" | head -n 1 || true)"
80+
81+
if [[ -z "${latest_tag}" ]]; then
82+
echo "No previous tag found, defaulting to patch"
83+
echo "bump=patch" >> "${GITHUB_OUTPUT}"
84+
exit 0
85+
fi
86+
87+
# Use git history to find exactly which PRs are part of this release
88+
pr_numbers="$(git log "${latest_tag}..HEAD" --oneline | grep -oE '#[0-9]+' | tr -d '#' | sort -u)"
89+
90+
if [[ -z "${pr_numbers}" ]]; then
91+
echo "No PRs found since ${latest_tag}, defaulting to patch"
92+
echo "bump=patch" >> "${GITHUB_OUTPUT}"
93+
exit 0
94+
fi
95+
96+
bump="patch"
97+
for pr in ${pr_numbers}; do
98+
labels="$(gh pr view "${pr}" --json labels --jq '.labels[].name' 2>/dev/null || true)"
99+
if echo "${labels}" | grep -q "semver: major"; then
100+
bump="major"
101+
break
102+
elif echo "${labels}" | grep -q "semver: minor"; then
103+
bump="minor"
104+
fi
105+
done
106+
107+
echo "Determined bump type: ${bump}"
108+
echo "bump=${bump}" >> "${GITHUB_OUTPUT}"
109+
54110
ci:
55111
name: CI
56112
needs: check-changes
@@ -60,7 +116,7 @@ jobs:
60116

61117
create-tag:
62118
name: Create release tag
63-
needs: [check-changes, ci]
119+
needs: [check-changes, determine-bump, ci]
64120
if: needs.check-changes.outputs.has_changes == 'true'
65121
runs-on: ubuntu-latest
66122
steps:
@@ -77,4 +133,4 @@ jobs:
77133
- name: Create release tag
78134
uses: ./.github/actions/create-release-tag
79135
with:
80-
bump: patch
136+
bump: ${{ needs.determine-bump.outputs.bump }}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Require release label
2+
3+
on:
4+
pull_request:
5+
types: [opened, labeled, unlabeled, synchronize, reopened]
6+
7+
jobs:
8+
check-label:
9+
name: Require release label
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Check for semver label
13+
env:
14+
LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }}
15+
run: |
16+
if ! echo "${LABELS}" | jq -e 'any(. == "semver: patch" or . == "semver: minor" or . == "semver: major")' > /dev/null; then
17+
echo "::error::PR must have a release label: 'semver: patch', 'semver: minor', or 'semver: major'"
18+
exit 1
19+
fi

0 commit comments

Comments
 (0)