1'use strict'; 2const common = require('../common'); 3const spawn = require('child_process').spawn; 4 5const BREAK_MESSAGE = new RegExp('(?:' + [ 6 'assert', 'break', 'break on start', 'debugCommand', 7 'exception', 'other', 'promiseRejection', 8].join('|') + ') in', 'i'); 9 10let TIMEOUT = common.platformTimeout(5000); 11if (common.isWindows) { 12 // Some of the windows machines in the CI need more time to receive 13 // the outputs from the client. 14 // https://github.com/nodejs/build/issues/3014 15 TIMEOUT = common.platformTimeout(15000); 16} 17 18function isPreBreak(output) { 19 return /Break on start/.test(output) && /1 \(function \(exports/.test(output); 20} 21 22function startCLI(args, flags = [], spawnOpts = {}) { 23 let stderrOutput = ''; 24 const child = 25 spawn(process.execPath, [...flags, 'inspect', ...args], spawnOpts); 26 27 const outputBuffer = []; 28 function bufferOutput(chunk) { 29 if (this === child.stderr) { 30 stderrOutput += chunk; 31 } 32 outputBuffer.push(chunk); 33 } 34 35 function getOutput() { 36 return outputBuffer.join('\n').replaceAll('\b', ''); 37 } 38 39 child.stdout.setEncoding('utf8'); 40 child.stdout.on('data', bufferOutput); 41 child.stderr.setEncoding('utf8'); 42 child.stderr.on('data', bufferOutput); 43 44 if (process.env.VERBOSE === '1') { 45 child.stdout.pipe(process.stdout); 46 child.stderr.pipe(process.stderr); 47 } 48 49 return { 50 flushOutput() { 51 const output = this.output; 52 outputBuffer.length = 0; 53 return output; 54 }, 55 56 waitFor(pattern) { 57 function checkPattern(str) { 58 if (Array.isArray(pattern)) { 59 return pattern.every((p) => p.test(str)); 60 } 61 return pattern.test(str); 62 } 63 64 return new Promise((resolve, reject) => { 65 function checkOutput() { 66 if (checkPattern(getOutput())) { 67 tearDown(); 68 resolve(); 69 } 70 } 71 72 function onChildClose(code, signal) { 73 tearDown(); 74 let message = 'Child exited'; 75 if (code) { 76 message += `, code ${code}`; 77 } 78 if (signal) { 79 message += `, signal ${signal}`; 80 } 81 message += ` while waiting for ${pattern}; found: ${this.output}`; 82 if (stderrOutput) { 83 message += `\n STDERR: ${stderrOutput}`; 84 } 85 reject(new Error(message)); 86 } 87 88 const timer = setTimeout(() => { 89 tearDown(); 90 reject(new Error([ 91 `Timeout (${TIMEOUT}) while waiting for ${pattern}`, 92 `found: ${this.output}`, 93 ].join('; '))); 94 }, TIMEOUT); 95 96 function tearDown() { 97 clearTimeout(timer); 98 child.stdout.removeListener('data', checkOutput); 99 child.removeListener('close', onChildClose); 100 } 101 102 child.on('close', onChildClose); 103 child.stdout.on('data', checkOutput); 104 checkOutput(); 105 }); 106 }, 107 108 waitForPrompt() { 109 return this.waitFor(/>\s+$/); 110 }, 111 112 async waitForInitialBreak() { 113 await this.waitFor(/break (?:on start )?in/i); 114 115 if (isPreBreak(this.output)) { 116 await this.command('next', false); 117 return this.waitFor(/break in/); 118 } 119 }, 120 121 get breakInfo() { 122 const output = this.output; 123 const breakMatch = 124 output.match(/break (?:on start )?in ([^\n]+):(\d+)\n/i); 125 126 if (breakMatch === null) { 127 throw new Error( 128 `Could not find breakpoint info in ${JSON.stringify(output)}`); 129 } 130 return { filename: breakMatch[1], line: +breakMatch[2] }; 131 }, 132 133 ctrlC() { 134 return this.command('.interrupt'); 135 }, 136 137 get output() { 138 return getOutput(); 139 }, 140 141 get rawOutput() { 142 return outputBuffer.join('').toString(); 143 }, 144 145 parseSourceLines() { 146 return getOutput().split('\n') 147 .map((line) => line.match(/(?:\*|>)?\s*(\d+)/)) 148 .filter((match) => match !== null) 149 .map((match) => +match[1]); 150 }, 151 152 writeLine(input, flush = true) { 153 if (flush) { 154 this.flushOutput(); 155 } 156 if (process.env.VERBOSE === '1') { 157 process.stderr.write(`< ${input}\n`); 158 } 159 child.stdin.write(input); 160 child.stdin.write('\n'); 161 }, 162 163 command(input, flush = true) { 164 this.writeLine(input, flush); 165 return this.waitForPrompt(); 166 }, 167 168 stepCommand(input) { 169 this.writeLine(input, true); 170 return this 171 .waitFor(BREAK_MESSAGE) 172 .then(() => this.waitForPrompt()); 173 }, 174 175 quit() { 176 return new Promise((resolve) => { 177 child.stdin.end(); 178 child.on('close', resolve); 179 }); 180 }, 181 }; 182} 183module.exports = startCLI; 184