| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d8475e1 commit 141de35
8 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -27,10 +27,11 @@ const { | |||
| 27 | 27 | const InspectClient = require('internal/debugger/inspect_client'); | |
| 28 | 28 | const { | |
| 29 | 29 | launchChildProcess, | |
| 30 | - writeUsageAndExit, | ||
| 30 | + writeInspectUsageAndExit, | ||
| 31 | 31 | } = require('internal/debugger/inspect_helpers'); | |
| 32 | 32 | const { | |
| 33 | - startProbeMode, | ||
| 33 | + parseProbeTokens, | ||
| 34 | + runProbeMode, | ||
| 34 | 35 | } = require('internal/debugger/inspect_probe'); | |
| 35 | 36 | const createRepl = require('internal/debugger/inspect_repl'); | |
| 36 | 37 | ||
@@ -39,6 +40,7 @@ const debuglog = util.debuglog('inspect'); | |||
| 39 | 40 | const { | |
| 40 | 41 | exitCodes: { | |
| 41 | 42 | kGenericUserError, | |
| 43 | + kInvalidCommandLineArgument, | ||
| 42 | 44 | kNoFailure, | |
| 43 | 45 | }, | |
| 44 | 46 | } = internalBinding('errors'); | |
@@ -220,7 +222,7 @@ class NodeInspector { | |||
| 220 | 222 | } | |
| 221 | 223 | } | |
| 222 | 224 | ||
| 223 | - function parseArgv(args) { | ||
| 225 | + function parseInteractiveArgs(args) { | ||
| 224 | 226 | const target = ArrayPrototypeShift(args); | |
| 225 | 227 | let host = '127.0.0.1'; | |
| 226 | 228 | let port = 9229; | |
@@ -264,20 +266,83 @@ function parseArgv(args) { | |||
| 264 | 266 | }; | |
| 265 | 267 | } | |
| 266 | 268 | ||
| 269 | + const kInspectArgOptions = { | ||
| 270 | + __proto__: null, | ||
| 271 | + expr: { type: 'string' }, | ||
| 272 | + help: { type: 'boolean', short: 'h' }, | ||
| 273 | + json: { type: 'boolean' }, | ||
| 274 | + // Port and timeout use type 'string' because parseArgs has no | ||
| 275 | + // numeric type; the values are parsed to integers by parseProbeTokens(). | ||
| 276 | + port: { type: 'string' }, | ||
| 277 | + preview: { type: 'boolean' }, | ||
| 278 | + probe: { type: 'string' }, | ||
| 279 | + timeout: { type: 'string' }, | ||
| 280 | + }; | ||
| 281 | + | ||
| 282 | + // Parses args once and decides whether the user wants the inspect help, probe | ||
| 283 | + // mode, or interactive mode. The mode is determined by the first option, | ||
| 284 | + // option-terminator, or positional token in the input. | ||
| 285 | + // | ||
| 286 | + // Returns one of: | ||
| 287 | + // { mode: 'help' } | ||
| 288 | + // { mode: 'probe', tokens, args } | ||
| 289 | + // { mode: 'interactive' } | ||
| 290 | + function parseInspectMode(args) { | ||
| 291 | + const { tokens } = util.parseArgs({ | ||
| 292 | + args, | ||
| 293 | + allowPositionals: true, | ||
| 294 | + options: kInspectArgOptions, | ||
| 295 | + strict: false, | ||
| 296 | + tokens: true, | ||
| 297 | + }); | ||
| 298 | + | ||
| 299 | + for (const token of tokens) { | ||
| 300 | + if (token.kind === 'option') { | ||
| 301 | + if (token.name === 'help') return { mode: 'help' }; | ||
| 302 | + if (token.name === 'probe') { | ||
| 303 | + // `--probe --help` / `--probe -h` (no value) consumes the help flag | ||
| 304 | + // as the probe's "value"; surface help instead of a probe error. | ||
| 305 | + if (!token.inlineValue && | ||
| 306 | + (token.value === '--help' || token.value === '-h')) { | ||
| 307 | + return { mode: 'help' }; | ||
| 308 | + } | ||
| 309 | + return { mode: 'probe', tokens, args }; | ||
| 310 | + } | ||
| 311 | + } | ||
| 312 | + if (token.kind === 'option-terminator' || token.kind === 'positional') { | ||
| 313 | + break; | ||
| 314 | + } | ||
| 315 | + } | ||
| 316 | + return { mode: 'interactive' }; | ||
| 317 | + } | ||
| 318 | + | ||
| 267 | 319 | function startInspect(argv = ArrayPrototypeSlice(process.argv, 2), | |
| 268 | 320 | stdin = process.stdin, | |
| 269 | 321 | stdout = process.stdout) { | |
| 270 | 322 | const invokedAs = `${process.argv0} ${process.argv[1]}`; | |
| 271 | 323 | ||
| 272 | 324 | if (argv.length < 1) { | |
| 273 | - writeUsageAndExit(invokedAs); | ||
| 325 | + writeInspectUsageAndExit(invokedAs, undefined, kInvalidCommandLineArgument); | ||
| 274 | 326 | } | |
| 275 | 327 | ||
| 276 | - if (startProbeMode(invokedAs, argv, stdout)) { | ||
| 328 | + const parsed = parseInspectMode(argv); | ||
| 329 | + | ||
| 330 | + if (parsed.mode === 'help') { | ||
| 331 | + writeInspectUsageAndExit(invokedAs); | ||
| 332 | + } | ||
| 333 | + | ||
| 334 | + if (parsed.mode === 'probe') { | ||
| 335 | + let probeOptions; | ||
| 336 | + try { | ||
| 337 | + probeOptions = parseProbeTokens(parsed.tokens, parsed.args); | ||
| 338 | + } catch (error) { | ||
| 339 | + writeInspectUsageAndExit(invokedAs, error.message, kInvalidCommandLineArgument); | ||
| 340 | + } | ||
| 341 | + runProbeMode(stdout, probeOptions); | ||
| 277 | 342 | return; | |
| 278 | 343 | } | |
| 279 | 344 | ||
| 280 | - const options = parseArgv(argv); | ||
| 345 | + const options = parseInteractiveArgs(argv); | ||
| 281 | 346 | const inspector = new NodeInspector(options, stdin, stdout); | |
| 282 | 347 | ||
| 283 | 348 | stdin.resume(); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -61,21 +61,67 @@ function ensureTrailingNewline(text) { | |||
| 61 | 61 | return StringPrototypeEndsWith(text, '\n') ? text : `${text}\n`; | |
| 62 | 62 | } | |
| 63 | 63 | ||
| 64 | - function writeUsageAndExit(invokedAs, message, exitCode = kInvalidCommandLineArgument) { | ||
| 64 | + function writeInspectUsageAndExit(invokedAs, message, exitCode) { | ||
| 65 | + const code = exitCode ?? (message ? kInvalidCommandLineArgument : 0); | ||
| 66 | + const out = code === 0 ? process.stdout : process.stderr; | ||
| 65 | 67 | if (message) { | |
| 66 | - process.stderr.write(`${message}\n`); | ||
| 68 | + out.write(`${message}\n`); | ||
| 67 | 69 | } | |
| 68 | - const text = | ||
| 69 | - `Usage: ${invokedAs} script.js\n` + | ||
| 70 | - ` ${invokedAs} <host>:<port>\n` + | ||
| 71 | - ` ${invokedAs} --port=<port> Use 0 for random port assignment\n` + | ||
| 72 | - ` ${invokedAs} -p <pid>\n` + | ||
| 73 | - ` ${invokedAs} [--json] [--timeout=<ms>] [--port=<port>] ` + | ||
| 74 | - `--probe <file>:<line>[:<col>] --expr <expr> ` + | ||
| 75 | - `[--probe <file>:<line>[:<col>] --expr <expr> ...] ` + | ||
| 76 | - `[--] [<node-option> ...] <script.js> [args...]\n`; | ||
| 77 | - process.stderr.write(text); | ||
| 78 | - process.exit(exitCode); | ||
| 70 | + out.write(`Usage: ${invokedAs} [--port=<port>] [<node-option> ...] | ||
| 71 | + [<script> [<script-args>] | <host>:<port> | -p <pid>] | ||
| 72 | + ${invokedAs} --probe <file>:<line>[:<col>] --expr <expr> | ||
| 73 | + [--probe <file>:<line>[:<col>] --expr <expr> ...] | ||
| 74 | + [--json] [--preview] [--timeout=<ms>] [--port=<port>] | ||
| 75 | + [--] [<node-option> ...] <script> [<script-args> ...] | ||
| 76 | + | ||
| 77 | + Interactive mode: Starts a live debugging session. | ||
| 78 | + | ||
| 79 | + Example: | ||
| 80 | + $ node inspect script.js | ||
| 81 | + | ||
| 82 | + Options: | ||
| 83 | + --port=<port> Inspector port for the debuggee (default: 9229) | ||
| 84 | + <script> The script to launch and debug. | ||
| 85 | + <host>:<port> Remote debugger to connect to. | ||
| 86 | + -p <pid> Attach to a running Node.js process by PID | ||
| 87 | + | ||
| 88 | + Semantics: | ||
| 89 | + * If neither a script nor a host:port nor -p is provided, node inspect starts | ||
| 90 | + the REPL. | ||
| 91 | + | ||
| 92 | + Non-interactive probe mode: Evaluates expressions whenever execution reaches | ||
| 93 | + specified source locations and prints all the evaluation results to stdout. | ||
| 94 | + | ||
| 95 | + Example: | ||
| 96 | + $ node inspect --probe app.js:10 --expr "user" | ||
| 97 | + --probe src/utils.js:5:15 --expr "config.options" | ||
| 98 | + --json --preview -- --no-warnings app.js --arg-for-app=foo | ||
| 99 | + | ||
| 100 | + Options: | ||
| 101 | + --probe <file>:<line>[:<col>] | ||
| 102 | + Source location of the probe (1-based, col defaults | ||
| 103 | + to 1). Matches by file basename, use a fuller path to | ||
| 104 | + disambiguate. Must be immediately followed by --expr. | ||
| 105 | + --expr <expr> Expression to evaluate in the lexical scope of the | ||
| 106 | + preceding --probe each time execution reaches it. | ||
| 107 | + Avoid probing let/const-bound variables at their | ||
| 108 | + declaration site or a ReferenceError may be thrown. | ||
| 109 | + --json Output JSON if specified, otherwise human-readable text. | ||
| 110 | + --preview Include V8 object previews in JSON output. | ||
| 111 | + --timeout <ms> Global session timeout (default: 30000). | ||
| 112 | + --port <port> Inspector port for the debuggee (default: 0 = random). | ||
| 113 | + | ||
| 114 | + Semantics: | ||
| 115 | + * Multiple --probe/--expr pairs are allowed. Same-location --probes share | ||
| 116 | + a pause and scope, their --exprs are evaluated in command-line order. | ||
| 117 | + * Use -- before any Node.js flags intended for the child process. | ||
| 118 | + * Target errors are surfaced in the report as a terminal 'error' event. | ||
| 119 | + The probing process exits 0 unless it encounters an error itself. | ||
| 120 | + | ||
| 121 | + See https://nodejs.org/api/debugger.html for details, including the | ||
| 122 | + probe output schema. | ||
| 123 | + `); | ||
| 124 | + process.exit(code); | ||
| 79 | 125 | } | |
| 80 | 126 | ||
| 81 | 127 | async function launchChildProcess(childArgs, inspectHost, inspectPort, | |
@@ -128,5 +174,5 @@ async function launchChildProcess(childArgs, inspectHost, inspectPort, | |||
| 128 | 174 | module.exports = { | |
| 129 | 175 | ensureTrailingNewline, | |
| 130 | 176 | launchChildProcess, | |
| 131 | - writeUsageAndExit, | ||
| 177 | + writeInspectUsageAndExit, | ||
| 132 | 178 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,14 +23,12 @@ const { | |||
| 23 | 23 | } = primordials; | |
| 24 | 24 | ||
| 25 | 25 | const { clearTimeout, setTimeout } = require('timers'); | |
| 26 | - const util = require('util'); | ||
| 27 | 26 | const { SideEffectFreeRegExpPrototypeSymbolReplace } = require('internal/util'); | |
| 28 | 27 | ||
| 29 | 28 | const InspectClient = require('internal/debugger/inspect_client'); | |
| 30 | 29 | const { | |
| 31 | 30 | ensureTrailingNewline, | |
| 32 | 31 | launchChildProcess, | |
| 33 | - writeUsageAndExit, | ||
| 34 | 32 | } = require('internal/debugger/inspect_helpers'); | |
| 35 | 33 | ||
| 36 | 34 | const { ERR_DEBUGGER_STARTUP_ERROR } = require('internal/errors').codes; | |
@@ -46,17 +44,6 @@ const kProbeVersion = 1; | |||
| 46 | 44 | const kProbeDisconnectSentinel = 'Waiting for the debugger to disconnect...'; | |
| 47 | 45 | const kDigitsRegex = /^\d+$/; | |
| 48 | 46 | const kInspectPortRegex = /^--inspect-port=(\d+)$/; | |
| 49 | - const kProbeArgOptions = { | ||
| 50 | - __proto__: null, | ||
| 51 | - expr: { type: 'string' }, | ||
| 52 | - json: { type: 'boolean' }, | ||
| 53 | - // Port and timeout use type 'string' because parseArgs has no | ||
| 54 | - // numeric type; the values are parsed to integers in parseProbeArgv(). | ||
| 55 | - port: { type: 'string' }, | ||
| 56 | - preview: { type: 'boolean' }, | ||
| 57 | - probe: { type: 'string' }, | ||
| 58 | - timeout: { type: 'string' }, | ||
| 59 | - }; | ||
| 60 | 47 | ||
| 61 | 48 | function parseUnsignedInteger(value, name, allowZero = false) { | |
| 62 | 49 | if (typeof value !== 'string' || RegExpPrototypeExec(kDigitsRegex, value) === null) { | |
@@ -274,29 +261,7 @@ function buildProbeTextReport(report) { | |||
| 274 | 261 | return ensureTrailingNewline(ArrayPrototypeJoin(lines, '\n')); | |
| 275 | 262 | } | |
| 276 | 263 | ||
| 277 | - function hasTopLevelProbeOption(args) { | ||
| 278 | - const { tokens } = util.parseArgs({ | ||
| 279 | - args, | ||
| 280 | - allowPositionals: true, | ||
| 281 | - options: kProbeArgOptions, | ||
| 282 | - strict: false, | ||
| 283 | - tokens: true, | ||
| 284 | - }); | ||
| 285 | - | ||
| 286 | - for (const token of tokens) { | ||
| 287 | - if (token.kind === 'option' && token.name === 'probe') { | ||
| 288 | - return true; | ||
| 289 | - } | ||
| 290 | - | ||
| 291 | - if (token.kind === 'option-terminator' || token.kind === 'positional') { | ||
| 292 | - return false; | ||
| 293 | - } | ||
| 294 | - } | ||
| 295 | - | ||
| 296 | - return false; | ||
| 297 | - } | ||
| 298 | - | ||
| 299 | - function parseProbeArgv(args) { | ||
| 264 | + function parseProbeTokens(tokens, args) { | ||
| 300 | 265 | let port = 0; | |
| 301 | 266 | let preview = false; | |
| 302 | 267 | let timeout = kProbeDefaultTimeout; | |
@@ -307,14 +272,6 @@ function parseProbeArgv(args) { | |||
| 307 | 272 | let expectedExprIndex = -1; | |
| 308 | 273 | const probes = []; | |
| 309 | 274 | ||
| 310 | - const { tokens } = util.parseArgs({ | ||
| 311 | - args, | ||
| 312 | - allowPositionals: true, | ||
| 313 | - options: kProbeArgOptions, | ||
| 314 | - strict: false, | ||
| 315 | - tokens: true, | ||
| 316 | - }); | ||
| 317 | - | ||
| 318 | 275 | for (const token of tokens) { | |
| 319 | 276 | if (token.kind === 'option-terminator') { | |
| 320 | 277 | sawSeparator = true; | |
@@ -764,22 +721,7 @@ async function runProbeMode(stdout, probeOptions) { | |||
| 764 | 721 | } | |
| 765 | 722 | } | |
| 766 | 723 | ||
| 767 | - function startProbeMode(invokedAs, args, stdout) { | ||
| 768 | - if (!hasTopLevelProbeOption(args)) { | ||
| 769 | - return false; | ||
| 770 | - } | ||
| 771 | - | ||
| 772 | - let probeOptions; | ||
| 773 | - try { | ||
| 774 | - probeOptions = parseProbeArgv(args); | ||
| 775 | - } catch (error) { | ||
| 776 | - writeUsageAndExit(invokedAs, error.message, kGenericUserError); | ||
| 777 | - } | ||
| 778 | - | ||
| 779 | - runProbeMode(stdout, probeOptions); | ||
| 780 | - return true; | ||
| 781 | - } | ||
| 782 | - | ||
| 783 | 724 | module.exports = { | |
| 784 | - startProbeMode, | ||
| 725 | + parseProbeTokens, | ||
| 726 | + runProbeMode, | ||
| 785 | 727 | }; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,34 @@ | |||
| 1 | + // Tests that --help / -h are forwarded to the debuggee when a script is | ||
| 2 | + // also passed, instead of being hijacked as the inspect help flag. | ||
| 3 | + | ||
| 4 | + import { skipIfInspectorDisabled } from '../common/index.mjs'; | ||
| 5 | + | ||
| 6 | + skipIfInspectorDisabled(); | ||
| 7 | + | ||
| 8 | + import * as fixtures from '../common/fixtures.mjs'; | ||
| 9 | + import startCLI from '../common/debugger.js'; | ||
| 10 | + | ||
| 11 | + import assert from 'assert'; | ||
| 12 | + | ||
| 13 | + async function getEvaluatedArgv(flag) { | ||
| 14 | + const script = fixtures.path('debugger', 'empty.js'); | ||
| 15 | + const cli = startCLI([script, flag]); | ||
| 16 | + try { | ||
| 17 | + await cli.waitForInitialBreak(); | ||
| 18 | + await cli.waitForPrompt(); | ||
| 19 | + await cli.command('exec process.argv'); | ||
| 20 | + return cli.output; | ||
| 21 | + } finally { | ||
| 22 | + await cli.quit(); | ||
| 23 | + } | ||
| 24 | + } | ||
| 25 | + | ||
| 26 | + async function checkForwardedHelp(flag) { | ||
| 27 | + const output = await getEvaluatedArgv(flag); | ||
| 28 | + assert(output.includes(`'${flag}'`), | ||
| 29 | + `expected debuggee process.argv to include "${flag}", got:\n${output}`); | ||
| 30 | + assert.doesNotMatch(output, /Usage: .+ inspect/); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + await checkForwardedHelp('--help'); | ||
| 34 | + await checkForwardedHelp('-h'); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,37 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + | ||
| 3 | + // Tests that `node inspect` help text is printed when | ||
| 4 | + // `--help` / `-h` is passed and there is no script, | ||
| 5 | + // or when there are no additional arguments. | ||
| 6 | + const common = require('../common'); | ||
| 7 | + common.skipIfInspectorDisabled(); | ||
| 8 | + | ||
| 9 | + const { | ||
| 10 | + spawnSyncAndAssert, | ||
| 11 | + spawnSyncAndExit, | ||
| 12 | + } = require('../common/child_process'); | ||
| 13 | + | ||
| 14 | + const usageRegex = /^Usage: .+ inspect .*debugger\.html/ms; | ||
| 15 | + | ||
| 16 | + // --help / -h prints the usage to stdout and exits with code 0, | ||
| 17 | + // for both interactive and probe-mode invocations. | ||
| 18 | + const helpArgs = [ | ||
| 19 | + ['inspect', '--help'], | ||
| 20 | + ['inspect', '-h'], | ||
| 21 | + ['inspect', '--probe', '--help'], | ||
| 22 | + ['inspect', '--probe', '-h'], | ||
| 23 | + ]; | ||
| 24 | + | ||
| 25 | + for (const args of helpArgs) { | ||
| 26 | + spawnSyncAndAssert(process.execPath, args, { | ||
| 27 | + stdout: usageRegex, | ||
| 28 | + }); | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + // `node inspect` with no args prints the usage to stderr and exits | ||
| 32 | + // with kInvalidCommandLineArgument (9). | ||
| 33 | + spawnSyncAndExit(process.execPath, ['inspect'], { | ||
| 34 | + status: 9, | ||
| 35 | + signal: null, | ||
| 36 | + stderr: usageRegex, | ||
| 37 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -13,7 +13,7 @@ spawnSyncAndExit(process.execPath, [ | |||
| 13 | 13 | probeScript, | |
| 14 | 14 | ], { | |
| 15 | 15 | signal: null, | |
| 16 | - status: 1, | ||
| 16 | + status: 9, | ||
| 17 | 17 | stderr: /Each --probe must be followed immediately by --expr/, | |
| 18 | 18 | trim: true, | |
| 19 | 19 | }); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments