Skip to content

Commit e63f928

Browse files
committed
improvement(ci): parallelize desktop release verification and packaging
1 parent 3d5b37f commit e63f928

2 files changed

Lines changed: 171 additions & 44 deletions

File tree

‎.github/CI.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ The current weights are median test durations in seconds from runs [41496623284]
7171

7272
Desktop native outputs are keyed by source, build recipes, compiler, SDK, OS, architecture, and Node header bytes. Every build still validates restored checksums, executable modes, universal architectures, and the helper signature; invalid or incomplete outputs rebuild. Native-input pushes to main/staging seed trusted Node 22 and 24 caches without repeating the PR smoke suite. PR cache entries never populate trusted branch caches.
7373

74+
Desktop releases resolve and validate their source once, then check out that immutable commit for packaging and four isolated smoke partitions. The release smoke partitions reuse the existing whole-file duration sharder on the release's Node 22 toolchain and run alongside signing and notarization. Historical tags without the sharder retain one complete smoke run, and native compilation caching is optional when the checked-out revision predates it. Packaging retains source checks, package validation, and packaged Electron smoke. Publication waits for packaging and every smoke partition, downloads the packaging job's immutable artifact ID from the same run, verifies any existing release assets, and uploads the updater manifest last. Artifact-only runs never publish.
75+
7476
Each shard uploads its coverage/timing report, the complete discovery report, and its deterministic shard plan. `bun run test:scripts scripts/duration-shards.test.ts scripts/desktop-e2e.test.ts` checks partition coverage, unknown files, deterministic ordering, and balancing. The CLI regression executes tiny browser-free Playwright fixtures to prove that imported test factories and paths containing regex characters are selected correctly; it launches no Electron or browser.
7577

7678
Local pushes run `lint:check` and `type-check` through Husky before Git sends commits. These gates reuse Turborepo's task cache; a failure stops the push. The pre-commit hook still formats staged files, and hosted CI owns tests, audits, builds, and acceptance checks.

‎.github/workflows/desktop-release.yml‎

Lines changed: 169 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,25 @@ on:
5757
default: true
5858

5959
permissions:
60-
contents: write
60+
contents: read
6161

6262
jobs:
63-
build:
64-
name: build
65-
runs-on: macos-26
66-
timeout-minutes: 60
63+
source:
64+
name: source
65+
runs-on: ubuntu-24.04
66+
timeout-minutes: 5
67+
outputs:
68+
commit: ${{ steps.source.outputs.commit }}
69+
name: ${{ steps.channel.outputs.name }}
70+
app_id: ${{ steps.channel.outputs.app_id }}
71+
origin: ${{ steps.channel.outputs.origin }}
72+
release_repository: ${{ steps.channel.outputs.release_repository }}
73+
token_kind: ${{ steps.channel.outputs.token_kind }}
74+
smoke_shards: ${{ steps.smoke.outputs.shards }}
6775
steps:
6876
- name: Checkout code
6977
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
7078
with:
71-
fetch-depth: 0
7279
persist-credentials: false
7380
ref: ${{ github.event_name == 'workflow_dispatch' && inputs.version || github.sha }}
7481

@@ -101,6 +108,7 @@ jobs:
101108
echo "Building $NAME ($APP_ID) for $RELEASE_REPOSITORY; default origin: ${ORIGIN:-production}"
102109
103110
- name: Validate release source
111+
id: source
104112
env:
105113
PUBLISH: ${{ inputs.publish }}
106114
SIGN: ${{ inputs.sign }}
@@ -120,6 +128,7 @@ jobs:
120128
exit 1
121129
fi
122130
if [ "$TOKEN_KIND" = stable ]; then
131+
git fetch --depth=1 origin "refs/tags/${VERSION}:refs/tags/${VERSION}"
123132
TAG_COMMIT="$(git rev-parse "refs/tags/${VERSION}^{commit}")"
124133
HEAD_COMMIT="$(git rev-parse HEAD)"
125134
if [ "$TAG_COMMIT" != "$HEAD_COMMIT" ]; then
@@ -128,6 +137,17 @@ jobs:
128137
fi
129138
fi
130139
140+
echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
141+
142+
- name: Select available smoke partitioning
143+
id: smoke
144+
run: |
145+
if [ -f scripts/desktop-e2e.ts ]; then
146+
echo 'shards=[1,2,3,4]' >> "$GITHUB_OUTPUT"
147+
else
148+
echo 'shards=[1]' >> "$GITHUB_OUTPUT"
149+
fi
150+
131151
- name: Validate release authentication
132152
if: ${{ inputs.publish }}
133153
env:
@@ -140,28 +160,51 @@ jobs:
140160
exit 1
141161
fi
142162
143-
- name: Setup Bun
163+
smoke:
164+
name: smoke (${{ matrix.shard }}/${{ strategy.job-total }})
165+
needs: source
166+
runs-on: macos-26
167+
timeout-minutes: 30
168+
strategy:
169+
fail-fast: false
170+
matrix:
171+
shard: ${{ fromJSON(needs.source.outputs.smoke_shards) }}
172+
steps:
173+
- &checkout-source
174+
name: Checkout verified source
175+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
176+
with:
177+
persist-credentials: false
178+
ref: ${{ needs.source.outputs.commit }}
179+
180+
- &setup-bun
181+
name: Setup Bun
144182
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
145183
with:
146184
bun-version: 1.4.4
147185

148-
- name: Setup Node
186+
- &setup-node
187+
name: Setup Node
149188
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v6
150189
with:
151190
node-version: 22
152191

153-
- name: Cache Electron binaries
192+
- name: Cache Electron and Playwright downloads
154193
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
155194
with:
156195
path: |
157196
~/Library/Caches/electron
158197
~/Library/Caches/electron-builder
159-
key: electron-cache-${{ runner.os }}-${{ hashFiles('apps/desktop/package.json') }}
198+
~/Library/Caches/ms-playwright
199+
key: desktop-downloads-${{ runner.os }}-${{ hashFiles('bun.lock') }}
200+
restore-keys: desktop-downloads-${{ runner.os }}-
160201

161-
- name: Install dependencies
202+
- &install-dependencies
203+
name: Install dependencies
162204
run: bun install --frozen-lockfile
163205

164-
- name: Inject release version
206+
- &inject-version
207+
name: Inject release version
165208
env:
166209
VERSION: ${{ inputs.version }}
167210
run: |
@@ -175,27 +218,94 @@ jobs:
175218
exit 1
176219
fi
177220
178-
- name: Verify desktop source
179-
run: |
180-
bun run --cwd apps/desktop lint:check
181-
bun run --cwd apps/desktop type-check
182-
bun run --cwd apps/desktop test
221+
- &native-cache
222+
name: Restore native compilation
223+
if: hashFiles('.github/actions/desktop-native-cache/action.yml') != ''
224+
uses: ./.github/actions/desktop-native-cache
183225

184-
- name: Bundle main and preload
226+
- &bundle-desktop
227+
name: Bundle main and preload
185228
working-directory: apps/desktop
186229
env:
187-
SIM_DESKTOP_DEFAULT_ORIGIN: ${{ steps.channel.outputs.origin }}
230+
SIM_DESKTOP_DEFAULT_ORIGIN: ${{ needs.source.outputs.origin }}
188231
run: bun run build
189232

190233
- name: Install system-browser fixture
191234
working-directory: apps/desktop
192235
run: bunx playwright install chromium
193236

