From 4a76956257e2e0ba2a0d53d091792b108e4eb59c Mon Sep 17 00:00:00 2001 From: sudacode Date: Tue, 11 Aug 2026 18:51:24 -0700 Subject: [PATCH] refactor(launcher): move detached app spawning into runtime utilities --- src/main-entry-runtime.test.ts | 13 +++++++++++++ src/main-entry-runtime.ts | 18 ++++++++++++++++++ src/main-entry.ts | 19 +------------------ 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/src/main-entry-runtime.test.ts b/src/main-entry-runtime.test.ts index 2fbcf1e3..3e95c9b9 100644 --- a/src/main-entry-runtime.test.ts +++ b/src/main-entry-runtime.test.ts @@ -25,8 +25,21 @@ import { applyBackgroundBootstrapCommandLineSwitches, applyEarlyLinuxCommandLineSwitches, resolveLinuxPasswordStoreValue, + spawnDetachedApp, } from './main-entry-runtime'; +test('detached app launch policy stays in the startup runtime utilities', () => { + const entrySource = fs.readFileSync(path.join(process.cwd(), 'src/main-entry.ts'), 'utf8'); + const runtimeSource = fs.readFileSync( + path.join(process.cwd(), 'src/main-entry-runtime.ts'), + 'utf8', + ); + + assert.equal(typeof spawnDetachedApp, 'function'); + assert.doesNotMatch(entrySource, /function spawnDetachedApp/); + assert.match(runtimeSource, /child\.unref\(\)/); +}); + test('background bootstrap exits through Electron so Chromium children shut down', () => { const exitCodes: number[] = []; exitBackgroundBootstrap({ exit: (code) => exitCodes.push(code) }); diff --git a/src/main-entry-runtime.ts b/src/main-entry-runtime.ts index 6854ff5f..0816fff4 100644 --- a/src/main-entry-runtime.ts +++ b/src/main-entry-runtime.ts @@ -1,7 +1,9 @@ import fs from 'node:fs'; import os from 'node:os'; +import { spawn } from 'node:child_process'; import { CliArgs, hasExplicitCommand, parseArgs, shouldStartApp } from './cli/args'; import { resolveConfigDir } from './config/path-resolution'; +import { resolveAppImageMountKeepaliveInvocation } from './main/appimage-mount-keepalive'; const BACKGROUND_ARG = '--background'; const START_ARG = '--start'; @@ -265,6 +267,22 @@ export function exitBackgroundBootstrap(app: BackgroundBootstrapAppLike): void { app.exit(0); } +export function spawnDetachedApp(childArgs: string[], env: NodeJS.ProcessEnv): void { + const keepalive = resolveAppImageMountKeepaliveInvocation(env); + const child = keepalive + ? spawn(keepalive.command, [...keepalive.args, ...childArgs], { + detached: true, + stdio: 'ignore', + env, + }) + : spawn(process.execPath, childArgs, { + detached: true, + stdio: 'ignore', + env, + }); + child.unref(); +} + export function shouldHandleHelpOnlyAtEntry(argv: string[], env: NodeJS.ProcessEnv): boolean { if (env.ELECTRON_RUN_AS_NODE === '1') return false; const args = parseCliArgs(argv); diff --git a/src/main-entry.ts b/src/main-entry.ts index 9174b84d..4b53efbc 100644 --- a/src/main-entry.ts +++ b/src/main-entry.ts @@ -1,5 +1,4 @@ import os from 'node:os'; -import { spawn } from 'node:child_process'; import { app, dialog, shell } from 'electron'; import { printHelp } from './cli/help'; import { @@ -20,9 +19,9 @@ import { shouldHandleHelpOnlyAtEntry, shouldHandleLaunchMpvAtEntry, shouldHandleStatsDaemonCommandAtEntry, + spawnDetachedApp, } from './main-entry-runtime'; import { requestSingleInstanceLockEarly } from './main/early-single-instance'; -import { resolveAppImageMountKeepaliveInvocation } from './main/appimage-mount-keepalive'; import { readConfiguredWindowsMpvLaunch } from './main-entry-launch-config'; import { isAppControlServerAvailable, sendAppControlCommand } from './shared/app-control-client'; import { @@ -75,22 +74,6 @@ function applySanitizedEnv(sanitizedEnv: NodeJS.ProcessEnv): void { } } -function spawnDetachedApp(childArgs: string[], env: NodeJS.ProcessEnv): void { - const keepalive = resolveAppImageMountKeepaliveInvocation(env); - const child = keepalive - ? spawn(keepalive.command, [...keepalive.args, ...childArgs], { - detached: true, - stdio: 'ignore', - env, - }) - : spawn(process.execPath, childArgs, { - detached: true, - stdio: 'ignore', - env, - }); - child.unref(); -} - function resolveBundledWindowsMpvPluginEntrypoint(): string | undefined { return ( resolvePackagedRuntimePluginPath({