-
Notifications
You must be signed in to change notification settings - Fork 6
279 lines (240 loc) · 10.6 KB
/
Copy pathrelease.yml
File metadata and controls
279 lines (240 loc) · 10.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
name: Build and Release
# Manual release flow (modeled on gruntwork-io/terragrunt's release.yml):
#
# 1. A maintainer creates a DRAFT GitHub release for the version, targeting a
# specific commit (not a branch). The draft holds the release notes.
# 2. They run this workflow via "Run workflow", passing that version.
# 3. `validate` confirms the draft exists, the tag is strict semver, and the
# draft targets a full commit SHA (so no new commits can slip in between
# draft creation and publishing).
# 4. The per-platform `build-*` jobs build and package from that exact commit.
# 5. `upload-assets` attaches the binaries + checksums to the draft release.
# 6. The maintainer publishes the draft from the Releases page when ready.
on:
workflow_dispatch:
inputs:
version:
description: "Version to build (must match an existing draft release, e.g. v1.0.2)"
required: true
type: string
permissions:
contents: write
jobs:
# Validate the release is an existing release with a valid semver tag,
# targeting an explicit commit SHA.
validate:
name: Validate Release
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
ref: ${{ steps.resolve.outputs.ref }}
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Resolve version and ref
id: resolve
env:
INPUT_VERSION: ${{ inputs.version }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: .github/scripts/release/resolve-version-ref.sh
- name: Validate semver
env:
VERSION: ${{ steps.resolve.outputs.version }}
run: .github/scripts/release/validate-semver.sh "$VERSION"
- name: Enforce commit SHA target
env:
REF: ${{ steps.resolve.outputs.ref }}
run: .github/scripts/release/enforce-commit-sha.sh "$REF"
build-macos:
name: Build macOS
needs: validate
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.validate.outputs.ref }}
- uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build Electron app
run: just build
# Build both arches in a single job, one arch at a time. This must NOT be
# a matrix and must NOT be a single `electron-builder --mac` that emits
# both arches at once, because:
# 1. `resources/bin/boilerplate` is a single, arch-specific binary
# (fetch-boilerplate deletes the alternate). Each arch must be
# packaged with its matching boilerplate, so we re-fetch between
# passes — a both-at-once build would bundle the wrong-arch CLI.
# 2. A matrix had each job build BOTH arches anyway (the package.json
# `mac.target` arch arrays override the `--arm64`/`--x64` flag), so
# every artifact was produced twice under two artifact bundles. The
# create-release `download-artifact ... merge-multiple: true` then
# extracted the colliding filenames concurrently into one dir, which
# tore the bytes of whichever copy lost the race and shipped a DMG
# that failed `hdiutil verify` ("disk not readable"). One job, one
# copy of each artifact, one upload bundle = no collision.
# The `arch` arrays were removed from package.json `mac.target` so the
# `--arm64`/`--x64` flag below actually restricts each pass to one arch.
- name: Package for macOS
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
APPLE_ID: ${{ secrets.MACOS_AC_LOGIN }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.MACOS_AC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.MACOS_AC_PROVIDER }}
run: |
set -euo pipefail
# Only enable code signing when a certificate is actually configured.
# An empty CSC_LINK is treated by electron-builder as a file path that
# resolves to the project root, failing with "<projectDir> not a file".
if [ -n "$MACOS_CERTIFICATE" ]; then
export CSC_LINK="$MACOS_CERTIFICATE"
export CSC_KEY_PASSWORD="$MACOS_CERTIFICATE_PASSWORD"
else
echo "No MACOS_CERTIFICATE secret set; building an unsigned macOS app."
export CSC_IDENTITY_AUTO_DISCOVERY=false
fi
# arm64 pass (boilerplate arch arm64). `--publish never`: the
# upload-assets job is the sole uploader; electron-builder must not
# also push to the release (its implicit tag-publish caused duplicate,
# racing uploads of identically named assets).
just fetch-boilerplate darwin arm64
mise x node -- npx electron-builder --mac --arm64 --publish never
# Preserve the arm64 auto-update manifest: the x64 pass below rewrites
# latest-mac.yml from scratch with only its own artifacts (electron-
# builder only merges manifests when publishing, and we use
# `--publish never`), so without this the arm64 entries are lost.
cp out/latest-mac.yml out/latest-mac-arm64.yml
# x64 pass (boilerplate arch amd64). Cross-built on the arm64 runner.
just fetch-boilerplate darwin amd64
mise x node -- npx electron-builder --mac --x64 --publish never
# Merge the arm64 entries back into the now-x64-only latest-mac.yml so
# the published manifest lists both arches. electron-updater picks the
# arch by scanning files[] for an "arm64" url; an arch-incomplete
# manifest serves arm64 Macs the x64 build (which then runs under
# Rosetta). arm64 first so the legacy top-level path/sha512 match a
# single both-arch build.
mise x node -- node .github/scripts/release/merge-mac-latest.mjs \
out/latest-mac.yml out/latest-mac-arm64.yml out/latest-mac.yml
rm -f out/latest-mac-arm64.yml
# Fail the build loudly if any artifact is corrupt, instead of shipping a
# bad file with a matching checksum (the original failure mode).
- name: Verify macOS artifacts
run: |
set -euo pipefail
for f in out/*.dmg; do
echo "Verifying $f"
hdiutil verify "$f"
done
for z in out/*.zip; do
echo "Testing $z"
unzip -t "$z" >/dev/null
done
- name: Upload macOS artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: macos
path: |
out/*.dmg
out/*.zip
out/latest-mac*.yml
retention-days: 7
build-linux:
name: Build Linux
needs: validate
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.validate.outputs.ref }}
- uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build Electron app
run: just build
- name: Fetch boilerplate
run: just fetch-boilerplate linux amd64
# `--publish never`: the upload-assets job is the sole uploader.
- name: Package for Linux
run: mise x node -- npx electron-builder --linux --publish never
- name: Upload Linux artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux-x64
path: |
out/*.AppImage
out/*.deb
out/latest-linux*.yml
retention-days: 7
build-windows:
name: Build Windows
needs: validate
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.validate.outputs.ref }}
- uses: jdx/mise-action@dba19683ed58901619b14f395a24841710cb4925 # v4.1.0
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build Electron app
run: just build
- name: Fetch boilerplate
shell: bash
run: just fetch-boilerplate windows amd64
# `--publish never`: the upload-assets job is the sole uploader.
- name: Package for Windows
run: mise x node -- npx electron-builder --win --publish never
- name: Upload Windows artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-x64
path: |
out/*.exe
out/latest*.yml
retention-days: 7
# Attach the built binaries + checksums to the existing draft release.
upload-assets:
name: Upload Release Assets
needs: [validate, build-macos, build-linux, build-windows]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ needs.validate.outputs.ref }}
- name: Check if release exists
id: check_release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.validate.outputs.version }}
run: .github/scripts/release/check-release-exists.sh
- name: Download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts/
merge-multiple: true
- name: Generate checksums
run: .github/scripts/release/generate-checksums.sh artifacts
- name: Upload assets to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.validate.outputs.version }}
CLOBBER: "true"
run: .github/scripts/release/upload-assets.sh artifacts
- name: Verify all assets uploaded
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ needs.validate.outputs.version }}
CLOBBER: "true"
run: .github/scripts/release/verify-assets-uploaded.sh artifacts
- name: Upload summary
if: always()
env:
VERSION: ${{ needs.validate.outputs.version }}
RELEASE_ID: ${{ steps.check_release.outputs.release_id }}
IS_DRAFT: ${{ steps.check_release.outputs.is_draft }}
run: .github/scripts/release/generate-upload-summary.sh artifacts