-
Notifications
You must be signed in to change notification settings - Fork 0
198 lines (178 loc) · 8.04 KB
/
Copy pathprepare-release-pr.yaml
File metadata and controls
198 lines (178 loc) · 8.04 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
---
name: Prepare Release PR
on:
schedule:
- cron: '0 20 * * 0,3'
workflow_dispatch:
inputs:
force-release:
description: Force a release even when no open release PR exists
required: false
default: false
type: boolean
permissions:
contents: read
concurrency:
group: release
cancel-in-progress: false
jobs:
prepare-release:
name: Prepare Release PR
runs-on: ubuntu-latest
steps:
- id: get-app-token
name: Get GitHub App Installation Token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
app-id: ${{ secrets.APPLICATION_ID }}
private-key: ${{ secrets.APPLICATION_PRIVATE_KEY }}
owner: ${{ github.repository_owner}}
- id: discover-release-pr
name: Discover existing release PR
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ steps.get-app-token.outputs.token }}
run: |
PR_NUMBER=$(gh pr list --head next --base release --state open --json number --jq '.[0].number // empty')
echo "pr-number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
if [ -n "$PR_NUMBER" ]; then
echo 'has-open-pr=true' >> "$GITHUB_OUTPUT"
else
echo 'has-open-pr=false' >> "$GITHUB_OUTPUT"
fi
- id: git-user
name: Get GitHub App User ID and Setup Git user
env:
GH_TOKEN: ${{ steps.get-app-token.outputs.token }}
run: |
name="${{ steps.get-app-token.outputs.app-slug }}[bot]"
user_id=$(gh api "/users/${name}" --jq .id)
email="${user_id}+${name}@users.noreply.github.com"
echo "user-email=${email}" >> "$GITHUB_OUTPUT"
echo "user-name=${name}" >> "$GITHUB_OUTPUT"
git config --global user.email "${email}"
git config --global user.name "${name}"
- name: Checkout `release` branch
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
filter: blob:none
ref: release
show-progress: false
token: ${{ steps.get-app-token.outputs.token }}
- name: Reset to last release tag and merge `main`
run: |
set -euo pipefail
last_tag=$(git tag --list 'v*' --sort=-version:refname | { grep -vF '+harness' || true; } | head -1)
echo "Resetting to ${last_tag}"
git reset --hard "${last_tag}"
git fetch origin main
if ! git merge --no-ff -Xtheirs -m 'skip: merge main [skip release]' origin/main; then
# Typical trigger: rename/rename on dist/artifact-*.js (the bundle hash
# changes every commit on main). -Xtheirs handles content conflicts but
# cannot resolve rename/rename. Bias fully to main's tree on conflict —
# the release branch's purpose is to mirror main.
echo "Merge had unresolvable conflicts; taking main's tree verbatim."
git merge --abort
git checkout origin/main -- .
git add -A
git commit --no-verify -m 'skip: merge main [skip release] (conflict-resolved)'
fi
- name: Setup Node.js and Bun
uses: ./.github/actions/setup
- id: release-preview
name: Release Preview
run: node --experimental-strip-types --experimental-transform-types scripts/release/preview-next-release.ts --to HEAD
- if: steps.release-preview.outputs.next_version != ''
name: Push `next` branch
run: git push --no-verify --force-with-lease origin HEAD:next
- if: steps.release-preview.outputs.next_version != ''
name: Generate PR body
run: |
VERSION="${{ steps.release-preview.outputs.next_version }}"
last_tag=$(git tag --list 'v*' --sort=-version:refname | { grep -vF '+harness' || true; } | head -1)
COMMIT_LIST=$(git log "${last_tag}..HEAD" --format='* %s (%h)' --no-merges --max-count=50)
TIMESTAMP=$(date -u +"%Y-%m-%d %H:%M UTC")
cat > /tmp/pr-body.md <<EOF
## Pending Release: v${VERSION}
**Merge this PR with a merge commit to trigger a release.**
### Commits Since Last Release
EOF
echo "${COMMIT_LIST:-No new commits}" >> /tmp/pr-body.md
echo "" >> /tmp/pr-body.md
echo "---" >> /tmp/pr-body.md
echo "*Auto-generated by the release pipeline. Updated: ${TIMESTAMP}*" >> /tmp/pr-body.md
- if: steps.release-preview.outputs.next_version != ''
env:
GH_TOKEN: ${{ steps.get-app-token.outputs.token }}
id: upsert-release-pr
name: Create or Update Release PR
run: |
PR_NUMBER="${{ steps.discover-release-pr.outputs.pr-number }}"
VERSION="${{ steps.release-preview.outputs.next_version }}"
TITLE="chore(release): pending release v${VERSION}"
if [ -n "$PR_NUMBER" ]; then
gh pr edit "$PR_NUMBER" --title "$TITLE" --body-file /tmp/pr-body.md
else
gh pr create --head next --base release --title "$TITLE" --body-file /tmp/pr-body.md
PR_NUMBER=$(gh pr list --head next --base release --state open --json number --jq '.[0].number // empty')
fi
echo "pr-number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
- if: steps.release-preview.outputs.next_version != ''
env:
GH_TOKEN: ${{ steps.get-app-token.outputs.token }}
PR_NUMBER: ${{ steps.upsert-release-pr.outputs.pr-number }}
name: Wait for release PR mergeability
run: |
if [ -z "$PR_NUMBER" ]; then
echo 'Missing release PR number after upsert'
exit 1
fi
attempt=0
while [ "$attempt" -lt 10 ]; do
MERGE_STATE=$(gh pr view "$PR_NUMBER" --json mergeStateStatus --jq '.mergeStateStatus')
if [ "$MERGE_STATE" = 'DIRTY' ] || [ "$MERGE_STATE" = 'BLOCKED' ]; then
echo "Release PR is not mergeable: ${MERGE_STATE}"
exit 1
fi
if [ "$MERGE_STATE" = 'CLEAN' ] || [ "$MERGE_STATE" = 'HAS_HOOKS' ] || [ "$MERGE_STATE" = 'UNSTABLE' ]; then
exit 0
fi
sleep 2
attempt=$((attempt + 1))
done
echo 'Timed out waiting for release PR mergeability'
exit 1
- if: steps.release-preview.outputs.next_version == '' && steps.discover-release-pr.outputs.has-open-pr == 'true'
env:
GH_TOKEN: ${{ steps.get-app-token.outputs.token }}
PR_NUMBER: ${{ steps.discover-release-pr.outputs.pr-number }}
name: Close stale pending release PR
run: gh pr close "$PR_NUMBER" --comment "Closing stale pending release PR because the latest preview predicts no release."
- if: steps.release-preview.outputs.next_version == ''
name: Delete stale `next` branch
run: git push --no-verify origin --delete next || true
- id: merge-policy
if: steps.release-preview.outputs.next_version != ''
name: Decide whether to merge release PR now
run: |
SHOULD_MERGE=false
if [ "${{ github.event_name }}" = 'schedule' ]; then
SHOULD_MERGE=true
fi
if [ "${{ github.event_name }}" = 'workflow_dispatch' ] && [ "${{ github.event.inputs.force-release }}" = 'true' ]; then
SHOULD_MERGE=true
fi
if [ "${{ github.event_name }}" = 'workflow_dispatch' ] && [ "${{ steps.discover-release-pr.outputs.has-open-pr }}" = 'true' ]; then
SHOULD_MERGE=true
fi
echo "should-merge=${SHOULD_MERGE}" >> "$GITHUB_OUTPUT"
- if: steps.release-preview.outputs.next_version != '' && steps.merge-policy.outputs.should-merge == 'true'
env:
GH_TOKEN: ${{ steps.get-app-token.outputs.token }}
PR_NUMBER: ${{ steps.upsert-release-pr.outputs.pr-number }}
name: Merge release PR with merge commit
run: |
if [ -n "$PR_NUMBER" ]; then
gh pr merge "$PR_NUMBER" --merge --delete-branch=false
fi