-
Notifications
You must be signed in to change notification settings - Fork 0
306 lines (277 loc) · 13.9 KB
/
Copy pathrelease.yml
File metadata and controls
306 lines (277 loc) · 13.9 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
name: Release
on:
push:
branches: [main]
workflow_dispatch:
inputs:
tag:
description: "Existing release tag to (re)build & attach binaries to, e.g. v1.0.0. Leave blank for the normal Changesets release flow."
required: false
type: string
# Changesets needs to push the version commit/tag and open the version PR;
# the binary job needs to upload an asset to the release.
permissions:
contents: write
pull-requests: write
# Don't run two releases at once, but let an in-progress one finish.
concurrency:
group: release
cancel-in-progress: false
jobs:
# 1. Changesets: open/update the "Version Packages" PR, or — once that PR is
# merged — tag the new version and create the GitHub Release.
release:
runs-on: ubuntu-latest
outputs:
published: ${{ steps.changesets.outputs.published }}
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 24
- name: Install release tooling
# Only Changesets is needed here; skip zero-native's postinstall (the
# native CLI download) — the binary jobs do a full install for that.
run: npm install --ignore-scripts
- name: Create version PR or tag a release
id: changesets
uses: changesets/action@v1
with:
version: npm run version
publish: npm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 2. Build the macOS app and attach it to the release Changesets just created.
binary-macos:
needs: release
# Normal flow: only when Changesets published a new version. Manual flow:
# a workflow_dispatch with a `tag` input rebuilds/attaches binaries to that
# existing release without needing a publish.
if: ${{ needs.release.outputs.published == 'true' || inputs.tag != '' }}
runs-on: macos-latest
# Signing + notarization secrets. When unset, the signing steps below are
# skipped and the build ships unsigned (first launch needs right-click →
# Open). Set all of them to ship a signed, notarized .dmg with no Gatekeeper
# prompt. See "Code signing" in README.md for how to obtain each.
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
APPLE_NOTARY_KEY: ${{ secrets.APPLE_NOTARY_KEY }}
APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
APPLE_NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
- name: Install dependencies (zero-native framework + CLI)
run: |
npm install
echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH"
- name: Resolve the released version
id: ver
run: |
# Manual dispatch with a tag attaches to that existing release;
# otherwise use the version Changesets just published. Changesets tags
# a single-package repo as v<version> (only monorepos use
# <name>@<version>), so the normal release tag is "v$VER".
if [ -n "${{ github.event.inputs.tag }}" ]; then
TAG="${{ github.event.inputs.tag }}"
VER="${TAG#v}"
else
VER=$(echo '${{ needs.release.outputs.publishedPackages }}' | jq -r '.[0].version')
TAG="v$VER"
fi
echo "version=$VER" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Build & package the macOS app
env:
# Embedded into the frontend by Next during `zig build` (which runs
# npm run build); tags desktop telemetry as platform=macos.
NEXT_PUBLIC_GA_ID: ${{ secrets.GA_MEASUREMENT_ID }}
run: |
zig build package \
-Doptimize=ReleaseFast \
-Dpackage-target=macos
- name: Import Developer ID certificate
if: ${{ env.MACOS_CERTIFICATE != '' }}
run: |
KEYCHAIN="$RUNNER_TEMP/signing.keychain-db"
KEYCHAIN_PWD="$(openssl rand -base64 24)"
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
security set-keychain-settings -lut 3600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
echo "$MACOS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" \
-P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null
# Put the temp keychain on the search list so codesign finds the identity.
security list-keychains -d user -s "$KEYCHAIN" \
$(security list-keychains -d user | sed s/\"//g)
rm -f "$RUNNER_TEMP/cert.p12"
# Diagnostics: list every imported identity (any policy) with its name —
# the name reveals the cert TYPE, which must be "Developer ID Application"
# (an Installer/Development cert won't satisfy the codesigning policy).
echo "=== All identities in the temp keychain ==="
security find-identity "$KEYCHAIN" || true
echo "=== Imported certificate (subject + validity window) ==="
security find-certificate -a -p "$KEYCHAIN" \
| openssl x509 -noout -subject -dates 2>/dev/null || true
# Gate on what codesign actually sees: a VALID codesigning identity on the
# full search list (temp keychain for the leaf+key, system keychain for the
# Apple intermediate/root the chain needs). Checking the temp keychain alone
# reports a false "0 valid" because the chain can't be built there.
echo "=== Valid code-signing identities on the search list (codesign's view) ==="
security find-identity -v -p codesigning
if [ "$(security find-identity -v -p codesigning | grep -c '"')" -eq 0 ]; then
echo "::error::No VALID code-signing identity. From the dump above, confirm the cert is a 'Developer ID Application' cert (not Installer/Development), is unexpired, and chains to Apple's intermediate. If the identity is listed but invalid, the Developer ID intermediate may be missing from the runner."
exit 1
fi
# Hand the keychain path to the signing steps so they target it explicitly.
echo "KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV"
- name: Sign the app (Developer ID + Hardened Runtime)
if: ${{ env.MACOS_CERTIFICATE != '' }}
run: |
APP="$(ls -d zig-out/package/*.app | head -n1)"
codesign --force --deep --options runtime --timestamp \
--keychain "$KEYCHAIN" \
--entitlements assets/keyparty.entitlements \
--sign "$MACOS_SIGN_IDENTITY" "$APP"
codesign --verify --strict --verbose=2 "$APP"
- name: Build a .dmg (branded, drag-to-Applications)
# Same script used for local testing (scripts/make-dmg.sh), so the
# install window here is identical to what you preview on a Mac. It
# picks up the already-signed .app from zig-out/package, lays it out
# over the branded backdrop next to an Applications drop-link, and
# sets the volume icon. Signing/notarization happens in the next step.
run: scripts/make-dmg.sh
- name: Sign & notarize the .dmg
if: ${{ env.MACOS_CERTIFICATE != '' }}
run: |
codesign --force --timestamp --keychain "$KEYCHAIN" \
--sign "$MACOS_SIGN_IDENTITY" "KeyParty.dmg"
if [ -n "$APPLE_NOTARY_KEY" ]; then
echo "$APPLE_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8"
# --wait blocks until Apple finishes; staple embeds the ticket so the
# .dmg launches offline with no Gatekeeper prompt.
xcrun notarytool submit "KeyParty.dmg" \
--key "$RUNNER_TEMP/notary.p8" \
--key-id "$APPLE_NOTARY_KEY_ID" \
--issuer "$APPLE_NOTARY_ISSUER" \
--wait
xcrun stapler staple "KeyParty.dmg"
rm -f "$RUNNER_TEMP/notary.p8"
else
echo "::warning::APPLE_NOTARY_KEY unset — .dmg is signed but NOT notarized; Gatekeeper will still warn."
fi
- name: Attach the .dmg to the release
# Version-less name -> https://github.com/<owner>/<repo>/releases/latest/download/KeyParty.dmg
# is a stable link the web build can point at.
run: gh release upload "${{ steps.ver.outputs.tag }}" "KeyParty.dmg" --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 3. Build the Windows app (full-screen kiosk WebView2 host) and attach it.
binary-windows:
needs: release
# Normal flow: only when Changesets published a new version. Manual flow:
# a workflow_dispatch with a `tag` input rebuilds/attaches binaries to that
# existing release without needing a publish.
if: ${{ needs.release.outputs.published == 'true' || inputs.tag != '' }}
runs-on: windows-latest
env:
# Where the WebView2 SDK headers land (NuGet, version stripped).
NUGET_DIR: packages/Microsoft.Web.WebView2
# Code signing is scaffolded but inert. Set this secret AND drop a real
# signing command into the "Sign the Windows exe" step to enable it (see
# "Code signing" in README.md). Until then the exe ships unsigned.
WINDOWS_SIGN_CERT: ${{ secrets.WINDOWS_SIGN_CERT }}
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: 24
- uses: mlugg/setup-zig@v2
with:
version: 0.16.0
# Sets the MSVC environment (INCLUDE, WindowsSdkDir, WindowsSdkVersion, …)
# so Zig's msvc target finds the toolchain and we can locate the winrt
# headers (wrl.h) the WebView2 host needs.
- uses: ilammy/msvc-dev-cmd@v1
- name: Install dependencies (zero-native framework + CLI)
shell: bash
run: |
npm install
echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH"
- name: Fetch the WebView2 SDK headers + loader
shell: bash
run: nuget install Microsoft.Web.WebView2 -OutputDirectory packages -ExcludeVersion
- name: Resolve the released version
id: ver
shell: bash
run: |
# Manual dispatch with a tag attaches to that existing release;
# otherwise use the version Changesets just published. Changesets tags
# a single-package repo as v<version> (only monorepos use
# <name>@<version>), so the normal release tag is "v$VER".
if [ -n "${{ github.event.inputs.tag }}" ]; then
TAG="${{ github.event.inputs.tag }}"
VER="${TAG#v}"
else
VER=$(echo '${{ needs.release.outputs.publishedPackages }}' | jq -r '.[0].version')
TAG="v$VER"
fi
echo "version=$VER" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Build the single-file Windows exe
shell: bash
env:
# Embedded into the frontend by Next during `zig build` (which runs
# npm run build); tags desktop telemetry as platform=windows.
NEXT_PUBLIC_GA_ID: ${{ secrets.GA_MEASUREMENT_ID }}
run: |
WV2_INC="$GITHUB_WORKSPACE/$NUGET_DIR/build/native/include"
WV2_LIB="$GITHUB_WORKSPACE/$NUGET_DIR/build/native/x64"
echo "WebView2 include: $WV2_INC"
echo "WebView2 lib: $WV2_LIB"
# Fail loudly if the SDK pieces are missing: without WebView2.h the host
# silently compiles to a blank-window stub; without the static loader lib
# it can't link a self-contained exe.
test -f "$WV2_INC/WebView2.h" || { echo "ERROR: WebView2.h not found at $WV2_INC"; ls -R "$GITHUB_WORKSPACE/$NUGET_DIR" 2>/dev/null | head -40; exit 1; }
test -f "$WV2_LIB/WebView2LoaderStatic.lib" || { echo "ERROR: WebView2LoaderStatic.lib not found at $WV2_LIB"; exit 1; }
# The frontend is embedded into the exe (served from memory) and the
# WebView2 loader is static-linked, so the release artifact is a single
# self-contained keyparty.exe. cl.exe finds the Windows SDK + winrt via
# %INCLUDE%.
zig build \
-Dtarget=x86_64-windows-msvc \
-Doptimize=ReleaseFast \
-Dwebview2-include="$WV2_INC" \
-Dwebview2-lib-dir="$WV2_LIB"
test -f zig-out/bin/keyparty.exe || { echo "ERROR: no exe produced"; ls -R zig-out; exit 1; }
# Version-less name -> .../releases/latest/download/KeyParty.exe is stable.
cp zig-out/bin/keyparty.exe "KeyParty.exe"
- name: Sign the Windows exe (scaffold — skipped until a cert is configured)
# Inert today (no WINDOWS_SIGN_CERT secret). Public-trust Windows certs now
# require hardware/cloud key storage, so plug your provider in here:
# • Microsoft Trusted Signing: azure/trusted-signing-action@v0
# • DigiCert KeyLocker / SSL.com eSigner: the provider's signtool dlib
# • signtool: signtool sign /fd SHA256 /tr <timestamp-url> /td SHA256 KeyParty.exe
# Replace the echo with the real command; the signed exe then flows through
# to the existing upload step unchanged.
if: ${{ env.WINDOWS_SIGN_CERT != '' }}
shell: bash
run: |
echo "TODO: sign KeyParty.exe (see step comment for provider options)."
- name: Attach the binary to the release
shell: bash
run: gh release upload "${{ steps.ver.outputs.tag }}" "KeyParty.exe" --clobber
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}