diff --git a/lib/_inspect.js b/lib/_inspect.js index afc0cbd..dab1062 100644 --- a/lib/_inspect.js +++ b/lib/_inspect.js @@ -53,52 +53,6 @@ function getDefaultPort() { return 9229; } -function runScript(script, scriptArgs, inspectPort, childPrint) { - return new Promise((resolve) => { - const args = [ - `--inspect-brk=${inspectPort}`, - ].concat([script], scriptArgs); - const child = spawn(process.execPath, args); - child.stdout.setEncoding('utf8'); - child.stderr.setEncoding('utf8'); - child.stdout.on('data', childPrint); - child.stderr.on('data', childPrint); - - let output = ''; - function waitForListenHint(text) { - output += text; - if (/^Debugger listening on/.test(output)) { - child.stderr.removeListener('data', waitForListenHint); - resolve(child); - } - } - - child.stderr.on('data', waitForListenHint); - }); -} - -function createAgentProxy(domain, client) { - const agent = new EventEmitter(); - agent.then = (...args) => { - // TODO: potentially fetch the protocol and pretty-print it here. - const descriptor = { - [util.inspect.custom](depth, { stylize }) { - return stylize(`[Agent ${domain}]`, 'special'); - }, - }; - return Promise.resolve(descriptor).then(...args); - }; - - return new Proxy(agent, { - get(target, name) { - if (name in target) return target[name]; - return function callVirtualMethod(params) { - return client.callMethod(`${domain}.${name}`, params); - }; - }, - }); -} - function portIsFree(host, port, timeout = 2000) { const retryDelay = 150; let didTimeOut = false; @@ -138,6 +92,56 @@ function portIsFree(host, port, timeout = 2000) { }); } +function runScript(script, scriptArgs, inspectHost, inspectPort, childPrint) { + return portIsFree(inspectHost, inspectPort) + .then(() => { + return new Promise((resolve) => { + const args = [ + '--inspect', + `--debug-brk=${inspectPort}`, + ].concat([script], scriptArgs); + const child = spawn(process.execPath, args); + child.stdout.setEncoding('utf8'); + child.stderr.setEncoding('utf8'); + child.stdout.on('data', childPrint); + child.stderr.on('data', childPrint); + + let output = ''; + function waitForListenHint(text) { + output += text; + if (/chrome-devtools:\/\//.test(output)) { + child.stderr.removeListener('data', waitForListenHint); + resolve(child); + } + } + + child.stderr.on('data', waitForListenHint); + }); + }); +} + +function createAgentProxy(domain, client) { + const agent = new EventEmitter(); + agent.then = (...args) => { + // TODO: potentially fetch the protocol and pretty-print it here. + const descriptor = { + [util.inspect.custom](depth, { stylize }) { + return stylize(`[Agent ${domain}]`, 'special'); + }, + }; + return Promise.resolve(descriptor).then(...args); + }; + + return new Proxy(agent, { + get(target, name) { + if (name in target) return target[name]; + return function callVirtualMethod(params) { + return client.callMethod(`${domain}.${name}`, params); + }; + }, + }); +} + class NodeInspector { constructor(options, stdin, stdout) { this.options = options; @@ -151,6 +155,7 @@ class NodeInspector { this._runScript = runScript.bind(null, options.script, options.scriptArgs, + options.host, options.port, this.childPrint.bind(this)); } else { @@ -219,12 +224,7 @@ class NodeInspector { this.killChild(); const { host, port } = this.options; - const runOncePortIsFree = () => { - return portIsFree(host, port) - .then(() => this._runScript()); - }; - - return runOncePortIsFree().then((child) => { + return this._runScript().then((child) => { this.child = child; let connectionAttempts = 0; @@ -308,6 +308,22 @@ function parseArgv([target, ...args]) { port = parseInt(portMatch[1], 10); script = args[0]; scriptArgs = args.slice(1); + } else if (args.length === 1 && /^\d+$/.test(args[0]) && target === '-p') { + // Start debugger against a given pid + const pid = parseInt(args[0], 10); + try { + process._debugProcess(pid); + } catch (e) { + if (e.code === 'ESRCH') { + /* eslint-disable no-console */ + console.error(`Target process: ${pid} doesn't exist.`); + /* eslint-enable no-console */ + process.exit(1); + } + throw e; + } + script = null; + isRemote = true; } return { @@ -326,6 +342,7 @@ function startInspect(argv = process.argv.slice(2), console.error(`Usage: ${invokedAs} script.js`); console.error(` ${invokedAs} :`); + console.error(` ${invokedAs} -p `); process.exit(1); } diff --git a/test/cli/launch.test.js b/test/cli/launch.test.js index 0690cc4..f7efc6e 100644 --- a/test/cli/launch.test.js +++ b/test/cli/launch.test.js @@ -36,7 +36,7 @@ test('examples/three-lines.js', (t) => { t.match(cli.output, 'debug>', 'prints a prompt'); t.match( cli.output, - new RegExp(`< Debugger listening on [^\n]*9229`), + /< Debugger listening on [^\n]*9229/, 'forwards child output'); }) .then(() => cli.command('["hello", "world"].join(" ")')) diff --git a/test/cli/pid.test.js b/test/cli/pid.test.js new file mode 100644 index 0000000..15d7fde --- /dev/null +++ b/test/cli/pid.test.js @@ -0,0 +1,52 @@ +'use strict'; +const { spawn } = require('child_process'); +const Path = require('path'); + +const { test } = require('tap'); + +const startCLI = require('./start-cli'); + +function launchTarget(...args) { + const childProc = spawn(process.execPath, args); + return Promise.resolve(childProc); +} + +// process.debugPort is our proxy for "the version of node used to run this +// test suite doesn't support SIGUSR1 for enabling --inspect for a process". +const defaultsToOldProtocol = process.debugPort === 5858; + +test('examples/alive.js', { skip: defaultsToOldProtocol }, (t) => { + const script = Path.join('examples', 'alive.js'); + let cli = null; + let target = null; + + function cleanup(error) { + if (cli) { + cli.quit(); + cli = null; + } + if (target) { + target.kill(); + target = null; + } + if (error) throw error; + } + + return launchTarget(script) + .then((childProc) => { + target = childProc; + cli = startCLI(['-p', `${target.pid}`]); + return cli.waitForPrompt(); + }) + .then(() => cli.command('sb("alive.js", 3)')) + .then(() => cli.waitFor(/break/)) + .then(() => cli.waitForPrompt()) + .then(() => { + t.match( + cli.output, + '> 3 ++x;', + 'marks the 3rd line'); + }) + .then(() => cleanup()) + .then(null, cleanup); +});