@saptools/cf-inspector 0.4.7 → 0.4.10
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 +15 -90
- package/dist/cli.js +196 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +48 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Built so an AI agent (or a CI job) can drive a debugger from a single shell comm
|
|
|
10
10
|
[](./LICENSE)
|
|
11
11
|
[](https://nodejs.org)
|
|
12
12
|
|
|
13
|
-
[Install](#-install) • [Quick Start](#-quick-start) • [CLI](#-cli) • [
|
|
13
|
+
[Install](#-install) • [Quick Start](#-quick-start) • [CLI](#-cli) • [How it works](#-how-it-works)
|
|
14
14
|
|
|
15
15
|
</div>
|
|
16
16
|
|
|
@@ -129,9 +129,10 @@ strict immediate error is preferred.
|
|
|
129
129
|
|
|
130
130
|
For Cloud Foundry targets, replace `--port` with
|
|
131
131
|
`--region/--org/--space/--app`. Cloud Foundry commands and tunnel readiness
|
|
132
|
-
allow up to 180 seconds by default.
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
allow up to 180 seconds by default. For commands without their own wait
|
|
133
|
+
semantics, `--timeout <seconds>` controls CF tunnel readiness. For breakpoint
|
|
134
|
+
and exception commands, `--timeout` is reserved for the command wait and tunnel
|
|
135
|
+
readiness keeps the 180-second default.
|
|
135
136
|
|
|
136
137
|
### 📡 `cf-inspector log`
|
|
137
138
|
|
|
@@ -266,105 +267,29 @@ cf-inspector eval --port 9229 --expr 'process.uptime()'
|
|
|
266
267
|
|
|
267
268
|
### 📜 `cf-inspector list-scripts`
|
|
268
269
|
|
|
269
|
-
Print every script the V8 instance knows about (useful for debugging path-mapping issues).
|
|
270
|
+
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
271
|
|
|
271
272
|
```bash
|
|
272
|
-
cf-inspector list-scripts --port 9229
|
|
273
|
+
cf-inspector list-scripts --port 9229 --filter 'dist/.+\.js'
|
|
273
274
|
```
|
|
274
275
|
|
|
275
|
-
###
|
|
276
|
+
### 🎯 `cf-inspector list-targets`
|
|
276
277
|
|
|
277
|
-
|
|
278
|
+
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.
|
|
278
279
|
|
|
279
280
|
```bash
|
|
280
|
-
cf-inspector
|
|
281
|
+
cf-inspector list-targets --port 9229
|
|
282
|
+
cf-inspector snapshot --port 9229 --target 1 --bp dist/worker.js:42
|
|
281
283
|
```
|
|
282
284
|
|
|
283
|
-
|
|
285
|
+
### 🔗 `cf-inspector attach`
|
|
284
286
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```ts
|
|
288
|
-
import {
|
|
289
|
-
connectInspector,
|
|
290
|
-
setBreakpoint,
|
|
291
|
-
waitForPause,
|
|
292
|
-
captureSnapshot,
|
|
293
|
-
evaluateOnFrame,
|
|
294
|
-
resume,
|
|
295
|
-
} from "@saptools/cf-inspector";
|
|
296
|
-
|
|
297
|
-
const session = await connectInspector({ port: 9229 });
|
|
298
|
-
const bp = await setBreakpoint(session, {
|
|
299
|
-
file: "src/handler.ts",
|
|
300
|
-
line: 42,
|
|
301
|
-
});
|
|
302
|
-
const pause = await waitForPause(session, { timeoutMs: 30_000 });
|
|
303
|
-
const snapshot = await captureSnapshot(session, pause, {
|
|
304
|
-
captures: ["this.user"],
|
|
305
|
-
maxValueLength: 4096,
|
|
306
|
-
});
|
|
307
|
-
const topFrame = pause.callFrames[0];
|
|
308
|
-
if (topFrame === undefined) {
|
|
309
|
-
throw new Error("Breakpoint paused without a call frame");
|
|
310
|
-
}
|
|
311
|
-
const customValue = await evaluateOnFrame(session, topFrame.callFrameId, "this.user");
|
|
312
|
-
await resume(session);
|
|
313
|
-
await session.dispose();
|
|
287
|
+
Connect, fetch the runtime version, print it, disconnect. Useful as a smoke-test that the tunnel is healthy.
|
|
314
288
|
|
|
315
|
-
|
|
289
|
+
```bash
|
|
290
|
+
cf-inspector attach --port 9229
|
|
316
291
|
```
|
|
317
292
|
|
|
318
|
-
<details>
|
|
319
|
-
<summary><b>📚 Full export list</b></summary>
|
|
320
|
-
|
|
321
|
-
| Export | Description |
|
|
322
|
-
| --- | --- |
|
|
323
|
-
| `connectInspector(options)` | Open a CDP WebSocket session against a port |
|
|
324
|
-
| `setBreakpoint(session, location)` | Set a breakpoint by file/line + optional remote root and `hitCount` |
|
|
325
|
-
| `removeBreakpoint(session, id)` | Remove a breakpoint by id |
|
|
326
|
-
| `setPauseOnExceptions(session, state)` | Configure exception pause state: `none / uncaught / caught / all` |
|
|
327
|
-
| `waitForPause(session, options)` | Resolve when the next `Debugger.paused` event fires; supports `pauseReasons` allow-list |
|
|
328
|
-
| `captureSnapshot(session, pause, options)` | Build a structured snapshot of the paused frame. Pass `includeScopes: true` to expand scopes, `stackDepth` + `stackCaptures` for multi-frame walks, or `maxValueLength` to override the default captured value limit |
|
|
329
|
-
| `captureException(session, pause, maxValueLength)` | Materialize the exception attached to a `Debugger.paused` event |
|
|
330
|
-
| `walkStack(session, frames, options)` | Walk a call stack and evaluate per-frame expressions |
|
|
331
|
-
| `evaluateOnFrame(session, frameId, expression)` | Evaluate in a paused frame |
|
|
332
|
-
| `evaluateGlobal(session, expression)` | Evaluate against the global Runtime |
|
|
333
|
-
| `listScripts(session)` | Return the scripts the V8 instance knows about |
|
|
334
|
-
| `resume(session)` | Resume execution |
|
|
335
|
-
| `streamLogpoint(session, options)` | Stream a non-pausing logpoint until duration / signal / max-events / transport-close |
|
|
336
|
-
| `buildLogpointCondition(sentinel, expression, options?)` | Build the CDP `condition` string for a logpoint with optional predicate / hit-count gates |
|
|
337
|
-
| `buildHitCountedCondition(hitCount, key, userCondition?)` | Build the hit-count gate used by `setBreakpoint({ hitCount })` |
|
|
338
|
-
| `parseRemoteRoot(value)` | Parse a literal/regex remote-root setting |
|
|
339
|
-
| `buildBreakpointUrlRegex(input)` | Build a CDP `urlRegex` for a file path |
|
|
340
|
-
| `CfInspectorError` | Rich error class with typed `code` |
|
|
341
|
-
|
|
342
|
-
</details>
|
|
343
|
-
|
|
344
|
-
<details>
|
|
345
|
-
<summary><b>🧪 Error codes</b></summary>
|
|
346
|
-
|
|
347
|
-
| Code | When |
|
|
348
|
-
| --- | --- |
|
|
349
|
-
| `INVALID_ARGUMENT` | A numeric flag (`--port`, `--timeout`, `--duration`, …) is not a positive integer |
|
|
350
|
-
| `INVALID_BREAKPOINT` | `--bp` / `--at` is not in `file:line` form, or line is not a positive integer |
|
|
351
|
-
| `INVALID_REMOTE_ROOT` | `--remote-root` regex did not compile |
|
|
352
|
-
| `INVALID_EXPRESSION` | `--condition` or `--expr` failed to parse on V8 (`Runtime.compileScript` reported a SyntaxError) — fast-fail before the breakpoint is set |
|
|
353
|
-
| `INVALID_HIT_COUNT` | `--hit-count` is not a positive integer |
|
|
354
|
-
| `INVALID_PAUSE_TYPE` | `cf-inspector exception --type` is not one of `uncaught / caught / all` |
|
|
355
|
-
| `BREAKPOINT_DID_NOT_BIND` | Reserved: a breakpoint resolved to no scripts. Currently surfaced as a stderr warning only — see `BreakpointHandle.resolvedLocations` for programmatic detection |
|
|
356
|
-
| `INSPECTOR_DISCOVERY_FAILED` | `/json/list` did not return a usable WebSocket URL |
|
|
357
|
-
| `INSPECTOR_CONNECTION_FAILED` | WebSocket handshake failed, or the connection closed mid-request |
|
|
358
|
-
| `CDP_REQUEST_FAILED` | A CDP method returned an error result, timed out, or failed to send |
|
|
359
|
-
| `BREAKPOINT_NOT_HIT` | The breakpoint did not hit before the timeout elapsed |
|
|
360
|
-
| `UNRELATED_PAUSE` | The target paused somewhere else and `--fail-on-unmatched-pause` was enabled |
|
|
361
|
-
| `UNRELATED_PAUSE_TIMEOUT` | The target stayed paused somewhere else until the snapshot timeout elapsed |
|
|
362
|
-
| `EVALUATION_FAILED` | Reserved for future use — current evaluation paths surface remote exceptions inline via `CapturedExpression.error` instead of throwing |
|
|
363
|
-
| `MISSING_TARGET` | Neither `--port` nor a complete CF target (`--region/--org/--space/--app`) was provided |
|
|
364
|
-
| `ABORTED` | Reserved for future use by long-running streams when an `AbortSignal` fires |
|
|
365
|
-
|
|
366
|
-
</details>
|
|
367
|
-
|
|
368
293
|
---
|
|
369
294
|
|
|
370
295
|
## 🔭 How it works
|
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
|
});
|
|
@@ -2081,8 +2147,9 @@ import process5 from "process";
|
|
|
2081
2147
|
function warnOnUnboundBreakpoints(handles) {
|
|
2082
2148
|
for (const handle of handles) {
|
|
2083
2149
|
if (handle.resolvedLocations.length === 0) {
|
|
2150
|
+
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
2151
|
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
|
|
2152
|
+
`[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
2153
|
`
|
|
2087
2154
|
);
|
|
2088
2155
|
}
|
|
@@ -2122,7 +2189,7 @@ function formatPauseLocation(pause) {
|
|
|
2122
2189
|
// src/cli/commands/exception.ts
|
|
2123
2190
|
var VALID_PAUSE_TYPES = ["uncaught", "caught", "all"];
|
|
2124
2191
|
async function handleException(opts) {
|
|
2125
|
-
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2192
|
+
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2126
2193
|
const prepared = prepareExceptionCommand(opts, target);
|
|
2127
2194
|
const result = await runExceptionCommand(prepared, opts);
|
|
2128
2195
|
if (opts.json) {
|
|
@@ -2201,7 +2268,8 @@ async function disablePauseOnExceptionsBestEffort(session) {
|
|
|
2201
2268
|
import process7 from "process";
|
|
2202
2269
|
async function handleListScripts(opts) {
|
|
2203
2270
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2204
|
-
const
|
|
2271
|
+
const filter = compileScriptUrlFilter(opts.filter);
|
|
2272
|
+
const scripts = (await withSession(target, (session) => Promise.resolve(listScripts(session)))).filter((script) => filter === void 0 || filter(script.url));
|
|
2205
2273
|
if (opts.json) {
|
|
2206
2274
|
writeJson(scripts);
|
|
2207
2275
|
return;
|
|
@@ -2211,6 +2279,92 @@ async function handleListScripts(opts) {
|
|
|
2211
2279
|
`);
|
|
2212
2280
|
}
|
|
2213
2281
|
}
|
|
2282
|
+
async function handleListTargets(opts) {
|
|
2283
|
+
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2284
|
+
const tunnel = await openTarget(target);
|
|
2285
|
+
try {
|
|
2286
|
+
const targets = await discoverInspectorTargets(tunnel.host, tunnel.port, 5e3);
|
|
2287
|
+
const indexedTargets = targets.map((entry, index) => ({ index, ...entry }));
|
|
2288
|
+
if (opts.json) {
|
|
2289
|
+
writeJson(indexedTargets);
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
for (const entry of indexedTargets) {
|
|
2293
|
+
process7.stdout.write(`${entry.index.toString()} ${entry.type} ${entry.title} ${entry.url}
|
|
2294
|
+
`);
|
|
2295
|
+
}
|
|
2296
|
+
} finally {
|
|
2297
|
+
await tunnel.dispose();
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
function compileScriptUrlFilter(pattern) {
|
|
2301
|
+
if (pattern === void 0 || pattern.length === 0) {
|
|
2302
|
+
return void 0;
|
|
2303
|
+
}
|
|
2304
|
+
const alternatives = splitPatternAlternatives(pattern).map((alternative) => parseFilterTokens(alternative)).filter((tokens) => tokens.length > 0);
|
|
2305
|
+
return (url) => alternatives.some((tokens) => matchesFilterTokens(url, tokens));
|
|
2306
|
+
}
|
|
2307
|
+
function splitPatternAlternatives(pattern) {
|
|
2308
|
+
const alternatives = [];
|
|
2309
|
+
let current = "";
|
|
2310
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
2311
|
+
const char = pattern[index] ?? "";
|
|
2312
|
+
if (char === "\\" && index + 1 < pattern.length) {
|
|
2313
|
+
current += `${char}${pattern[index + 1] ?? ""}`;
|
|
2314
|
+
index++;
|
|
2315
|
+
} else if (char === "|") {
|
|
2316
|
+
alternatives.push(current);
|
|
2317
|
+
current = "";
|
|
2318
|
+
} else {
|
|
2319
|
+
current += char;
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
alternatives.push(current);
|
|
2323
|
+
return alternatives;
|
|
2324
|
+
}
|
|
2325
|
+
function parseFilterTokens(pattern) {
|
|
2326
|
+
const tokens = [];
|
|
2327
|
+
let literal = "";
|
|
2328
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
2329
|
+
const char = pattern[index] ?? "";
|
|
2330
|
+
const nextChar = pattern[index + 1];
|
|
2331
|
+
if (char === "\\" && nextChar !== void 0) {
|
|
2332
|
+
literal += nextChar;
|
|
2333
|
+
index++;
|
|
2334
|
+
} else if (char === "." && (nextChar === "*" || nextChar === "+")) {
|
|
2335
|
+
if (literal.length > 0) {
|
|
2336
|
+
tokens.push(literal);
|
|
2337
|
+
}
|
|
2338
|
+
literal = "";
|
|
2339
|
+
tokens.push({ kind: "wildcard", minChars: nextChar === "+" ? 1 : 0 });
|
|
2340
|
+
index++;
|
|
2341
|
+
} else {
|
|
2342
|
+
literal += char;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
if (literal.length > 0) {
|
|
2346
|
+
tokens.push(literal);
|
|
2347
|
+
}
|
|
2348
|
+
return tokens;
|
|
2349
|
+
}
|
|
2350
|
+
function matchesFilterTokens(value, tokens) {
|
|
2351
|
+
let position = 0;
|
|
2352
|
+
for (const token of tokens) {
|
|
2353
|
+
if (typeof token === "string") {
|
|
2354
|
+
const nextPosition = value.indexOf(token, position);
|
|
2355
|
+
if (nextPosition === -1) {
|
|
2356
|
+
return false;
|
|
2357
|
+
}
|
|
2358
|
+
position = nextPosition + token.length;
|
|
2359
|
+
} else {
|
|
2360
|
+
if (token.minChars === 1 && position >= value.length) {
|
|
2361
|
+
return false;
|
|
2362
|
+
}
|
|
2363
|
+
position += token.minChars;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
return true;
|
|
2367
|
+
}
|
|
2214
2368
|
|
|
2215
2369
|
// src/cli/commands/log.ts
|
|
2216
2370
|
import process9 from "process";
|
|
@@ -2534,7 +2688,7 @@ import { performance as performance4 } from "perf_hooks";
|
|
|
2534
2688
|
import process10 from "process";
|
|
2535
2689
|
init_types();
|
|
2536
2690
|
async function handleSnapshot(opts) {
|
|
2537
|
-
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2691
|
+
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2538
2692
|
const prepared = prepareSnapshotCommand(opts, target);
|
|
2539
2693
|
const reportProgress = opts.quiet === true ? void 0 : writeProgress;
|
|
2540
2694
|
const result = await runSnapshotCommand(prepared, opts, reportProgress);
|
|
@@ -2660,7 +2814,7 @@ import { performance as performance5 } from "perf_hooks";
|
|
|
2660
2814
|
import process11 from "process";
|
|
2661
2815
|
init_types();
|
|
2662
2816
|
async function handleWatch(opts) {
|
|
2663
|
-
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
2817
|
+
const target = await resolveTargetWithCurrentCfTarget(opts, { useTimeoutForTunnel: false });
|
|
2664
2818
|
const prepared = prepareWatchCommand(opts, target);
|
|
2665
2819
|
let stoppedReason = "signal";
|
|
2666
2820
|
let emitted = 0;
|
|
@@ -2878,8 +3032,9 @@ function writeWatchSummary(reason, emitted, json) {
|
|
|
2878
3032
|
}
|
|
2879
3033
|
|
|
2880
3034
|
// src/cli/program.ts
|
|
2881
|
-
function applyTargetOptions(cmd) {
|
|
2882
|
-
|
|
3035
|
+
function applyTargetOptions(cmd, options = {}) {
|
|
3036
|
+
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)");
|
|
3037
|
+
return options.includeTimeout === false ? withBaseOptions : withBaseOptions.option("--timeout <seconds>", "Timeout for CF tunnel readiness in seconds (default: 180)");
|
|
2883
3038
|
}
|
|
2884
3039
|
var collectStrings = (value, prev = []) => [
|
|
2885
3040
|
...prev,
|
|
@@ -2894,12 +3049,14 @@ async function main(argv) {
|
|
|
2894
3049
|
registerException(program);
|
|
2895
3050
|
registerEval(program);
|
|
2896
3051
|
registerListScripts(program);
|
|
3052
|
+
registerListTargets(program);
|
|
2897
3053
|
registerAttach(program);
|
|
2898
3054
|
await program.parseAsync([...argv]);
|
|
2899
3055
|
}
|
|
2900
3056
|
function registerSnapshot(program) {
|
|
2901
3057
|
applyTargetOptions(
|
|
2902
|
-
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
|
|
3058
|
+
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume"),
|
|
3059
|
+
{ includeTimeout: false }
|
|
2903
3060
|
).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("--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
3061
|
await handleSnapshot(opts);
|
|
2905
3062
|
});
|
|
@@ -2913,14 +3070,16 @@ function registerLog(program) {
|
|
|
2913
3070
|
}
|
|
2914
3071
|
function registerWatch(program) {
|
|
2915
3072
|
applyTargetOptions(
|
|
2916
|
-
program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits")
|
|
3073
|
+
program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits"),
|
|
3074
|
+
{ includeTimeout: false }
|
|
2917
3075
|
).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("--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
3076
|
await handleWatch(opts);
|
|
2919
3077
|
});
|
|
2920
3078
|
}
|
|
2921
3079
|
function registerException(program) {
|
|
2922
3080
|
applyTargetOptions(
|
|
2923
|
-
program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume")
|
|
3081
|
+
program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume"),
|
|
3082
|
+
{ includeTimeout: false }
|
|
2924
3083
|
).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
3084
|
await handleException(opts);
|
|
2926
3085
|
});
|
|
@@ -2935,10 +3094,17 @@ function registerEval(program) {
|
|
|
2935
3094
|
function registerListScripts(program) {
|
|
2936
3095
|
applyTargetOptions(
|
|
2937
3096
|
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) => {
|
|
3097
|
+
).option("--filter <pattern>", "Only include script URLs matching this pattern").option("--no-json", "Print scriptId<TAB>url instead of JSON").action(async (opts) => {
|
|
2939
3098
|
await handleListScripts(opts);
|
|
2940
3099
|
});
|
|
2941
3100
|
}
|
|
3101
|
+
function registerListTargets(program) {
|
|
3102
|
+
applyTargetOptions(
|
|
3103
|
+
program.command("list-targets").description("Print inspector targets from /json/list for selecting workers with --target")
|
|
3104
|
+
).option("--no-json", "Print index<TAB>type<TAB>title<TAB>url instead of JSON").action(async (opts) => {
|
|
3105
|
+
await handleListTargets(opts);
|
|
3106
|
+
});
|
|
3107
|
+
}
|
|
2942
3108
|
function registerAttach(program) {
|
|
2943
3109
|
applyTargetOptions(
|
|
2944
3110
|
program.command("attach").description("Connect, fetch the inspector version, and disconnect (smoke-test)")
|