From 5241ff3fcc0673d0dd920dacd1e06d5d5d1a4748 Mon Sep 17 00:00:00 2001 From: sudacode Date: Sun, 1 Mar 2026 15:06:34 -0800 Subject: [PATCH] fix: suppress startup warnings for help output --- src/core/services/app-lifecycle.test.ts | 111 ++++++++++++++++++++++++ src/core/services/app-lifecycle.ts | 12 +-- src/main-entry-runtime.test.ts | 39 +++++++++ src/main-entry-runtime.ts | 42 +++++++++ src/main-entry.ts | 39 ++++----- 5 files changed, 216 insertions(+), 27 deletions(-) create mode 100644 src/core/services/app-lifecycle.test.ts create mode 100644 src/main-entry-runtime.test.ts create mode 100644 src/main-entry-runtime.ts diff --git a/src/core/services/app-lifecycle.test.ts b/src/core/services/app-lifecycle.test.ts new file mode 100644 index 0000000..ebaa4b7 --- /dev/null +++ b/src/core/services/app-lifecycle.test.ts @@ -0,0 +1,111 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { CliArgs } from '../../cli/args'; +import { AppLifecycleServiceDeps, startAppLifecycle } from './app-lifecycle'; + +function makeArgs(overrides: Partial = {}): CliArgs { + return { + background: false, + start: false, + stop: false, + toggle: false, + toggleVisibleOverlay: false, + settings: false, + show: false, + hide: false, + showVisibleOverlay: false, + hideVisibleOverlay: false, + copySubtitle: false, + copySubtitleMultiple: false, + mineSentence: false, + mineSentenceMultiple: false, + updateLastCardFromClipboard: false, + refreshKnownWords: false, + toggleSecondarySub: false, + triggerFieldGrouping: false, + triggerSubsync: false, + markAudioCard: false, + openRuntimeOptions: false, + anilistStatus: false, + anilistLogout: false, + anilistSetup: false, + anilistRetryQueue: false, + jellyfin: false, + jellyfinLogin: false, + jellyfinLogout: false, + jellyfinLibraries: false, + jellyfinItems: false, + jellyfinSubtitles: false, + jellyfinSubtitleUrlsOnly: false, + jellyfinPlay: false, + jellyfinRemoteAnnounce: false, + jellyfinPreviewAuth: false, + texthooker: false, + help: false, + autoStartOverlay: false, + generateConfig: false, + backupOverwrite: false, + debug: false, + ...overrides, + }; +} + +function createDeps(overrides: Partial = {}) { + const calls: string[] = []; + let lockCalls = 0; + + const deps: AppLifecycleServiceDeps = { + shouldStartApp: () => false, + parseArgs: () => makeArgs(), + requestSingleInstanceLock: () => { + lockCalls += 1; + return true; + }, + quitApp: () => { + calls.push('quitApp'); + }, + onSecondInstance: () => {}, + handleCliCommand: () => {}, + printHelp: () => { + calls.push('printHelp'); + }, + logNoRunningInstance: () => { + calls.push('logNoRunningInstance'); + }, + whenReady: () => {}, + onWindowAllClosed: () => {}, + onWillQuit: () => {}, + onActivate: () => {}, + isDarwinPlatform: () => false, + onReady: async () => {}, + onWillQuitCleanup: () => {}, + shouldRestoreWindowsOnActivate: () => false, + restoreWindowsOnActivate: () => {}, + shouldQuitOnWindowAllClosed: () => true, + ...overrides, + }; + + return { deps, calls, getLockCalls: () => lockCalls }; +} + +test('startAppLifecycle handles --help without acquiring single-instance lock', () => { + const { deps, calls, getLockCalls } = createDeps({ + shouldStartApp: () => false, + }); + + startAppLifecycle(makeArgs({ help: true }), deps); + + assert.equal(getLockCalls(), 0); + assert.deepEqual(calls, ['printHelp', 'quitApp']); +}); + +test('startAppLifecycle still acquires lock for startup commands', () => { + const { deps, getLockCalls } = createDeps({ + shouldStartApp: () => true, + whenReady: () => {}, + }); + + startAppLifecycle(makeArgs({ start: true }), deps); + + assert.equal(getLockCalls(), 1); +}); diff --git a/src/core/services/app-lifecycle.ts b/src/core/services/app-lifecycle.ts index da4536a..83dbdfe 100644 --- a/src/core/services/app-lifecycle.ts +++ b/src/core/services/app-lifecycle.ts @@ -87,6 +87,12 @@ export function createAppLifecycleDepsRuntime( } export function startAppLifecycle(initialArgs: CliArgs, deps: AppLifecycleServiceDeps): void { + if (initialArgs.help && !deps.shouldStartApp(initialArgs)) { + deps.printHelp(); + deps.quitApp(); + return; + } + const gotTheLock = deps.requestSingleInstanceLock(); if (!gotTheLock) { deps.quitApp(); @@ -101,12 +107,6 @@ export function startAppLifecycle(initialArgs: CliArgs, deps: AppLifecycleServic } }); - if (initialArgs.help && !deps.shouldStartApp(initialArgs)) { - deps.printHelp(); - deps.quitApp(); - return; - } - if (!deps.shouldStartApp(initialArgs)) { if (initialArgs.stop && !initialArgs.start) { deps.quitApp(); diff --git a/src/main-entry-runtime.test.ts b/src/main-entry-runtime.test.ts new file mode 100644 index 0000000..c8d127e --- /dev/null +++ b/src/main-entry-runtime.test.ts @@ -0,0 +1,39 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { + sanitizeHelpEnv, + sanitizeBackgroundEnv, + shouldDetachBackgroundLaunch, + shouldHandleHelpOnlyAtEntry, +} from './main-entry-runtime'; + +test('shouldHandleHelpOnlyAtEntry detects help-only invocation', () => { + assert.equal(shouldHandleHelpOnlyAtEntry(['--help'], {}), true); + assert.equal(shouldHandleHelpOnlyAtEntry(['--help', '--start'], {}), false); + assert.equal(shouldHandleHelpOnlyAtEntry(['--start'], {}), false); + assert.equal(shouldHandleHelpOnlyAtEntry(['--help'], { ELECTRON_RUN_AS_NODE: '1' }), false); +}); + +test('sanitizeHelpEnv suppresses warnings and lsfg layer', () => { + const env = sanitizeHelpEnv({ + VK_INSTANCE_LAYERS: 'foo:lsfg-vk:bar', + }); + assert.equal(env.NODE_NO_WARNINGS, '1'); + assert.equal('VK_INSTANCE_LAYERS' in env, false); +}); + +test('sanitizeBackgroundEnv marks background child and keeps warning suppression', () => { + const env = sanitizeBackgroundEnv({ + VK_INSTANCE_LAYERS: 'foo:lsfg-vk:bar', + }); + assert.equal(env.SUBMINER_BACKGROUND_CHILD, '1'); + assert.equal(env.NODE_NO_WARNINGS, '1'); + assert.equal('VK_INSTANCE_LAYERS' in env, false); +}); + +test('shouldDetachBackgroundLaunch only for first background invocation', () => { + assert.equal(shouldDetachBackgroundLaunch(['--background'], {}), true); + assert.equal(shouldDetachBackgroundLaunch(['--background'], { SUBMINER_BACKGROUND_CHILD: '1' }), false); + assert.equal(shouldDetachBackgroundLaunch(['--background'], { ELECTRON_RUN_AS_NODE: '1' }), false); + assert.equal(shouldDetachBackgroundLaunch(['--start'], {}), false); +}); diff --git a/src/main-entry-runtime.ts b/src/main-entry-runtime.ts new file mode 100644 index 0000000..3970c5c --- /dev/null +++ b/src/main-entry-runtime.ts @@ -0,0 +1,42 @@ +import { CliArgs, parseArgs, shouldStartApp } from './cli/args'; + +const BACKGROUND_ARG = '--background'; +const BACKGROUND_CHILD_ENV = 'SUBMINER_BACKGROUND_CHILD'; + +function removeLsfgLayer(env: NodeJS.ProcessEnv): void { + if (typeof env.VK_INSTANCE_LAYERS === 'string' && /lsfg/i.test(env.VK_INSTANCE_LAYERS)) { + delete env.VK_INSTANCE_LAYERS; + } +} + +function parseCliArgs(argv: string[]): CliArgs { + return parseArgs(argv); +} + +export function shouldDetachBackgroundLaunch(argv: string[], env: NodeJS.ProcessEnv): boolean { + if (env.ELECTRON_RUN_AS_NODE === '1') return false; + if (!argv.includes(BACKGROUND_ARG)) return false; + if (env[BACKGROUND_CHILD_ENV] === '1') return false; + return true; +} + +export function shouldHandleHelpOnlyAtEntry(argv: string[], env: NodeJS.ProcessEnv): boolean { + if (env.ELECTRON_RUN_AS_NODE === '1') return false; + const args = parseCliArgs(argv); + return args.help && !shouldStartApp(args); +} + +export function sanitizeHelpEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + const env = { ...baseEnv }; + if (!env.NODE_NO_WARNINGS) { + env.NODE_NO_WARNINGS = '1'; + } + removeLsfgLayer(env); + return env; +} + +export function sanitizeBackgroundEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + const env = sanitizeHelpEnv(baseEnv); + env[BACKGROUND_CHILD_ENV] = '1'; + return env; +} diff --git a/src/main-entry.ts b/src/main-entry.ts index b0d5a49..4abe1bb 100644 --- a/src/main-entry.ts +++ b/src/main-entry.ts @@ -1,26 +1,13 @@ import { spawn } from 'node:child_process'; +import { printHelp } from './cli/help'; +import { + sanitizeBackgroundEnv, + sanitizeHelpEnv, + shouldDetachBackgroundLaunch, + shouldHandleHelpOnlyAtEntry, +} from './main-entry-runtime'; -const BACKGROUND_ARG = '--background'; -const BACKGROUND_CHILD_ENV = 'SUBMINER_BACKGROUND_CHILD'; - -function shouldDetachBackgroundLaunch(argv: string[], env: NodeJS.ProcessEnv): boolean { - if (env.ELECTRON_RUN_AS_NODE === '1') return false; - if (!argv.includes(BACKGROUND_ARG)) return false; - if (env[BACKGROUND_CHILD_ENV] === '1') return false; - return true; -} - -function sanitizeBackgroundEnv(baseEnv: NodeJS.ProcessEnv): NodeJS.ProcessEnv { - const env = { ...baseEnv }; - env[BACKGROUND_CHILD_ENV] = '1'; - if (!env.NODE_NO_WARNINGS) { - env.NODE_NO_WARNINGS = '1'; - } - if (typeof env.VK_INSTANCE_LAYERS === 'string' && /lsfg/i.test(env.VK_INSTANCE_LAYERS)) { - delete env.VK_INSTANCE_LAYERS; - } - return env; -} +const DEFAULT_TEXTHOOKER_PORT = 5174; if (shouldDetachBackgroundLaunch(process.argv, process.env)) { const child = spawn(process.execPath, process.argv.slice(1), { @@ -32,4 +19,14 @@ if (shouldDetachBackgroundLaunch(process.argv, process.env)) { process.exit(0); } +if (shouldHandleHelpOnlyAtEntry(process.argv, process.env)) { + const sanitizedEnv = sanitizeHelpEnv(process.env); + process.env.NODE_NO_WARNINGS = sanitizedEnv.NODE_NO_WARNINGS; + if (!sanitizedEnv.VK_INSTANCE_LAYERS) { + delete process.env.VK_INSTANCE_LAYERS; + } + printHelp(DEFAULT_TEXTHOOKER_PORT); + process.exit(0); +} + require('./main.js');