Eric Guo's blog.cloud-mes.com

Hoping writing JS, Ruby & Rails and Go article, but fallback to DevOps note

Build X86 Electron on Arm64 Mac

• Permalink

When building a Windows Electron app on Apple Silicon macOS, packaging defaults to the host unless you pin the target. In @opencode/desktop, set OPENCODE_TARGET (preferred) so both the Electron build and electron-builder agree on windows-x64 — including native bindings such as @lydell/node-pty-win32-x64.

bun run pack alone does not emit the one-click NSIS .exe. For Windows it uses --dir and copies an unpacked app to the repo-root dist/desktop/app. The NSIS installer is a second pack that wraps a prepackaged app via OPENCODE_DESKTOP_APP (same split the release workflow uses after signing).

Build Windows x64 (unpacked, then NSIS)

From packages/desktop, with the SigmaAgents tab extension selected:

Cross-build Windows x64 unpacked app, then one-click NSIS
export OPENCODE_DESKTOP_EXTENSION=/Users/guochunzhong/git/oss/opencode/packages/desktop-tab
cd /Users/guochunzhong/git/oss/opencode/packages/desktop
# 1) Compile out/ for Windows x64 (native modules + CLI target)
OPENCODE_TARGET=windows-x64 bun run build
# 2) Pack unpacked app → ../../dist/desktop/app
OPENCODE_TARGET=windows-x64 bun run pack
# 3) Copy outside trees that pack deletes (packages/desktop/dist and ../../dist/desktop)
rm -rf /tmp/sigma-agents-win-app
cp -R ../../dist/desktop/app /tmp/sigma-agents-win-app
# 4) Build the one-click NSIS installer around that app
OPENCODE_TARGET=windows-x64 OPENCODE_DESKTOP_APP=/tmp/sigma-agents-win-app bun run pack

Result (repo root):

Expected one-click NSIS artifact
dist/desktop/SigmaAgents-win-x64-<version>.exe

electron-builder also writes the same file under packages/desktop/dist/ during the run; pack copies the single .exe into dist/desktop/. Artifact naming comes from artifactName: "SigmaAgents-${os}-${arch}-${version}.${ext}" (${os} → win).

Pin a stamped version with OPENCODE_VERSION on both build and pack. Channel / icons use OPENCODE_CHANNEL (dev default, beta, or prod / latest).

For Windows ARM64, use OPENCODE_TARGET=windows-arm64 on the same sequence (OPENCODE_DESKTOP_APP still required for the .exe).

RUST_TARGET=x86_64-pc-windows-msvc still works as a fallback for target resolution, but OPENCODE_TARGET=windows-x64 is what the desktop README and release jobs use.

Why two pack steps

script/pack.ts branches on Windows:

  • Without OPENCODE_DESKTOP_APP: electron-builder --dir → unpacked win-unpacked → copy to dist/desktop/app.
  • With OPENCODE_DESKTOP_APP: strip dangling symlinks in the prepackaged tree, then electron-builder --prepackaged <app> → NSIS (oneClick: true) → copy the .exe to dist/desktop/.

Do not point OPENCODE_DESKTOP_APP at packages/desktop/dist/win-unpacked or ../../dist/desktop/app. pack deletes both at startup. Use a copy outside those trees (CI uses $RUNNER_TEMP/app/app).

Caveat: dangling .bin symlinks and 7-Zip

On macOS, packaging used to copy thape-config/node_modules/.bin/* as absolute symlinks into packages/desktop/dist/win-unpacked/.... When the NSIS pack deletes dist/, those links dangle. 7-Zip then exits with warnings (errno=2), and electron-builder treats exit code 1 as failure while building @opencodedesktop-…-x64.nsis.7z.

That is handled in-tree now:

  • electron-builder.config.ts excludes node_modules/.bin from the bundled thape-config (Unix package-manager shims are unused at runtime).
  • script/pack.ts removes any remaining dangling symlinks under OPENCODE_DESKTOP_APP before --prepackaged.

You no longer need a manual find … -delete before step 4. Old /tmp/sigma-agents-win-app copies from before that fix still work once step 4 runs, because pack strips the dangling links itself.

Verify native bindings

After build, confirm the main bundle targets Windows x64:

Check the bundled node-pty platform binding
rg "node-pty" out/main/index.js
# should show: @lydell/node-pty-win32-x64

After the first (unpacked) pack:

Check the packaged node-pty native module
npx asar list ../../dist/desktop/app/resources/app.asar | rg "windowsTerminal"
# should exist under: node_modules/@lydell/node-pty-win32-x64/

Without OPENCODE_TARGET (or an equivalent target), you can get an installer that launches but crashes with Cannot find module './windowsTerminal' because the host darwin-arm64 binding was baked in.

Caveat: Code Signing

The .exe built on macOS is not a release-signed Windows artifact. CI packs an unpacked app, signs it on Windows, then runs OPENCODE_DESKTOP_APP=<signed-app> bun run pack, and signs the installer afterward. Local macOS builds skip that pipeline.

CLI build

Build the linux x64 cli
bun run build:node --target=linux-x64
opencode serve --service # to test if thape-config can write

Electron

Comments