fix(runtime): hide direct child process windows
This commit is contained in:
parent
1b086f41b7
commit
876527a51d
11 changed files with 41 additions and 39 deletions
|
|
@ -54,6 +54,7 @@ export class GitDiffFallback {
|
|||
cwd: projectPath,
|
||||
maxBuffer: GIT_MAX_BUFFER,
|
||||
timeout: GIT_TIMEOUT,
|
||||
windowsHide: true,
|
||||
});
|
||||
return stdout;
|
||||
} catch {
|
||||
|
|
@ -75,7 +76,7 @@ export class GitDiffFallback {
|
|||
const { stdout } = await execFileAsync(
|
||||
'git',
|
||||
['log', '--format=%H', '--before', timestamp, '-1', '--', relativePath],
|
||||
{ cwd: projectPath, timeout: GIT_TIMEOUT }
|
||||
{ cwd: projectPath, timeout: GIT_TIMEOUT, windowsHide: true }
|
||||
);
|
||||
return stdout.trim() || null;
|
||||
} catch {
|
||||
|
|
@ -98,7 +99,7 @@ export class GitDiffFallback {
|
|||
const { stdout } = await execFileAsync(
|
||||
'git',
|
||||
['diff', fromCommit, toCommit, '--', relativePath],
|
||||
{ cwd: projectPath, timeout: GIT_TIMEOUT }
|
||||
{ cwd: projectPath, timeout: GIT_TIMEOUT, windowsHide: true }
|
||||
);
|
||||
return stdout || null;
|
||||
} catch {
|
||||
|
|
@ -120,7 +121,7 @@ export class GitDiffFallback {
|
|||
const { stdout } = await execFileAsync(
|
||||
'git',
|
||||
['log', `--max-count=${maxCount}`, '--format=%H|%aI|%s', '--', relativePath],
|
||||
{ cwd: projectPath, timeout: GIT_TIMEOUT }
|
||||
{ cwd: projectPath, timeout: GIT_TIMEOUT, windowsHide: true }
|
||||
);
|
||||
|
||||
return stdout
|
||||
|
|
@ -148,6 +149,7 @@ export class GitDiffFallback {
|
|||
await execFileAsync('git', ['rev-parse', '--is-inside-work-tree'], {
|
||||
cwd: projectPath,
|
||||
timeout: GIT_TIMEOUT,
|
||||
windowsHide: true,
|
||||
});
|
||||
this.gitRepoCache.set(projectPath, true);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import {
|
|||
getMcpConfigsBasePath,
|
||||
getMcpServerBasePath,
|
||||
} from '@main/utils/pathDecoder';
|
||||
import { execCli } from '@main/utils/childProcess';
|
||||
import { createLogger } from '@shared/utils/logger';
|
||||
import { execFile } from 'child_process';
|
||||
import { randomUUID } from 'crypto';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
|
@ -185,17 +185,11 @@ async function resolveNodePath(options?: McpLaunchSpecResolveOptions): Promise<s
|
|||
|
||||
try {
|
||||
emitProgress(options, 'node-runtime', 'Resolving Node.js runtime for MCP server...');
|
||||
const resolved = await new Promise<string>((resolve, reject) => {
|
||||
execFile(
|
||||
'node',
|
||||
['-e', 'process.stdout.write(process.execPath)'],
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
},
|
||||
(err, stdout) => (err ? reject(err) : resolve(stdout.trim()))
|
||||
);
|
||||
const { stdout } = await execCli('node', ['-e', 'process.stdout.write(process.execPath)'], {
|
||||
encoding: 'utf-8',
|
||||
timeout: 5000,
|
||||
});
|
||||
const resolved = stdout.trim();
|
||||
if (resolved) {
|
||||
_resolvedNodePath = resolved;
|
||||
emitProgress(options, 'node-runtime-found', 'Using resolved Node.js runtime...');
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ function execGit(args: string[], cwd: string): Promise<string> {
|
|||
execFile(
|
||||
'git',
|
||||
args,
|
||||
{ cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024 },
|
||||
{ cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024, windowsHide: true },
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
const message = String(stderr || error.message || 'git command failed').trim();
|
||||
|
|
|
|||
|
|
@ -6426,6 +6426,7 @@ export class TeamProvisioningService {
|
|||
encoding: 'utf8',
|
||||
maxBuffer: 16 * 1024,
|
||||
timeout: 1000,
|
||||
windowsHide: true,
|
||||
},
|
||||
(error, stdout) => {
|
||||
if (error) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ function execGit(args: string[], cwd: string): Promise<string> {
|
|||
execFile(
|
||||
'git',
|
||||
args,
|
||||
{ cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024 },
|
||||
{ cwd, timeout: GIT_TIMEOUT_MS, maxBuffer: 1024 * 1024, windowsHide: true },
|
||||
(error, stdout, stderr) => {
|
||||
if (error) {
|
||||
const message = String(stderr || error.message || 'git command failed').trim();
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ function execFileText(
|
|||
encoding: 'utf8',
|
||||
timeout,
|
||||
maxBuffer,
|
||||
windowsHide: true,
|
||||
},
|
||||
(error: ExecFileException | null, stdout: string | Buffer) => {
|
||||
if (error) {
|
||||
|
|
|
|||
|
|
@ -376,8 +376,8 @@ export function killProcessTree(
|
|||
'System32',
|
||||
'taskkill.exe'
|
||||
);
|
||||
execFile(taskkillPath, ['/T', '/F', '/PID', String(child.pid)], () => {
|
||||
// Best-effort — ignore errors (process may have already exited)
|
||||
execFile(taskkillPath, ['/T', '/F', '/PID', String(child.pid)], { windowsHide: true }, () => {
|
||||
// Best-effort - ignore errors (process may have already exited)
|
||||
});
|
||||
return;
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export function killProcessByPid(pid: number): void {
|
|||
'System32',
|
||||
'taskkill.exe'
|
||||
);
|
||||
execFile(taskkillPath, ['/T', '/F', '/PID', String(pid)], () => {
|
||||
// Best-effort — ignore errors (process may have already exited)
|
||||
execFile(taskkillPath, ['/T', '/F', '/PID', String(pid)], { windowsHide: true }, () => {
|
||||
// Best-effort - ignore errors (process may have already exited)
|
||||
});
|
||||
} catch {
|
||||
// taskkill failed to spawn, fall through to process.kill()
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ async function readShellEnv(shellPath: string, args: string[]): Promise<NodeJS.P
|
|||
const child = spawn(shellPath, args, {
|
||||
env: process.env,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
windowsHide: true,
|
||||
});
|
||||
const chunks: Buffer[] = [];
|
||||
let settled = false;
|
||||
|
|
|
|||
|
|
@ -9,19 +9,7 @@ const hoisted = vi.hoisted(() => ({
|
|||
isPackaged: false,
|
||||
version: '9.9.9-test',
|
||||
},
|
||||
execFileMock: vi.fn(
|
||||
(
|
||||
_file: string,
|
||||
_args: readonly string[],
|
||||
_options:
|
||||
| { encoding?: string; timeout?: number }
|
||||
| ((error: Error | null, stdout: string, stderr: string) => void),
|
||||
callback?: (error: Error | null, stdout: string, stderr: string) => void
|
||||
) => {
|
||||
const cb = typeof _options === 'function' ? _options : callback;
|
||||
cb?.(null, '/mock/node', '');
|
||||
}
|
||||
),
|
||||
execCliMock: vi.fn(async () => ({ stdout: '/mock/node', stderr: '' })),
|
||||
}));
|
||||
|
||||
let mockHomeDir = '';
|
||||
|
|
@ -29,11 +17,11 @@ type ModuleLoad = (request: string, parent: NodeModule | undefined, isMain: bool
|
|||
const moduleInternal = Module as unknown as { _load: ModuleLoad };
|
||||
const originalModuleLoad = moduleInternal._load;
|
||||
|
||||
vi.mock('child_process', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('child_process')>();
|
||||
vi.mock('@main/utils/childProcess', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@main/utils/childProcess')>();
|
||||
return {
|
||||
...actual,
|
||||
execFile: hoisted.execFileMock,
|
||||
execCli: hoisted.execCliMock,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -189,7 +177,7 @@ describe('TeamMcpConfigBuilder', () => {
|
|||
setAppDataBasePath(tempAppData);
|
||||
setPackagedMode(false);
|
||||
setResourcesPath(undefined);
|
||||
hoisted.execFileMock.mockClear();
|
||||
hoisted.execCliMock.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -283,6 +271,21 @@ describe('TeamMcpConfigBuilder', () => {
|
|||
expectNodeEntry(server, builtEntry);
|
||||
});
|
||||
|
||||
it('uses the shared CLI helper for the Node.js runtime resolver', async () => {
|
||||
mockBuiltWorkspaceEntryAvailable();
|
||||
const builder = new TeamMcpConfigBuilder();
|
||||
|
||||
const configPath = await builder.writeConfigFile();
|
||||
createdPaths.push(configPath);
|
||||
|
||||
expect(readGeneratedServer(configPath)?.command).toBe('/mock/node');
|
||||
expect(hoisted.execCliMock).toHaveBeenCalledWith(
|
||||
'node',
|
||||
['-e', 'process.stdout.write(process.execPath)'],
|
||||
expect.objectContaining({ encoding: 'utf-8', timeout: 5000 })
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps generated team MCP config minimal and does not inline top-level user MCP', async () => {
|
||||
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'team-mcp-home-'));
|
||||
const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'team-mcp-project-'));
|
||||
|
|
|
|||
|
|
@ -117,13 +117,13 @@ describe('shellEnv', () => {
|
|||
1,
|
||||
'/bin/zsh',
|
||||
['-lic', 'env -0'],
|
||||
expect.any(Object)
|
||||
expect.objectContaining({ windowsHide: true })
|
||||
);
|
||||
expect(hoisted.spawn).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'/bin/zsh',
|
||||
['-ic', 'env -0'],
|
||||
expect.any(Object)
|
||||
expect.objectContaining({ windowsHide: true })
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue