-
Notifications
You must be signed in to change notification settings - Fork 14
193 lines (168 loc) · 6.7 KB
/
auto-release.yml
File metadata and controls
193 lines (168 loc) · 6.7 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
name: Auto Release
# Sensor: when unreleased commits on main trip a threshold, either auto-tag
# (patch fast lane) or open a release-ready issue (feat threshold).
# See docs/HARNESS.md.
#
# Triggers (any one fires):
# - >=5 feat/fix commits since last stable tag
# - >=7 days since last stable tag AND new commits exist
# - any fix: present (patch fast lane)
#
# Version bump:
# - any feat: present -> minor (X.Y+1.0) — opens issue, does NOT tag
# - else -> patch (X.Y.Z+1) — auto-tags + dispatches release.yml
#
# Why the split: feat: changes carry more risk than fix: patches and benefit
# from a human running the local Tart VM e2e suite before tag. The issue is
# a nudge, not a hard gate — a human can tag without running it.
#
# Why we dispatch release.yml on patch instead of relying on the tag-push
# trigger: GitHub deliberately suppresses workflow events fired by
# GITHUB_TOKEN, so a tag pushed from here would NOT start release.yml.
# We push the tag and then explicitly
# `gh workflow run release.yml -f version=<tag>`.
on:
push:
branches: [main]
workflow_dispatch:
inputs:
dry_run:
description: 'Dry-run: compute decision and exit without tagging.'
type: boolean
default: true
concurrency:
group: auto-release
cancel-in-progress: false
permissions:
contents: write
actions: write
issues: write
jobs:
evaluate:
name: auto-release sensor
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch tags
run: git fetch --tags --force
- name: Decide
id: decide
env:
DRY_RUN: ${{ inputs.dry_run }}
run: |
set -euo pipefail
last_tag=$(git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname \
| grep -Ev -- '-(rc|beta|alpha)' \
| head -n1 || true)
if [ -z "$last_tag" ]; then
echo "::warning::no stable tag found — skipping auto-release."
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "last_tag=$last_tag"
commits=$(git log --format='%s' "${last_tag}..HEAD")
if [ -z "$commits" ]; then
echo "no commits since $last_tag — skip"
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
feat_count=$(printf '%s\n' "$commits" | grep -cE '^feat(\(.+\))?!?:' || true)
fix_count=$(printf '%s\n' "$commits" | grep -cE '^fix(\(.+\))?!?:' || true)
combined=$((feat_count + fix_count))
last_ts=$(git log -1 --format=%ct "$last_tag")
now=$(date -u +%s)
days=$(( (now - last_ts) / 86400 ))
echo "feat=$feat_count fix=$fix_count combined=$combined days_since=$days"
reason=""
if [ "$combined" -ge 5 ]; then
reason="${combined} feat/fix commits since ${last_tag}"
elif [ "$days" -ge 7 ]; then
reason="${days} days since ${last_tag} with new commits"
elif [ "$fix_count" -ge 1 ]; then
reason="fix present (patch fast lane)"
else
echo "no threshold tripped — skip"
echo "release=false" >> "$GITHUB_OUTPUT"
exit 0
fi
IFS='.' read -r major minor patch <<<"${last_tag#v}"
if [ "$feat_count" -ge 1 ]; then
minor=$((minor + 1))
patch=0
bump="minor"
else
patch=$((patch + 1))
bump="patch"
fi
new_tag="v${major}.${minor}.${patch}"
echo "decision: release ${new_tag} (${bump}) — ${reason}"
{
echo "release=true"
echo "new_tag=${new_tag}"
echo "bump=${bump}"
echo "reason=${reason}"
} >> "$GITHUB_OUTPUT"
if [ "${DRY_RUN:-false}" = "true" ]; then
echo "::notice::DRY_RUN — would tag ${new_tag} (${reason})"
echo "dry_run=true" >> "$GITHUB_OUTPUT"
fi
- name: Tag and dispatch release (patch fast lane)
if: |
steps.decide.outputs.release == 'true'
&& steps.decide.outputs.dry_run != 'true'
&& steps.decide.outputs.bump == 'patch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEW_TAG: ${{ steps.decide.outputs.new_tag }}
REASON: ${{ steps.decide.outputs.reason }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "$NEW_TAG" -m "Auto-release: $REASON"
git push origin "$NEW_TAG"
gh workflow run release.yml -f version="$NEW_TAG"
echo "::notice::tagged $NEW_TAG and dispatched release.yml — $REASON"
- name: Open release-ready issue (feat threshold)
if: |
steps.decide.outputs.release == 'true'
&& steps.decide.outputs.dry_run != 'true'
&& steps.decide.outputs.bump == 'minor'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NEW_TAG: ${{ steps.decide.outputs.new_tag }}
REASON: ${{ steps.decide.outputs.reason }}
run: |
set -euo pipefail
# If an open release-ready issue already exists for this tag, skip
# (we don't want to spam the queue every push to main).
existing=$(gh issue list \
--label release-ready \
--state open \
--search "in:title ${NEW_TAG}" \
--json number --jq '.[0].number' || true)
if [ -n "$existing" ]; then
echo "::notice::release-ready issue #${existing} already open for ${NEW_TAG} — skipping"
exit 0
fi
body=$(cat <<EOF
Auto-release sensor wants to cut **${NEW_TAG}** (minor bump).
**Why:** ${REASON}
Because this release includes \`feat:\` changes, the sensor did
**not** auto-tag. Run the destructive e2e suite locally, then
cut the release manually:
- [ ] L4 CI (\`vm-e2e-spike.yml\`) is green on the latest commit on \`main\`
- [ ] sanity-check the curl|bash smoke and cli-compat results in the most recent test.yml run on main
- [ ] \`git tag -a ${NEW_TAG} -m "..."\` and \`git push origin ${NEW_TAG}\`
- [ ] close this issue
L4 CI is not yet a hard merge gate, but \`feat:\` changes carry
more risk than \`fix:\` patches — verify it before tagging.
EOF
)
gh issue create \
--title "Release ready: ${NEW_TAG}" \
--label release-ready \
--body "$body"
echo "::notice::opened release-ready issue for ${NEW_TAG} — ${REASON}"