-
Notifications
You must be signed in to change notification settings - Fork 53
225 lines (198 loc) · 8.67 KB
/
Copy pathcd.yml
File metadata and controls
225 lines (198 loc) · 8.67 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
name: CD
on:
workflow_dispatch:
push:
branches: [develop, main]
# Pre-release on develop is interruptible (newer commit supersedes the older
# build); a stable release on main must never be cancelled mid-run, hence the
# ref-conditional cancel-in-progress.
concurrency:
group: cd-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
permissions:
contents: write
pull-requests: write
jobs:
# ═══════════════════════════════════════════════
# DEVELOP PATH: Pre-release
# ═══════════════════════════════════════════════
pre-release:
if: >-
github.ref == 'refs/heads/develop'
|| (github.event_name == 'workflow_dispatch' && github.ref != 'refs/heads/main')
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true
- name: Compute next version from conventional commits
id: tag
run: |
# Latest stable tag follows the release-please format: icm-v0.10.42.
# The `^icm-v…$` anchor filters out any pre-release suffix
# (e.g. icm-v0.10.42-rc.1) without filtering on a bare dash —
# the icm- prefix itself contains one, which broke the original
# `grep -v '-'` borrowed from rtk-ai/rtk where tags are bare `vX.Y.Z`.
LATEST_TAG=$(git tag -l 'icm-v[0-9]*.[0-9]*.[0-9]*' --sort=-version:refname \
| grep -E '^icm-v[0-9]+\.[0-9]+\.[0-9]+$' | head -1)
if [ -z "$LATEST_TAG" ]; then
echo "::error::No stable icm-v* release tag found"
exit 1
fi
LATEST_VERSION="${LATEST_TAG#icm-v}"
echo "Latest stable: $LATEST_TAG"
COMMITS=$(git log "${LATEST_TAG}..HEAD" --format="%s")
HAS_BREAKING=$(echo "$COMMITS" | grep -cE '^[a-z]+(\(.+\))?!:' || true)
HAS_FEAT=$(echo "$COMMITS" | grep -cE '^feat(\(.+\))?:' || true)
HAS_FIX=$(echo "$COMMITS" | grep -cE '^fix(\(.+\))?:' || true)
echo "Commits since $LATEST_TAG: breaking=$HAS_BREAKING feat=$HAS_FEAT fix=$HAS_FIX"
# release-please-config.json sets bump-minor-pre-major +
# bump-patch-for-minor-pre-major, so for major=0:
# breaking -> minor bump
# feat -> patch bump
# fix -> patch bump
IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_VERSION"
if [ "$MAJOR" -eq 0 ]; then
if [ "$HAS_BREAKING" -gt 0 ]; then
MINOR=$((MINOR + 1)); PATCH=0
elif [ "$HAS_FEAT" -gt 0 ] || [ "$HAS_FIX" -gt 0 ]; then
PATCH=$((PATCH + 1))
else
PATCH=$((PATCH + 1))
fi
else
if [ "$HAS_BREAKING" -gt 0 ]; then
MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0
elif [ "$HAS_FEAT" -gt 0 ]; then
MINOR=$((MINOR + 1)); PATCH=0
else
PATCH=$((PATCH + 1))
fi
fi
VERSION="${MAJOR}.${MINOR}.${PATCH}"
TAG="icm-dev-v${VERSION}-rc.${{ github.run_number }}"
echo "Next version: $VERSION (from $LATEST_VERSION)"
echo "Pre-release tag: $TAG"
if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q .; then
echo "::error::Tag ${TAG} already exists"
exit 1
fi
echo "tag=$TAG" >> $GITHUB_OUTPUT
build-prerelease:
name: Build pre-release
needs: pre-release
if: needs.pre-release.outputs.tag != ''
uses: ./.github/workflows/release.yml
with:
tag: ${{ needs.pre-release.outputs.tag }}
prerelease: true
permissions:
contents: write
secrets: inherit
# ═══════════════════════════════════════════════
# MAIN PATH: Stable release via release-please
# ═══════════════════════════════════════════════
release-please:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.releases_created }}
tag_name: ${{ steps.release.outputs['crates/icm-cli--tag_name'] }}
steps:
# The default workflow token cannot open PRs in this org; mint a
# short-lived token from the configured GitHub App instead.
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: googleapis/release-please-action@v4
id: release
with:
token: ${{ steps.app-token.outputs.token }}
build-release:
name: Build and upload release assets
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
uses: ./.github/workflows/release.yml
with:
tag: ${{ needs.release-please.outputs.tag_name }}
prerelease: false
permissions:
contents: write
secrets: inherit
update-latest-tag:
name: Update 'latest' tag
needs: [release-please, build-release]
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Update latest tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -fa latest -m "Latest stable release (${{ needs.release-please.outputs.tag_name }})"
git push origin latest --force
# ═══════════════════════════════════════════════
# POST-RELEASE: back-merge release commits to develop
# ═══════════════════════════════════════════════
#
# release-please commits (version bumps + CHANGELOG) land only on main, so
# develop falls one commit behind on every release. Without this job the
# drift accumulates and the next develop -> main PR carries no-op churn.
back-merge-develop:
name: Back-merge main into develop
needs: [release-please, build-release]
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
# The default workflow token cannot open PRs in this org; mint a
# short-lived token from the configured GitHub App instead.
- uses: actions/create-github-app-token@v3
id: app-token
with:
app-id: ${{ secrets.APP_CLIENT_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: develop
token: ${{ steps.app-token.outputs.token }}
- name: Open back-merge PR
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
TAG: ${{ needs.release-please.outputs.tag_name }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
BRANCH="chore/back-merge-main-${TAG}"
git checkout -b "$BRANCH"
# Fast-forward when possible (release-please commits are linear on
# top of develop); fall back to a merge commit if develop diverged.
if ! git merge origin/main --ff-only 2>/dev/null; then
git merge origin/main --no-edit \
-m "chore: back-merge main into develop (${TAG})"
fi
git push origin "$BRANCH"
# Ensure the 'automated' label exists before referencing it: a missing
# label makes `gh pr create --label` fail and aborts the back-merge PR
# ("could not add label: 'automated' not found"). Idempotent.
gh label create automated \
--color ededed \
--description "Automated PR (back-merge / release tooling)" \
2>/dev/null || true
gh pr create \
--base develop \
--head "$BRANCH" \
--title "chore: sync release ${TAG} into develop" \
--body "Automated back-merge of release-please commits from \`main\` into \`develop\` so the next \`develop -> main\` PR stays free of release-commit drift." \
--label "automated"