fix(emulator): take serve-sim 0.1.47 so the iOS simulator works on Xcode 27 (#24228)

* fix(emulator): take serve-sim 0.1.47 so the iOS simulator works on Xcode 27

serve-sim 0.1.40's helper binary hard-linked SimulatorKit at its pre-Xcode 27
path, so every iOS emulator start failed with a dyld error on Xcode 27.
0.1.47 replaces that binary with a napi addon that finds SimulatorKit in
either location, and runs the stream helper as a node process.

Match the new helper process (serve-sim.js ... --exit-on-simulator-shutdown)
while still matching legacy serve-sim-bin helpers left by an older Orca, and
mark the package's new standalone helpers executable.

* refactor(emulator): drop redundant serve-sim chmod; the package already ships helpers executable

serve-sim publishes its helpers as 755 and pnpm, fs.cpSync and electron-builder
all preserve the mode, so the executables list and every chmod over it are dead.

* fix(emulator): give the materialized serve-sim runtime its node dependencies

serve-sim 0.1.47's entry imports `ws`. Packaged macOS builds run serve-sim from
a copy under userData, and that copy had no node_modules, so every iOS emulator
attach failed with ERR_MODULE_NOT_FOUND. Dev builds were unaffected because
they run serve-sim straight from pnpm's node_modules.

Lay the copy out as <version>/node_modules/serve-sim and link each dependency
the package declares to the bundle's installed sibling, so transitive deps keep
resolving from the bundle. A runtime in the old flat layout, or one whose links
dangle because the app moved, is rebuilt instead of reused.
This commit is contained in:
Jinwoo Hong
2026-09-30 21:04:31 -04:00
committed by GitHub
parent 6729f1b8e2
commit 2ab179ccf7
8 changed files with 276 additions and 83 deletions
-18
View File
@@ -407,7 +407,6 @@ module.exports = {
verifyPackagedPluginResources(resourcesDir)
finalizePackagedRipgrep(resourcesDir)
chmodUnixCliLaunchers(resourcesDir, context.electronPlatformName)
chmodMacServeSimHelpers(resourcesDir, context.electronPlatformName)
for (const filename of readdirSync(resourcesDir)) {
if (!filename.startsWith('agent-browser-')) {
continue
@@ -718,23 +717,6 @@ function chmodUnixCliLaunchers(resourcesDir, electronPlatformName) {
}
}
function chmodMacServeSimHelpers(resourcesDir, electronPlatformName) {
if (electronPlatformName !== 'darwin') {
return
}
const helperPaths = [
join(resourcesDir, 'serve-sim', 'bin', 'serve-sim-bin'),
join(resourcesDir, 'serve-sim', 'dist', 'simcam', 'serve-sim-camera-helper'),
join(resourcesDir, 'node_modules', 'serve-sim', 'bin', 'serve-sim-bin'),
join(resourcesDir, 'node_modules', 'serve-sim', 'dist', 'simcam', 'serve-sim-camera-helper')
]
for (const helperPath of helperPaths) {
if (existsSync(helperPath)) {
chmodSync(helperPath, 0o755)
}
}
}
async function signMacComputerUseHelper(helperAppPath, packager) {
if (!existsSync(helperAppPath)) {
if (isMacRelease) {
+1 -1
View File
@@ -191,7 +191,7 @@
"proper-lockfile": "4.1.2",
"qrcode": "^1.5.4",
"react-i18next": "17.0.15",
"serve-sim": "0.1.40",
"serve-sim": "0.1.47",
"sherpa-onnx": "1.12.37",
"ssh2": "^1.17.0",
"tldts": "7.4.14",
+12 -6
View File
@@ -176,8 +176,8 @@ importers:
specifier: 17.0.15
version: 17.0.15(i18next@26.3.1(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)
serve-sim:
specifier: 0.1.40
version: 0.1.40(typescript@7.0.2)
specifier: 0.1.47
version: 0.1.47(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)
sherpa-onnx:
specifier: 1.12.37
version: 1.12.37
@@ -6930,9 +6930,9 @@ packages:
resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==}
engines: {node: '>=10'}
serve-sim@0.1.40:
resolution: {integrity: sha512-32JSUriN/EnNR3zx6fmiOKjuFrbA8VHWR2J3dX8T51Z/xkSmrSLOwaz/3W9lPiCIaF2O3LT4qpAhLkp0f1kVaw==}
engines: {node: '>=18'}
serve-sim@0.1.47:
resolution: {integrity: sha512-zDibExbPY6x6Oo173vwtx6zrvVG3464ReC6Q+DxlLqzhL5gmPVJWBwXSh5/BpaYI4PzszaRwL3N47Vjc3lDy9w==}
engines: {node: '>=20'}
hasBin: true
serve-static@2.2.1:
@@ -14368,11 +14368,17 @@ snapshots:
type-fest: 0.13.1
optional: true
serve-sim@0.1.40(typescript@7.0.2):
serve-sim@0.1.47(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2):
dependencies:
inspect-webkit: 0.0.5(typescript@7.0.2)
sonner: 2.0.7(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
ws: 8.21.3
transitivePeerDependencies:
- bufferutil
- react
- react-dom
- typescript
- utf-8-validate
serve-static@2.2.1(supports-color@7.2.0):
dependencies:
+1 -17
View File
@@ -1,13 +1,5 @@
import { runProcess } from '../../shared/child-process/run-process'
import {
accessSync,
chmodSync,
constants,
existsSync,
mkdirSync,
readFileSync,
writeFileSync
} from 'node:fs'
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { app } from 'electron'
import { platform, tmpdir } from 'node:os'
import { delimiter, join } from 'node:path'
@@ -125,14 +117,6 @@ export function resolveServeSimExecutable(): ServeSimExecutable {
const nodeModulesPackageDir = join(app.getAppPath(), 'node_modules', 'serve-sim')
const nodeModulesEntry = join(nodeModulesPackageDir, 'dist', 'serve-sim.js')
if (existsSync(nodeModulesEntry)) {
const helperBin = join(nodeModulesPackageDir, 'bin', 'serve-sim-bin')
if (existsSync(helperBin) && process.platform !== 'win32') {
try {
accessSync(helperBin, constants.X_OK)
} catch {
chmodSync(helperBin, 0o755)
}
}
return { command: process.execPath, baseArgs: [nodeModulesEntry], usesElectronAsNode: true }
}
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
import { parseServeSimHelperProcesses } from './serve-sim-helper-processes'
describe('parseServeSimHelperProcesses', () => {
it('returns exact serve-sim-bin helper processes from ps output', () => {
it('returns legacy serve-sim-bin helper processes from ps output', () => {
const psOutput = `
101 /Applications/serve-sim/bin/serve-sim-bin UDID-1 --port 3100
102 /Applications/serve-sim/bin/serve-sim UDID-1 --port 3100
@@ -22,6 +22,29 @@ describe('parseServeSimHelperProcesses', () => {
])
})
it('returns node-hosted serve-sim helpers and skips one-shot serve-sim commands', () => {
const runtime = '/Users/me/Library/Application Support/Orca/serve-sim-runtime/1.4.0'
const psOutput = `
301 /Applications/Orca.app/Contents/MacOS/Orca ${runtime}/dist/serve-sim.js UDID-1 --port 3100 --host 127.0.0.1 --exit-on-simulator-shutdown
302 /Applications/Orca.app/Contents/MacOS/Orca ${runtime}/dist/serve-sim.js tap 0.5 0.5 -d UDID-1
303 /Applications/Orca.app/Contents/MacOS/Orca ${runtime}/dist/serve-sim.js --detach -q UDID-1
304 /usr/local/bin/serve-sim UDID-2 --port 3101 --host 127.0.0.1 --exit-on-simulator-shutdown
305 node /tmp/not-serve-sim.js UDID-3 --exit-on-simulator-shutdown
`
expect(parseServeSimHelperProcesses(psOutput)).toEqual([
{
pid: 301,
command: `/Applications/Orca.app/Contents/MacOS/Orca ${runtime}/dist/serve-sim.js UDID-1 --port 3100 --host 127.0.0.1 --exit-on-simulator-shutdown`
},
{
pid: 304,
command:
'/usr/local/bin/serve-sim UDID-2 --port 3101 --host 127.0.0.1 --exit-on-simulator-shutdown'
}
])
})
it('scans ps output without line-array splitting', () => {
const splitSpy = vi.spyOn(String.prototype, 'split')
try {
@@ -8,6 +8,8 @@ export type ServeSimHelperProcess = {
command: string
}
const SERVE_SIM_DETACHED_HELPER_FLAG = '--exit-on-simulator-shutdown'
type ServeSimHelperProcessLookupOptions = {
helperPid?: number
includeOrphaned?: boolean
@@ -25,6 +27,17 @@ function execFileText(command: string, args: string[]): Promise<string> {
})
}
// Why: serve-sim >= 0.1.47 hosts the helper in node; keep serve-sim-bin so a pre-update helper is still reaped.
function isServeSimHelperCommand(command: string): boolean {
if (/(^|\/)serve-sim-bin(?:\s|$)/.test(command)) {
return true
}
return (
commandContainsToken(command, SERVE_SIM_DETACHED_HELPER_FLAG) &&
/(^|\/)serve-sim(?:\.js)?\s/.test(command)
)
}
export function parseServeSimHelperProcesses(psOutput: string): ServeSimHelperProcess[] {
const helpers: ServeSimHelperProcess[] = []
for (const line of iterateProcessOutputLines(psOutput)) {
@@ -34,7 +47,7 @@ export function parseServeSimHelperProcesses(psOutput: string): ServeSimHelperPr
}
const pid = Number(match[1])
const command = match[2] ?? ''
if (!Number.isInteger(pid) || !/(^|\/)serve-sim-bin(?:\s|$)/.test(command)) {
if (!Number.isInteger(pid) || !isServeSimHelperCommand(command)) {
continue
}
helpers.push({ pid, command })
@@ -1,27 +1,48 @@
import { mkdirSync, writeFileSync } from 'node:fs'
import { mkdtemp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'
import { cpSync, mkdirSync, readdirSync, realpathSync, symlinkSync, writeFileSync } from 'node:fs'
import {
mkdtemp,
mkdir,
readFile,
readdir,
readlink,
rename,
rm,
stat,
writeFile
} from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { runProcess } from '../../shared/child-process/run-process'
import { materializeServeSimRuntime } from './serve-sim-runtime-materializer'
const DYLIB_CONTENT = Buffer.from('signed-simcam-dylib-mach-o-bytes')
async function createBundledServeSimPackage(root: string): Promise<string> {
const packageDir = join(root, 'bundled-serve-sim')
const nodeModulesDir = join(root, 'bundle', 'node_modules')
const packageDir = join(nodeModulesDir, 'serve-sim')
await mkdir(join(packageDir, 'dist', 'simcam'), { recursive: true })
await mkdir(join(packageDir, 'bin'), { recursive: true })
await mkdir(join(nodeModulesDir, 'ws'), { recursive: true })
await writeFile(join(nodeModulesDir, 'ws', 'package.json'), '{"name":"ws"}')
await writeFile(
join(packageDir, 'package.json'),
JSON.stringify({ name: 'serve-sim', dependencies: { ws: '^8', 'not-bundled': '^1' } })
)
await writeFile(join(packageDir, 'dist', 'serve-sim.js'), 'console.log("serve-sim")')
await writeFile(join(packageDir, 'dist', 'simcam', 'libSimCameraInjector.dylib'), DYLIB_CONTENT, {
mode: 0o644
})
await writeFile(join(packageDir, 'dist', 'simcam', 'serve-sim-camera-helper'), 'helper', {
mode: 0o644
mode: 0o755
})
await writeFile(join(packageDir, 'bin', 'serve-sim-bin'), 'bin', { mode: 0o644 })
return packageDir
}
function symlinkDir(target: string, path: string): void {
// 'junction' lets Windows create the link without symlink privilege; POSIX ignores it.
symlinkSync(target, path, 'junction')
}
describe('materializeServeSimRuntime', () => {
const cleanupPaths: string[] = []
@@ -49,19 +70,15 @@ describe('materializeServeSimRuntime', () => {
clearQuarantine
})
expect(materialized).toBe(join(root, 'runtime', '1.2.3'))
expect(materialized).toBe(join(root, 'runtime', '1.2.3', 'node_modules', 'serve-sim'))
// The dylib must be byte-identical to the bundled (Developer-ID-signed) copy.
const dylibPath = join(materialized!, 'dist', 'simcam', 'libSimCameraInjector.dylib')
expect(await readFile(dylibPath)).toEqual(DYLIB_CONTENT)
expect(clearQuarantine).toHaveBeenCalledTimes(1)
expect(clearQuarantine).toHaveBeenCalledWith(expect.stringContaining('.staging-1.2.3-'))
if (process.platform !== 'win32') {
for (const executable of [
join(materialized!, 'bin', 'serve-sim-bin'),
join(materialized!, 'dist', 'simcam', 'serve-sim-camera-helper')
]) {
expect(((await stat(executable)).mode & 0o111) !== 0).toBe(true)
}
const helper = join(materialized!, 'dist', 'simcam', 'serve-sim-camera-helper')
expect(((await stat(helper)).mode & 0o111) !== 0).toBe(true)
}
})
@@ -97,7 +114,7 @@ describe('materializeServeSimRuntime', () => {
clearQuarantine: () => {}
})
expect(materialized).toBe(join(targetRootDir, '1.2.3'))
expect(materialized).toBe(join(targetRootDir, '1.2.3', 'node_modules', 'serve-sim'))
await expect(stat(join(targetRootDir, '1.0.0'))).rejects.toThrow()
})
@@ -105,22 +122,24 @@ describe('materializeServeSimRuntime', () => {
const root = await createRoot()
const bundledPackageDir = await createBundledServeSimPackage(root)
const targetRootDir = join(root, 'runtime')
const targetDir = join(targetRootDir, '1.2.3')
const winnerNodeModulesDir = join(targetRootDir, '1.2.3', 'node_modules')
const winnerPackageDir = join(winnerNodeModulesDir, 'serve-sim')
// Simulate another instance finishing first: right before our rename, drop a
// complete target dir in place so renameSync fails but the entry exists.
// complete target dir in place so renameSync fails but the runtime is current.
const materialized = materializeServeSimRuntime({
bundledPackageDir,
targetRootDir,
version: '1.2.3',
clearQuarantine: () => {
mkdirSync(join(targetDir, 'dist'), { recursive: true })
writeFileSync(join(targetDir, 'dist', 'serve-sim.js'), 'winner')
mkdirSync(join(winnerPackageDir, 'dist'), { recursive: true })
writeFileSync(join(winnerPackageDir, 'dist', 'serve-sim.js'), 'winner')
symlinkDir(join(root, 'bundle', 'node_modules', 'ws'), join(winnerNodeModulesDir, 'ws'))
}
})
expect(materialized).toBe(targetDir)
expect(await readFile(join(targetDir, 'dist', 'serve-sim.js'), 'utf8')).toBe('winner')
expect(materialized).toBe(winnerPackageDir)
expect(await readFile(join(winnerPackageDir, 'dist', 'serve-sim.js'), 'utf8')).toBe('winner')
const leftovers = (await readdir(targetRootDir)).filter((name) => name.startsWith('.staging'))
expect(leftovers).toEqual([])
})
@@ -157,4 +176,121 @@ describe('materializeServeSimRuntime', () => {
expect(materialized).toBeNull()
})
it('links each bundled dependency next to the copy and skips ones the bundle lacks', async () => {
const root = await createRoot()
const bundledPackageDir = await createBundledServeSimPackage(root)
const materialized = materializeServeSimRuntime({
bundledPackageDir,
targetRootDir: join(root, 'runtime'),
version: '1.2.3',
clearQuarantine: () => {}
})
const nodeModulesDir = join(root, 'runtime', '1.2.3', 'node_modules')
expect(materialized).toBe(join(nodeModulesDir, 'serve-sim'))
expect(realpathSync(join(nodeModulesDir, 'ws'))).toBe(
realpathSync(join(root, 'bundle', 'node_modules', 'ws'))
)
expect((await readdir(nodeModulesDir)).sort()).toEqual(['serve-sim', 'ws'])
})
it('clears quarantine on the copied package before linking into the bundle', async () => {
const root = await createRoot()
const bundledPackageDir = await createBundledServeSimPackage(root)
const seenEntries: string[][] = []
materializeServeSimRuntime({
bundledPackageDir,
targetRootDir: join(root, 'runtime'),
version: '1.2.3',
clearQuarantine: (dir) => {
seenEntries.push([dir, ...readdirSync(join(dir, '..'))])
}
})
expect(seenEntries).toHaveLength(1)
expect(seenEntries[0][0]).toMatch(/\.staging-1\.2\.3-\d+[/\\]node_modules[/\\]serve-sim$/)
expect(seenEntries[0].slice(1)).toEqual(['serve-sim'])
})
it('rebuilds a same-version runtime left in the old flat layout', async () => {
const root = await createRoot()
const bundledPackageDir = await createBundledServeSimPackage(root)
const targetRootDir = join(root, 'runtime')
await mkdir(join(targetRootDir, '1.2.3', 'dist'), { recursive: true })
await writeFile(join(targetRootDir, '1.2.3', 'dist', 'serve-sim.js'), 'old layout')
const materialized = materializeServeSimRuntime({
bundledPackageDir,
targetRootDir,
version: '1.2.3',
clearQuarantine: () => {}
})
expect(materialized).toBe(join(targetRootDir, '1.2.3', 'node_modules', 'serve-sim'))
await expect(stat(join(targetRootDir, '1.2.3', 'dist'))).rejects.toThrow()
})
it('rebuilds when the app moved and the dependency links dangle', async () => {
const root = await createRoot()
const bundledPackageDir = await createBundledServeSimPackage(root)
const targetRootDir = join(root, 'runtime')
materializeServeSimRuntime({
bundledPackageDir,
targetRootDir,
version: '1.2.3',
clearQuarantine: () => {}
})
await rename(join(root, 'bundle'), join(root, 'moved-bundle'))
const movedPackageDir = join(root, 'moved-bundle', 'node_modules', 'serve-sim')
const materialized = materializeServeSimRuntime({
bundledPackageDir: movedPackageDir,
targetRootDir,
version: '1.2.3',
clearQuarantine: () => {}
})
expect(materialized).toBe(join(targetRootDir, '1.2.3', 'node_modules', 'serve-sim'))
expect(await readlink(join(targetRootDir, '1.2.3', 'node_modules', 'ws'))).toContain(
'moved-bundle'
)
})
// Regression: serve-sim 0.1.47's ESM entry imports `ws`, which a bare package copy cannot resolve.
it('lets the real serve-sim entry resolve every declared dependency from the copy', async () => {
const root = await createRoot()
const installedPackageDir = realpathSync(join(process.cwd(), 'node_modules', 'serve-sim'))
const manifest = JSON.parse(await readFile(join(installedPackageDir, 'package.json'), 'utf8'))
const dependencyNames = Object.keys(manifest.dependencies ?? {})
expect(dependencyNames).toContain('ws')
// Mirror the packaged app: a real serve-sim dir with its dependencies hoisted beside it.
const bundledNodeModulesDir = join(root, 'Resources', 'node_modules')
const bundledPackageDir = join(bundledNodeModulesDir, 'serve-sim')
cpSync(installedPackageDir, bundledPackageDir, { recursive: true })
for (const name of dependencyNames) {
symlinkDir(join(installedPackageDir, '..', name), join(bundledNodeModulesDir, name))
}
const materialized = materializeServeSimRuntime({
bundledPackageDir,
targetRootDir: join(root, 'runtime'),
version: '1.2.3',
clearQuarantine: () => {}
})
expect(materialized).not.toBeNull()
// Probe from the entry's own directory so ESM resolution walks up exactly as serve-sim.js does.
const probePath = join(materialized!, 'dist', 'orca-dependency-probe.mjs')
await writeFile(
probePath,
`for (const name of ${JSON.stringify(dependencyNames)}) import.meta.resolve(name)\n`
)
const result = await runProcess({ program: process.execPath, args: [probePath] })
expect(result.stderr).toBe('')
expect(result.code).toBe(0)
})
})
@@ -1,6 +1,16 @@
import { execFileSync } from 'node:child_process'
import { chmodSync, cpSync, existsSync, mkdirSync, readdirSync, renameSync, rmSync } from 'node:fs'
import { join } from 'node:path'
import {
cpSync,
existsSync,
mkdirSync,
readdirSync,
readFileSync,
realpathSync,
renameSync,
rmSync,
symlinkSync
} from 'node:fs'
import { dirname, join } from 'node:path'
export type ServeSimRuntimeMaterializerOptions = {
bundledPackageDir: string
@@ -9,11 +19,6 @@ export type ServeSimRuntimeMaterializerOptions = {
clearQuarantine?: (dir: string) => void
}
const EXECUTABLE_RELATIVE_PATHS = [
join('bin', 'serve-sim-bin'),
join('dist', 'simcam', 'serve-sim-camera-helper')
]
function defaultClearQuarantine(dir: string): void {
if (process.platform !== 'darwin') {
return
@@ -47,45 +52,89 @@ function pruneStaleServeSimRuntimes(targetRootDir: string, keepVersion: string):
}
}
function readDependencyNames(packageDir: string): string[] {
const manifest: unknown = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'))
if (!manifest || typeof manifest !== 'object' || !('dependencies' in manifest)) {
return []
}
const { dependencies } = manifest
return dependencies && typeof dependencies === 'object' ? Object.keys(dependencies) : []
}
// Why: the copy has no node_modules of its own, so its bare imports (e.g. `ws`) must resolve
// through links to the bundle's installed siblings; a dangling link (moved or translocated app) is stale.
function bundledDependencyLinks(
bundledPackageDir: string,
runtimeNodeModulesDir: string
): { linkPath: string; targetPath: string }[] {
const bundledNodeModulesDir = dirname(realpathSync(bundledPackageDir))
return readDependencyNames(bundledPackageDir)
.map((name) => ({
linkPath: join(runtimeNodeModulesDir, name),
targetPath: join(bundledNodeModulesDir, name)
}))
.filter(({ targetPath }) => existsSync(targetPath))
}
function isMaterializedRuntimeCurrent(bundledPackageDir: string, versionDir: string): boolean {
const nodeModulesDir = join(versionDir, 'node_modules')
if (!existsSync(join(nodeModulesDir, 'serve-sim', 'dist', 'serve-sim.js'))) {
return false
}
try {
return bundledDependencyLinks(bundledPackageDir, nodeModulesDir).every(
({ linkPath, targetPath }) => realpathSync(linkPath) === realpathSync(targetPath)
)
} catch {
return false
}
}
// Copies the bundled serve-sim package to a per-version directory outside the
// signed app bundle and strips quarantine, so the camera dylib injected from
// it is not subject to Gatekeeper assessment. The bundled dylib stays signed
// and in place (it must, or the app fails notarization) — this only relocates
// the copy that actually gets DYLD-injected. serve-sim resolves the dylib and
// helper relative to its own entry, so the whole package moves together.
// Layout: <version>/node_modules/serve-sim plus links to its bundled dependencies.
export function materializeServeSimRuntime(
options: ServeSimRuntimeMaterializerOptions
): string | null {
const { bundledPackageDir, targetRootDir, version } = options
const clearQuarantine = options.clearQuarantine ?? defaultClearQuarantine
const targetDir = join(targetRootDir, version)
const entryPath = join(targetDir, 'dist', 'serve-sim.js')
if (existsSync(entryPath)) {
return targetDir
const packageDir = join(targetDir, 'node_modules', 'serve-sim')
if (isMaterializedRuntimeCurrent(bundledPackageDir, targetDir)) {
return packageDir
}
const stagingDir = join(targetRootDir, `.staging-${version}-${process.pid}`)
const stagingNodeModulesDir = join(stagingDir, 'node_modules')
try {
mkdirSync(targetRootDir, { recursive: true })
pruneStaleServeSimRuntimes(targetRootDir, version)
rmSync(stagingDir, { recursive: true, force: true })
rmSync(targetDir, { recursive: true, force: true })
cpSync(bundledPackageDir, stagingDir, { recursive: true })
for (const relativePath of EXECUTABLE_RELATIVE_PATHS) {
const executablePath = join(stagingDir, relativePath)
if (existsSync(executablePath)) {
chmodSync(executablePath, 0o755)
}
const stagingPackageDir = join(stagingNodeModulesDir, 'serve-sim')
// Why realpath: cpSync copies a symlinked package dir (pnpm layout) as a link back into the bundle.
cpSync(realpathSync(bundledPackageDir), stagingPackageDir, { recursive: true })
// Why before linking: the recursive xattr walk must not reach into the signed bundle.
clearQuarantine(stagingPackageDir)
for (const { linkPath, targetPath } of bundledDependencyLinks(
bundledPackageDir,
stagingNodeModulesDir
)) {
mkdirSync(dirname(linkPath), { recursive: true })
symlinkSync(targetPath, linkPath, 'junction')
}
clearQuarantine(stagingDir)
try {
renameSync(stagingDir, targetDir)
} catch (error) {
// Another app instance sharing userData may have finished first.
if (!existsSync(entryPath)) {
if (!isMaterializedRuntimeCurrent(bundledPackageDir, targetDir)) {
throw error
}
}
return existsSync(entryPath) ? targetDir : null
return isMaterializedRuntimeCurrent(bundledPackageDir, targetDir) ? packageDir : null
} catch {
return null
} finally {