-
-
Notifications
You must be signed in to change notification settings - Fork 1
269 lines (238 loc) · 11.4 KB
/
Copy pathrelease.yml
File metadata and controls
269 lines (238 loc) · 11.4 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
name: Releaser
on:
push:
tags:
- 'v*'
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
pantry:
name: pantry (${{ matrix.platform.name }})
strategy:
fail-fast: false
matrix:
platform:
- { os: macos-15, name: darwin-arm64, deps: '' }
- { os: ubuntu-latest, name: linux-x86-64, deps: 'sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev' }
runs-on: ${{ matrix.platform.os }}
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Pantry (provides Bun, Zig, etc.)
uses: pantry-pm/pantry/packages/action@main
- name: Install System Dependencies
if: matrix.platform.deps != ''
run: ${{ matrix.platform.deps }}
- name: Install Dependencies
run: bun install
- name: Extract Version
id: version
run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
- name: First-party Zig dependencies
uses: ./.github/actions/first-party-zig-deps
- name: Build Native Binary
run: bun run build:core
# ── Cross-compile all targets ────────────────────────────────
- name: First-party Zig dependencies
uses: ./.github/actions/first-party-zig-deps
- name: Cross-compile additional targets (macOS)
if: runner.os == 'macOS'
working-directory: packages/zig
run: |
VERSION="${{ steps.version.outputs.version }}"
SDK_PATH="$(xcrun --show-sdk-path)"
mkdir -p zig-out/cross
echo "::group::Cross-compile darwin-x64"
eval "$(pantry env | sed -n '/^export /,$p')" && zig build -Doptimize=ReleaseSafe -Dversion="$VERSION" -Dtarget=x86_64-macos -Dmacos-sdk="$SDK_PATH"
mkdir -p zig-out/cross/darwin-x64 && cp zig-out/bin/craft zig-out/cross/darwin-x64/craft
echo "::endgroup::"
# Restore native ARM64 binary. `zig-out/bin` is cleared first because
# the publish step zips that directory whole and unfiltered, so
# anything a cross build left in it ships to users under the host's
# archive name.
rm -rf zig-out/bin
eval "$(pantry env | sed -n '/^export /,$p')" && zig build -Doptimize=ReleaseSafe -Dversion="$VERSION"
- name: First-party Zig dependencies
uses: ./.github/actions/first-party-zig-deps
- name: Cross-compile additional targets (Linux)
if: runner.os == 'Linux'
working-directory: packages/zig
run: |
VERSION="${{ steps.version.outputs.version }}"
mkdir -p zig-out/cross
# Only cross-compile targets that do not require a target sysroot.
# Linux/FreeBSD GUI builds link GTK/WebKit system libraries, so
# non-host triples need dedicated sysroots before they are reliable.
for target_spec in \
"x86_64-windows:windows-x64:craft.exe"; do
IFS=: read -r zig_target dir_name bin_name <<< "$target_spec"
echo "::group::Cross-compile $dir_name"
eval "$(pantry env | sed -n '/^export /,$p')"
zig build -Doptimize=ReleaseSafe -Dversion="$VERSION" -Dtarget="$zig_target"
mkdir -p "zig-out/cross/$dir_name" && cp "zig-out/bin/$bin_name" "zig-out/cross/$dir_name/$bin_name"
echo "Built $dir_name"
echo "::endgroup::"
done
# Restore native linux-x64 binary. Clearing `zig-out/bin` first is
# load-bearing: the windows cross build above leaves craft.exe there,
# nothing removes it, and the publish step zips the whole directory —
# so craft-linux-x64.zip shipped a Windows PE binary alongside the
# Linux one.
rm -rf zig-out/bin
eval "$(pantry env | sed -n '/^export /,$p')" && zig build -Doptimize=ReleaseSafe -Dversion="$VERSION"
# ── macOS Code Signing & Notarization ──────────────────────────
# The signing credentials live encrypted in `.env.production`, not as
# seven repository secrets.
#
# One key instead of seven values: the ciphertext is committed and
# reviewable, rotating a certificate is a commit rather than seven trips
# through a settings page, and the same file works locally — `buddy
# env:get` reads a value on a laptop with no GitHub involved.
# What that trades away
# is that the ciphertext is public, so the whole of the secrecy is
# `DOTENV_PRIVATE_KEY_PRODUCTION`, which is the one thing that stays a
# repository secret.
#
# Decrypted into `$GITHUB_ENV` so every step below reads `$APPLE_ID` the
# way it always did. Each value is masked first: `$GITHUB_ENV` does *not*
# mask on its own, and an unmasked notarization password is one `set -x`
# away from the log.
- name: Load macOS release credentials
if: runner.os == 'macOS'
env:
DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }}
run: |
set -euo pipefail
if [ -z "${DOTENV_PRIVATE_KEY_PRODUCTION:-}" ]; then
echo "::error::DOTENV_PRIVATE_KEY_PRODUCTION is not set. It is the only"
echo "::error::secret this release needs; see .env.production for what it opens."
exit 1
fi
for name in APPLE_CERTIFICATE_BASE64 APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY KEYCHAIN_PASSWORD APPLE_ID APPLE_APP_PASSWORD APPLE_TEAM_ID; do
value="$(bunx --bun buddy env:get "$name" --file .env.production 2>/dev/null || true)"
if [ -z "$value" ]; then
echo "::error::$name is empty in .env.production."
echo "::error::Set it with: bunx buddy env:set $name '<value>' --file .env.production"
exit 1
fi
echo "::add-mask::$value"
printf '%s=%s\n' "$name" "$value" >> "$GITHUB_ENV"
done
- name: Import signing certificate
if: runner.os == 'macOS'
run: |
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > certificate.p12
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
rm certificate.p12
- name: Sign macOS binary
if: runner.os == 'macOS'
env:
# Default to the strict distribution entitlements; CI can opt in to
# the dev plist (sandbox off, library validation off) by setting
# CRAFT_ENTITLEMENTS=dev for local debugging — never for release.
ENTITLEMENTS_VARIANT: ${{ vars.CRAFT_ENTITLEMENTS || 'distribution' }}
run: |
set -euo pipefail
ENTITLEMENTS="scripts/entitlements-${ENTITLEMENTS_VARIANT}.plist"
if [ ! -f "$ENTITLEMENTS" ]; then
echo "::error file=$ENTITLEMENTS::missing — required for hardened-runtime signing"
exit 1
fi
echo "Using entitlements: $ENTITLEMENTS"
for binary in \
packages/zig/zig-out/bin/craft \
packages/zig/zig-out/cross/darwin-x64/craft; do
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" \
--entitlements "$ENTITLEMENTS" "$binary"
codesign --verify --deep --strict "$binary"
done
echo "macOS ARM64 and x64 binaries signed successfully"
- name: Notarize macOS binaries
if: runner.os == 'macOS'
run: |
set -euo pipefail
for tuple in \
"packages/zig/zig-out/bin/craft:arm64" \
"packages/zig/zig-out/cross/darwin-x64/craft:x64"; do
IFS=: read -r binary architecture <<< "$tuple"
archive="craft-${architecture}.zip"
ditto -c -k --keepParent "$binary" "$archive"
# A raw Mach-O binary has no stapler-supported container. Submit
# an archive containing the exact signed binary and require
# Apple's final Accepted result; Gatekeeper retrieves that ticket
# online for the binary distributed by the release.
xcrun notarytool submit "$archive" \
--apple-id "$APPLE_ID" \
--password "$APPLE_APP_PASSWORD" \
--team-id "$APPLE_TEAM_ID" \
--wait \
--timeout 30m
codesign --verify --deep --strict "$binary"
rm "$archive"
done
# ── Publish & Release ──────────────────────────────
# Pantry action handles everything:
# 1. Publishes to pantry registry (zig package)
# 2. Auto-packages binaries from zig-out/ into platform-named zips
# 3. Creates/updates GitHub release with all artifacts + changelog
- name: Publish & Release
uses: pantry-pm/pantry/packages/action@main
with:
install: 'false'
publish: 'zig'
package-dir: packages/zig
token: ${{ secrets.PANTRY_TOKEN }}
release: 'true'
release-changelog: CHANGELOG.md
release-token: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
# Replaces the old `notify` job, which pointed at
# .github/actions/discord-notify — a path that has never existed in
# this repository. Its `hashFiles` guard skipped the only real step,
# so the job checked out the repo, did nothing and reported success:
# a failed release looked notified. The action does this natively.
discord-webhook: ${{ secrets.DISCORD_WEBHOOK_URL }}
notification-title: craft ${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }}
npm:
name: npm
# Ordered after the binaries, not gated on them.
#
# `needs:` alone waits for every matrix leg to *succeed*, and the
# darwin-arm64 leg exits early when the Apple signing secrets are absent —
# so a missing certificate has been silently blocking every npm publish
# since v0.0.55. Fifteen releases where the tag, the changelog and the
# GitHub release all landed and not one package reached the registry.
#
# Nothing here consumes the binary job's artifacts: this checks out, runs
# `bun install`, and publishes packages built from TypeScript. Signing a Zig
# binary and publishing an SDK are independent, and the workflow now says
# so. `!cancelled()` rather than `always()` so a cancelled run still stops.
needs: [pantry]
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Pantry (provides Bun, Zig, etc.)
uses: pantry-pm/pantry/packages/action@main
- name: Install Dependencies
run: bun install
- name: Publish to npm
run: pantry publish --npm --access public
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}