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