Fix Windows ARM64 installer payload corruption and modernize build - #2
Merged
Merged
Conversation
…the Windows arm64 installer electron 33 -> 43.4.1 (Chromium 150, Node 24.18.1), electron-builder 25 -> 26.15.3, better-sqlite3 11 -> 13.0.3, vite 6 -> 7 with electron-vite 5, @types/node and the documented toolchain to Node 24. Windows on ARM installed without its binaries. Not BCJ2, which is what the payload of the working 0.1.6 x64 installer already uses: electron-builder 26 swapped the bundled 7za 16.02 for a downloaded 7-Zip 24.09, which auto-applies the ARM64 filter (7-Zip 23.01+) to ARM64 PE files, while the NSIS side still ships the Nsis7z plugin built in 2019. The plugin answers "Unsupported Method" for exactly the nine native binaries and installs everything else. Reproduced on the real payload: without the override nine files carry method 0A and a decoder of the plugin's vintage refuses them; with it every file is BCJ LZMA2 and all 135 extract. scripts/electron-builder.cjs sets ELECTRON_BUILDER_7Z_FILTER=BCJ for Windows arm64 only, so x64 keeps the BCJ2 it already ships with and macOS, whose dmg and zip targets never reach the 7z path, is untouched. scripts/verify-windows-installer.cjs runs from afterAllArtifactBuild: it carves the embedded archive out of every .exe and re-tests it with a decoder older than the plugin, so a payload the installer cannot unpack fails the build instead of reaching a release page. CI now builds both Windows installers (arm64 packaged on the x64 runner - the payload is inspected, never executed) so the check has something to inspect on every push. better-sqlite3 13 is a Node-API addon shipping prebuilt binaries for every platform, so the cross-rebuild machinery is gone: scripts/rebuild-native.cjs, the postinstall and rebuild:native scripts, and npmRebuild. Each artifact now carries only the one prebuild it can load. Verified by loading the package's own binary under Electron 43. plist is pinned to 3.1.1: app-builder-lib pins 3.1.0, which calls parseFromString without a mime type, and the existing @xmldom/xmldom 0.9.11 override turns that into an error that fails mac packaging outright. Build targets are now written out in electron.vite.config.ts. electron-vite infers them from a table that ends at Electron 39 and falls back to its oldest entry, so Electron 43 would have compiled to a chrome94/node16 target without a word. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qb5ER3Mq6Bq2atVoKDo6f
…p macOS crash reports tests/thread-index.test.ts dies on the macOS runner with SIGABRT and nothing else: the assertion on result.signal runs before the one that carries result.stderr, so the child's stdout and stderr are discarded and the crash has no explanation. Both Electron tests now build the transcript first and pass it to every assertion. The runner writes a stage marker straight to fd 1 before each phase. console.log to a pipe is buffered and lost when a process aborts; a synchronous write is not, so the last marker on stdout names the phase the crash happened in - fixtures, scanSessions, the thread index, or the exit itself. Diagnostic, to be removed once that is known. CI dumps the newest macOS crash reports when a job fails, decoding the .ips to the exception, the termination reason and the faulting thread's frames with the image each frame belongs to. That answers the one question worth answering: whether the process aborts inside Electron or inside better_sqlite3.node. The matrix no longer fail-fasts. A red macOS leg was cancelling the Windows leg, which is the one that builds the installers and runs the payload check. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qb5ER3Mq6Bq2atVoKDo6f
Electron 33 shipped "postinstall": "node install.js", so its binary was always on disk
before tests started. Electron 43 has no install script at all - require('electron')
notices the missing binary and downloads it right there, on first use. vitest runs test
files in parallel, so several of them hit that at once: the CI log shows two "Downloading
Electron binary..." lines, each starting its own download into the same dist/ directory,
while another file was already spawning what had been written so far.
macOS aborted at launch on the truncated Mach-O - dyld "segment '__TEXT' load command
content extends beyond end of file", terminated at launch, which is where the SIGABRT
came from. Windows failed to spawn at all. Whichever file happened to lose the race
failed while its twin, arriving two seconds later, passed against a complete binary.
A vitest globalSetup resolves the binary once, before the first test file, so the
download can no longer overlap a spawn. Nothing in the app changed, and this is not
CI-specific: a fresh clone with a cold Electron cache raced the same way locally.
The stage markers added while diagnosing this are gone - the process died before any
of the runner's JavaScript ran, so they never had anything to report. The two things
that did the work stay: the assertion transcript, which carried the dyld message that
named the cause, and the macOS crash-report decoder in CI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016qb5ER3Mq6Bq2atVoKDo6f
Only this branch's work stands between v0.1.6 and this release: the Electron 43 / Node 24 / better-sqlite3 13 upgrade, the Windows arm64 installer fix, and the test and CI changes that came out of it. Nothing under electron/, src/ or shared/ was touched, so the release notes lead with the one thing users can actually feel - Electron 43 requires macOS 12, where 0.1.6 ran on macOS 11. Both numbers are read from the two Electron builds' own Info.plist. The notes say the published Windows installer is x64 only, because it is: the release workflow builds --win nsis --x64 and nothing else. The arm64 fix is described as what it is, something that matters when building your own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016qb5ER3Mq6Bq2atVoKDo6f
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR fixes a critical issue where Windows ARM64 installers silently ship without native binaries, modernizes the build toolchain, and adds verification to catch similar issues in the future.
The Problem: electron-builder 26 compresses app payloads with 7-Zip 24.09, which automatically applies the ARM64 filter to ARM64 binaries. However, the bundled NSIS installer still uses the
Nsis7zplugin from 2019, whose decoder predates the ARM64 filter (introduced in 7-Zip 23.01). When the installer runs, it silently fails to extract nine native binaries (the app exe, d3dcompiler_47, dxcompiler, dxil, ffmpeg, libEGL, libGLESv2, vk_swiftshader, and vulkan-1), leaving a broken app that appears to install successfully.The Solution:
New
scripts/electron-builder.cjs: Wraps electron-builder and setsELECTRON_BUILDER_7Z_FILTER=BCJfor Windows ARM64 builds. BCJ is an x86 filter that every 7z decoder understands, including the 2019 NSIS plugin. It costs ~15% on ARM64 binaries but ensures they extract correctly.New
scripts/verify-windows-installer.cjs: Runs after all artifacts are built and re-tests each Windows installer with 7zip-bin (which ships 7-Zip 16.02, older than the NSIS plugin). It verifies:Removed
scripts/rebuild-native.cjs: No longer needed. better-sqlite3 is a Node-API addon that ships prebuilt binaries for all platforms, so rebuilding against Electron headers produces only a host-only copy that nothing loads.Updated build configuration:
npmRebuild: falsein electron-builder.yml (better-sqlite3 doesn't need rebuilding)Modernized dependencies:
Build script simplification: Removed manual native rebuilds from
build:win-arm64and all other build commands. The wrapper script handles filter selection automatically.Verification
pnpm checkpassesCleanup safety
Privacy
https://claude.ai/code/session_016qb5ER3Mq6Bq2atVoKDo6f