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