Add Electron packaging smoke test to catch missing dependencies (#7336)

* ci(electron): verify packaged app.asar and smoke-test launch

Adds coverage for the gap that let #7320 ship: CI built electron TS
and ran electron-builder on release, but never exercised the packaged
binary. A stray relative import reaching out of electron/** was
compiled by tsc into src/app/util/*.js, fell outside the files glob
in electron-builder.yaml, and was missing from app.asar — the main
process crashed on launch for every 18.2.7 user.

Two new checks, both running on ubuntu-latest:

1. tools/verify-electron-requires.js extracts app.asar and re-resolves
   every relative require() under electron/** against the packaged
   tree. Runs on every PR that touches electron/**, electron-builder
   config, package.json, or the workflow itself. ~10s after the build.

2. A launch-under-xvfb smoke test starts the packaged binary, waits
   30s for a crash, and greps stderr for 'Cannot find module' /
   'Uncaught Exception'. Runs on push to master, release tags, and
   manual dispatch (not every PR — needs xvfb setup + idle wait).

Exposed as npm run electron:verify-asar for local use after dist.

* ci(electron): harden smoke workflow and verify script from review

Review feedback on the previous commit surfaced a handful of real
defects. Addressed here:

verify-electron-requires.js
- Guard resolved paths against escaping the extracted asar tree.
  Without this, a relative require with enough `..` climbs above the
  temp dir and Node's resolver hits the host filesystem, masking a
  genuinely missing module behind a stray file on the CI runner.
- Walk .cjs and .mjs files too. electron/simple-store.test.cjs ships
  via the `electron/**/*` glob and was being skipped.
- Move cleanup out of the path where `process.exit(1)` runs inside a
  `try/finally` (synchronous exit does not guarantee `finally`), so
  the temp dir is reliably removed.
- Code comment noting that esbuild-bundled preload.js is opaque to
  this walker — that coverage comes from the launch smoke test.

electron-smoke.yml
- Broaden pull_request paths filter. The previous filter only matched
  electron/**, which is exactly the wrong answer: #7320's root cause
  was a file under src/app/util/ that electron/ imported. Now uses a
  negative list — docs, translations, android/, ios/, other workflows.
- Explicit Xvfb screen geometry (`-screen 0 1280x720x24`) and
  `--disable-dev-shm-usage` on the Electron side. Both known CI
  flake preventers.
- Expanded crash-marker regex: SIGSEGV, Segmentation fault,
  TypeError:, ReferenceError:, FATAL ERROR, Check failed. Also scan
  the log during the liveness loop, not just at the end — catches
  main-process exceptions that get logged without killing the
  process.
- Move the launch log under .tmp/smoke/ so upload-artifact@v7 reliably
  finds it on failure; /tmp paths are fragile across runner images.
- Sweep stray electron worker processes on teardown via pkill.

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Johannes Millan 2026-04-23 21:53:28 +02:00 committed by GitHub
parent e5aec5ceb2
commit e72aa77336
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 352 additions and 0 deletions

View file

@ -72,6 +72,7 @@
"e2e:docker:all": "docker compose -f docker-compose.e2e.yaml -f docker-compose.yaml -f docker-compose.supersync.yaml up -d app webdav db supersync && ./scripts/wait-for-app.sh && ./scripts/wait-for-webdav.sh && ./scripts/wait-for-supersync.sh && E2E_BASE_URL=http://localhost:${APP_PORT:-4242} npm run e2e:all -- --workers=6; EXIT_CODE=$?; docker compose -f docker-compose.e2e.yaml -f docker-compose.yaml -f docker-compose.supersync.yaml down; exit $EXIT_CODE",
"electron": "NODE_ENV=PROD electron .",
"electron:build": "tsc -p electron/tsconfig.electron.json && node electron/scripts/bundle-preload.js",
"electron:verify-asar": "node tools/verify-electron-requires.js .tmp/app-builds/linux-unpacked/resources/app.asar",
"electron:watch": "tsc -p electron/tsconfig.electron.json --watch",
"electronBuilderOnly": "electron-builder",
"empty": "echo 'EMPTY YEAH'",