diff --git a/src/electron.ts b/src/electron.ts index 43d0482..3698879 100644 --- a/src/electron.ts +++ b/src/electron.ts @@ -170,5 +170,32 @@ export function startElectron(root: string | undefined): ChildProcess { // successful exit. Map a signal death to a non-zero status instead. ps.on('close', (code, signal) => process.exit(code ?? (signal ? 1 : 0))) + forwardTerminationSignals(ps) + return ps } + +let signalHandlersInstalled = false +let currentElectronPs: ChildProcess | undefined + +// Forward termination signals to the Electron child, matching electron's own +// CLI wrapper (node_modules/electron/cli.js). Without this, Ctrl-C kills the +// parent immediately and the Electron child can get stuck on macOS when a +// before-quit handler calls preventDefault then later app.quit(). See #899. +function forwardTerminationSignals(ps: ChildProcess): void { + currentElectronPs = ps + if (signalHandlersInstalled) return + signalHandlersInstalled = true + for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) { + process.on(signal, () => { + const target = currentElectronPs + if (target && target.exitCode === null && !target.killed) { + try { + target.kill(signal) + } catch { + // already gone + } + } + }) + } +}