194-
- name: Run Electron smoke tests
237+
- name: Install tmux
238+
run: brew install tmux
239+
240+
- name: Run Electron smoke partition
195241
working-directory: apps/desktop
196242
env:
197-
SIM_DESKTOP_DEFAULT_ORIGIN: ${{ steps.channel.outputs.origin }}
198-
run: bun run test:e2e
243+
SIM_DESKTOP_DEFAULT_ORIGIN: ${{ needs.source.outputs.origin }}
244+
PLAYWRIGHT_JSON_OUTPUT_FILE: test-results/desktop-smoke.json
245+
BACKGROUND_EXECUTOR_REPORT_PATH: test-results/background-executor-report.json
246+
TERMINAL_CANCEL_REPORT_PATH: test-results/terminal-cancel-report.json
247+
CONTINUE_SIGN_IN_REPORT_PATH: test-results/continue-sign-in-report.json
248+
run: |
249+
if [ -f ../../scripts/desktop-e2e.ts ]; then
250+
bun run ../../scripts/desktop-e2e.ts ${{ matrix.shard }}/${{ strategy.job-total }}
251+
else
252+
bun run test:e2e --reporter=line,json
253+
fi
254+
255+
- name: Upload smoke coverage and timings
256+
if: always()
257+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
258+
with:
259+
name: desktop-release-smoke-${{ matrix.shard }}
260+
path: |
261+
apps/desktop/test-results/*.json
262+
if-no-files-found: ignore
263+
retention-days: 7
264+
265+
- name: Upload Electron failure artifacts
266+
if: failure()
267+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
268+
with:
269+
name: desktop-release-smoke-failure-${{ matrix.shard }}
270+
path: apps/desktop/test-results/
271+
if-no-files-found: ignore
272+
retention-days: 7
273+
274+
build:
275+
name: build
276+
needs: source
277+
outputs:
278+
artifact_id: ${{ steps.artifacts.outputs.artifact-id }}
279+
runs-on: macos-26
280+
timeout-minutes: 60
281+
steps:
282+
- *checkout-source
283+
284+
- *setup-bun
285+
286+
- *setup-node
287+
288+
- name: Cache Electron binaries
289+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v5
290+
with:
291+
path: |
292+
~/Library/Caches/electron
293+
~/Library/Caches/electron-builder
294+
key: electron-cache-${{ runner.os }}-${{ hashFiles('apps/desktop/package.json') }}
295+
296+
- *install-dependencies
297+
298+
- *inject-version
299+
300+
- *native-cache
301+
302+
- name: Verify desktop source
303+
run: |
304+
bun run --cwd apps/desktop lint:check
305+
bun run --cwd apps/desktop type-check
306+
bun run --cwd apps/desktop test
307+
308+
- *bundle-desktop
199309

200310
- name: Write App Store Connect API key
201311
if: ${{ inputs.sign }}
@@ -253,8 +363,8 @@ jobs:
253363
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
254364
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
255365
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
256-
PRODUCT_NAME: ${{ steps.channel.outputs.name }}
257-
APP_ID: ${{ steps.channel.outputs.app_id }}
366+
PRODUCT_NAME: ${{ needs.source.outputs.name }}
367+
APP_ID: ${{ needs.source.outputs.app_id }}
258368
run: >
259369
bunx electron-builder --mac --publish never
260370
-c.productName="$PRODUCT_NAME" -c.appId="$APP_ID"
@@ -274,8 +384,8 @@ jobs:
274384
working-directory: apps/desktop
275385
env:
276386
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
277-
PRODUCT_NAME: ${{ steps.channel.outputs.name }}
278-
APP_ID: ${{ steps.channel.outputs.app_id }}
387+
PRODUCT_NAME: ${{ needs.source.outputs.name }}
388+
APP_ID: ${{ needs.source.outputs.app_id }}
279389
run: >
280390
bunx electron-builder --mac --publish never -c.mac.notarize=false
281391
-c.mac.identity=- -c.mac.hardenedRuntime=false
@@ -333,9 +443,9 @@ jobs:
333443
- name: Validate signature and notarization
334444
if: ${{ inputs.sign }}
335445
env:
336-
APP_ID: ${{ steps.channel.outputs.app_id }}
446+
APP_ID: ${{ needs.source.outputs.app_id }}
337447
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
338-
PRODUCT_NAME: ${{ steps.channel.outputs.name }}
448+
PRODUCT_NAME: ${{ needs.source.outputs.name }}
339449
VERSION: ${{ inputs.version }}
340450
run: |
341451
SEMVER="${VERSION#v}"
@@ -403,13 +513,40 @@ jobs:
403513
fi
404514
SIM_DESKTOP_EXECUTABLE="$EXECUTABLE" bunx playwright test e2e/packaged-smoke.spec.ts
405515
516+
- name: Upload artifacts to the workflow run
517+
id: artifacts
518+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
519+
with:
520+
name: sim-desktop-${{ inputs.version }}
521+
compression-level: 0
522+
path: |
523+
apps/desktop/release/*.dmg
524+
apps/desktop/release/*.zip
525+
apps/desktop/release/*.blockmap
526+
apps/desktop/release/latest-mac.yml
527+
retention-days: 7
528+
529+
publish:
530+
name: publish
531+
needs: [source, build, smoke]
532+
if: inputs.publish
533+
runs-on: ubuntu-24.04
534+
timeout-minutes: 10
535+
permissions:
536+
contents: write
537+
steps:
538+
- name: Download verified desktop artifacts
539+
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
540+
with:
541+
artifact-ids: ${{ needs.build.outputs.artifact_id }}
542+
path: apps/desktop/release
543+
406544
- name: Upload artifacts to the release
407-
if: ${{ inputs.publish }}
408545
env:
409546
DESKTOP_RELEASE_TOKEN: ${{ secrets.DESKTOP_RELEASE_TOKEN }}
410-
RELEASE_REPOSITORY: ${{ steps.channel.outputs.release_repository }}
547+
RELEASE_REPOSITORY: ${{ needs.source.outputs.release_repository }}
411548
SOURCE_RELEASE_TOKEN: ${{ github.token }}
412-
TOKEN_KIND: ${{ steps.channel.outputs.token_kind }}
549+
TOKEN_KIND: ${{ needs.source.outputs.token_kind }}
413550
VERSION: ${{ inputs.version }}
414551
run: |
415552
case "$TOKEN_KIND" in
@@ -448,7 +585,7 @@ jobs:
448585
local ARTIFACT="$1"
449586
local NAME SIZE DIGEST REMOTE REMOTE_SIZE REMOTE_DIGEST
450587
NAME="$(basename "$ARTIFACT")"
451-
SIZE="$(stat -f%z "$ARTIFACT")"
588+
SIZE="$(stat -c%s "$ARTIFACT")"
452589
DIGEST="sha256:$(shasum -a 256 "$ARTIFACT" | awk '{print $1}')"
453590
REMOTE="$(jq -c --arg name "$NAME" '.assets[] | select(.name == $name)' <<< "$RELEASE_JSON")"
454591
if [ -n "$REMOTE" ]; then
@@ -467,15 +604,3 @@ jobs:
467604
upload_or_verify "$ARTIFACT"
468605
done
469606
upload_or_verify "${ARTIFACTS[4]}"
470-
471-
- name: Upload artifacts to the workflow run
472-
if: ${{ !inputs.publish }}
473-
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
474-
with:
475-
name: sim-desktop-${{ inputs.version }}
476-
path: |
477-
apps/desktop/release/*.dmg
478-
apps/desktop/release/*.zip
479-
apps/desktop/release/*.blockmap
480-
apps/desktop/release/latest-mac.yml
481-
retention-days: 7

0 commit comments

Comments
 (0)