mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
fix(server): keep Explorer windows visible #983
Do not pass windowsHide to detached GUI open targets, which forwards SW_HIDE to Explorer and IDE windows on Windows. Preserve detached process lifetime and ignored stdio. Tests: open-target 33/33; server 1624/1624; coverage 5/5 with changed lines 6/6; real macOS Finder reveal passed for a temporary CJK and ampersand path. Remaining validation: packaged Windows 11 Explorer and IDE window smoke. Confidence: high Scope-risk: narrow
This commit is contained in:
parent
936d970751
commit
20bb86ab63
50
src/server/__tests__/open-target-launch-options.test.ts
Normal file
50
src/server/__tests__/open-target-launch-options.test.ts
Normal file
@ -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<string, unknown>
|
||||
|
||||
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 })
|
||||
}
|
||||
})
|
||||
})
|
||||
@ -265,6 +265,17 @@ async function defaultPathExists(targetPath: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<OpenTargetLaunchResult> {
|
||||
return await new Promise((resolveLaunch) => {
|
||||
let settled = false
|
||||
@ -275,11 +286,7 @@ async function defaultLaunch(command: string, args: string[]): Promise<OpenTarge
|
||||
}
|
||||
|
||||
try {
|
||||
const child = spawn(command, args, {
|
||||
detached: true,
|
||||
stdio: 'ignore',
|
||||
windowsHide: true,
|
||||
})
|
||||
const child = spawn(command, args, getDefaultLaunchSpawnOptions())
|
||||
|
||||
child.once('error', (error) => {
|
||||
settle({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user