@saptools/cf-inspector 0.4.8 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -5
- package/dist/cli.js +233 -32
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +63 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,7 @@ cf-inspector snapshot --port 9229 \
|
|
|
94
94
|
| `--condition <expr>` | Only pause when this JS expression evaluates truthy in the paused frame. Errors in the condition are silently treated as `false` by V8 |
|
|
95
95
|
| `--hit-count <n>` | Skip the first N − 1 hits and only pause on the Nth (combines with `--condition` via logical AND) |
|
|
96
96
|
| `--capture <expr,…>` | Top-level comma-separated expressions to evaluate in the paused frame; nested commas inside objects, arrays, calls, or strings are preserved. Object results are materialized to JSON strings when serializable, with fallback to CDP descriptions for non-serializable values |
|
|
97
|
+
| `--setup-eval <expr>` | Repeatable, order-preserving global expression evaluated inside the inspected process before breakpoint setup. It can mutate runtime state, so use it only in controlled debug sessions |
|
|
97
98
|
| `--stack-depth <n>` | Walk this many call frames per hit (default: `1`, top frame only). When `> 1`, the result includes a `stack` array |
|
|
98
99
|
| `--stack-captures <expr,…>` | Expressions to evaluate on each call frame in the captured stack |
|
|
99
100
|
| `--timeout <seconds>` | How long to wait for the breakpoint to hit (default: `30`) |
|
|
@@ -129,9 +130,10 @@ strict immediate error is preferred.
|
|
|
129
130
|
|
|
130
131
|
For Cloud Foundry targets, replace `--port` with
|
|
131
132
|
`--region/--org/--space/--app`. Cloud Foundry commands and tunnel readiness
|
|
132
|
-
allow up to 180 seconds by default.
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
allow up to 180 seconds by default. For commands without their own wait
|
|
134
|
+
semantics, `--timeout <seconds>` controls CF tunnel readiness. For breakpoint
|
|
135
|
+
and exception commands, `--timeout` is reserved for the command wait and tunnel
|
|
136
|
+
readiness keeps the 180-second default.
|
|
135
137
|
|
|
136
138
|
### 📡 `cf-inspector log`
|
|
137
139
|
|
|
@@ -202,6 +204,7 @@ Each event is a `WatchEvent`:
|
|
|
202
204
|
| `--port <number>` | Local port the inspector or tunnel listens on |
|
|
203
205
|
| `--bp <file:line>` | **Required.** Source location to capture on (repeatable) |
|
|
204
206
|
| `--capture <expr,…>` | Top-level comma-separated expressions to evaluate per hit |
|
|
207
|
+
| `--setup-eval <expr>` | Repeatable, order-preserving global expression evaluated inside the inspected process before breakpoint setup. It can mutate runtime state, so use it only in controlled debug sessions |
|
|
205
208
|
| `--condition <expr>` | Only emit hits where this expression evaluates truthy |
|
|
206
209
|
| `--hit-count <n>` | Start emitting once the line has been hit N or more times |
|
|
207
210
|
| `--remote-root <value>` | Path-mapping anchor (same DSL as `snapshot`) |
|
|
@@ -266,10 +269,19 @@ cf-inspector eval --port 9229 --expr 'process.uptime()'
|
|
|
266
269
|
|
|
267
270
|
### 📜 `cf-inspector list-scripts`
|
|
268
271
|
|
|
269
|
-
Print every script the V8 instance knows about (useful for debugging path-mapping issues).
|
|
272
|
+
Print every script the V8 instance knows about (useful for debugging path-mapping issues). Add `--filter <pattern>` to narrow noisy script lists with a literal/wildcard pattern; `|` separates alternatives and `.*` / `.+` match variable text.
|
|
270
273
|
|
|
271
274
|
```bash
|
|
272
|
-
cf-inspector list-scripts --port 9229
|
|
275
|
+
cf-inspector list-scripts --port 9229 --filter 'dist/.+\.js'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 🎯 `cf-inspector list-targets`
|
|
279
|
+
|
|
280
|
+
Print `/json/list` inspector targets with stable indexes. Use the index with `--target <index>` when a long-running worker thread appears as a separate target.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
cf-inspector list-targets --port 9229
|
|
284
|
+
cf-inspector snapshot --port 9229 --target 1 --bp dist/worker.js:42
|
|
273
285
|
```
|
|
274
286
|
|
|
275
287
|
### 🔗 `cf-inspector attach`
|
package/dist/cli.js
CHANGED
|
@@ -409,15 +409,54 @@ async function openCfTunnel(target) {
|
|
|
409
409
|
...target.signal === void 0 ? {} : { signal: target.signal },
|
|
410
410
|
...target.onStatus === void 0 ? {} : { onStatus: target.onStatus }
|
|
411
411
|
};
|
|
412
|
-
|
|
412
|
+
try {
|
|
413
|
+
const handle = await startDebugger(opts);
|
|
414
|
+
return {
|
|
415
|
+
localPort: handle.session.localPort,
|
|
416
|
+
handle,
|
|
417
|
+
dispose: async () => {
|
|
418
|
+
await handle.dispose();
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
} catch (err) {
|
|
422
|
+
return reuseExistingTunnelOrThrow(err, target.onStatus);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function reuseExistingTunnelOrThrow(err, onStatus) {
|
|
426
|
+
if (!isSessionAlreadyRunningError(err)) {
|
|
427
|
+
throw err;
|
|
428
|
+
}
|
|
429
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
430
|
+
const port = extractExistingTunnelPort(message);
|
|
431
|
+
if (port === void 0) {
|
|
432
|
+
throw err;
|
|
433
|
+
}
|
|
434
|
+
const warning = `Reusing existing tunnel on port ${port.toString()}`;
|
|
435
|
+
onStatus?.("ready", warning);
|
|
413
436
|
return {
|
|
414
|
-
localPort:
|
|
415
|
-
|
|
416
|
-
dispose: async () => {
|
|
417
|
-
await handle.dispose();
|
|
418
|
-
}
|
|
437
|
+
localPort: port,
|
|
438
|
+
dispose: () => Promise.resolve()
|
|
419
439
|
};
|
|
420
440
|
}
|
|
441
|
+
function isSessionAlreadyRunningError(err) {
|
|
442
|
+
if (typeof err !== "object" || err === null) {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
const code = err.code;
|
|
446
|
+
return code === "SESSION_ALREADY_RUNNING";
|
|
447
|
+
}
|
|
448
|
+
function extractExistingTunnelPort(message) {
|
|
449
|
+
const match = /on port (\d+)/i.exec(message);
|
|
450
|
+
if (match === null) {
|
|
451
|
+
return void 0;
|
|
452
|
+
}
|
|
453
|
+
const rawPort = match[1];
|
|
454
|
+
if (rawPort === void 0) {
|
|
455
|
+
return void 0;
|
|
456
|
+
}
|
|
457
|
+
const port = Number.parseInt(rawPort, 10);
|
|
458
|
+
return Number.isNaN(port) ? void 0 : port;
|
|
459
|
+
}
|
|
421
460
|
|
|
422
461
|
// src/inspector/session.ts
|
|
423
462
|
import { performance } from "perf_hooks";
|
|
@@ -782,11 +821,12 @@ async function connectInspector(options) {
|
|
|
782
821
|
const host = options.host ?? DEFAULT_HOST;
|
|
783
822
|
const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
784
823
|
const targets = await discoverInspectorTargets(host, options.port, connectTimeoutMs);
|
|
785
|
-
const
|
|
824
|
+
const targetIndex = options.targetIndex ?? 0;
|
|
825
|
+
const target = targets[targetIndex];
|
|
786
826
|
if (!target) {
|
|
787
827
|
throw new CfInspectorError(
|
|
788
828
|
"INSPECTOR_DISCOVERY_FAILED",
|
|
789
|
-
`No inspector
|
|
829
|
+
`No inspector target at index ${targetIndex.toString()} on ${host}:${options.port.toString()} (available: ${targets.length.toString()})`
|
|
790
830
|
);
|
|
791
831
|
}
|
|
792
832
|
const client = await CdpClient.connect({
|
|
@@ -882,22 +922,23 @@ function parsePositiveInt(raw, label) {
|
|
|
882
922
|
}
|
|
883
923
|
return value;
|
|
884
924
|
}
|
|
885
|
-
async function resolveTargetWithCurrentCfTarget(opts) {
|
|
925
|
+
async function resolveTargetWithCurrentCfTarget(opts, options = {}) {
|
|
886
926
|
const port = parsePositiveInt(opts.port, "--port");
|
|
927
|
+
const targetIndex = parseTargetIndex(opts.target);
|
|
887
928
|
if (port !== void 0) {
|
|
888
|
-
return { kind: "port", port, host: opts.host ?? "127.0.0.1" };
|
|
929
|
+
return { kind: "port", port, host: opts.host ?? "127.0.0.1", ...targetIndexOption(targetIndex) };
|
|
889
930
|
}
|
|
890
931
|
const app = optionalText(opts.app);
|
|
891
932
|
if (app === void 0) {
|
|
892
933
|
throw missingTargetError();
|
|
893
934
|
}
|
|
894
|
-
const
|
|
935
|
+
const tunnelTimeoutSec = parseTunnelTimeout(opts, options);
|
|
895
936
|
const region = optionalText(opts.region);
|
|
896
937
|
const apiEndpoint = optionalText(opts.apiEndpoint);
|
|
897
938
|
const org = optionalText(opts.org);
|
|
898
939
|
const space = optionalText(opts.space);
|
|
899
940
|
if (region !== void 0 && org !== void 0 && space !== void 0) {
|
|
900
|
-
return buildCfTarget(region, apiEndpoint, org, space, app,
|
|
941
|
+
return buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, targetIndex);
|
|
901
942
|
}
|
|
902
943
|
const current = await readCurrentTarget();
|
|
903
944
|
if (current === void 0) {
|
|
@@ -912,10 +953,30 @@ async function resolveTargetWithCurrentCfTarget(opts) {
|
|
|
912
953
|
org ?? current.org,
|
|
913
954
|
space ?? current.space,
|
|
914
955
|
app,
|
|
915
|
-
|
|
956
|
+
tunnelTimeoutSec,
|
|
957
|
+
targetIndex
|
|
916
958
|
);
|
|
917
959
|
}
|
|
918
|
-
function
|
|
960
|
+
function parseTunnelTimeout(opts, options) {
|
|
961
|
+
if (options.useTimeoutForTunnel === false) {
|
|
962
|
+
return DEFAULT_CF_TIMEOUT_SEC;
|
|
963
|
+
}
|
|
964
|
+
return parsePositiveInt(opts.timeout, "--timeout") ?? DEFAULT_CF_TIMEOUT_SEC;
|
|
965
|
+
}
|
|
966
|
+
function parseTargetIndex(raw) {
|
|
967
|
+
if (raw === void 0) {
|
|
968
|
+
return void 0;
|
|
969
|
+
}
|
|
970
|
+
const value = Number.parseInt(raw, 10);
|
|
971
|
+
if (Number.isNaN(value) || value < 0 || value.toString() !== raw.trim()) {
|
|
972
|
+
throw new CfInspectorError("INVALID_ARGUMENT", `Invalid --target: "${raw}" \u2014 expected a non-negative integer`);
|
|
973
|
+
}
|
|
974
|
+
return value;
|
|
975
|
+
}
|
|
976
|
+
function targetIndexOption(targetIndex) {
|
|
977
|
+
return targetIndex === void 0 ? {} : { targetIndex };
|
|
978
|
+
}
|
|
979
|
+
function buildCfTarget(region, apiEndpoint, org, space, app, tunnelTimeoutSec, targetIndex) {
|
|
919
980
|
return {
|
|
920
981
|
kind: "cf",
|
|
921
982
|
region,
|
|
@@ -923,7 +984,8 @@ function buildCfTarget(region, apiEndpoint, org, space, app, cfTimeoutSec) {
|
|
|
923
984
|
org,
|
|
924
985
|
space,
|
|
925
986
|
app,
|
|
926
|
-
|
|
987
|
+
tunnelTimeoutMs: tunnelTimeoutSec * 1e3,
|
|
988
|
+
...targetIndexOption(targetIndex)
|
|
927
989
|
};
|
|
928
990
|
}
|
|
929
991
|
function optionalText(value) {
|
|
@@ -966,7 +1028,11 @@ async function withSession(target, fn, reportProgress) {
|
|
|
966
1028
|
reportProgress?.(
|
|
967
1029
|
`Connecting to the Node.js inspector at ${tunnel.host}:${tunnel.port.toString()}...`
|
|
968
1030
|
);
|
|
969
|
-
session = await connectInspector({
|
|
1031
|
+
session = await connectInspector({
|
|
1032
|
+
port: tunnel.port,
|
|
1033
|
+
host: tunnel.host,
|
|
1034
|
+
...targetIndexOption(target.targetIndex)
|
|
1035
|
+
});
|
|
970
1036
|
reportProgress?.("Inspector session is ready.");
|
|
971
1037
|
return await fn(session, tunnel.port);
|
|
972
1038
|
} finally {
|
|
@@ -993,10 +1059,10 @@ async function openTarget(target, reportProgress) {
|
|
|
993
1059
|
org: target.org,
|
|
994
1060
|
space: target.space,
|
|
995
1061
|
app: target.app,
|
|
996
|
-
tunnelReadyTimeoutMs: target.
|
|
1062
|
+
tunnelReadyTimeoutMs: target.tunnelTimeoutMs,
|
|
997
1063
|
...reportProgress === void 0 ? {} : {
|
|
998
|
-
onStatus: (status) => {
|
|
999
|
-
reportProgress(formatCfTunnelStatus(status));
|
|
1064
|
+
onStatus: (status, message) => {
|
|
1065
|
+
reportProgress(message ?? formatCfTunnelStatus(status));
|
|
1000
1066
|
}
|
|
1001
1067
|
}
|
|
1002
1068
|
});
|
|
@@ -1058,6 +1124,20 @@ async function evaluateGlobal(session, expression) {
|
|
|
1058
1124
|
silent: true
|
|
1059
1125
|
});
|
|
1060
1126
|
}
|
|
1127
|
+
async function runSetupEvals(session, expressions) {
|
|
1128
|
+
for (const expression of expressions) {
|
|
1129
|
+
const result = await evaluateGlobal(session, expression);
|
|
1130
|
+
if (result.exceptionDetails !== void 0) {
|
|
1131
|
+
throw new CfInspectorError(
|
|
1132
|
+
"SETUP_EVAL_FAILED",
|
|
1133
|
+
exceptionDetailsMessage(result, "setup evaluation failed")
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function exceptionDetailsMessage(result, fallback) {
|
|
1139
|
+
return typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : fallback;
|
|
1140
|
+
}
|
|
1061
1141
|
function listScripts(session) {
|
|
1062
1142
|
return [...session.scripts.values()];
|
|
1063
1143
|
}
|
|
@@ -2081,8 +2161,9 @@ import process5 from "process";
|
|
|
2081
2161
|
function warnOnUnboundBreakpoints(handles) {
|
|
2082
2162
|
for (const handle of handles) {
|
|
2083
2163
|
if (handle.resolvedLocations.length === 0) {
|
|
2164
|
+
const tsHint = handle.file.endsWith(".ts") ? " Hint: Source TS breakpoints may not bind. Try inspecting loaded scripts with list-scripts and target the compiled .js file instead." : "";
|
|
2084
2165
|
process5.stderr.write(
|
|
2085
|
-
`[cf-inspector] warning: breakpoint ${handle.file}:${handle.line.toString()} did not bind to any loaded script. Check the path or pass --remote-root. Use 'list-scripts' to inspect what V8 currently has loaded
|
|
2166
|
+
`[cf-inspector] warning: breakpoint ${handle.file}:${handle.line.toString()} did not bind to any loaded script. Check the path or pass --remote-root. Use 'list-scripts' to inspect what V8 currently has loaded.${tsHint}
|
|
2086
2167
|
`
|
|
2087
2168
|
);
|
|
2088
2169
|
}
|
|
@@ -2122,7 +2203,7 @@ function formatPauseLocation(pause) {
|
|
|
2122
2203
|
// src/cli/commands/exception.ts
|
|
2123
2204
|
var VALID_PAUSE_TYPES = ["uncaught", "caught", "all"];
|
|
2124
2205
|
async function handleException(opts) {
|
|
2125
|
-
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2206
|
+
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2126
2207
|
const prepared = prepareExceptionCommand(opts, target);
|
|
2127
2208
|
const result = await runExceptionCommand(prepared, opts);
|
|
2128
2209
|
if (opts.json) {
|
|
@@ -2201,7 +2282,8 @@ async function disablePauseOnExceptionsBestEffort(session) {
|
|
|
2201
2282
|
import process7 from "process";
|
|
2202
2283
|
async function handleListScripts(opts) {
|
|
2203
2284
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2204
|
-
const
|
|
2285
|
+
const filter = compileScriptUrlFilter(opts.filter);
|
|
2286
|
+
const scripts = (await withSession(target, (session) => Promise.resolve(listScripts(session)))).filter((script) => filter === void 0 || filter(script.url));
|
|
2205
2287
|
if (opts.json) {
|
|
2206
2288
|
writeJson(scripts);
|
|
2207
2289
|
return;
|
|
@@ -2211,6 +2293,92 @@ async function handleListScripts(opts) {
|
|
|
2211
2293
|
`);
|
|
2212
2294
|
}
|
|
2213
2295
|
}
|
|
2296
|
+
async function handleListTargets(opts) {
|
|
2297
|
+
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2298
|
+
const tunnel = await openTarget(target);
|
|
2299
|
+
try {
|
|
2300
|
+
const targets = await discoverInspectorTargets(tunnel.host, tunnel.port, 5e3);
|
|
2301
|
+
const indexedTargets = targets.map((entry, index) => ({ index, ...entry }));
|
|
2302
|
+
if (opts.json) {
|
|
2303
|
+
writeJson(indexedTargets);
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
for (const entry of indexedTargets) {
|
|
2307
|
+
process7.stdout.write(`${entry.index.toString()} ${entry.type} ${entry.title} ${entry.url}
|
|
2308
|
+
`);
|
|
2309
|
+
}
|
|
2310
|
+
} finally {
|
|
2311
|
+
await tunnel.dispose();
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
function compileScriptUrlFilter(pattern) {
|
|
2315
|
+
if (pattern === void 0 || pattern.length === 0) {
|
|
2316
|
+
return void 0;
|
|
2317
|
+
}
|
|
2318
|
+
const alternatives = splitPatternAlternatives(pattern).map((alternative) => parseFilterTokens(alternative)).filter((tokens) => tokens.length > 0);
|
|
2319
|
+
return (url) => alternatives.some((tokens) => matchesFilterTokens(url, tokens));
|
|
2320
|
+
}
|
|
2321
|
+
function splitPatternAlternatives(pattern) {
|
|
2322
|
+
const alternatives = [];
|
|
2323
|
+
let current = "";
|
|
2324
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
2325
|
+
const char = pattern[index] ?? "";
|
|
2326
|
+
if (char === "\\" && index + 1 < pattern.length) {
|
|
2327
|
+
current += `${char}${pattern[index + 1] ?? ""}`;
|
|
2328
|
+
index++;
|
|
2329
|
+
} else if (char === "|") {
|
|
2330
|
+
alternatives.push(current);
|
|
2331
|
+
current = "";
|
|
2332
|
+
} else {
|
|
2333
|
+
current += char;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
alternatives.push(current);
|
|
2337
|
+
return alternatives;
|
|
2338
|
+
}
|
|
2339
|
+
function parseFilterTokens(pattern) {
|
|
2340
|
+
const tokens = [];
|
|
2341
|
+
let literal = "";
|
|
2342
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
2343
|
+
const char = pattern[index] ?? "";
|
|
2344
|
+
const nextChar = pattern[index + 1];
|
|
2345
|
+
if (char === "\\" && nextChar !== void 0) {
|
|
2346
|
+
literal += nextChar;
|
|
2347
|
+
index++;
|
|
2348
|
+
} else if (char === "." && (nextChar === "*" || nextChar === "+")) {
|
|
2349
|
+
if (literal.length > 0) {
|
|
2350
|
+
tokens.push(literal);
|
|
2351
|
+
}
|
|
2352
|
+
literal = "";
|
|
2353
|
+
tokens.push({ kind: "wildcard", minChars: nextChar === "+" ? 1 : 0 });
|
|
2354
|
+
index++;
|
|
2355
|
+
} else {
|
|
2356
|
+
literal += char;
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
if (literal.length > 0) {
|
|
2360
|
+
tokens.push(literal);
|
|
2361
|
+
}
|
|
2362
|
+
return tokens;
|
|
2363
|
+
}
|
|
2364
|
+
function matchesFilterTokens(value, tokens) {
|
|
2365
|
+
let position = 0;
|
|
2366
|
+
for (const token of tokens) {
|
|
2367
|
+
if (typeof token === "string") {
|
|
2368
|
+
const nextPosition = value.indexOf(token, position);
|
|
2369
|
+
if (nextPosition === -1) {
|
|
2370
|
+
return false;
|
|
2371
|
+
}
|
|
2372
|
+
position = nextPosition + token.length;
|
|
2373
|
+
} else {
|
|
2374
|
+
if (token.minChars === 1 && position >= value.length) {
|
|
2375
|
+
return false;
|
|
2376
|
+
}
|
|
2377
|
+
position += token.minChars;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return true;
|
|
2381
|
+
}
|
|
2214
2382
|
|
|
2215
2383
|
// src/cli/commands/log.ts
|
|
2216
2384
|
import process9 from "process";
|
|
@@ -2534,7 +2702,7 @@ import { performance as performance4 } from "perf_hooks";
|
|
|
2534
2702
|
import process10 from "process";
|
|
2535
2703
|
init_types();
|
|
2536
2704
|
async function handleSnapshot(opts) {
|
|
2537
|
-
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2705
|
+
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2538
2706
|
const prepared = prepareSnapshotCommand(opts, target);
|
|
2539
2707
|
const reportProgress = opts.quiet === true ? void 0 : writeProgress;
|
|
2540
2708
|
const result = await runSnapshotCommand(prepared, opts, reportProgress);
|
|
@@ -2557,8 +2725,10 @@ function prepareSnapshotCommand(opts, target) {
|
|
|
2557
2725
|
const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
|
|
2558
2726
|
const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
|
|
2559
2727
|
const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
|
|
2728
|
+
const setupEvals = parseSetupEvals(opts.setupEval);
|
|
2560
2729
|
return {
|
|
2561
2730
|
target,
|
|
2731
|
+
setupEvals,
|
|
2562
2732
|
breakpoints: opts.bp.map((spec) => parseBreakpointSpec(spec)),
|
|
2563
2733
|
captures: parseCaptureList(opts.capture),
|
|
2564
2734
|
remoteRoot: parseRemoteRoot(opts.remoteRoot),
|
|
@@ -2572,6 +2742,12 @@ function prepareSnapshotCommand(opts, target) {
|
|
|
2572
2742
|
}
|
|
2573
2743
|
async function runSnapshotCommand(command, opts, reportProgress) {
|
|
2574
2744
|
return await withSession(command.target, async (session) => {
|
|
2745
|
+
if (command.setupEvals.length > 0) {
|
|
2746
|
+
const setupCount = command.setupEvals.length;
|
|
2747
|
+
reportProgress?.(`Running ${setupCount.toString()} setup ${setupCount === 1 ? "evaluation" : "evaluations"}...`);
|
|
2748
|
+
await runSetupEvals(session, command.setupEvals);
|
|
2749
|
+
reportProgress?.("Setup evaluation complete.");
|
|
2750
|
+
}
|
|
2575
2751
|
if (command.condition !== void 0) {
|
|
2576
2752
|
reportProgress?.("Validating the breakpoint condition...");
|
|
2577
2753
|
await validateExpression(session, command.condition);
|
|
@@ -2654,13 +2830,17 @@ async function resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportPro
|
|
|
2654
2830
|
return withPausedDuration(snapshot, null);
|
|
2655
2831
|
}
|
|
2656
2832
|
}
|
|
2833
|
+
function parseSetupEvals(raw) {
|
|
2834
|
+
const values = Array.isArray(raw) ? raw : [];
|
|
2835
|
+
return values.filter((expr) => typeof expr === "string" && expr.trim().length > 0).map((expr) => expr.trim());
|
|
2836
|
+
}
|
|
2657
2837
|
|
|
2658
2838
|
// src/cli/commands/watch.ts
|
|
2659
2839
|
import { performance as performance5 } from "perf_hooks";
|
|
2660
2840
|
import process11 from "process";
|
|
2661
2841
|
init_types();
|
|
2662
2842
|
async function handleWatch(opts) {
|
|
2663
|
-
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2843
|
+
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2664
2844
|
const prepared = prepareWatchCommand(opts, target);
|
|
2665
2845
|
let stoppedReason = "signal";
|
|
2666
2846
|
let emitted = 0;
|
|
@@ -2687,8 +2867,10 @@ function prepareWatchCommand(opts, target) {
|
|
|
2687
2867
|
const hitCount = parsePositiveInt(opts.hitCount, "--hit-count");
|
|
2688
2868
|
const stackDepth = parsePositiveInt(opts.stackDepth, "--stack-depth");
|
|
2689
2869
|
const condition = opts.condition !== void 0 && opts.condition.trim().length > 0 ? opts.condition.trim() : void 0;
|
|
2870
|
+
const setupEvals = parseSetupEvals2(opts.setupEval);
|
|
2690
2871
|
return {
|
|
2691
2872
|
target,
|
|
2873
|
+
setupEvals,
|
|
2692
2874
|
breakpoints: opts.bp.map((spec) => parseBreakpointSpec(spec)),
|
|
2693
2875
|
captures: parseCaptureList(opts.capture),
|
|
2694
2876
|
remoteRoot: parseRemoteRoot(opts.remoteRoot),
|
|
@@ -2703,6 +2885,9 @@ function prepareWatchCommand(opts, target) {
|
|
|
2703
2885
|
};
|
|
2704
2886
|
}
|
|
2705
2887
|
async function runWatchLoop(session, command, opts, signal) {
|
|
2888
|
+
if (command.setupEvals.length > 0) {
|
|
2889
|
+
await runSetupEvals(session, command.setupEvals);
|
|
2890
|
+
}
|
|
2706
2891
|
if (command.condition !== void 0) {
|
|
2707
2892
|
await validateExpression(session, command.condition);
|
|
2708
2893
|
}
|
|
@@ -2876,10 +3061,15 @@ function writeWatchSummary(reason, emitted, json) {
|
|
|
2876
3061
|
`
|
|
2877
3062
|
);
|
|
2878
3063
|
}
|
|
3064
|
+
function parseSetupEvals2(raw) {
|
|
3065
|
+
const values = Array.isArray(raw) ? raw : [];
|
|
3066
|
+
return values.filter((expr) => typeof expr === "string" && expr.trim().length > 0).map((expr) => expr.trim());
|
|
3067
|
+
}
|
|
2879
3068
|
|
|
2880
3069
|
// src/cli/program.ts
|
|
2881
|
-
function applyTargetOptions(cmd) {
|
|
2882
|
-
|
|
3070
|
+
function applyTargetOptions(cmd, options = {}) {
|
|
3071
|
+
const withBaseOptions = cmd.option("--port <number>", "Local port the inspector or tunnel listens on").option("--host <host>", "Hostname (default: 127.0.0.1)", "127.0.0.1").option("--region <key>", "CF region key (default: current cf target)").option("--api-endpoint <url>", "CF API endpoint override for --region").option("--org <name>", "CF org name (default: current cf target)").option("--space <name>", "CF space name (default: current cf target)").option("--app <name>", "CF app name when not using --port").option("--target <index>", "Inspector target index from /json/list (default: 0)");
|
|
3072
|
+
return options.includeTimeout === false ? withBaseOptions : withBaseOptions.option("--timeout <seconds>", "Timeout for CF tunnel readiness in seconds (default: 180)");
|
|
2883
3073
|
}
|
|
2884
3074
|
var collectStrings = (value, prev = []) => [
|
|
2885
3075
|
...prev,
|
|
@@ -2894,13 +3084,15 @@ async function main(argv) {
|
|
|
2894
3084
|
registerException(program);
|
|
2895
3085
|
registerEval(program);
|
|
2896
3086
|
registerListScripts(program);
|
|
3087
|
+
registerListTargets(program);
|
|
2897
3088
|
registerAttach(program);
|
|
2898
3089
|
await program.parseAsync([...argv]);
|
|
2899
3090
|
}
|
|
2900
3091
|
function registerSnapshot(program) {
|
|
2901
3092
|
applyTargetOptions(
|
|
2902
|
-
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
|
|
2903
|
-
|
|
3093
|
+
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume"),
|
|
3094
|
+
{ includeTimeout: false }
|
|
3095
|
+
).option("--bp <file:line>", "Breakpoint location (repeatable; first hit wins), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--setup-eval <expr>", "Evaluate a global setup expression before breakpoint setup (repeatable)", collectStrings, []).option("--timeout <seconds>", "How long to wait for the breakpoint to hit (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--condition <expr>", "Only pause when this JS expression evaluates truthy in the paused frame").option("--hit-count <n>", "Only pause after the breakpoint has been hit N or more times").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1, only top frame)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame in the stack").option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--no-json", "Print a human-readable summary instead of JSON").option("--quiet", "Suppress progress messages on stderr").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--fail-on-unmatched-pause", "Fail immediately if the target pauses somewhere else").action(async (opts) => {
|
|
2904
3096
|
await handleSnapshot(opts);
|
|
2905
3097
|
});
|
|
2906
3098
|
}
|
|
@@ -2913,14 +3105,16 @@ function registerLog(program) {
|
|
|
2913
3105
|
}
|
|
2914
3106
|
function registerWatch(program) {
|
|
2915
3107
|
applyTargetOptions(
|
|
2916
|
-
program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits")
|
|
2917
|
-
|
|
3108
|
+
program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits"),
|
|
3109
|
+
{ includeTimeout: false }
|
|
3110
|
+
).option("--bp <file:line>", "Breakpoint location (repeatable), e.g. src/handler.ts:42", collectStrings, []).option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate per hit").option("--setup-eval <expr>", "Evaluate a global setup expression before breakpoint setup (repeatable)", collectStrings, []).option("--condition <expr>", "Only emit hits where this JS expression evaluates truthy").option("--hit-count <n>", "Start emitting after the line has been hit N or more times").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--duration <seconds>", "Stop streaming after N seconds (default: run until SIGINT)").option("--max-events <n>", "Stop streaming after emitting N watch events").option("--timeout <seconds>", "How long to wait for the next hit before giving up (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--stack-depth <n>", "Walk this many call frames per hit (default: 1)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame").option("--include-scopes", "Include expanded paused-frame scopes per hit").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
|
|
2918
3111
|
await handleWatch(opts);
|
|
2919
3112
|
});
|
|
2920
3113
|
}
|
|
2921
3114
|
function registerException(program) {
|
|
2922
3115
|
applyTargetOptions(
|
|
2923
|
-
program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume")
|
|
3116
|
+
program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume"),
|
|
3117
|
+
{ includeTimeout: false }
|
|
2924
3118
|
).option("--type <state>", "Pause type: uncaught (default), caught, or all").option("--capture <expr,\u2026>", "Top-level comma-separated expressions to evaluate in the paused frame").option("--remote-root <value>", "Path-mapping anchor: literal path or regex:<pattern> / /pattern/flags").option("--timeout <seconds>", "How long to wait for an exception (default: 30)").option("--max-value-length <chars>", "Maximum characters per captured value before truncation (default: 4096)").option("--stack-depth <n>", "Walk this many call frames when capturing (default: 1)").option("--stack-captures <expr,\u2026>", "Expressions to evaluate on each call frame in the stack").option("--include-scopes", "Include expanded paused-frame scopes in the snapshot").option("--keep-paused", "Skip Debugger.resume after capture; Node may resume when this CLI disconnects").option("--no-json", "Print a human-readable summary instead of JSON").action(async (opts) => {
|
|
2925
3119
|
await handleException(opts);
|
|
2926
3120
|
});
|
|
@@ -2935,10 +3129,17 @@ function registerEval(program) {
|
|
|
2935
3129
|
function registerListScripts(program) {
|
|
2936
3130
|
applyTargetOptions(
|
|
2937
3131
|
program.command("list-scripts").description("Print the scripts the V8 instance currently knows about")
|
|
2938
|
-
).option("--no-json", "Print scriptId<TAB>url instead of JSON").action(async (opts) => {
|
|
3132
|
+
).option("--filter <pattern>", "Only include script URLs matching this pattern").option("--no-json", "Print scriptId<TAB>url instead of JSON").action(async (opts) => {
|
|
2939
3133
|
await handleListScripts(opts);
|
|
2940
3134
|
});
|
|
2941
3135
|
}
|
|
3136
|
+
function registerListTargets(program) {
|
|
3137
|
+
applyTargetOptions(
|
|
3138
|
+
program.command("list-targets").description("Print inspector targets from /json/list for selecting workers with --target")
|
|
3139
|
+
).option("--no-json", "Print index<TAB>type<TAB>title<TAB>url instead of JSON").action(async (opts) => {
|
|
3140
|
+
await handleListTargets(opts);
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
2942
3143
|
function registerAttach(program) {
|
|
2943
3144
|
applyTargetOptions(
|
|
2944
3145
|
program.command("attach").description("Connect, fetch the inspector version, and disconnect (smoke-test)")
|