-
Notifications
You must be signed in to change notification settings - Fork 10
187 lines (174 loc) · 7.15 KB
/
Copy pathversion-bump.yml
File metadata and controls
187 lines (174 loc) · 7.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
name: Version Bump
on:
push:
branches: [main, master]
workflow_dispatch:
inputs:
bump:
description: "Force a specific bump (auto|patch|minor|major|none)"
required: false
default: "auto"
type: choice
options: [auto, patch, minor, major, none]
concurrency:
group: version-bump-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
env:
# Opt into Node.js 24 for actions to silence the Node 20 deprecation warning.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
bump:
name: Bump version & tag
runs-on: ubuntu-latest
if: |
github.event_name == 'workflow_dispatch' ||
!startsWith(github.event.head_commit.message, 'chore(release):')
outputs:
new_version: ${{ steps.bump.outputs.new_version }}
bumped: ${{ steps.bump.outputs.bumped }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Determine bump type
id: classify
shell: bash
# NOTE: intentionally NOT using `set -e`. The grep pipelines below are
# expected to return non-zero when no match is found, which is normal
# control flow — letting `-e` kill the step caused historical false
# failures. Errors that matter are reported explicitly.
run: |
set +e
# ----------------------------------------------------------------
# Resolve commit range since the last semver tag (or full history).
# ----------------------------------------------------------------
last_tag="$(git describe --tags --abbrev=0 2>/dev/null || true)"
if [ -n "$last_tag" ]; then
range="${last_tag}..HEAD"
else
range="HEAD"
fi
msgs="$(git log --pretty=%s "$range" 2>/dev/null || true)"
# ----------------------------------------------------------------
# Honour an explicit input from workflow_dispatch.
# ----------------------------------------------------------------
forced="${{ github.event.inputs.bump }}"
if [ -n "$forced" ] && [ "$forced" != "auto" ]; then
echo "bump=$forced" >> "$GITHUB_OUTPUT"
echo "explicit_version=" >> "$GITHUB_OUTPUT"
echo "Forced bump: $forced"
exit 0
fi
# ----------------------------------------------------------------
# Look for an explicit "v1.2.3"-style tag in any commit message so
# manual releases still get tagged.
# ----------------------------------------------------------------
explicit="$(printf '%s\n' "$msgs" | grep -oiE '\bv[0-9]+\.[0-9]+\.[0-9]+\b' | head -n1 | sed 's/^[vV]//' || true)"
# ----------------------------------------------------------------
# Conventional Commits classification.
# ----------------------------------------------------------------
bump=none
if [ -n "$msgs" ]; then
while IFS= read -r m; do
[ -z "$m" ] && continue
if printf '%s' "$m" | grep -qiE '^(feat|fix|refactor|perf)(\(.+\))?!:|BREAKING CHANGE'; then
bump=major
break
elif printf '%s' "$m" | grep -qiE '^feat(\(.+\))?:'; then
bump=minor
elif [ "$bump" != "minor" ] && printf '%s' "$m" | grep -qiE '^fix(\(.+\))?:'; then
bump=patch
fi
done <<< "$msgs"
fi
# ----------------------------------------------------------------
# Loose-keyword fallback for repos that don't use Conventional Commits.
# ----------------------------------------------------------------
if [ "$bump" = "none" ]; then
if printf '%s' "$msgs" | grep -qiE 'feature|added|implement'; then
bump=minor
elif printf '%s' "$msgs" | grep -qiE 'fix|bug|error|crash'; then
bump=patch
elif [ -n "$explicit" ]; then
bump=patch
elif printf '%s' "$msgs" | grep -qiE '\b(bump|release|publish|version)\b'; then
bump=patch
fi
fi
echo "bump=$bump" >> "$GITHUB_OUTPUT"
echo "explicit_version=$explicit" >> "$GITHUB_OUTPUT"
echo "Detected bump: $bump (explicit: ${explicit:-none})"
exit 0
- name: Bump pyproject version
id: bump
if: steps.classify.outputs.bump != 'none' || steps.classify.outputs.explicit_version != ''
shell: bash
run: |
set -e
python - <<'PY' >> "$GITHUB_OUTPUT"
import re, pathlib, sys
bump = "${{ steps.classify.outputs.bump }}"
explicit = "${{ steps.classify.outputs.explicit_version }}"
p = pathlib.Path("pyproject.toml")
if not p.exists():
print("bumped=false")
sys.exit(0)
txt = p.read_text(encoding="utf-8")
m = re.search(r'(?m)^version\s*=\s*"([^"]+)"', txt)
if not m:
print("bumped=false")
sys.exit(0)
current = m.group(1)
if explicit:
new = explicit
else:
try:
maj, mn, pt = (int(x) for x in current.split("."))
except Exception:
print("bumped=false")
sys.exit(0)
if bump == "major": maj, mn, pt = maj + 1, 0, 0
elif bump == "minor": mn, pt = mn + 1, 0
elif bump == "patch": pt = pt + 1
else: new = current; print(f"new_version={new}"); print("bumped=false"); sys.exit(0)
new = f"{maj}.{mn}.{pt}"
if new != current:
p.write_text(
re.sub(r'(?m)^version\s*=\s*"[^"]+"', f'version = "{new}"', txt),
encoding="utf-8",
)
print(f"new_version={new}")
print("bumped=true")
else:
print(f"new_version={new}")
print("bumped=false")
PY
- name: Commit, tag, push
if: steps.bump.outputs.bumped == 'true'
env:
NEW: ${{ steps.bump.outputs.new_version }}
shell: bash
run: |
set -e
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Skip entirely if the tag already exists (manual tag or prior run).
if git rev-parse "v${NEW}" >/dev/null 2>&1 || \
git ls-remote --exit-code --tags origin "refs/tags/v${NEW}" >/dev/null 2>&1; then
echo "Tag v${NEW} already exists; nothing to do."
exit 0
fi
git add pyproject.toml
if ! git diff --cached --quiet; then
git commit -m "chore(release): v${NEW}"
fi
git tag -a "v${NEW}" -m "Release v${NEW}"
git push origin HEAD --follow-tags