2016-12-11 14:36:58 -08:00
|
|
|
'use strict';
|
2021-04-23 15:55:18 -07:00
|
|
|
const common = require('../common');
|
2016-12-11 14:36:58 -08:00
|
|
|
const spawn = require('child_process').spawn;
|
|
|
|
|
|
2017-04-04 09:47:43 -07:00
|
|
|
const BREAK_MESSAGE = new RegExp('(?:' + [
|
|
|
|
|
'assert', 'break', 'break on start', 'debugCommand',
|
2023-03-17 14:32:39 +00:00
|
|
|
'exception', 'other', 'promiseRejection', 'step',
|
2017-04-04 09:47:43 -07:00
|
|
|
].join('|') + ') in', 'i');
|
|
|
|
|
|
2025-02-10 18:27:08 -05:00
|
|
|
let TIMEOUT = common.platformTimeout(10000);
|
2025-10-24 10:21:32 -04:00
|
|
|
// Some macOS and Windows machines require more time to receive the outputs from the client.
|
|
|
|
|
// https://github.com/nodejs/build/issues/3014
|
|
|
|
|
if (common.isWindows || common.isMacOS) {
|
2022-08-24 21:52:54 +08:00
|
|
|
TIMEOUT = common.platformTimeout(15000);
|
|
|
|
|
}
|
2021-04-08 23:55:45 -07:00
|
|
|
|
2019-06-03 10:05:10 +02:00
|
|
|
function isPreBreak(output) {
|
|
|
|
|
return /Break on start/.test(output) && /1 \(function \(exports/.test(output);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-20 18:37:28 +01:00
|
|
|
function startCLI(args, flags = [], spawnOpts = {}, opts = { randomPort: true }) {
|
2021-04-08 23:55:45 -07:00
|
|
|
let stderrOutput = '';
|
2025-07-20 18:37:28 +01:00
|
|
|
const child = spawn(process.execPath, [
|
|
|
|
|
...flags,
|
|
|
|
|
'inspect',
|
|
|
|
|
...(opts.randomPort !== false ? ['--port=0'] : []),
|
|
|
|
|
...args,
|
|
|
|
|
], spawnOpts);
|
2016-12-11 14:36:58 -08:00
|
|
|
|
|
|
|
|
const outputBuffer = [];
|
|
|
|
|
function bufferOutput(chunk) {
|
2021-04-08 23:55:45 -07:00
|
|
|
if (this === child.stderr) {
|
|
|
|
|
stderrOutput += chunk;
|
2016-12-11 14:36:58 -08:00
|
|
|
}
|
2021-07-07 07:01:01 -07:00
|
|
|
outputBuffer.push(chunk);
|
2016-12-11 14:36:58 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getOutput() {
|
2021-04-08 23:55:45 -07:00
|
|
|
return outputBuffer.join('\n').replaceAll('\b', '');
|
2016-12-11 14:36:58 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
child.stdout.setEncoding('utf8');
|
|
|
|
|
child.stdout.on('data', bufferOutput);
|
|
|
|
|
child.stderr.setEncoding('utf8');
|
|
|
|
|
child.stderr.on('data', bufferOutput);
|
|
|
|
|
|
|
|
|
|
if (process.env.VERBOSE === '1') {
|
2021-04-08 23:55:45 -07:00
|
|
|
child.stdout.pipe(process.stdout);
|
2016-12-11 14:36:58 -08:00
|
|
|
child.stderr.pipe(process.stderr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
flushOutput() {
|
|
|
|
|
const output = this.output;
|
|
|
|
|
outputBuffer.length = 0;
|
|
|
|
|
return output;
|
|
|
|
|
},
|
|
|
|
|
|
2021-04-08 23:55:45 -07:00
|
|
|
waitFor(pattern) {
|
2016-12-11 14:36:58 -08:00
|
|
|
function checkPattern(str) {
|
|
|
|
|
if (Array.isArray(pattern)) {
|
|
|
|
|
return pattern.every((p) => p.test(str));
|
|
|
|
|
}
|
|
|
|
|
return pattern.test(str);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
function checkOutput() {
|
|
|
|
|
if (checkPattern(getOutput())) {
|
2021-04-08 23:55:45 -07:00
|
|
|
tearDown();
|
2016-12-11 14:36:58 -08:00
|
|
|
resolve();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-08 23:55:45 -07:00
|
|
|
function onChildClose(code, signal) {
|
|
|
|
|
tearDown();
|
|
|
|
|
let message = 'Child exited';
|
|
|
|
|
if (code) {
|
|
|
|
|
message += `, code ${code}`;
|
|
|
|
|
}
|
|
|
|
|
if (signal) {
|
|
|
|
|
message += `, signal ${signal}`;
|
|
|
|
|
}
|
|
|
|
|
message += ` while waiting for ${pattern}; found: ${this.output}`;
|
|
|
|
|
if (stderrOutput) {
|
|
|
|
|
message += `\n STDERR: ${stderrOutput}`;
|
|
|
|
|
}
|
|
|
|
|
reject(new Error(message));
|
2016-12-11 14:36:58 -08:00
|
|
|
}
|
|
|
|
|
|
2025-10-31 14:18:50 +01:00
|
|
|
// Capture stack trace here to show where waitFor was called from when it times out.
|
|
|
|
|
const timeoutErr = new Error(`Timeout (${TIMEOUT}) while waiting for ${pattern}`);
|
2016-12-11 14:36:58 -08:00
|
|
|
const timer = setTimeout(() => {
|
2021-04-08 23:55:45 -07:00
|
|
|
tearDown();
|
2025-10-31 14:18:50 +01:00
|
|
|
timeoutErr.output = this.output;
|
|
|
|
|
reject(timeoutErr);
|
2021-04-08 23:55:45 -07:00
|
|
|
}, TIMEOUT);
|
2016-12-11 14:36:58 -08:00
|
|
|
|
|
|
|
|
function tearDown() {
|
|
|
|
|
clearTimeout(timer);
|
|
|
|
|
child.stdout.removeListener('data', checkOutput);
|
2021-04-08 23:55:45 -07:00
|
|
|
child.removeListener('close', onChildClose);
|
2016-12-11 14:36:58 -08:00
|
|
|
}
|
|
|
|
|
|
2021-04-08 23:55:45 -07:00
|
|
|
child.on('close', onChildClose);
|
2016-12-11 14:36:58 -08:00
|
|
|
child.stdout.on('data', checkOutput);
|
|
|
|
|
checkOutput();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2021-04-08 23:55:45 -07:00
|
|
|
waitForPrompt() {
|
|
|
|
|
return this.waitFor(/>\s+$/);
|
2016-12-11 14:36:58 -08:00
|
|
|
},
|
|
|
|
|
|
2021-10-06 22:22:02 +08:00
|
|
|
async waitForInitialBreak() {
|
2021-10-07 00:53:02 +08:00
|
|
|
await this.waitFor(/break (?:on start )?in/i);
|
|
|
|
|
|
|
|
|
|
if (isPreBreak(this.output)) {
|
|
|
|
|
await this.command('next', false);
|
|
|
|
|
return this.waitFor(/break in/);
|
|
|
|
|
}
|
2017-04-04 09:47:43 -07:00
|
|
|
},
|
|
|
|
|
|
2019-06-03 10:05:10 +02:00
|
|
|
get breakInfo() {
|
|
|
|
|
const output = this.output;
|
|
|
|
|
const breakMatch =
|
2023-03-17 14:32:39 +00:00
|
|
|
output.match(/(step |break (?:on start )?)in ([^\n]+):(\d+)\n/i);
|
2019-06-03 10:05:10 +02:00
|
|
|
|
|
|
|
|
if (breakMatch === null) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`Could not find breakpoint info in ${JSON.stringify(output)}`);
|
|
|
|
|
}
|
2023-03-17 14:32:39 +00:00
|
|
|
return { filename: breakMatch[2], line: +breakMatch[3] };
|
2019-06-03 10:05:10 +02:00
|
|
|
},
|
|
|
|
|
|
2016-12-11 14:36:58 -08:00
|
|
|
ctrlC() {
|
|
|
|
|
return this.command('.interrupt');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
get output() {
|
|
|
|
|
return getOutput();
|
|
|
|
|
},
|
|
|
|
|
|
2025-11-07 14:28:03 +01:00
|
|
|
get stderrOutput() {
|
|
|
|
|
return stderrOutput;
|
|
|
|
|
},
|
|
|
|
|
|
2016-12-11 14:36:58 -08:00
|
|
|
get rawOutput() {
|
|
|
|
|
return outputBuffer.join('').toString();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
parseSourceLines() {
|
|
|
|
|
return getOutput().split('\n')
|
|
|
|
|
.map((line) => line.match(/(?:\*|>)?\s*(\d+)/))
|
|
|
|
|
.filter((match) => match !== null)
|
|
|
|
|
.map((match) => +match[1]);
|
|
|
|
|
},
|
|
|
|
|
|
2019-06-03 10:05:10 +02:00
|
|
|
writeLine(input, flush = true) {
|
2017-04-04 09:47:43 -07:00
|
|
|
if (flush) {
|
|
|
|
|
this.flushOutput();
|
|
|
|
|
}
|
2019-06-03 10:05:10 +02:00
|
|
|
if (process.env.VERBOSE === '1') {
|
|
|
|
|
process.stderr.write(`< ${input}\n`);
|
|
|
|
|
}
|
2016-12-11 14:36:58 -08:00
|
|
|
child.stdin.write(input);
|
|
|
|
|
child.stdin.write('\n');
|
2019-06-03 10:05:10 +02:00
|
|
|
},
|
|
|
|
|
|
|
|
|
|
command(input, flush = true) {
|
|
|
|
|
this.writeLine(input, flush);
|
2016-12-11 14:36:58 -08:00
|
|
|
return this.waitForPrompt();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
stepCommand(input) {
|
2019-06-03 10:05:10 +02:00
|
|
|
this.writeLine(input, true);
|
2016-12-11 14:36:58 -08:00
|
|
|
return this
|
2017-04-04 09:47:43 -07:00
|
|
|
.waitFor(BREAK_MESSAGE)
|
2016-12-11 14:36:58 -08:00
|
|
|
.then(() => this.waitForPrompt());
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
quit() {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
child.stdin.end();
|
2021-04-08 23:55:45 -07:00
|
|
|
child.on('close', resolve);
|
2016-12-11 14:36:58 -08:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
module.exports = startCLI;
|