@saptools/cf-inspector 0.7.1 โ 0.8.1
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 +54 -1
- package/dist/cli.js +236 -27
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +28 -6
- package/dist/index.js +61 -4
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -37,6 +37,8 @@ Built so an AI agent (or a CI job) can drive a debugger from a single shell comm
|
|
|
37
37
|
- ๐ก **Non-pausing logpoints** โ `cf-inspector log --at file:line --expr 'JSON.stringify({โฆ})'` streams JSON Lines as the line executes without pausing the inspectee, with optional `--condition`, `--hit-count`, and `--max-events`
|
|
38
38
|
- ๐ก๏ธ **Read-only capture guard** โ snapshot, watch, and exception captures use V8's side-effect analysis by default; `--allow-mutation` is an explicit escape hatch
|
|
39
39
|
- ๐งต **Automatic worker fan-out** โ snapshot, watch, exception, and log attach to the main isolate plus every current or newly-spawned NodeWorker; explicit selectors remain available for pinning
|
|
40
|
+
- ๐ฆ **Machine-readable readiness** โ opt into a versioned `breakpoint-armed` stderr event before triggering external traffic; no prose matching or guessed delay required
|
|
41
|
+
- ๐ **Isolate-aware script listing** โ `list-scripts` aggregates the main isolate and every current worker, tagging every script with its isolate
|
|
40
42
|
- ๐ง **Agent-friendly** โ JSON-by-default I/O, deterministic shapes, and explicit `truncated`/`originalLength`/`omittedCount` metadata for bounded values
|
|
41
43
|
- ๐งญ **Path mapping** โ local `src/handler.ts:42` is matched against the remote URL via a `urlRegex`, with optional `--remote-root` literal or regex (same DSL as `cds-debug`)
|
|
42
44
|
- ๐ **Composes with `cf-debugger`** โ pass `--app/--region/--org/--space` and the tunnel is opened automatically; pass `--port` to attach to anything CDP-speaking
|
|
@@ -97,6 +99,42 @@ Use `--worker-id <id>` to pin a stable live worker ID from `list-targets`,
|
|
|
97
99
|
pin a raw inspector target, or `--main-only` to deliberately ignore workers.
|
|
98
100
|
Explicit selectors preserve single-isolate behavior.
|
|
99
101
|
|
|
102
|
+
### Automation readiness contract
|
|
103
|
+
|
|
104
|
+
Commands that arm debugger behavior before waiting (`snapshot`, `watch`,
|
|
105
|
+
`exception`, and `log`) accept `--ready-event`. The flag writes exactly one
|
|
106
|
+
compact JSON object to `stderr` after every session present during initial
|
|
107
|
+
arming has completed its CDP setup and immediately before the command waits or
|
|
108
|
+
streams:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{"event":"breakpoint-armed","schemaVersion":1,"command":"snapshot","sessions":3,"resolvedLocations":2,"timeoutMs":30000}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This is the supported synchronization contract for callers that must start the
|
|
115
|
+
CLI before firing an HTTP request, queue job, or other external trigger. Parse
|
|
116
|
+
the JSON line and require `event === "breakpoint-armed"` plus
|
|
117
|
+
`schemaVersion === 1`, then confirm `command` matches the invocation; do not
|
|
118
|
+
poll prose such as `Waiting up to`.
|
|
119
|
+
|
|
120
|
+
| Field | Contract |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `event` | Always `"breakpoint-armed"` |
|
|
123
|
+
| `schemaVersion` | Event schema version; currently `1` |
|
|
124
|
+
| `command` | `snapshot`, `watch`, `exception`, or `log` |
|
|
125
|
+
| `sessions` | Number of main/worker sessions whose initial arming completed before emission |
|
|
126
|
+
| `resolvedLocations` | Total locations V8 had already resolved, or `null` for `exception`; `0` can still represent an accepted URL breakpoint that may resolve when a script loads |
|
|
127
|
+
| `timeoutMs` | Snapshot/exception wait or watch per-hit timeout; `null` for log |
|
|
128
|
+
|
|
129
|
+
Workers attaching after this one-time event are still armed dynamically, but
|
|
130
|
+
are not included in its `sessions` or `resolvedLocations` totals. If initial
|
|
131
|
+
arming fails or the command is cancelled first, no readiness event is emitted.
|
|
132
|
+
With `log --ready-event`, matching events received while the remaining initial
|
|
133
|
+
sessions are still arming are neither emitted nor counted, so the log stream
|
|
134
|
+
cannot precede its readiness marker.
|
|
135
|
+
Without `--ready-event`, stderr/stdout behavior is unchanged. An explicitly
|
|
136
|
+
requested event is still emitted with `snapshot --quiet`.
|
|
137
|
+
|
|
100
138
|
---
|
|
101
139
|
|
|
102
140
|
## ๐งฐ CLI
|
|
@@ -143,6 +181,7 @@ cf-inspector snapshot --port 9229 \
|
|
|
143
181
|
| `--include-scopes` | Include expanded paused-frame scopes under `topFrame.scopes`. Omitted by default to keep targeted captures concise |
|
|
144
182
|
| `--no-json` | Print a human-readable summary instead of JSON |
|
|
145
183
|
| `--quiet` | Suppress snapshot progress messages on stderr |
|
|
184
|
+
| `--ready-event` | Emit the versioned `breakpoint-armed` JSON event on stderr after every current isolate is armed |
|
|
146
185
|
| `--keep-paused` | Skip `Debugger.resume` after capture |
|
|
147
186
|
| `--fail-on-unmatched-pause` | Fail immediately if the target pauses somewhere else instead of waiting cooperatively |
|
|
148
187
|
|
|
@@ -269,6 +308,7 @@ When the user expression throws, the event is emitted with `error` instead of `v
|
|
|
269
308
|
| `--max-value-length <chars>` | Maximum characters per log value (streaming default: `4096`). Truncated events include `truncated` and `originalLength` |
|
|
270
309
|
| `--remote-root <value>` | Optional path-mapping anchor (same DSL as `snapshot`) |
|
|
271
310
|
| `--no-json` | Print human-readable lines instead of JSON Lines |
|
|
311
|
+
| `--ready-event` | Emit the versioned `breakpoint-armed` JSON event on stderr after every current isolate is armed |
|
|
272
312
|
|
|
273
313
|
Native logpoint expressions and conditions have no V8 side-effect gate. The
|
|
274
314
|
CLI warns when its best-effort syntax scan recognizes assignments or common
|
|
@@ -319,6 +359,7 @@ Each event is a `WatchEvent`:
|
|
|
319
359
|
| `--allow-mutation` | Disable the capture side-effect guard and explicitly allow mutation-shaped native conditions |
|
|
320
360
|
| `--include-scopes` | Include expanded paused-frame scopes per hit |
|
|
321
361
|
| `--no-json` | Print human-readable lines instead of JSON Lines |
|
|
362
|
+
| `--ready-event` | Emit the versioned `breakpoint-armed` JSON event on stderr after every current isolate is armed |
|
|
322
363
|
|
|
323
364
|
### ๐ฅ `cf-inspector exception`
|
|
324
365
|
|
|
@@ -365,6 +406,7 @@ Result is a `SnapshotResult` with an extra `exception` field:
|
|
|
365
406
|
| `--max-value-length <chars>` | Maximum characters per captured value (one-shot default: `131072`) |
|
|
366
407
|
| `--keep-paused` | Skip `Debugger.resume` after capture |
|
|
367
408
|
| `--no-json` | Print a human-readable summary instead of JSON |
|
|
409
|
+
| `--ready-event` | Emit the versioned `breakpoint-armed` JSON event on stderr after pause-on-exception is active in every current isolate |
|
|
368
410
|
|
|
369
411
|
### ๐งฎ `cf-inspector eval`
|
|
370
412
|
|
|
@@ -382,12 +424,23 @@ cf-inspector eval --port 9229 --expr 'process.uptime()'
|
|
|
382
424
|
|
|
383
425
|
### ๐ `cf-inspector list-scripts`
|
|
384
426
|
|
|
385
|
-
Print every script
|
|
427
|
+
Print every script known by the main isolate and each currently attached
|
|
428
|
+
NodeWorker (useful for debugging path-mapping issues). Each JSON entry carries
|
|
429
|
+
`isolate: {"kind":"main"}` or
|
|
430
|
+
`isolate: {"kind":"worker","workerId":"โฆ"}`, so duplicate per-isolate
|
|
431
|
+
`scriptId` values remain unambiguous. Add `--filter <pattern>` to narrow noisy
|
|
432
|
+
script lists with a literal/wildcard pattern; `|` separates alternatives and
|
|
433
|
+
`.*` / `.+` match variable text.
|
|
386
434
|
|
|
387
435
|
```bash
|
|
388
436
|
cf-inspector list-scripts --port 9229 --filter 'dist/.+\.js'
|
|
389
437
|
```
|
|
390
438
|
|
|
439
|
+
With `--no-json`, rows are
|
|
440
|
+
`scriptId<TAB>url<TAB>isolate`, where isolate is `main` or
|
|
441
|
+
`worker:<workerId>`. `--main-only`, `--worker-id`, `--worker`, and `--target`
|
|
442
|
+
retain their normal narrowing behavior.
|
|
443
|
+
|
|
391
444
|
### ๐ฏ `cf-inspector list-targets`
|
|
392
445
|
|
|
393
446
|
Print raw `/json/list` inspector targets with stable `index` values, likely
|
package/dist/cli.js
CHANGED
|
@@ -391,6 +391,15 @@ function writeProgress(message) {
|
|
|
391
391
|
process2.stderr.write(`[cf-inspector] ${message}
|
|
392
392
|
`);
|
|
393
393
|
}
|
|
394
|
+
function writeArmedEvent(event) {
|
|
395
|
+
const payload = {
|
|
396
|
+
event: "breakpoint-armed",
|
|
397
|
+
schemaVersion: 1,
|
|
398
|
+
...event
|
|
399
|
+
};
|
|
400
|
+
process2.stderr.write(`${JSON.stringify(payload)}
|
|
401
|
+
`);
|
|
402
|
+
}
|
|
394
403
|
function writeJson(value) {
|
|
395
404
|
process2.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
396
405
|
`);
|
|
@@ -630,7 +639,7 @@ import { performance as performance2 } from "perf_hooks";
|
|
|
630
639
|
// src/cdp/client.ts
|
|
631
640
|
init_types();
|
|
632
641
|
import { EventEmitter } from "events";
|
|
633
|
-
var DEFAULT_REQUEST_TIMEOUT_MS =
|
|
642
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 6e4;
|
|
634
643
|
function parseMessage(raw) {
|
|
635
644
|
try {
|
|
636
645
|
const value = JSON.parse(raw);
|
|
@@ -3103,6 +3112,8 @@ var BreakpointFanout = class {
|
|
|
3103
3112
|
detachError;
|
|
3104
3113
|
activeRace;
|
|
3105
3114
|
pauseReasons = [];
|
|
3115
|
+
pendingSetupError;
|
|
3116
|
+
preserveReadinessErrors = false;
|
|
3106
3117
|
constructor(group, setupSession, pauseReasons = []) {
|
|
3107
3118
|
this.pauseReasons = pauseReasons;
|
|
3108
3119
|
this.detach = group.onSession((session) => {
|
|
@@ -3128,13 +3139,51 @@ var BreakpointFanout = class {
|
|
|
3128
3139
|
this.activeRace?.remove(session);
|
|
3129
3140
|
});
|
|
3130
3141
|
this.detachError = group.onError((error) => {
|
|
3142
|
+
if (this.setupErrors.length === 0 && this.pendingSetupError === void 0) {
|
|
3143
|
+
this.pendingSetupError = error;
|
|
3144
|
+
}
|
|
3131
3145
|
for (const reject of this.setupErrors) {
|
|
3132
3146
|
reject(error);
|
|
3133
3147
|
}
|
|
3134
3148
|
});
|
|
3135
3149
|
}
|
|
3136
|
-
async ready() {
|
|
3137
|
-
|
|
3150
|
+
async ready(options = {}) {
|
|
3151
|
+
if (options.includeNewSessions !== true) {
|
|
3152
|
+
const records = [...this.records.values()];
|
|
3153
|
+
await Promise.all(records.map((record) => record.setup));
|
|
3154
|
+
options.onReady?.(this.outcomesFor(records));
|
|
3155
|
+
return;
|
|
3156
|
+
}
|
|
3157
|
+
this.preserveReadinessErrors = true;
|
|
3158
|
+
const pendingError = this.takePendingSetupError();
|
|
3159
|
+
if (pendingError !== void 0) {
|
|
3160
|
+
throw pendingError;
|
|
3161
|
+
}
|
|
3162
|
+
let rejectGroupError = () => void 0;
|
|
3163
|
+
const groupError = new Promise((_resolve, reject) => {
|
|
3164
|
+
rejectGroupError = reject;
|
|
3165
|
+
});
|
|
3166
|
+
this.setupErrors.push(rejectGroupError);
|
|
3167
|
+
try {
|
|
3168
|
+
let stableRecords;
|
|
3169
|
+
while (stableRecords === void 0) {
|
|
3170
|
+
const records = [...this.records.values()];
|
|
3171
|
+
await Promise.race([
|
|
3172
|
+
Promise.all(records.map((record) => record.setup)),
|
|
3173
|
+
groupError
|
|
3174
|
+
]);
|
|
3175
|
+
const stable = records.length === this.records.size && records.every((record) => this.records.get(record.session) === record);
|
|
3176
|
+
if (stable) {
|
|
3177
|
+
stableRecords = records;
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
options.onReady?.(this.outcomesFor(stableRecords));
|
|
3181
|
+
} finally {
|
|
3182
|
+
const index = this.setupErrors.indexOf(rejectGroupError);
|
|
3183
|
+
if (index >= 0) {
|
|
3184
|
+
this.setupErrors.splice(index, 1);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3138
3187
|
}
|
|
3139
3188
|
trackHandle(session, handle) {
|
|
3140
3189
|
const record = this.records.get(session);
|
|
@@ -3143,11 +3192,19 @@ var BreakpointFanout = class {
|
|
|
3143
3192
|
}
|
|
3144
3193
|
}
|
|
3145
3194
|
availableOutcomes() {
|
|
3146
|
-
return [...this.records.values()]
|
|
3195
|
+
return this.outcomesFor([...this.records.values()]);
|
|
3196
|
+
}
|
|
3197
|
+
outcomesFor(records) {
|
|
3198
|
+
return records.map((record) => ({
|
|
3147
3199
|
session: record.session,
|
|
3148
3200
|
setup: { handles: record.handles }
|
|
3149
3201
|
}));
|
|
3150
3202
|
}
|
|
3203
|
+
takePendingSetupError() {
|
|
3204
|
+
const error = this.pendingSetupError;
|
|
3205
|
+
this.pendingSetupError = void 0;
|
|
3206
|
+
return error;
|
|
3207
|
+
}
|
|
3151
3208
|
async waitForFirst(timeoutMs, options = {}, signal) {
|
|
3152
3209
|
if (this.activeRace !== void 0) {
|
|
3153
3210
|
throw new CfInspectorError("INVALID_ARGUMENT", "A fan-out pause race is already active");
|
|
@@ -3156,6 +3213,12 @@ var BreakpointFanout = class {
|
|
|
3156
3213
|
this.pauseReasons = options.pauseReasons ?? [];
|
|
3157
3214
|
this.activeRace = race;
|
|
3158
3215
|
this.setupErrors.push(race.reject);
|
|
3216
|
+
if (this.preserveReadinessErrors) {
|
|
3217
|
+
const pendingError = this.takePendingSetupError();
|
|
3218
|
+
if (pendingError !== void 0) {
|
|
3219
|
+
race.reject(pendingError);
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3159
3222
|
for (const record of this.records.values()) {
|
|
3160
3223
|
race.add(record);
|
|
3161
3224
|
}
|
|
@@ -4126,7 +4189,20 @@ async function runExceptionCommand(command, opts, signal) {
|
|
|
4126
4189
|
let winner;
|
|
4127
4190
|
let preserveWinner = false;
|
|
4128
4191
|
try {
|
|
4129
|
-
await fanout.ready(
|
|
4192
|
+
await fanout.ready(opts.readyEvent === true ? {
|
|
4193
|
+
includeNewSessions: true,
|
|
4194
|
+
onReady: (outcomes) => {
|
|
4195
|
+
if (signal?.aborted === true) {
|
|
4196
|
+
return;
|
|
4197
|
+
}
|
|
4198
|
+
writeArmedEvent({
|
|
4199
|
+
command: "exception",
|
|
4200
|
+
sessions: outcomes.length,
|
|
4201
|
+
resolvedLocations: null,
|
|
4202
|
+
timeoutMs: command.timeoutMs
|
|
4203
|
+
});
|
|
4204
|
+
}
|
|
4205
|
+
} : {});
|
|
4130
4206
|
const hit = await fanout.waitForFirst(command.timeoutMs, {
|
|
4131
4207
|
pauseReasons: ["exception", "promiseRejection"],
|
|
4132
4208
|
unmatchedPausePolicy: "wait-for-resume"
|
|
@@ -4179,16 +4255,44 @@ import process9 from "process";
|
|
|
4179
4255
|
async function handleListScripts(opts) {
|
|
4180
4256
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
4181
4257
|
const filter = compileScriptUrlFilter(opts.filter);
|
|
4182
|
-
const scripts = (await
|
|
4258
|
+
const scripts = (await withSessions(target, (group) => {
|
|
4259
|
+
const sessions = group.list();
|
|
4260
|
+
warnOnListScriptsSelection(target, {
|
|
4261
|
+
targetCount: group.targetCount,
|
|
4262
|
+
targetIndex: group.targetIndex,
|
|
4263
|
+
...sessions[0]?.workerTargets === void 0 ? {} : { workerTargets: sessions[0].workerTargets }
|
|
4264
|
+
});
|
|
4265
|
+
return Promise.resolve(collectListedScripts(sessions));
|
|
4266
|
+
})).filter((script) => filter === void 0 || filter(script.url));
|
|
4183
4267
|
if (opts.json) {
|
|
4184
4268
|
writeJson(scripts);
|
|
4185
4269
|
return;
|
|
4186
4270
|
}
|
|
4187
4271
|
for (const script of scripts) {
|
|
4188
|
-
process9.stdout.write(
|
|
4189
|
-
|
|
4272
|
+
process9.stdout.write(
|
|
4273
|
+
`${script.scriptId} ${script.url} ${formatIsolate2(script.isolate)}
|
|
4274
|
+
`
|
|
4275
|
+
);
|
|
4190
4276
|
}
|
|
4191
4277
|
}
|
|
4278
|
+
function warnOnListScriptsSelection(target, selection) {
|
|
4279
|
+
const autoAttach = target.targetIndex === void 0 && target.workerIndex === void 0 && target.workerId === void 0 && target.mainOnly !== true;
|
|
4280
|
+
const isolateWasExplicit = autoAttach || target.workerIndex !== void 0 || target.workerId !== void 0 || target.mainOnly === true;
|
|
4281
|
+
warnOnImplicitInspectorSelection(
|
|
4282
|
+
selection,
|
|
4283
|
+
target.targetIndex !== void 0,
|
|
4284
|
+
isolateWasExplicit
|
|
4285
|
+
);
|
|
4286
|
+
}
|
|
4287
|
+
function collectListedScripts(sessions) {
|
|
4288
|
+
return sessions.flatMap((session) => listScripts(session).map((script) => ({
|
|
4289
|
+
...script,
|
|
4290
|
+
isolate: session.isolate ?? { kind: "main" }
|
|
4291
|
+
})));
|
|
4292
|
+
}
|
|
4293
|
+
function formatIsolate2(isolate) {
|
|
4294
|
+
return isolate.kind === "worker" ? `worker:${isolate.workerId}` : "main";
|
|
4295
|
+
}
|
|
4192
4296
|
async function handleListTargets(opts) {
|
|
4193
4297
|
const target = await resolveTargetWithCurrentCfTarget(opts);
|
|
4194
4298
|
const tunnel = await openTarget(target);
|
|
@@ -4521,6 +4625,9 @@ async function streamLogpoint(session, options) {
|
|
|
4521
4625
|
if (event === void 0) {
|
|
4522
4626
|
return;
|
|
4523
4627
|
}
|
|
4628
|
+
if (options.eventGate?.() === false) {
|
|
4629
|
+
return;
|
|
4630
|
+
}
|
|
4524
4631
|
emitted += 1;
|
|
4525
4632
|
try {
|
|
4526
4633
|
options.onEvent(event);
|
|
@@ -4639,6 +4746,7 @@ async function handleLog(opts) {
|
|
|
4639
4746
|
...condition === void 0 ? {} : { condition },
|
|
4640
4747
|
maxValueLength,
|
|
4641
4748
|
json: opts.json,
|
|
4749
|
+
emitReadyEvent: opts.readyEvent === true,
|
|
4642
4750
|
signal
|
|
4643
4751
|
});
|
|
4644
4752
|
writeLogSummary(result.stoppedReason, result.emitted, opts.json);
|
|
@@ -4650,9 +4758,13 @@ async function runLogGroup(group, options) {
|
|
|
4650
4758
|
const tasks = /* @__PURE__ */ new Set();
|
|
4651
4759
|
const removedSessions = /* @__PURE__ */ new Set();
|
|
4652
4760
|
const results = [];
|
|
4761
|
+
const pendingArming = /* @__PURE__ */ new Set();
|
|
4762
|
+
const resolvedLocations = /* @__PURE__ */ new Map();
|
|
4653
4763
|
let fatalError;
|
|
4654
4764
|
let emitted = 0;
|
|
4655
4765
|
let reason = "signal";
|
|
4766
|
+
let sessionRegistrationComplete = false;
|
|
4767
|
+
let readyEventEmitted = !options.emitReadyEvent;
|
|
4656
4768
|
let resolveStop;
|
|
4657
4769
|
const stopped = new Promise((resolve) => {
|
|
4658
4770
|
resolveStop = resolve;
|
|
@@ -4675,10 +4787,35 @@ async function runLogGroup(group, options) {
|
|
|
4675
4787
|
const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
|
|
4676
4788
|
finish("duration");
|
|
4677
4789
|
}, options.durationMs);
|
|
4790
|
+
const detachError = options.emitReadyEvent ? group.onError((error) => {
|
|
4791
|
+
if (controller.signal.aborted) {
|
|
4792
|
+
return;
|
|
4793
|
+
}
|
|
4794
|
+
fatalError = error;
|
|
4795
|
+
finish("transport-closed");
|
|
4796
|
+
}) : () => void 0;
|
|
4797
|
+
const emitReadyEventIfArmed = () => {
|
|
4798
|
+
if (readyEventEmitted || !sessionRegistrationComplete || pendingArming.size > 0 || resolvedLocations.size === 0 || controller.signal.aborted) {
|
|
4799
|
+
return;
|
|
4800
|
+
}
|
|
4801
|
+
writeArmedEvent({
|
|
4802
|
+
command: "log",
|
|
4803
|
+
sessions: resolvedLocations.size,
|
|
4804
|
+
resolvedLocations: [...resolvedLocations.values()].reduce(
|
|
4805
|
+
(total, count) => total + count,
|
|
4806
|
+
0
|
|
4807
|
+
),
|
|
4808
|
+
timeoutMs: null
|
|
4809
|
+
});
|
|
4810
|
+
readyEventEmitted = true;
|
|
4811
|
+
};
|
|
4678
4812
|
const startSession = (session) => {
|
|
4679
4813
|
if (controller.signal.aborted) {
|
|
4680
4814
|
return;
|
|
4681
4815
|
}
|
|
4816
|
+
if (!readyEventEmitted) {
|
|
4817
|
+
pendingArming.add(session);
|
|
4818
|
+
}
|
|
4682
4819
|
const task = (async () => {
|
|
4683
4820
|
await validateExpression(session, options.expression);
|
|
4684
4821
|
if (options.condition !== void 0) {
|
|
@@ -4692,6 +4829,7 @@ async function runLogGroup(group, options) {
|
|
|
4692
4829
|
...options.condition === void 0 ? {} : { condition: options.condition },
|
|
4693
4830
|
maxValueLength: options.maxValueLength,
|
|
4694
4831
|
signal: controller.signal,
|
|
4832
|
+
...options.emitReadyEvent ? { eventGate: () => readyEventEmitted } : {},
|
|
4695
4833
|
onEvent: (event) => {
|
|
4696
4834
|
if (controller.signal.aborted) {
|
|
4697
4835
|
return;
|
|
@@ -4704,6 +4842,11 @@ async function runLogGroup(group, options) {
|
|
|
4704
4842
|
},
|
|
4705
4843
|
onBreakpointSet: (handle) => {
|
|
4706
4844
|
warnOnUnboundBreakpoints([handle]);
|
|
4845
|
+
if (!readyEventEmitted) {
|
|
4846
|
+
resolvedLocations.set(session, handle.resolvedLocations.length);
|
|
4847
|
+
pendingArming.delete(session);
|
|
4848
|
+
emitReadyEventIfArmed();
|
|
4849
|
+
}
|
|
4707
4850
|
}
|
|
4708
4851
|
});
|
|
4709
4852
|
})();
|
|
@@ -4716,6 +4859,9 @@ async function runLogGroup(group, options) {
|
|
|
4716
4859
|
}
|
|
4717
4860
|
},
|
|
4718
4861
|
(error) => {
|
|
4862
|
+
if (options.emitReadyEvent && (removedSessions.has(session) || fatalError !== void 0)) {
|
|
4863
|
+
return;
|
|
4864
|
+
}
|
|
4719
4865
|
fatalError = error;
|
|
4720
4866
|
finish("transport-closed");
|
|
4721
4867
|
}
|
|
@@ -4724,8 +4870,27 @@ async function runLogGroup(group, options) {
|
|
|
4724
4870
|
});
|
|
4725
4871
|
};
|
|
4726
4872
|
const detach = group.onSession(startSession);
|
|
4873
|
+
sessionRegistrationComplete = true;
|
|
4874
|
+
emitReadyEventIfArmed();
|
|
4727
4875
|
const detachRemoved = group.onSessionRemoved((session) => {
|
|
4728
4876
|
removedSessions.add(session);
|
|
4877
|
+
if (!readyEventEmitted) {
|
|
4878
|
+
if (controller.signal.aborted) {
|
|
4879
|
+
pendingArming.delete(session);
|
|
4880
|
+
resolvedLocations.delete(session);
|
|
4881
|
+
return;
|
|
4882
|
+
}
|
|
4883
|
+
if (pendingArming.has(session)) {
|
|
4884
|
+
fatalError = new CfInspectorError(
|
|
4885
|
+
"INSPECTOR_CONNECTION_FAILED",
|
|
4886
|
+
"A worker detached before logpoint arming completed; no readiness event was emitted."
|
|
4887
|
+
);
|
|
4888
|
+
finish("transport-closed");
|
|
4889
|
+
return;
|
|
4890
|
+
}
|
|
4891
|
+
resolvedLocations.delete(session);
|
|
4892
|
+
emitReadyEventIfArmed();
|
|
4893
|
+
}
|
|
4729
4894
|
});
|
|
4730
4895
|
try {
|
|
4731
4896
|
await stopped;
|
|
@@ -4733,6 +4898,7 @@ async function runLogGroup(group, options) {
|
|
|
4733
4898
|
} finally {
|
|
4734
4899
|
detach();
|
|
4735
4900
|
detachRemoved();
|
|
4901
|
+
detachError();
|
|
4736
4902
|
if (timer !== void 0) {
|
|
4737
4903
|
clearTimeout(timer);
|
|
4738
4904
|
}
|
|
@@ -4839,18 +5005,38 @@ async function runSnapshotCommand(command, opts, reportProgress, signal) {
|
|
|
4839
5005
|
let winner;
|
|
4840
5006
|
let preserveWinner = false;
|
|
4841
5007
|
try {
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
5008
|
+
const reportArmedSetup = (outcomes) => {
|
|
5009
|
+
if (opts.readyEvent === true && signal?.aborted === true) {
|
|
5010
|
+
return;
|
|
5011
|
+
}
|
|
5012
|
+
if (command.setupEvals.length > 0) {
|
|
5013
|
+
reportProgress?.("Setup evaluation complete.");
|
|
5014
|
+
}
|
|
5015
|
+
if (command.condition !== void 0) {
|
|
5016
|
+
reportProgress?.("Breakpoint condition is valid.");
|
|
5017
|
+
}
|
|
5018
|
+
const setup = reportBreakpointOutcomes(outcomes, reportProgress);
|
|
5019
|
+
reportProgress?.(
|
|
5020
|
+
`Waiting up to ${(command.timeoutMs / 1e3).toString()}s for a breakpoint hit...`
|
|
5021
|
+
);
|
|
5022
|
+
if (opts.readyEvent === true && signal?.aborted !== true) {
|
|
5023
|
+
writeArmedEvent({
|
|
5024
|
+
command: "snapshot",
|
|
5025
|
+
sessions: setup.sessions,
|
|
5026
|
+
resolvedLocations: setup.resolvedLocations,
|
|
5027
|
+
timeoutMs: command.timeoutMs
|
|
5028
|
+
});
|
|
5029
|
+
}
|
|
5030
|
+
};
|
|
5031
|
+
if (opts.readyEvent === true) {
|
|
5032
|
+
await fanout.ready({
|
|
5033
|
+
includeNewSessions: true,
|
|
5034
|
+
onReady: reportArmedSetup
|
|
5035
|
+
});
|
|
5036
|
+
} else {
|
|
5037
|
+
await fanout.ready();
|
|
5038
|
+
reportArmedSetup(fanout.availableOutcomes());
|
|
4848
5039
|
}
|
|
4849
|
-
const outcomes = fanout.availableOutcomes();
|
|
4850
|
-
reportBreakpointOutcomes(outcomes, reportProgress);
|
|
4851
|
-
reportProgress?.(
|
|
4852
|
-
`Waiting up to ${(command.timeoutMs / 1e3).toString()}s for a breakpoint hit...`
|
|
4853
|
-
);
|
|
4854
5040
|
const hit = await fanout.waitForFirst(command.timeoutMs, {
|
|
4855
5041
|
unmatchedPausePolicy: opts.failOnUnmatchedPause === true ? "fail" : "wait-for-resume",
|
|
4856
5042
|
...opts.failOnUnmatchedPause === true ? {} : { onUnmatchedPause: warnOnUnmatchedPause }
|
|
@@ -4898,11 +5084,12 @@ function reportBreakpointOutcomes(outcomes, reportProgress) {
|
|
|
4898
5084
|
reportProgress?.(
|
|
4899
5085
|
`Breakpoint setup complete: ${locations.toString()} resolved ${locations === 1 ? "location" : "locations"}.`
|
|
4900
5086
|
);
|
|
4901
|
-
return;
|
|
5087
|
+
return { sessions: outcomes.length, resolvedLocations: locations };
|
|
4902
5088
|
}
|
|
4903
5089
|
reportProgress?.(
|
|
4904
5090
|
`Breakpoint setup complete: sessions=${outcomes.length.toString()} boundSessions=${boundSessions.toString()} resolvedLocations=${locations.toString()}.`
|
|
4905
5091
|
);
|
|
5092
|
+
return { sessions: outcomes.length, resolvedLocations: locations };
|
|
4906
5093
|
}
|
|
4907
5094
|
async function captureSnapshotResult(session, pause, command, opts, reportProgress) {
|
|
4908
5095
|
const pausedStartedAt = pause.receivedAtMs ?? performance6.now();
|
|
@@ -5023,7 +5210,26 @@ async function runWatchGroup(group, command, opts, signal) {
|
|
|
5023
5210
|
let stoppedReason = "signal";
|
|
5024
5211
|
const deadline = computeDeadline(command.durationMs);
|
|
5025
5212
|
try {
|
|
5026
|
-
await fanout.ready(
|
|
5213
|
+
await fanout.ready(opts.readyEvent === true ? {
|
|
5214
|
+
includeNewSessions: true,
|
|
5215
|
+
onReady: (outcomes) => {
|
|
5216
|
+
if (signal.aborted || remainingForLoop(deadline, command.perHitTimeoutMs) <= 0) {
|
|
5217
|
+
return;
|
|
5218
|
+
}
|
|
5219
|
+
writeArmedEvent({
|
|
5220
|
+
command: "watch",
|
|
5221
|
+
sessions: outcomes.length,
|
|
5222
|
+
resolvedLocations: outcomes.reduce(
|
|
5223
|
+
(total, outcome) => total + outcome.setup.handles.reduce(
|
|
5224
|
+
(sessionTotal, handle) => sessionTotal + handle.resolvedLocations.length,
|
|
5225
|
+
0
|
|
5226
|
+
),
|
|
5227
|
+
0
|
|
5228
|
+
),
|
|
5229
|
+
timeoutMs: command.perHitTimeoutMs
|
|
5230
|
+
});
|
|
5231
|
+
}
|
|
5232
|
+
} : {});
|
|
5027
5233
|
while (!signal.aborted) {
|
|
5028
5234
|
const remainingMs = remainingForLoop(deadline, command.perHitTimeoutMs);
|
|
5029
5235
|
if (remainingMs <= 0) {
|
|
@@ -5197,6 +5403,7 @@ var collectStrings = (value, prev = []) => [
|
|
|
5197
5403
|
...prev,
|
|
5198
5404
|
value
|
|
5199
5405
|
];
|
|
5406
|
+
var READY_EVENT_DESCRIPTION = "Emit a versioned breakpoint-armed JSON event on stderr after every current isolate is armed";
|
|
5200
5407
|
function readPackageVersion() {
|
|
5201
5408
|
let current = dirname(fileURLToPath(import.meta.url));
|
|
5202
5409
|
for (let depth = 0; depth < 4; depth += 1) {
|
|
@@ -5242,14 +5449,14 @@ function registerSnapshot(program) {
|
|
|
5242
5449
|
applyTargetOptions(
|
|
5243
5450
|
program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume"),
|
|
5244
5451
|
{ includeTimeout: false }
|
|
5245
|
-
).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: 131072)").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("--allow-mutation", "Allow mutation-capable captures and native breakpoint conditions to run").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) => {
|
|
5452
|
+
).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: 131072)").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("--allow-mutation", "Allow mutation-capable captures and native breakpoint conditions to run").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("--ready-event", READY_EVENT_DESCRIPTION).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) => {
|
|
5246
5453
|
await handleSnapshot(opts);
|
|
5247
5454
|
});
|
|
5248
5455
|
}
|
|
5249
5456
|
function registerLog(program) {
|
|
5250
5457
|
applyTargetOptions(
|
|
5251
5458
|
program.command("log").description("Stream a non-pausing logpoint: log an expression each time a line executes")
|
|
5252
|
-
).requiredOption("--at <file:line>", "Logpoint location, e.g. src/handler.ts:42").requiredOption("--expr <expression>", "JavaScript expression to log on each hit").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 log events").option("--hit-count <n>", "Start logging once the line has been hit N or more times").option("--condition <expr>", "Only log when this JS expression evaluates truthy on the inspectee").option("--max-value-length <chars>", "Maximum characters per log value before truncation (default: 4096)").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
|
|
5459
|
+
).requiredOption("--at <file:line>", "Logpoint location, e.g. src/handler.ts:42").requiredOption("--expr <expression>", "JavaScript expression to log on each hit").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 log events").option("--hit-count <n>", "Start logging once the line has been hit N or more times").option("--condition <expr>", "Only log when this JS expression evaluates truthy on the inspectee").option("--max-value-length <chars>", "Maximum characters per log value before truncation (default: 4096)").option("--no-json", "Print human-readable lines instead of JSON Lines").option("--ready-event", READY_EVENT_DESCRIPTION).action(async (opts) => {
|
|
5253
5460
|
await handleLog(opts);
|
|
5254
5461
|
});
|
|
5255
5462
|
}
|
|
@@ -5257,7 +5464,7 @@ function registerWatch(program) {
|
|
|
5257
5464
|
applyTargetOptions(
|
|
5258
5465
|
program.command("watch").description("Stream a snapshot per breakpoint hit (multi-shot watch); resume between hits"),
|
|
5259
5466
|
{ includeTimeout: false }
|
|
5260
|
-
).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("--allow-mutation", "Allow mutation-capable captures and native breakpoint conditions to run").option("--include-scopes", "Include expanded paused-frame scopes per hit").option("--no-json", "Print human-readable lines instead of JSON Lines").action(async (opts) => {
|
|
5467
|
+
).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("--allow-mutation", "Allow mutation-capable captures and native breakpoint conditions to run").option("--include-scopes", "Include expanded paused-frame scopes per hit").option("--no-json", "Print human-readable lines instead of JSON Lines").option("--ready-event", READY_EVENT_DESCRIPTION).action(async (opts) => {
|
|
5261
5468
|
await handleWatch(opts);
|
|
5262
5469
|
});
|
|
5263
5470
|
}
|
|
@@ -5265,7 +5472,7 @@ function registerException(program) {
|
|
|
5265
5472
|
applyTargetOptions(
|
|
5266
5473
|
program.command("exception").description("Pause on a thrown exception, capture the value and frame, then resume"),
|
|
5267
5474
|
{ includeTimeout: false }
|
|
5268
|
-
).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: 131072)").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("--allow-mutation", "Allow mutation-capable capture expressions to run").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) => {
|
|
5475
|
+
).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: 131072)").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("--allow-mutation", "Allow mutation-capable capture expressions to run").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").option("--ready-event", READY_EVENT_DESCRIPTION).action(async (opts) => {
|
|
5269
5476
|
await handleException(opts);
|
|
5270
5477
|
});
|
|
5271
5478
|
}
|
|
@@ -5278,8 +5485,10 @@ function registerEval(program) {
|
|
|
5278
5485
|
}
|
|
5279
5486
|
function registerListScripts(program) {
|
|
5280
5487
|
applyTargetOptions(
|
|
5281
|
-
program.command("list-scripts").description(
|
|
5282
|
-
|
|
5488
|
+
program.command("list-scripts").description(
|
|
5489
|
+
"Print scripts from the main isolate and every worker, tagged by isolate"
|
|
5490
|
+
)
|
|
5491
|
+
).option("--filter <pattern>", "Only include script URLs matching this pattern").option("--no-json", "Print scriptId<TAB>url<TAB>isolate instead of JSON").action(async (opts) => {
|
|
5283
5492
|
await handleListScripts(opts);
|
|
5284
5493
|
});
|
|
5285
5494
|
}
|