-
Notifications
You must be signed in to change notification settings - Fork 232
186 lines (168 loc) · 7.02 KB
/
Copy pathprepare-release.yml
File metadata and controls
186 lines (168 loc) · 7.02 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
name: Prepare release PR
on:
workflow_dispatch:
inputs:
release_branch:
description: 'Release branch (must already exist, format: release/X.Y.Z)'
required: true
type: string
dry_run:
description: 'Print the planned commit and PR body without pushing or opening a PR'
required: false
type: boolean
default: false
permissions:
contents: write
pull-requests: write
jobs:
prepare:
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BASE_BRANCH: master
# Workflow inputs are exposed as environment variables and referenced as
# "$RELEASE_BRANCH" in run: blocks, never interpolated as ${{ }} into an
# inline script. Direct interpolation splices the raw value into the shell
# before it runs, which is a script-injection sink (see ST-482).
RELEASE_BRANCH: ${{ inputs.release_branch }}
DRY_RUN: ${{ inputs.dry_run }}
steps:
- name: Validate inputs
id: validate
run: |
set -euo pipefail
if [[ ! "$RELEASE_BRANCH" =~ ^release/([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
echo "::error::release_branch must match 'release/X.Y.Z' (got: $RELEASE_BRANCH)"
exit 1
fi
version="${BASH_REMATCH[1]}"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "release_date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
- name: Checkout release branch
uses: actions/checkout@v4
with:
ref: ${{ inputs.release_branch }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Use Node.js 22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Configure git
run: |
git config user.name 'snowplow-ci'
git config user.email 'ci@snowplowanalytics.com'
- name: Verify branch state
id: state
env:
VERSION: ${{ steps.validate.outputs.version }}
run: |
set -euo pipefail
git fetch origin "$BASE_BRANCH"
# Idempotent re-run: if HEAD is already "Prepare for X.Y.Z release",
# skip the bump+commit and only refresh the PR body.
head_subject="$(git log -1 --pretty=%s)"
if [[ "$head_subject" == "Prepare for $VERSION release" ]]; then
echo "Branch HEAD is already the prepare-release commit; will skip bump+commit and only refresh the PR."
echo "already_prepared=true" >> "$GITHUB_OUTPUT"
else
echo "already_prepared=false" >> "$GITHUB_OUTPUT"
fi
- name: Collect commits since previous release
id: commits
run: |
set -euo pipefail
# Previous tag = highest semver tag reachable from BASE_BRANCH.
prev_tag="$(git tag --merged "origin/$BASE_BRANCH" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1 || true)"
if [[ -z "$prev_tag" ]]; then
echo "::error::Could not determine previous release tag"
exit 1
fi
echo "prev_tag=$prev_tag" >> "$GITHUB_OUTPUT"
# Commits on the release branch since prev_tag. classify-commits.sh
# drops the commits the JS publish pipeline creates automatically
# ("Bump versions [skip ci]", "Update changelogs [skip ci]",
# "Applying documentation updates.") along with other chores.
git log --no-merges "$prev_tag..HEAD" --pretty='%h %s' > commits-raw.txt || true
git log --merges "$prev_tag..HEAD" --pretty='%h %s' >> commits-raw.txt || true
# De-dup by short sha, preserve order
awk '!seen[$1]++' commits-raw.txt > commits.txt
echo "Commits since $prev_tag:"
cat commits.txt
- name: Annotate commits with author + external flag
env:
REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
: > commits-annotated.txt
while read -r line; do
[[ -z "$line" ]] && continue
sha="$(echo "$line" | awk '{print $1}')"
subject="$(echo "$line" | cut -d' ' -f2-)"
pr_num="$(echo "$subject" | grep -oE '#[0-9]+' | head -1 | tr -d '#' || true)"
login=""
external="false"
if [[ -n "$pr_num" ]]; then
if json="$(gh api "repos/$REPOSITORY/pulls/$pr_num" 2>/dev/null)"; then
login="$(echo "$json" | jq -r '.user.login // ""')"
assoc="$(echo "$json" | jq -r '.author_association // ""')"
case "$assoc" in
MEMBER|OWNER|COLLABORATOR) external="false" ;;
*) external="true" ;;
esac
fi
fi
echo "$sha $subject -- author=$login external=$external" >> commits-annotated.txt
done < commits.txt
echo "Annotated commits:"
cat commits-annotated.txt
- name: Set nextBump in version-policies.json
if: steps.state.outputs.already_prepared == 'false'
env:
VERSION: ${{ steps.validate.outputs.version }}
run: node .github/scripts/prepare-release.js "$VERSION"
- name: Commit "Prepare for release"
if: steps.state.outputs.already_prepared == 'false' && inputs.dry_run == false
env:
VERSION: ${{ steps.validate.outputs.version }}
run: |
set -euo pipefail
git add common/config/rush/version-policies.json
# If the script was a no-op (nextBump already correct), skip the commit.
if git diff --cached --quiet; then
echo "No staged changes; nextBump was already set correctly. Skipping commit."
else
git commit -m "Prepare for $VERSION release"
git push origin "$RELEASE_BRANCH"
fi
- name: Show planned changes (dry run)
if: inputs.dry_run == true
run: |
echo "=== Diff that would be committed ==="
git --no-pager diff
echo "===================================="
- name: Generate PR body
run: |
set -euo pipefail
./.github/scripts/classify-commits.sh < commits-annotated.txt > classified.tsv
./.github/scripts/format-pr-body.sh < classified.tsv > pr-body.md
echo "=== Generated PR body ==="
cat pr-body.md
echo "========================="
- name: Open or update release PR
if: inputs.dry_run == false
env:
VERSION: ${{ steps.validate.outputs.version }}
run: |
set -euo pipefail
existing="$(gh pr list --head "$RELEASE_BRANCH" --base "$BASE_BRANCH" --state open --json number --jq '.[0].number' || true)"
if [[ -n "$existing" ]]; then
gh pr edit "$existing" --title "Release/$VERSION" --body-file pr-body.md
echo "Updated existing PR #$existing"
else
gh pr create \
--base "$BASE_BRANCH" \
--head "$RELEASE_BRANCH" \
--title "Release/$VERSION" \
--body-file pr-body.md
fi