diff --git a/src/server/__tests__/open-target-launch-options.test.ts b/src/server/__tests__/open-target-launch-options.test.ts new file mode 100644 index 00000000..335cee90 --- /dev/null +++ b/src/server/__tests__/open-target-launch-options.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, it } from 'bun:test' +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { delimiter, join } from 'node:path' +import { + createOpenTargetService, + getDefaultLaunchSpawnOptions, +} from '../services/openTargetService.js' + +describe('open target launch options', () => { + it('does not hide external application windows on Windows', () => { + const options = getDefaultLaunchSpawnOptions() as Record + + expect(options.windowsHide).toBeUndefined() + expect(options).toEqual({ + detached: true, + stdio: 'ignore', + }) + }) + + it.skipIf(process.platform === 'win32')('uses those options in the production launcher', async () => { + const root = await mkdtemp(join(tmpdir(), 'cc-haha-open-target-launch-')) + const binDir = join(root, 'bin') + const targetDir = join(root, 'target') + const commandPath = join(binDir, 'code') + const originalPath = process.env.PATH + + await mkdir(binDir) + await mkdir(targetDir) + await writeFile(commandPath, '#!/bin/sh\nexit 0\n', { mode: 0o755 }) + process.env.PATH = `${binDir}${delimiter}${originalPath ?? ''}` + + try { + const service = createOpenTargetService({ + platform: 'linux', + commandExists: async (command) => command === 'code', + }) + + await expect(service.openTarget({ targetId: 'vscode', path: targetDir })) + .resolves.toMatchObject({ ok: true, targetId: 'vscode' }) + } finally { + if (originalPath === undefined) { + delete process.env.PATH + } else { + process.env.PATH = originalPath + } + await rm(root, { recursive: true, force: true }) + } + }) +}) diff --git a/src/server/services/openTargetService.ts b/src/server/services/openTargetService.ts index 20a17c60..8a29505c 100644 --- a/src/server/services/openTargetService.ts +++ b/src/server/services/openTargetService.ts @@ -265,6 +265,17 @@ async function defaultPathExists(targetPath: string): Promise { } } +/** + * Keep external applications detached without setting `windowsHide`: on + * Windows that flag passes SW_HIDE to GUI targets such as Explorer and IDEs. + */ +export function getDefaultLaunchSpawnOptions() { + return { + detached: true as const, + stdio: 'ignore' as const, + } +} + async function defaultLaunch(command: string, args: string[]): Promise { return await new Promise((resolveLaunch) => { let settled = false @@ -275,11 +286,7 @@ async function defaultLaunch(command: string, args: string[]): Promise { settle({