-
Notifications
You must be signed in to change notification settings - Fork 0
258 lines (235 loc) · 9.94 KB
/
Copy pathset_version.yml
File metadata and controls
258 lines (235 loc) · 9.94 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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
name: Set Version
on:
workflow_call:
inputs:
target_branch:
description: 'Branch to update (e.g., main, hotfix/foo, v1.2, feature/foo)'
type: string
required: true
mode:
description: |
How to set the version:
- explicit: set to a specific value (e.g., 1.3-alpha)
- bump: bump major/minor/patch from current SimpleVersion and apply optional prerelease
- auto: policy-based (main/vX.Y -> stable; hotfix/* -> next patch -alpha; feature branches -> next minor -alpha)
type: string
required: true
default: auto
version:
description: 'Required when mode=explicit (e.g., 1.3-alpha, 1.2.3, 1.4.0-rc)'
type: string
required: false
increment:
description: 'When mode=bump: major | minor | patch'
type: string
required: false
default: minor
prerelease:
description: 'When mode=bump: prerelease suffix without leading dash (e.g., alpha, beta, rc). Empty for stable.'
type: string
required: false
default: ""
commit_message:
description: 'Optional custom commit message'
type: string
required: false
default: "chore(nbgv): set version via CI"
outputs:
new_simple:
description: "Final SimpleVersion (after any auto-bump)"
value: ${{ jobs.set-version.outputs.new_simple }}
new_prerelease:
description: "Final PrereleaseVersion (after any auto-bump)"
value: ${{ jobs.set-version.outputs.new_prerelease }}
new_nuget:
description: "Final NuGetPackageVersion (after any auto-bump)"
value: ${{ jobs.set-version.outputs.new_nuget }}
tag:
description: "Final tag name to use (v<NuGetPackageVersion>)"
value: ${{ jobs.set-version.outputs.tag }}
permissions:
contents: write
pull-requests: write
jobs:
set-version:
runs-on: ubuntu-latest
outputs:
new_simple: ${{ steps.final.outputs.simple }}
new_prerelease: ${{ steps.final.outputs.pre }}
new_nuget: ${{ steps.final.outputs.nuget }}
tag: ${{ steps.final.outputs.tag }}
steps:
# Mint a short-lived token from the Stillpoint Release Bot GitHub App.
# The app has contents:write and can bypass branch protection (added
# to bypass_pull_request_allowances.apps on each repo's main branch),
# so the version.json push below can land on a protected branch
# without loosening protection for other workflows like Dependabot.
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
- name: Checkout branch
uses: actions/checkout@v6
with:
ref: ${{ inputs.target_branch }}
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Install nbgv
run: |
dotnet tool install nbgv --version 3.9.50 --tool-path "$HOME/.nbgv-tool"
echo "$HOME/.nbgv-tool" >> "$GITHUB_PATH"
- name: Read current version
id: cur
run: |
nbgv get-version --format json > cur.json
echo "simple=$(jq -r .SimpleVersion cur.json)" >> $GITHUB_OUTPUT
echo "pre=$(jq -r .PrereleaseVersion cur.json)" >> $GITHUB_OUTPUT
echo "nuget=$(jq -r .NuGetPackageVersion cur.json)" >> $GITHUB_OUTPUT
echo "Current: $(cat cur.json)"
- name: Decide desired baseline
id: plan
env:
MODE: ${{ inputs.mode }}
SIMPLE: ${{ steps.cur.outputs.simple }}
PRE: ${{ steps.cur.outputs.pre }}
INCR: ${{ inputs.increment }}
PRENAME: ${{ inputs.prerelease }}
EXPLICIT: ${{ inputs.version }}
BR: ${{ inputs.target_branch }}
run: |
set -euo pipefail
parse_semver() { IFS='.' read -r MAJOR MINOR PATCH <<< "$1"; PATCH=${PATCH:-0}; }
NEW=""
case "$MODE" in
explicit)
[[ -n "$EXPLICIT" ]] || { echo "version is required for mode=explicit"; exit 1; }
NEW="$EXPLICIT"
;;
bump)
parse_semver "$SIMPLE"
case "$INCR" in
major) MAJOR=$((MAJOR+1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR+1)); PATCH=0 ;;
patch) PATCH=$((PATCH+1)) ;;
*) echo "increment must be major|minor|patch"; exit 1 ;;
esac
[[ -n "$PRENAME" ]] && NEW="${MAJOR}.${MINOR}.${PATCH}-${PRENAME}" || NEW="${MAJOR}.${MINOR}.${PATCH}"
;;
auto)
parse_semver "$SIMPLE"
if [[ "$BR" == "main" || "$BR" =~ ^v[0-9]+\.[0-9]+$ ]]; then
NEW="${MAJOR}.${MINOR}.${PATCH}"
elif [[ "$BR" =~ ^hotfix/ ]]; then
[[ -z "$PRE" ]] && PATCH=$((PATCH+1))
NEW="${MAJOR}.${MINOR}.${PATCH}-alpha"
else
# Feature branches (e.g., feature/*, bugfix/*, ad hoc): next minor -alpha
if [[ -z "$PRE" ]]; then
MINOR=$((MINOR+1)); NEW="${MAJOR}.${MINOR}-alpha"
else
NEW="${MAJOR}.${MINOR}.${PATCH}-alpha"
fi
fi
;;
*) echo "Unsupported mode: $MODE"; exit 1 ;;
esac
echo "new=$NEW" >> $GITHUB_OUTPUT
echo "Desired baseline: $NEW"
- name: Apply baseline and enforce monotonic increase
id: ensure
env:
NEW: ${{ steps.plan.outputs.new }}
BR: ${{ inputs.target_branch }}
MSG: ${{ inputs.commit_message }}
run: |
set -euo pipefail
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor }}@users.noreply.github.com"
# Helpers
pure() { echo "${1%%-*}"; } # strip -prerelease
verlte() { [ "$(printf "%s\n%s\n" "$1" "$2" | sort -V | head -1)" = "$1" ]; } # $1 <= $2 (SemVer-ish)
parse() { IFS='.' read -r MAJOR MINOR PATCH <<< "$1"; PATCH=${PATCH:-0}; }
# Highest existing tag (v-prefixed) -> HIGHEST (no 'v')
git fetch --tags --force
HIGHEST=$(git tag -l 'v*' | sed 's/^v//' | sort -V | tail -1 || true)
echo "Highest existing tag: ${HIGHEST:-<none>}"
attempt=0
max=4
while :; do
attempt=$((attempt+1))
echo "Attempt #$attempt: set baseline '$NEW'"
# write version.json (no-op if unchanged)
nbgv set-version "$NEW"
git add version.json
if git diff --cached --quiet; then
echo "No change to version.json (already $NEW)."
else
git commit -m "$MSG: $NEW"
git push origin HEAD:${{ inputs.target_branch }}
fi
# compute candidate from NBGV after applying baseline
nbgv get-version --format json > v.json
CANDIDATE="$(jq -r .NuGetPackageVersion v.json)"
SIMPLE="$(jq -r .SimpleVersion v.json)"
PRE="$(jq -r .PrereleaseVersion v.json)"
TAG="v${CANDIDATE}"
echo "Candidate version: $CANDIDATE (Simple=$SIMPLE, Pre='$PRE')"
# If no tags exist yet, we're done.
if [[ -z "$HIGHEST" ]]; then
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "simple=$SIMPLE" >> "$GITHUB_OUTPUT"
echo "pre=$PRE" >> "$GITHUB_OUTPUT"
echo "nuget=$CANDIDATE" >> "$GITHUB_OUTPUT"
break
fi
# Require strictly greater than highest tag
if verlte "$CANDIDATE" "$HIGHEST"; then
echo "⚠️ $CANDIDATE <= highest tag $HIGHEST → bumping per branch policy…"
# Choose bump base as the max of current Simple vs highest pure (major.minor.patch)
HPURE="$(pure "$HIGHEST")"
parse "$SIMPLE"; cM=$MAJOR; cN=$MINOR; cP=$PATCH
parse "$HPURE"; hM=$MAJOR; hN=$MINOR; hP=$PATCH
# pick the larger (major,minor,patch) tuple
useH=0
if [[ $hM -gt $cM ]] || { [[ $hM -eq $cM ]] && [[ $hN -gt $cN || ( $hN -eq $cN && $hP -ge $cP ) ]]; }; then
useH=1
fi
if [[ "$BR" == "main" || "$BR" =~ ^v[0-9]+\.[0-9]+$ ]]; then
if [[ $useH -eq 1 ]]; then
NEW="${hM}.${hN}.$((hP+1))"
else
NEW="${cM}.${cN}.$((cP+1))"
fi
elif [[ "$BR" =~ ^hotfix/ ]]; then
if [[ $useH -eq 1 ]]; then
NEW="${hM}.${hN}.$((hP+1))-alpha"
else
NEW="${cM}.${cN}.$((cP+1))-alpha"
fi
else # feature branches
if [[ $useH -eq 1 ]]; then
NEW="${hM}.$((hN+1))-alpha"
else
NEW="${cM}.$((cN+1))-alpha"
fi
fi
echo "Auto-bumped baseline → $NEW"
[[ $attempt -lt $max ]] && continue || { echo "❌ Exhausted attempts."; exit 1; }
fi
# Candidate is strictly greater than any existing tag → done
echo "✅ Monotonic increase satisfied: $CANDIDATE > ${HIGHEST}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "simple=$SIMPLE" >> "$GITHUB_OUTPUT"
echo "pre=$PRE" >> "$GITHUB_OUTPUT"
echo "nuget=$CANDIDATE" >> "$GITHUB_OUTPUT"
break
done
- name: Final outputs for callers
id: final
run: |
echo "simple=${{ steps.ensure.outputs.simple }}" >> $GITHUB_OUTPUT
echo "pre=${{ steps.ensure.outputs.pre }}" >> $GITHUB_OUTPUT
echo "nuget=${{ steps.ensure.outputs.nuget }}" >> $GITHUB_OUTPUT
echo "tag=${{ steps.ensure.outputs.tag }}" >> $GITHUB_OUTPUT