mirror of
https://github.com/stablyai/orca.git
synced 2026-10-08 00:02:38 +00:00
fix: isolate headless Bun daemon scope launches
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { bunOwnedRuntimeArgs } from '../../shared/bun-owned-runtime-args'
|
||||
import { spawnDaemonChildProcess } from './daemon-launched-child-spawn'
|
||||
|
||||
const { spawn, fork } = vi.hoisted(() => ({ spawn: vi.fn(), fork: vi.fn() }))
|
||||
@@ -20,7 +21,11 @@ const options = {
|
||||
macosLoginSessionWatch: false
|
||||
}
|
||||
|
||||
afterEach(() => vi.clearAllMocks())
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.unstubAllEnvs()
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('daemon launch scope ownership', () => {
|
||||
it('only arms lifetime cleanup through the private scope launcher', () => {
|
||||
@@ -49,3 +54,47 @@ describe('daemon launch scope ownership', () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('headless Bun daemon launch', () => {
|
||||
function useBun(): void {
|
||||
vi.spyOn(process, 'versions', 'get').mockReturnValue({ ...process.versions, bun: '1.4.2' })
|
||||
vi.stubEnv('NODE_OPTIONS', '--require=untrusted.js')
|
||||
vi.stubEnv('NODE_PATH', '/untrusted')
|
||||
vi.stubEnv('BUN_OPTIONS', '--preload=untrusted.js')
|
||||
}
|
||||
|
||||
it('isolates scoped Bun launches before the daemon entry', () => {
|
||||
useBun()
|
||||
spawnDaemonChildProcess(options, true)
|
||||
const call = spawn.mock.calls[0][0]
|
||||
const executableIndex = call.args.indexOf(process.execPath)
|
||||
expect(call.args.slice(executableIndex + 1, executableIndex + 5)).toEqual([
|
||||
...bunOwnedRuntimeArgs(),
|
||||
options.forkEntryPath
|
||||
])
|
||||
for (const key of ['ELECTRON_RUN_AS_NODE', 'NODE_OPTIONS', 'NODE_PATH', 'BUN_OPTIONS']) {
|
||||
expect(call.env[key]).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('scrubs direct Bun forks while preserving the selected ConPTY library', () => {
|
||||
useBun()
|
||||
vi.stubEnv('BUN_CONPTY_LIBRARY', '/verified/conpty.dll')
|
||||
spawnDaemonChildProcess(options, false)
|
||||
expect(fork.mock.calls[0][0].env).toEqual(
|
||||
expect.objectContaining({
|
||||
BUN_CONPTY_LIBRARY: '/verified/conpty.dll',
|
||||
ORCA_USER_DATA_PATH: options.userDataPath
|
||||
})
|
||||
)
|
||||
expect(fork.mock.calls[0][0].env.NODE_OPTIONS).toBeUndefined()
|
||||
})
|
||||
|
||||
it('preserves an explicitly selected Node executable', () => {
|
||||
useBun()
|
||||
spawnDaemonChildProcess({ ...options, relocatedExecPath: '/alternate/node' }, true)
|
||||
const call = spawn.mock.calls[0][0]
|
||||
expect(call.args).not.toContain('--no-install')
|
||||
expect(call.env.ELECTRON_RUN_AS_NODE).toBe('1')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { forkProcess, type ForkSpec } from '../../shared/child-process/fork-process'
|
||||
import { spawnProcess, type SpawnedProcess } from '../../shared/child-process/run-process'
|
||||
import { bunOwnedRuntimeArgs } from '../../shared/bun-owned-runtime-args'
|
||||
import { getAppEnvironment } from '../../shared/app-environment'
|
||||
import { buildDurableDaemonScopeCommand } from './daemon-cgroup-scope'
|
||||
import { daemonLogArgs } from './daemon-launch-paths'
|
||||
@@ -57,13 +58,22 @@ export function spawnDaemonChildProcess(
|
||||
): SpawnedProcess {
|
||||
const { forkEntryPath, relocatedExecPath, userDataPath, launchNonce } = options
|
||||
const scriptArgs = buildDaemonScriptArgs(options)
|
||||
const usesBun = Boolean(
|
||||
process.versions.bun && (!relocatedExecPath || relocatedExecPath === process.execPath)
|
||||
)
|
||||
// Why: run as plain Node so Electron's GPU/display init can't interfere with node-pty's posix_spawn of the spawn-helper.
|
||||
const daemonEnv = {
|
||||
const daemonEnv: NodeJS.ProcessEnv = {
|
||||
...process.env,
|
||||
ELECTRON_RUN_AS_NODE: '1',
|
||||
// Why: the detached plain-Node daemon has no AppEnvironment, but shell rcfiles must live outside swept tmp.
|
||||
ORCA_USER_DATA_PATH: userDataPath
|
||||
}
|
||||
if (usesBun) {
|
||||
delete daemonEnv.ELECTRON_RUN_AS_NODE
|
||||
delete daemonEnv.NODE_OPTIONS
|
||||
delete daemonEnv.NODE_PATH
|
||||
delete daemonEnv.BUN_OPTIONS
|
||||
}
|
||||
// Why cwd: detached daemons outlive dev worktrees; userData keeps process.cwd() valid after a repo/worktree is deleted.
|
||||
// Why detached/stdio: detached+unref outlives Electron; stdout 'ignore' (else blocks exit), stderr 'pipe' captures startup crashes lost in v1.4.129-rc.1.
|
||||
const childOptions: Pick<ForkSpec, 'cwd' | 'detached' | 'stdio'> = {
|
||||
@@ -83,7 +93,12 @@ export function spawnDaemonChildProcess(
|
||||
}
|
||||
const scoped = buildDurableDaemonScopeCommand(
|
||||
relocatedExecPath ?? process.execPath,
|
||||
[forkEntryPath, ...scriptArgs, '--fresh-daemon-scope'],
|
||||
[
|
||||
...(usesBun ? bunOwnedRuntimeArgs() : []),
|
||||
forkEntryPath,
|
||||
...scriptArgs,
|
||||
'--fresh-daemon-scope'
|
||||
],
|
||||
launchNonce,
|
||||
daemonEnv
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user