'use strict'; const common = require('../common'); const spawn = require('child_process').spawn; const BREAK_MESSAGE = new RegExp('(?:' + [ 'assert', 'break', 'break on start', 'debugCommand', 'exception', 'other', 'promiseRejection', ].join('|') + ') in', 'i'); const TIMEOUT = common.platformTimeout(5000); function isPreBreak(output) { return /Break on start/.test(output) && /1 \(function \(exports/.test(output); } function startCLI(args, flags = [], spawnOpts = {}) { let stderrOutput = ''; const child = spawn(process.execPath, [...flags, 'inspect', ...args], spawnOpts); const outputBuffer = []; function bufferOutput(chunk) { if (this === child.stderr) { stderrOutput += chunk; } outputBuffer.push(chunk); } function getOutput() { return outputBuffer.join('\n').replace(/\b/g, ''); } child.stdout.setEncoding('utf8'); child.stdout.on('data', bufferOutput); child.stderr.setEncoding('utf8'); child.stderr.on('data', bufferOutput); if (process.env.VERBOSE === '1') { child.stdout.pipe(process.stdout); child.stderr.pipe(process.stderr); } return { flushOutput() { const output = this.output; outputBuffer.length = 0; return output; }, waitFor(pattern) { 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())) { tearDown(); resolve(); } } 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)); } const timer = setTimeout(() => { tearDown(); reject(new Error([ `Timeout (${TIMEOUT}) while waiting for ${pattern}`, `found: ${this.output}`, ].join('; '))); }, TIMEOUT); function tearDown() { clearTimeout(timer); child.stdout.removeListener('data', checkOutput); child.removeListener('close', onChildClose); } child.on('close', onChildClose); child.stdout.on('data', checkOutput); checkOutput(); }); }, waitForPrompt() { return this.waitFor(/>\s+$/); }, waitForInitialBreak() { return this.waitFor(/break (?:on start )?in/i) .then(() => { if (isPreBreak(this.output)) { return this.command('next', false) .then(() => this.waitFor(/break in/)); } }); }, get breakInfo() { const output = this.output; const breakMatch = output.match(/break (?:on start )?in ([^\n]+):(\d+)\n/i); if (breakMatch === null) { throw new Error( `Could not find breakpoint info in ${JSON.stringify(output)}`); } return { filename: breakMatch[1], line: +breakMatch[2] }; }, ctrlC() { return this.command('.interrupt'); }, get output() { return getOutput(); }, 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]); }, writeLine(input, flush = true) { if (flush) { this.flushOutput(); } if (process.env.VERBOSE === '1') { process.stderr.write(`< ${input}\n`); } child.stdin.write(input); child.stdin.write('\n'); }, command(input, flush = true) { this.writeLine(input, flush); return this.waitForPrompt(); }, stepCommand(input) { this.writeLine(input, true); return this .waitFor(BREAK_MESSAGE) .then(() => this.waitForPrompt()); }, quit() { return new Promise((resolve) => { child.stdin.end(); child.on('close', resolve); }); }, }; } module.exports = startCLI;