From d9d3dfdd08dff51420eb9afb82792fd01ff1d0a5 Mon Sep 17 00:00:00 2001 From: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Date: Thu, 12 Nov 2026 00:10:11 +0000 Subject: [PATCH] fix(emulator): make iPhone Duo fold work in release builds and show serve-sim's real error (#41051) * fix(emulator): let iPhone Duo fold in release builds Release signing gave serve-sim's in-simulator helpers the hardened runtime, which drops the DYLD_ROOT_PATH that simctl spawn hands them, so the Duo HID and accessibility helpers aborted at launch. Sign just those two with only allow-dyld-environment-variables. serve-sim's uncaught errors print its minified source, which contains the "No serve-sim server running" text, so every crash was reported as no active emulator. Report the thrown error line instead. Fixes #40509 * fix(emulator): keep multi-line serve-sim error messages * refactor(emulator): simplify serve-sim failure message parsing --- .../macos/sign-mac-app.mjs | 15 ++++++- src/main/emulator/serve-sim-execution.test.ts | 39 +++++++++++++++++++ src/main/emulator/serve-sim-execution.ts | 10 ++++- 3 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/main/emulator/serve-sim-execution.test.ts diff --git a/config/scripts/desktop-distribution/macos/sign-mac-app.mjs b/config/scripts/desktop-distribution/macos/sign-mac-app.mjs index 046dca0cbfb..d32b58196eb 100644 --- a/config/scripts/desktop-distribution/macos/sign-mac-app.mjs +++ b/config/scripts/desktop-distribution/macos/sign-mac-app.mjs @@ -1,8 +1,21 @@ import { createRequire } from 'node:module' +// serve-sim launches these with `simctl spawn`, whose DYLD_ROOT_PATH the hardened runtime drops (#40509). +const IOS_SIMULATOR_HELPER_PATHS = [ + '/node_modules/serve-sim/dist/simduo/serve-sim-duo-hid', + '/node_modules/serve-sim/dist/simax/serve-sim-ax-settings' +] +const IOS_SIMULATOR_HELPER_ENTITLEMENTS = ['com.apple.security.cs.allow-dyld-environment-variables'] + export function signMacApp(options, { sign, retry }) { + const optionsForFile = (filePath, context) => { + const fileOptions = options.optionsForFile?.(filePath, context) + return IOS_SIMULATOR_HELPER_PATHS.some((helperPath) => filePath.endsWith(helperPath)) + ? { ...fileOptions, entitlements: IOS_SIMULATOR_HELPER_ENTITLEMENTS } + : fileOptions + } // A custom signer bypasses electron-builder's retry wrapper. - return retry(() => sign({ ...options, batchCodesignCalls: true }), { + return retry(() => sign({ ...options, optionsForFile, batchCodesignCalls: true }), { retries: 3, interval: 5000, backoff: 5000 diff --git a/src/main/emulator/serve-sim-execution.test.ts b/src/main/emulator/serve-sim-execution.test.ts new file mode 100644 index 00000000000..e44963e9992 --- /dev/null +++ b/src/main/emulator/serve-sim-execution.test.ts @@ -0,0 +1,39 @@ +import { expect, it, vi } from 'vitest' + +const { runProcessMock } = vi.hoisted(() => ({ runProcessMock: vi.fn() })) + +vi.mock('@orca/process-host', () => ({ runProcess: runProcessMock })) + +import { execServeSimCommand } from './serve-sim-execution' + +// Shape of serve-sim 0.1.47's real `fold 180 -d -q` crash: the minified source line holds +// the "No serve-sim server running" text of every other command (#40509). +const UNCAUGHT_FOLD_FAILURE = `file:///Users/me/Library/Application Support/orca/serve-sim-runtime/1.4.255/node_modules/serve-sim/dist/serve-sim.js:161 +Shutting down...\`);async function GG($,Q){let Z=s(Q);if(!Z)console.error("No serve-sim server running. Run \`serve-sim\` first."),process.exit(1);let J=await C5(Z.wsUrl)} + + +Error: Simulator rejected the fold. An iPhone Duo with guest HID support is required. + at J.onmessage (file:///Users/me/Library/Application Support/orca/serve-sim-runtime/1.4.255/node_modules/serve-sim/dist/serve-sim.js:161:6107) + at callListener (/Users/me/node_modules/ws/lib/event-target.js:290:14) + +Node.js v24.22.1 +` + +it('reports the thrown serve-sim error instead of no active emulator', async () => { + runProcessMock.mockResolvedValueOnce({ + code: 1, + signal: null, + stdout: '', + stderr: UNCAUGHT_FOLD_FAILURE, + timedOut: false + }) + await expect( + execServeSimCommand({ command: 'serve-sim', baseArgs: [], usesElectronAsNode: false }, [ + 'fold', + '180' + ]) + ).rejects.toMatchObject({ + code: 'emulator_error', + message: 'Error: Simulator rejected the fold. An iPhone Duo with guest HID support is required.' + }) +}) diff --git a/src/main/emulator/serve-sim-execution.ts b/src/main/emulator/serve-sim-execution.ts index 6551e76477a..e0103f8000a 100644 --- a/src/main/emulator/serve-sim-execution.ts +++ b/src/main/emulator/serve-sim-execution.ts @@ -171,6 +171,12 @@ export function stripEmulatorTargetArgs(args: string[]): string[] { return stripped } +function serveSimFailureMessage(output: string): string { + // Uncaught throws print serve-sim's minified source (every CLI message) first; keep the thrown error. + const thrown = /^\w*Error: [\s\S]*?(?=^\s+at |^Node\.js v)/m.exec(output) + return thrown?.[0].trim() || output.trim() || 'serve-sim command failed' +} + export async function execServeSimCommand( executable: ServeSimExecutable, args: string[], @@ -198,9 +204,9 @@ export async function execServeSimCommand( ) } if (result.code !== 0 || result.timedOut) { - const message = result.stdout || result.stderr || 'serve-sim command failed' + const message = serveSimFailureMessage(result.stdout || result.stderr) // Why: serve-sim also says "not running" for a missing camera helper; that needs its own message. - if (/no serve-sim server/i.test(message)) { + if (/^no serve-sim server/im.test(message)) { throw new EmulatorError( 'emulator_no_active', 'No active emulator for this worktree — use orca emulator devices/attach or open the pane'