fix: handle detached launch spawn errors (#3957)
This commit is contained in:
parent
964dbbc4e6
commit
2c2ac2edbf
|
|
@ -1,3 +1,4 @@
|
|||
import { EventEmitter } from 'events'
|
||||
import { resolve } from 'path'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
|
|
@ -9,7 +10,12 @@ vi.mock('child_process', () => ({
|
|||
spawn: spawnMock
|
||||
}))
|
||||
|
||||
import { serveOrcaApp } from './launch'
|
||||
import { launchOrcaApp, serveOrcaApp } from './launch'
|
||||
|
||||
class FakeChildProcess extends EventEmitter {
|
||||
kill = vi.fn()
|
||||
unref = vi.fn()
|
||||
}
|
||||
|
||||
describe('serveOrcaApp', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -147,3 +153,27 @@ describe('serveOrcaApp', () => {
|
|||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('launchOrcaApp', () => {
|
||||
beforeEach(() => {
|
||||
spawnMock.mockReset()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env.ORCA_OPEN_COMMAND
|
||||
delete process.env.ORCA_APP_EXECUTABLE
|
||||
delete process.env.ORCA_APP_EXECUTABLE_NEEDS_APP_ROOT
|
||||
})
|
||||
|
||||
it('handles asynchronous detached spawn errors without throwing', async () => {
|
||||
process.env.ORCA_APP_EXECUTABLE = '/missing/Orca'
|
||||
const child = new FakeChildProcess()
|
||||
spawnMock.mockReturnValue(child)
|
||||
|
||||
launchOrcaApp()
|
||||
child.emit('error', new Error('ENOENT'))
|
||||
await Promise.resolve()
|
||||
|
||||
expect(child.unref).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -5,22 +5,16 @@ import { RuntimeClientError } from './types'
|
|||
export function launchOrcaApp(): void {
|
||||
const overrideCommand = process.env.ORCA_OPEN_COMMAND
|
||||
if (typeof overrideCommand === 'string' && overrideCommand.trim().length > 0) {
|
||||
spawnProcess(overrideCommand, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
shell: true
|
||||
}).unref()
|
||||
spawnDetached(overrideCommand, [], { shell: true })
|
||||
return
|
||||
}
|
||||
|
||||
const overrideExecutable = process.env.ORCA_APP_EXECUTABLE
|
||||
if (typeof overrideExecutable === 'string' && overrideExecutable.trim().length > 0) {
|
||||
spawnProcess(overrideExecutable, getExecutableAppArgs(), {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
spawnDetached(overrideExecutable, getExecutableAppArgs(), {
|
||||
...getExecutableSpawnOptions(overrideExecutable),
|
||||
env: stripElectronRunAsNode(process.env)
|
||||
}).unref()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -31,20 +25,16 @@ export function launchOrcaApp(): void {
|
|||
// Why: launching the inner MacOS binary directly can trigger macOS app
|
||||
// launch failures and bypass normal bundle lifecycle. The public
|
||||
// packaged CLI should re-open the .app the same way Finder does.
|
||||
spawnProcess('open', [appBundlePath], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
spawnDetached('open', [appBundlePath], {
|
||||
env: stripElectronRunAsNode(process.env)
|
||||
}).unref()
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
spawnProcess(process.execPath, [], {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
spawnDetached(process.execPath, [], {
|
||||
env: stripElectronRunAsNode(process.env)
|
||||
}).unref()
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +44,18 @@ export function launchOrcaApp(): void {
|
|||
)
|
||||
}
|
||||
|
||||
function spawnDetached(command: string, args: string[], options: SpawnOptions): void {
|
||||
const child = spawnProcess(command, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
...options
|
||||
})
|
||||
// Why: detached launch errors are reported asynchronously after this function
|
||||
// returns; openOrca already reports the user-facing timeout if startup fails.
|
||||
child.once('error', () => {})
|
||||
child.unref()
|
||||
}
|
||||
|
||||
export function serveOrcaApp(
|
||||
args: {
|
||||
json?: boolean
|
||||
|
|
|
|||
Loading…
Reference in New Issue