-
Notifications
You must be signed in to change notification settings - Fork 0
315 lines (283 loc) · 11.6 KB
/
patch-release.yml
File metadata and controls
315 lines (283 loc) · 11.6 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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
name: Patch Release (re-upload assets to an existing tag)
# Use this workflow when you want to ship a small fix to an already-published
# release WITHOUT cutting a new version. It:
#
# 1. Rebuilds all five platform binaries from the current `main` HEAD.
# 2. Force-moves the existing tag forward to that HEAD so the source the
# tag points at matches the binaries.
# 3. Re-uploads the binary archives + install scripts to the existing
# release, overwriting the old assets.
# 4. Leaves the release page's title, body, contributors, and "Full
# Changelog" link untouched.
#
# Refuses to run if Cargo.toml has been bumped past the patched tag — that's a
# real release, not a patch, so use `.github/workflows/release.yml` instead.
#
# This force-moves a published tag. Users who already downloaded the old
# binary are unaffected (they keep what they have); users who install fresh
# get the patched build.
on:
workflow_dispatch:
inputs:
version:
description: 'Existing tag to amend (e.g., v0.1.0)'
required: true
type: string
patch_note:
description: 'Optional markdown bullet to prepend under "## 🩹 Patches". Leave blank to skip body edit.'
required: false
type: string
default: ''
# Don't race patches on the same tag. Also don't race with a regular release
# of the same tag (concurrency groups share the same name shape).
concurrency:
group: release-${{ inputs.version }}
cancel-in-progress: false
permissions:
contents: write
env:
CARGO_TERM_COLOR: always
jobs:
# ── Preflight ───────────────────────────────────────────────────────
preflight:
runs-on: ubuntu-latest
name: Preflight checks
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Must be on main branch
run: |
if [[ "${{ github.ref }}" != "refs/heads/main" ]]; then
echo "::error::Patch releases must be triggered from the main branch (got ${{ github.ref }})"
exit 1
fi
- name: Target release must already exist
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if ! gh release view "${{ inputs.version }}" \
--repo "${{ github.repository }}" >/dev/null 2>&1; then
echo "::error::Release ${{ inputs.version }} does not exist."
echo "::error::To cut a brand-new release, use the regular Release workflow instead."
exit 1
fi
- name: Cargo.toml version must still match the patched tag
run: |
CARGO_VERSION=$(grep '^version' src-rust/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
TAG_VERSION="${{ inputs.version }}"
TAG_VERSION="${TAG_VERSION#v}" # strip leading v
if [[ "$CARGO_VERSION" != "$TAG_VERSION" ]]; then
echo "::error::Cargo.toml ($CARGO_VERSION) does not match patched tag ($TAG_VERSION)."
echo "::error::Patch releases must not bump the version — use the regular Release workflow if you intend to ship a new version."
exit 1
fi
echo "Version still pinned at $CARGO_VERSION — safe to patch in place."
# ── Build matrix ────────────────────────────────────────────────────
# Mirrors release.yml exactly — keep these two job specs in sync.
build:
needs: preflight
strategy:
fail-fast: true
matrix:
include:
- target: x86_64-pc-windows-msvc
os: windows-latest
artifact: coven-code-windows-x86_64
ext: .exe
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
artifact: coven-code-linux-x86_64
ext: ""
- target: aarch64-unknown-linux-gnu
os: ubuntu-latest
artifact: coven-code-linux-aarch64
ext: ""
cross: true
- target: x86_64-apple-darwin
os: macos-latest
artifact: coven-code-macos-x86_64
ext: ""
- target: aarch64-apple-darwin
os: macos-latest
artifact: coven-code-macos-aarch64
ext: ""
runs-on: ${{ matrix.os }}
name: Build ${{ matrix.artifact }}
steps:
- uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Install Linux system dependencies
if: runner.os == 'Linux' && !matrix.cross
run: |
sudo apt-get update
sudo apt-get install -y libasound2-dev libssl-dev pkg-config
- name: Install cross (aarch64-linux)
if: matrix.cross
run: cargo install cross --git https://github.com/cross-rs/cross
- name: Cache cargo registry & build
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
src-rust/target
key: ${{ matrix.target }}-cargo-${{ hashFiles('src-rust/Cargo.lock') }}
restore-keys: ${{ matrix.target }}-cargo-
- name: Build release binary (native)
if: ${{ !matrix.cross }}
working-directory: src-rust
run: cargo build --release --locked --package claurst --target ${{ matrix.target }}
- name: Create Cross.toml for cross-compilation
if: matrix.cross
working-directory: src-rust
run: |
cat > Cross.toml << 'EOF'
[target.aarch64-unknown-linux-gnu]
pre-build = [
"dpkg --add-architecture $CROSS_DEB_ARCH",
"apt-get update",
"apt-get install -y pkg-config libssl-dev:$CROSS_DEB_ARCH libasound2-dev:$CROSS_DEB_ARCH"
]
EOF
- name: Build release binary (cross)
if: matrix.cross
working-directory: src-rust
run: cross build --release --locked --package claurst --target ${{ matrix.target }}
- name: Verify binary exists
shell: bash
run: |
BINARY="src-rust/target/${{ matrix.target }}/release/coven-code${{ matrix.ext }}"
if [[ ! -f "$BINARY" ]]; then
echo "::error::Binary not found at $BINARY"
exit 1
fi
ls -lh "$BINARY"
- name: Stage binary for packaging
shell: bash
run: |
mkdir -p "stage/${{ matrix.artifact }}"
cp "src-rust/target/${{ matrix.target }}/release/coven-code${{ matrix.ext }}" \
"stage/${{ matrix.artifact }}/coven-code${{ matrix.ext }}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: stage/${{ matrix.artifact }}/coven-code${{ matrix.ext }}
# ── Re-upload assets to the existing release ────────────────────────
patch:
needs: build
runs-on: ubuntu-latest
name: Replace release assets
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Verify all expected assets exist
run: |
EXPECTED=(
coven-code-windows-x86_64
coven-code-linux-x86_64
coven-code-linux-aarch64
coven-code-macos-x86_64
coven-code-macos-aarch64
)
MISSING=()
for name in "${EXPECTED[@]}"; do
if [[ ! -d "artifacts/$name" ]]; then
MISSING+=("$name")
fi
done
if [[ ${#MISSING[@]} -gt 0 ]]; then
echo "::error::Missing artifacts: ${MISSING[*]}"
exit 1
fi
echo "All 5 artifacts present."
- name: Prepare release archives
run: |
mkdir -p release
for dir in artifacts/*/; do
name=$(basename "$dir")
binary=$(find "$dir" -maxdepth 1 -type f | head -1)
if [[ -z "$binary" ]]; then
echo "::error::No file in $dir"
exit 1
fi
if [[ "$binary" == *.exe ]]; then
(cd "$dir" && zip "../../release/${name}.zip" "$(basename "$binary")")
else
chmod +x "$binary"
tar -czf "release/${name}.tar.gz" -C "$dir" "$(basename "$binary")"
fi
done
# Restage install scripts in case they changed since the original release.
if [[ -f install.sh ]]; then
cp install.sh release/install.sh
fi
if [[ -f install.ps1 ]]; then
cp install.ps1 release/install.ps1
fi
echo "Patched release assets:"
ls -lh release/
# Force-move the tag to the current HEAD so that source pinned by the
# tag matches the binaries we're about to upload. Without this, the
# tag still points at the original commit and the assets diverge from
# the source — confusing for anyone running `git checkout v0.1.0`.
- name: Move tag to patched HEAD
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -f "${{ inputs.version }}" "${{ github.sha }}"
git push -f origin "refs/tags/${{ inputs.version }}"
echo "Tag ${{ inputs.version }} now points at ${{ github.sha }}."
# `gh release upload --clobber` overwrites assets one at a time without
# touching the release body, name, draft state, or prerelease flag.
# That's exactly what we want — the release page reads the same; only
# the download links serve fresh bytes.
- name: Replace release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
for f in release/*; do
echo "→ Uploading $(basename "$f")"
done
gh release upload "${{ inputs.version }}" release/* \
--repo "${{ github.repository }}" \
--clobber
# Prepend the patch bullet to the release body, idempotent: if a
# `## 🩹 Patches` section already exists, subsequent bullets stack into
# the same list at the top instead of creating duplicate sections.
# Skipped when no patch_note was provided (e.g. manual asset-only
# re-upload from the Actions UI).
- name: Append patch note to release body
if: inputs.patch_note != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
TAG: ${{ inputs.version }}
PATCH_BULLET: ${{ inputs.patch_note }}
run: python3 scripts/append-patch-note.py
- name: Summary
run: |
{
echo "## ✅ Patched ${{ inputs.version }} in place"
echo
echo "- Tag force-moved to commit \`${{ github.sha }}\`."
echo "- 5 binary archives + install scripts rebuilt and re-uploaded."
if [[ -n "${{ inputs.patch_note }}" ]]; then
echo "- Release body received a new bullet under \`## 🩹 Patches\` at the top."
else
echo "- Release title, body, contributors, and Full Changelog link unchanged."
fi
echo
echo "Anyone who installs fresh from \`releases/latest/download/…\` will pick up the patched build."
echo "Existing installs are **not** auto-updated — users must reinstall or run \`coven-code upgrade --force\`."
} >> "$GITHUB_STEP_SUMMARY"