-
Notifications
You must be signed in to change notification settings - Fork 0
169 lines (157 loc) · 7.54 KB
/
Copy pathversion-bump.yml
File metadata and controls
169 lines (157 loc) · 7.54 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
name: Version Bump
# Runs after the "CI" workflow finishes on main, and only proceeds if it
# succeeded — never bump/tag/release a commit that doesn't even compile.
# (This used to run directly on `push: branches: [main]`, racing CI instead
# of waiting for it: a commit that failed to compile could still get bumped,
# tagged, and dispatched to release.yml, which would then fail after the
# tag was already public.)
#
# Once gated on a green CI run, it decides from Conventional Commits since
# the last tag whether this deserves a new version: fix: -> patch,
# feat: -> minor, a `!` after the type/scope or a `BREAKING CHANGE:` footer
# -> major. Anything else (chore/docs/refactor/test/ci/style, or a push with
# no new commits since the last tag) bumps nothing and the job exits early.
#
# When a bump is due, it writes the new version into all 4 files the
# project's manual convention already touched (package.json,
# package-lock.json, src-tauri/Cargo.toml, src-tauri/Cargo.lock,
# src-tauri/tauri.conf.json), commits, tags `vX.Y.Z`, and pushes both.
#
# The push uses the workflow's own GITHUB_TOKEN. GitHub explicitly does not
# let a GITHUB_TOKEN-authored push trigger *other* workflow runs (this is
# what stops an infinite loop here too, on top of the guard below) — so the
# `v*` tag this job pushes will NOT auto-fire release.yml's `on: push:
# tags:` trigger. The final step compensates by explicitly dispatching
# release.yml via the API (`gh workflow run`), which release.yml has to
# opt into with `workflow_dispatch:`.
on:
workflow_run:
workflows: ["CI"]
types: [completed]
permissions:
contents: write
actions: write
# The bump commit's own push re-triggers "CI" (it runs on every push to
# main), which in turn re-triggers this workflow — the guard below turns
# that into a fast, harmless no-op (see it below), but two overlapping runs
# racing to push/tag at once would still be possible without this: e.g. a
# human push and the bump commit's CI run completing within moments of each
# other. Queue them instead of letting them interleave.
concurrency:
group: version-bump-main
cancel-in-progress: false
jobs:
bump:
runs-on: ubuntu-latest
# Only proceed for a green CI run on main — never for a failed/cancelled
# run, and never for CI runs from a PR branch (those also trigger "CI").
# The commit-message check is the same anti-recursion guard as before,
# now reading workflow_run's own head_commit instead of push's.
# The whole if: value must be quoted: an unquoted YAML scalar containing
# ": " (a colon followed by a space, as in 'chore: bump version to')
# gets parsed as a nested mapping key instead of plain text, which is
# an invalid workflow file GitHub Actions won't even schedule a job for
# (this exact bug broke this workflow's first real run).
if: >-
${{
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' &&
!startsWith(github.event.workflow_run.head_commit.message, 'chore: bump version to')
}}
steps:
- uses: actions/checkout@v4
with:
# Pin to the exact commit CI just validated, not whatever main's
# tip happens to be when this job starts — those should be the
# same commit, but pinning removes any doubt/race.
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
- name: Determine bump level from commits since the last tag
id: bump
run: |
set -e
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
RANGE="$LAST_TAG..HEAD"
echo "Scanning commits since $LAST_TAG"
else
RANGE="HEAD"
echo "No prior tag found; scanning full history"
fi
SUBJECTS=$(git log "$RANGE" --pretty=%s)
BODIES=$(git log "$RANGE" --pretty=%b)
LEVEL="none"
# The Conventional Commits spec requires BREAKING CHANGE as a footer
# token at the start of its own line ("BREAKING CHANGE: ..."), not
# just the phrase appearing anywhere in the body — a body merely
# *describing* the convention (e.g. this very commit's own message)
# would otherwise false-trigger a major bump.
if echo "$SUBJECTS" | grep -qE '^[a-zA-Z]+(\([^)]*\))?!:' || echo "$BODIES" | grep -qE '^BREAKING[ -]CHANGE:'; then
LEVEL="major"
elif echo "$SUBJECTS" | grep -qE '^feat(\([^)]*\))?:'; then
LEVEL="minor"
elif echo "$SUBJECTS" | grep -qE '^fix(\([^)]*\))?:'; then
LEVEL="patch"
fi
echo "level=$LEVEL" >> "$GITHUB_OUTPUT"
echo "Bump level: $LEVEL"
- name: Compute next version
id: version
if: steps.bump.outputs.level != 'none'
run: |
set -e
CURRENT=$(node -p "require('./package.json').version")
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
case "${{ steps.bump.outputs.level }}" in
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
patch) PATCH=$((PATCH + 1)) ;;
esac
NEW="$MAJOR.$MINOR.$PATCH"
echo "new=$NEW" >> "$GITHUB_OUTPUT"
echo "Bumping $CURRENT -> $NEW"
- uses: actions/setup-node@v4
if: steps.bump.outputs.level != 'none'
with:
node-version: 22
- uses: dtolnay/rust-toolchain@stable
if: steps.bump.outputs.level != 'none'
- uses: swatinem/rust-cache@v2
if: steps.bump.outputs.level != 'none'
with:
workspaces: src-tauri
- name: Write the new version into every version file
if: steps.bump.outputs.level != 'none'
run: |
set -e
NEW="${{ steps.version.outputs.new }}"
# Updates package.json and package-lock.json together, the same
# way the project's manual bump convention did.
npm version "$NEW" --no-git-tag-version --allow-same-version
# Cargo.toml's version line is the package's own — the first
# `version = "..."` in the file, right under [package].
sed -i "0,/^version = \"[^\"]*\"/s//version = \"$NEW\"/" src-tauri/Cargo.toml
sed -i "s/\"version\": \"[0-9.]*\"/\"version\": \"$NEW\"/" src-tauri/tauri.conf.json
# `cargo metadata` resolves the dependency graph — including
# syncing Cargo.lock's afkode entry to the new Cargo.toml version —
# without invoking any build script. `cargo check`/`cargo build`
# would, and this app's Linux GTK/WebKit system deps (installed by
# release.yml, not here) aren't present on this job's runner.
(cd src-tauri && cargo metadata --format-version 1 > /dev/null)
- name: Commit, tag, and push
if: steps.bump.outputs.level != 'none'
run: |
set -e
NEW="${{ steps.version.outputs.new }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add package.json package-lock.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/tauri.conf.json
git commit -m "chore: bump version to $NEW"
git tag "v$NEW"
git push origin HEAD:main
git push origin "v$NEW"
- name: Trigger the release build
if: steps.bump.outputs.level != 'none'
env:
GH_TOKEN: ${{ github.token }}
run: gh workflow run release.yml --ref "v${{ steps.version.outputs.new }}"