in src/utils/InteractiveChildProcess.ts [74:137]
private async startCore(): Promise<void> {
this._startTime = Date.now();
const formattedArgs: string = this._options.args.join(' ');
const workingDirectory = this._options.workingDirectory || os.tmpdir();
const options: cp.SpawnOptions = {
cwd: workingDirectory,
// Using shell=true would mean that we can pass paths that will be resolved by the shell, but since
// the command is run in the shell, handling errors (such as command not found) would be more indirect,
// coming through STDERR instead of the error event
shell: false
};
this.writeLineToOutputChannel(`Starting executable: "${this._options.command}" ${formattedArgs}`);
this._childProc = cp.spawn(this._options.command, this._options.args, options);
this._childProc.stdout?.on('data', (data: string | Buffer) => {
const text = data.toString();
this._onStdOutEmitter.fire(text);
this.writeLineToOutputChannel(text);
});
this._childProc.stderr?.on('data', (data: string | Buffer) => {
const text = data.toString();
this._onStdErrEmitter.fire(text);
this.writeLineToOutputChannel(text, stdErrPrefix);
});
this._childProc.on('error', (error: unknown) => {
const improvedError = improveError(error);
this.setError(improvedError);
});
this._childProc.on('close', (code: number | null) => {
if (isNumber(code) && code !== 0) {
this.setError(`The process exited with code ${code}.`);
} else if (!this._isKilling) {
this.setError(`The process exited prematurely.`);
}
});
// Wait for the process to start up
// eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor
await new Promise<void>(async (resolve, reject) => {
const started = Date.now();
// eslint-disable-next-line no-constant-condition
while (true) {
if (!!this._error || this._isKilling) {
reject(this._error);
break;
} else if (this._childProc.pid) {
resolve();
break;
} else {
if (Date.now() > started + processStartupTimeout) {
reject("The process did not start in a timely manner");
break;
}
await delay(50);
}
}
});
}