Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/mcp/vscode-node/util.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as cp from 'child_process';
7
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
8
9
export interface ICommandExecutor {
10
executeWithTimeout(
11
command: string,
12
args: string[],
13
cwd: string,
14
timeoutMs?: number,
15
expectZeroExitCode?: boolean,
16
cancellationToken?: CancellationToken): Promise<{ stdout: string; stderr: string; exitCode: number }>;
17
}
18
19
export class CommandExecutor implements ICommandExecutor {
20
async executeWithTimeout(
21
command: string,
22
args: string[],
23
cwd: string,
24
timeoutMs?: number,
25
expectZeroExitCode?: boolean,
26
cancellationToken?: CancellationToken
27
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
28
return await executeWithTimeout(
29
command,
30
args,
31
cwd,
32
timeoutMs,
33
expectZeroExitCode,
34
cancellationToken
35
);
36
}
37
}
38
39
const GRACEFUL_SHUTDOWN_TIMEOUT_MS = 10000;
40
41
async function executeWithTimeout(
42
command: string,
43
args: string[],
44
cwd: string,
45
timeoutMs: number = 60000,
46
expectZeroExitCode: boolean = true,
47
cancellationToken?: CancellationToken) {
48
49
return await new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve, reject) => {
50
const stdout: string[] = [];
51
const stderr: string[] = [];
52
let settled = false;
53
54
const child: cp.ChildProcessWithoutNullStreams = cp.spawn(command, args, {
55
stdio: 'pipe',
56
env: { ...process.env },
57
cwd: cwd,
58
});
59
60
child.stdout.setEncoding('utf8');
61
child.stderr.setEncoding('utf8');
62
63
child.stdout.on('data', (data) => stdout.push(data));
64
child.stderr.on('data', (data) => stderr.push(data));
65
66
const timeoutHandler = setTimeout(() => {
67
if (!settled) {
68
settled = true;
69
child.kill('SIGTERM');
70
setTimeout(() => {
71
if (!child.killed) {
72
child.kill('SIGKILL');
73
}
74
}, GRACEFUL_SHUTDOWN_TIMEOUT_MS);
75
reject(new Error(`Process timed out after ${timeoutMs}ms`));
76
}
77
}, timeoutMs);
78
79
const cancellationHandler = cancellationToken?.onCancellationRequested(() => {
80
if (!settled) {
81
settled = true;
82
clearTimeout(timeoutHandler);
83
child.kill('SIGTERM');
84
setTimeout(() => {
85
if (!child.killed) {
86
child.kill('SIGKILL');
87
}
88
}, GRACEFUL_SHUTDOWN_TIMEOUT_MS);
89
reject(new Error(`Process cancelled`));
90
}
91
});
92
93
child.on('error', (error) => {
94
if (!settled) {
95
settled = true;
96
clearTimeout(timeoutHandler);
97
cancellationHandler?.dispose();
98
reject(error);
99
}
100
});
101
102
child.on('close', (code) => {
103
if (!settled) {
104
settled = true;
105
clearTimeout(timeoutHandler);
106
cancellationHandler?.dispose();
107
108
if (expectZeroExitCode && code !== 0) {
109
reject(new Error(`Process ${child.pid} (${command}) failed with code ${code}.
110
stdout: ${stdout.join('')}
111
stderr: ${stderr.join('')}`));
112
} else {
113
resolve({
114
stdout: stdout.join(''),
115
stderr: stderr.join(''),
116
exitCode: code ?? -1,
117
});
118
}
119
}
120
});
121
});
122
}
123
124