@saptools/cf-inspector 0.3.17 → 0.3.18

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/dist/cli.js CHANGED
@@ -118,275 +118,6 @@ import { Command } from "commander";
118
118
  // src/cli/commands/attach.ts
119
119
  import process2 from "process";
120
120
 
121
- // src/pathMapper.ts
122
- init_types();
123
- var REGEX_PREFIX = "regex:";
124
- var REGEX_FLAGS_PATTERN = /^[dgimsuvy]*$/;
125
- var TS_JS_EXT_PATTERN = /\.(?:ts|js|mts|mjs|cts|cjs)$/i;
126
- function parseBreakpointSpec(input) {
127
- const idx = input.lastIndexOf(":");
128
- if (idx <= 0 || idx === input.length - 1) {
129
- throw new CfInspectorError(
130
- "INVALID_BREAKPOINT",
131
- `Breakpoint must be in 'file:line' form, received: "${input}"`
132
- );
133
- }
134
- const file = input.slice(0, idx).trim();
135
- const lineRaw = input.slice(idx + 1).trim();
136
- const line = Number.parseInt(lineRaw, 10);
137
- if (!Number.isInteger(line) || line <= 0 || line.toString() !== lineRaw) {
138
- throw new CfInspectorError(
139
- "INVALID_BREAKPOINT",
140
- `Breakpoint line must be a positive integer, received: "${lineRaw}"`
141
- );
142
- }
143
- if (file.length === 0) {
144
- throw new CfInspectorError(
145
- "INVALID_BREAKPOINT",
146
- `Breakpoint file path is empty in "${input}"`
147
- );
148
- }
149
- return { file, line };
150
- }
151
- function parseRemoteRoot(value) {
152
- const trimmed = value?.trim();
153
- if (trimmed === void 0 || trimmed.length === 0) {
154
- return { kind: "none" };
155
- }
156
- if (trimmed.startsWith(REGEX_PREFIX)) {
157
- return toRegex(trimmed.slice(REGEX_PREFIX.length), "");
158
- }
159
- const slashRegex = parseSlashDelimited(trimmed);
160
- if (slashRegex !== void 0) {
161
- return toRegex(slashRegex.pattern, slashRegex.flags);
162
- }
163
- return { kind: "literal", value: stripTrailingSlash(trimmed) };
164
- }
165
- function toRegex(pattern, flags) {
166
- try {
167
- const regex = new RegExp(pattern, flags);
168
- return { kind: "regex", pattern, flags, regex };
169
- } catch (err) {
170
- const message = err instanceof Error ? err.message : String(err);
171
- throw new CfInspectorError(
172
- "INVALID_REMOTE_ROOT",
173
- `Failed to compile remote-root regex "${pattern}" with flags "${flags}": ${message}`
174
- );
175
- }
176
- }
177
- function parseSlashDelimited(value) {
178
- if (!value.startsWith("/")) {
179
- return void 0;
180
- }
181
- const closing = findLastUnescapedSlash(value);
182
- if (closing <= 0) {
183
- return void 0;
184
- }
185
- const flags = value.slice(closing + 1);
186
- if (flags.length === 0 || !REGEX_FLAGS_PATTERN.test(flags)) {
187
- return void 0;
188
- }
189
- return { pattern: value.slice(1, closing), flags };
190
- }
191
- function findLastUnescapedSlash(value) {
192
- for (let i = value.length - 1; i > 0; i--) {
193
- if (value[i] === "/" && !isEscaped(value, i)) {
194
- return i;
195
- }
196
- }
197
- return -1;
198
- }
199
- function isEscaped(value, idx) {
200
- let backslashes = 0;
201
- for (let i = idx - 1; i >= 0; i--) {
202
- if (value[i] === "\\") {
203
- backslashes++;
204
- } else {
205
- break;
206
- }
207
- }
208
- return backslashes % 2 === 1;
209
- }
210
- function stripTrailingSlash(value) {
211
- if (value.length > 1 && value.endsWith("/")) {
212
- return value.slice(0, -1);
213
- }
214
- return value;
215
- }
216
- function normalizeRegexRootPattern(pattern) {
217
- const withoutStartAnchor = pattern.startsWith("^") ? pattern.slice(1) : pattern;
218
- const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
219
- return stripTrailingSlash(withoutEndAnchor);
220
- }
221
- function buildFileUrlRegex(rootPattern, tail) {
222
- const separator = rootPattern.endsWith("/") ? "" : "/";
223
- return `^file://${rootPattern}${separator}${tail}$`;
224
- }
225
- function escapeRegExp(value) {
226
- return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
227
- }
228
- function normalizeRelative(file) {
229
- return file.replaceAll(/^[./\\]+/g, "").replaceAll("\\", "/");
230
- }
231
- function dropExtension(file) {
232
- const match = TS_JS_EXT_PATTERN.exec(file);
233
- if (!match) {
234
- return { stem: file, matchedExt: false };
235
- }
236
- return { stem: file.slice(0, match.index), matchedExt: true };
237
- }
238
- var EXT_GROUP = String.raw`\.(?:ts|js|mts|mjs|cts|cjs)`;
239
- var OPTIONAL_EXT_GROUP = String.raw`(?:\.(?:ts|js|mts|mjs|cts|cjs))?`;
240
- function buildBreakpointUrlRegex(input) {
241
- const normalized = normalizeRelative(input.file);
242
- const { stem, matchedExt } = dropExtension(normalized);
243
- const escapedStem = escapeRegExp(stem);
244
- const tail = matchedExt ? `${escapedStem}${EXT_GROUP}` : `${escapedStem}${OPTIONAL_EXT_GROUP}`;
245
- switch (input.remoteRoot.kind) {
246
- case "none": {
247
- return `(?:^|/)${tail}$`;
248
- }
249
- case "literal": {
250
- const escapedRoot = escapeRegExp(input.remoteRoot.value);
251
- return buildFileUrlRegex(escapedRoot, tail);
252
- }
253
- case "regex": {
254
- const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
255
- return buildFileUrlRegex(rootPattern, tail);
256
- }
257
- }
258
- }
259
-
260
- // src/inspector/breakpoints.ts
261
- init_types();
262
-
263
- // src/inspector/conversions.ts
264
- function asString(value, fallback = "") {
265
- return typeof value === "string" ? value : fallback;
266
- }
267
- function asNumber(value, fallback = 0) {
268
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
269
- }
270
- function toResolvedLocations(value) {
271
- if (!Array.isArray(value)) {
272
- return [];
273
- }
274
- return value.flatMap((entry) => {
275
- if (typeof entry !== "object" || entry === null) {
276
- return [];
277
- }
278
- const candidate = entry;
279
- const scriptId = asString(candidate.scriptId);
280
- if (scriptId.length === 0) {
281
- return [];
282
- }
283
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
284
- const lineNumber = asNumber(candidate.lineNumber);
285
- const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
286
- return [result];
287
- });
288
- }
289
- function toScopeChain(value) {
290
- if (!Array.isArray(value)) {
291
- return [];
292
- }
293
- return value.flatMap((entry) => {
294
- if (typeof entry !== "object" || entry === null) {
295
- return [];
296
- }
297
- const candidate = entry;
298
- const type = asString(candidate.type);
299
- if (type.length === 0) {
300
- return [];
301
- }
302
- const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
303
- const name = typeof candidate.name === "string" ? candidate.name : void 0;
304
- const base = name === void 0 ? { type } : { type, name };
305
- return [objectId === void 0 ? base : { ...base, objectId }];
306
- });
307
- }
308
- function toCallFrames(value) {
309
- if (!Array.isArray(value)) {
310
- return [];
311
- }
312
- return value.flatMap((entry) => {
313
- if (typeof entry !== "object" || entry === null) {
314
- return [];
315
- }
316
- const candidate = entry;
317
- const callFrameId = asString(candidate.callFrameId);
318
- if (callFrameId.length === 0) {
319
- return [];
320
- }
321
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
322
- const base = {
323
- callFrameId,
324
- functionName: asString(candidate.functionName),
325
- lineNumber: asNumber(candidate.location?.lineNumber),
326
- columnNumber: asNumber(candidate.location?.columnNumber),
327
- scopeChain: toScopeChain(candidate.scopeChain)
328
- };
329
- return [url === void 0 ? base : { ...base, url }];
330
- });
331
- }
332
- function toPauseEvent(params, receivedAtMs) {
333
- return {
334
- reason: asString(params.reason),
335
- hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
336
- callFrames: toCallFrames(params.callFrames),
337
- receivedAtMs
338
- };
339
- }
340
- function topFrameLocation(pause) {
341
- const top = pause.callFrames[0];
342
- if (top === void 0) {
343
- return "(no call frame)";
344
- }
345
- const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
346
- return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
347
- }
348
- function pauseDetail(pause) {
349
- return JSON.stringify({
350
- reason: pause.reason,
351
- hitBreakpoints: pause.hitBreakpoints,
352
- topFrame: topFrameLocation(pause)
353
- });
354
- }
355
-
356
- // src/inspector/breakpoints.ts
357
- async function setBreakpoint(session, input) {
358
- const remoteRoot = input.remoteRoot ?? { kind: "none" };
359
- const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
360
- const params = {
361
- lineNumber: input.line - 1,
362
- urlRegex
363
- };
364
- if (input.condition !== void 0 && input.condition.length > 0) {
365
- params["condition"] = input.condition;
366
- }
367
- const result = await session.client.send(
368
- "Debugger.setBreakpointByUrl",
369
- params
370
- );
371
- const breakpointId = asString(result.breakpointId);
372
- if (breakpointId.length === 0) {
373
- throw new CfInspectorError(
374
- "CDP_REQUEST_FAILED",
375
- `setBreakpointByUrl did not return a breakpointId for ${input.file}:${input.line.toString()}`
376
- );
377
- }
378
- return {
379
- breakpointId,
380
- file: input.file,
381
- line: input.line,
382
- urlRegex,
383
- resolvedLocations: toResolvedLocations(result.locations)
384
- };
385
- }
386
- async function removeBreakpoint(session, breakpointId) {
387
- await session.client.send("Debugger.removeBreakpoint", { breakpointId });
388
- }
389
-
390
121
  // src/inspector/discovery.ts
391
122
  init_types();
392
123
  import { request } from "http";
@@ -505,164 +236,93 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
505
236
  return { browser, protocolVersion };
506
237
  }
507
238
 
508
- // src/inspector/pause.ts
509
- init_types();
510
- import { performance } from "perf_hooks";
511
- function pauseMatches(pause, breakpointIds) {
512
- if (breakpointIds === void 0 || breakpointIds.length === 0) {
513
- return true;
514
- }
515
- return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
516
- }
517
- function remainingUntil(deadlineMs) {
518
- return Math.max(0, deadlineMs - performance.now());
519
- }
520
- function hasResumedSincePause(session, pause) {
521
- const pauseAt = pause.receivedAtMs;
522
- const resumedAt = session.debuggerState.lastResumedAtMs;
523
- return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
524
- }
525
- function throwBreakpointTimeout(timeoutMs) {
526
- throw new CfInspectorError(
527
- "BREAKPOINT_NOT_HIT",
528
- `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
529
- );
530
- }
531
- function throwUnrelatedPauseTimeout(pause, timeoutMs) {
532
- throw new CfInspectorError(
533
- "UNRELATED_PAUSE_TIMEOUT",
534
- `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
535
- pauseDetail(pause)
536
- );
239
+ // src/cli/output.ts
240
+ import process from "process";
241
+ function writeJson(value) {
242
+ process.stdout.write(`${JSON.stringify(value, null, 2)}
243
+ `);
537
244
  }
538
- async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
539
- if (hasResumedSincePause(session, pause)) {
540
- return;
541
- }
542
- const remainingMs = remainingUntil(deadlineMs);
543
- if (remainingMs <= 0) {
544
- throwUnrelatedPauseTimeout(pause, timeoutMs);
245
+ function writeHumanSnapshot(snapshot) {
246
+ const pausedDuration = snapshot.pausedDurationMs === null ? "unknown" : `${snapshot.pausedDurationMs.toFixed(1)}ms`;
247
+ const lines = [];
248
+ lines.push(
249
+ `Snapshot @ ${snapshot.capturedAt}`,
250
+ ` reason: ${snapshot.reason}`,
251
+ ` paused: ${pausedDuration}`
252
+ );
253
+ if (snapshot.topFrame) {
254
+ appendFrameLines(lines, snapshot);
545
255
  }
546
- try {
547
- await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
548
- session.debuggerState.lastResumedAtMs = performance.now();
549
- } catch (err) {
550
- if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
551
- throwUnrelatedPauseTimeout(pause, timeoutMs);
256
+ if (snapshot.captures.length > 0) {
257
+ lines.push(" captures:");
258
+ for (const capture of snapshot.captures) {
259
+ const detail = capture.error ?? capture.value ?? "undefined";
260
+ lines.push(` ${capture.expression} = ${detail}`);
552
261
  }
553
- throw err;
554
262
  }
263
+ process.stdout.write(`${lines.join("\n")}
264
+ `);
555
265
  }
556
- async function handleUnmatchedPause(session, pause, options, deadlineMs) {
557
- if (options.unmatchedPausePolicy === "fail") {
558
- throw new CfInspectorError(
559
- "UNRELATED_PAUSE",
560
- "Target paused before this command's breakpoint was reached",
561
- pauseDetail(pause)
562
- );
266
+ function appendFrameLines(lines, snapshot) {
267
+ const frame = snapshot.topFrame;
268
+ if (frame === void 0) {
269
+ return;
563
270
  }
564
- if (hasResumedSincePause(session, pause)) {
271
+ const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
272
+ const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
273
+ lines.push(
274
+ ` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
275
+ );
276
+ if (frame.scopes === void 0) {
565
277
  return;
566
278
  }
567
- options.onUnmatchedPause?.(pause);
568
- await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
569
- }
570
- async function waitForPause(session, options) {
571
- const deadlineMs = performance.now() + options.timeoutMs;
572
- const buffer = session.pauseBuffer;
573
- while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
574
- while (buffer.length > 0) {
575
- const buffered = buffer.shift();
576
- if (buffered === void 0) {
577
- continue;
578
- }
579
- if (pauseMatches(buffered, options.breakpointIds)) {
580
- return buffered;
581
- }
582
- await handleUnmatchedPause(session, buffered, options, deadlineMs);
583
- }
584
- const pause = await waitForLivePause(session, options, deadlineMs);
585
- if (pauseMatches(pause, options.breakpointIds)) {
586
- return pause;
279
+ for (const scope of frame.scopes) {
280
+ lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars):`);
281
+ for (const variable of scope.variables) {
282
+ lines.push(` ${variable.name} = ${variable.value}`);
587
283
  }
588
- await handleUnmatchedPause(session, pause, options, deadlineMs);
589
284
  }
590
- throwBreakpointTimeout(options.timeoutMs);
591
285
  }
592
- async function waitForLivePause(session, options, deadlineMs) {
593
- const remainingMs = remainingUntil(deadlineMs);
594
- if (remainingMs <= 0) {
595
- throwBreakpointTimeout(options.timeoutMs);
596
- }
597
- session.pauseWaitGate.active = true;
598
- let receivedAtMs;
599
- let params;
600
- try {
601
- params = await session.client.waitFor("Debugger.paused", {
602
- timeoutMs: remainingMs,
603
- predicate: () => {
604
- receivedAtMs = performance.now();
605
- return true;
606
- }
607
- });
608
- } finally {
609
- session.pauseWaitGate.active = false;
286
+ function writeLogEvent(event, json) {
287
+ if (json) {
288
+ process.stdout.write(`${JSON.stringify(event)}
289
+ `);
290
+ return;
610
291
  }
611
- return toPauseEvent(params, receivedAtMs ?? performance.now());
612
- }
613
-
614
- // src/inspector/runtime.ts
615
- init_types();
616
- async function resume(session) {
617
- await session.client.send("Debugger.resume");
618
- }
619
- async function evaluateOnFrame(session, callFrameId, expression) {
620
- return await session.client.send("Debugger.evaluateOnCallFrame", {
621
- callFrameId,
622
- expression,
623
- returnByValue: false,
624
- generatePreview: true,
625
- silent: true
626
- });
627
- }
628
- async function evaluateGlobal(session, expression) {
629
- return await session.client.send("Runtime.evaluate", {
630
- expression,
631
- returnByValue: false,
632
- generatePreview: true,
633
- silent: true
634
- });
635
- }
636
- function listScripts(session) {
637
- return [...session.scripts.values()];
638
- }
639
- async function validateExpression(session, expression) {
640
- const result = await session.client.send("Runtime.compileScript", {
641
- expression,
642
- sourceURL: "<cf-inspector-validate>",
643
- persistScript: false
644
- });
645
- if (result.exceptionDetails === void 0) {
292
+ if (event.error !== void 0) {
293
+ process.stdout.write(`[${event.ts}] ${event.at} !err ${event.error}
294
+ `);
646
295
  return;
647
296
  }
648
- const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
649
- throw new CfInspectorError("INVALID_EXPRESSION", description);
297
+ process.stdout.write(`[${event.ts}] ${event.at} ${event.value ?? ""}
298
+ `);
650
299
  }
651
- async function getProperties(session, objectId) {
652
- const result = await session.client.send("Runtime.getProperties", {
653
- objectId,
654
- ownProperties: true,
655
- accessorPropertiesOnly: false,
656
- generatePreview: true
657
- });
658
- if (!Array.isArray(result.result)) {
659
- return [];
660
- }
661
- return result.result;
300
+
301
+ // src/cf/tunnel.ts
302
+ import { startDebugger } from "@saptools/cf-debugger";
303
+ async function openCfTunnel(target) {
304
+ const opts = {
305
+ region: target.region,
306
+ org: target.org,
307
+ space: target.space,
308
+ app: target.app,
309
+ ...target.tunnelReadyTimeoutMs === void 0 ? {} : { tunnelReadyTimeoutMs: target.tunnelReadyTimeoutMs },
310
+ ...target.preferredPort === void 0 ? {} : { preferredPort: target.preferredPort },
311
+ ...target.verbose === void 0 ? {} : { verbose: target.verbose },
312
+ ...target.signal === void 0 ? {} : { signal: target.signal }
313
+ };
314
+ const handle = await startDebugger(opts);
315
+ return {
316
+ localPort: handle.session.localPort,
317
+ handle,
318
+ dispose: async () => {
319
+ await handle.dispose();
320
+ }
321
+ };
662
322
  }
663
323
 
664
324
  // src/inspector/session.ts
665
- import { performance as performance2 } from "perf_hooks";
325
+ import { performance } from "perf_hooks";
666
326
 
667
327
  // src/cdp/client.ts
668
328
  init_types();
@@ -888,7 +548,102 @@ var CdpClient = class _CdpClient {
888
548
  };
889
549
 
890
550
  // src/inspector/session.ts
891
- init_types();
551
+ init_types();
552
+
553
+ // src/inspector/conversions.ts
554
+ function asString(value, fallback = "") {
555
+ return typeof value === "string" ? value : fallback;
556
+ }
557
+ function asNumber(value, fallback = 0) {
558
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
559
+ }
560
+ function toResolvedLocations(value) {
561
+ if (!Array.isArray(value)) {
562
+ return [];
563
+ }
564
+ return value.flatMap((entry) => {
565
+ if (typeof entry !== "object" || entry === null) {
566
+ return [];
567
+ }
568
+ const candidate = entry;
569
+ const scriptId = asString(candidate.scriptId);
570
+ if (scriptId.length === 0) {
571
+ return [];
572
+ }
573
+ const url = typeof candidate.url === "string" ? candidate.url : void 0;
574
+ const lineNumber = asNumber(candidate.lineNumber);
575
+ const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
576
+ return [result];
577
+ });
578
+ }
579
+ function toScopeChain(value) {
580
+ if (!Array.isArray(value)) {
581
+ return [];
582
+ }
583
+ return value.flatMap((entry) => {
584
+ if (typeof entry !== "object" || entry === null) {
585
+ return [];
586
+ }
587
+ const candidate = entry;
588
+ const type = asString(candidate.type);
589
+ if (type.length === 0) {
590
+ return [];
591
+ }
592
+ const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
593
+ const name = typeof candidate.name === "string" ? candidate.name : void 0;
594
+ const base = name === void 0 ? { type } : { type, name };
595
+ return [objectId === void 0 ? base : { ...base, objectId }];
596
+ });
597
+ }
598
+ function toCallFrames(value) {
599
+ if (!Array.isArray(value)) {
600
+ return [];
601
+ }
602
+ return value.flatMap((entry) => {
603
+ if (typeof entry !== "object" || entry === null) {
604
+ return [];
605
+ }
606
+ const candidate = entry;
607
+ const callFrameId = asString(candidate.callFrameId);
608
+ if (callFrameId.length === 0) {
609
+ return [];
610
+ }
611
+ const url = typeof candidate.url === "string" ? candidate.url : void 0;
612
+ const base = {
613
+ callFrameId,
614
+ functionName: asString(candidate.functionName),
615
+ lineNumber: asNumber(candidate.location?.lineNumber),
616
+ columnNumber: asNumber(candidate.location?.columnNumber),
617
+ scopeChain: toScopeChain(candidate.scopeChain)
618
+ };
619
+ return [url === void 0 ? base : { ...base, url }];
620
+ });
621
+ }
622
+ function toPauseEvent(params, receivedAtMs) {
623
+ return {
624
+ reason: asString(params.reason),
625
+ hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
626
+ callFrames: toCallFrames(params.callFrames),
627
+ receivedAtMs
628
+ };
629
+ }
630
+ function topFrameLocation(pause) {
631
+ const top = pause.callFrames[0];
632
+ if (top === void 0) {
633
+ return "(no call frame)";
634
+ }
635
+ const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
636
+ return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
637
+ }
638
+ function pauseDetail(pause) {
639
+ return JSON.stringify({
640
+ reason: pause.reason,
641
+ hitBreakpoints: pause.hitBreakpoints,
642
+ topFrame: topFrameLocation(pause)
643
+ });
644
+ }
645
+
646
+ // src/inspector/session.ts
892
647
  var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
893
648
  var DEFAULT_HOST = "127.0.0.1";
894
649
  var PAUSE_BUFFER_LIMIT = 32;
@@ -922,14 +677,14 @@ async function connectInspector(options) {
922
677
  return;
923
678
  }
924
679
  const params = raw;
925
- const event = toPauseEvent(params, performance2.now());
680
+ const event = toPauseEvent(params, performance.now());
926
681
  if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
927
682
  pauseBuffer.shift();
928
683
  }
929
684
  pauseBuffer.push(event);
930
685
  });
931
686
  client.on("Debugger.resumed", () => {
932
- debuggerState.lastResumedAtMs = performance2.now();
687
+ debuggerState.lastResumedAtMs = performance.now();
933
688
  });
934
689
  await client.send("Runtime.enable");
935
690
  await client.send("Debugger.enable");
@@ -950,91 +705,6 @@ async function connectInspector(options) {
950
705
  };
951
706
  }
952
707
 
953
- // src/cli/output.ts
954
- import process from "process";
955
- function writeJson(value) {
956
- process.stdout.write(`${JSON.stringify(value, null, 2)}
957
- `);
958
- }
959
- function writeHumanSnapshot(snapshot) {
960
- const pausedDuration = snapshot.pausedDurationMs === null ? "unknown" : `${snapshot.pausedDurationMs.toFixed(1)}ms`;
961
- const lines = [];
962
- lines.push(
963
- `Snapshot @ ${snapshot.capturedAt}`,
964
- ` reason: ${snapshot.reason}`,
965
- ` paused: ${pausedDuration}`
966
- );
967
- if (snapshot.topFrame) {
968
- appendFrameLines(lines, snapshot);
969
- }
970
- if (snapshot.captures.length > 0) {
971
- lines.push(" captures:");
972
- for (const capture of snapshot.captures) {
973
- const detail = capture.error ?? capture.value ?? "undefined";
974
- lines.push(` ${capture.expression} = ${detail}`);
975
- }
976
- }
977
- process.stdout.write(`${lines.join("\n")}
978
- `);
979
- }
980
- function appendFrameLines(lines, snapshot) {
981
- const frame = snapshot.topFrame;
982
- if (frame === void 0) {
983
- return;
984
- }
985
- const fnName = frame.functionName.length === 0 ? "(anonymous)" : frame.functionName;
986
- const sourceUrl = frame.url !== void 0 && frame.url.length > 0 ? frame.url : "(unknown)";
987
- lines.push(
988
- ` frame: ${fnName} ${sourceUrl}:${frame.line.toString()}:${frame.column.toString()}`
989
- );
990
- if (frame.scopes === void 0) {
991
- return;
992
- }
993
- for (const scope of frame.scopes) {
994
- lines.push(` scope ${scope.type} (${scope.variables.length.toString()} vars):`);
995
- for (const variable of scope.variables) {
996
- lines.push(` ${variable.name} = ${variable.value}`);
997
- }
998
- }
999
- }
1000
- function writeLogEvent(event, json) {
1001
- if (json) {
1002
- process.stdout.write(`${JSON.stringify(event)}
1003
- `);
1004
- return;
1005
- }
1006
- if (event.error !== void 0) {
1007
- process.stdout.write(`[${event.ts}] ${event.at} !err ${event.error}
1008
- `);
1009
- return;
1010
- }
1011
- process.stdout.write(`[${event.ts}] ${event.at} ${event.value ?? ""}
1012
- `);
1013
- }
1014
-
1015
- // src/cf/tunnel.ts
1016
- import { startDebugger } from "@saptools/cf-debugger";
1017
- async function openCfTunnel(target) {
1018
- const opts = {
1019
- region: target.region,
1020
- org: target.org,
1021
- space: target.space,
1022
- app: target.app,
1023
- ...target.tunnelReadyTimeoutMs === void 0 ? {} : { tunnelReadyTimeoutMs: target.tunnelReadyTimeoutMs },
1024
- ...target.preferredPort === void 0 ? {} : { preferredPort: target.preferredPort },
1025
- ...target.verbose === void 0 ? {} : { verbose: target.verbose },
1026
- ...target.signal === void 0 ? {} : { signal: target.signal }
1027
- };
1028
- const handle = await startDebugger(opts);
1029
- return {
1030
- localPort: handle.session.localPort,
1031
- handle,
1032
- dispose: async () => {
1033
- await handle.dispose();
1034
- }
1035
- };
1036
- }
1037
-
1038
708
  // src/cli/target.ts
1039
709
  init_types();
1040
710
 
@@ -1134,67 +804,293 @@ async function handleAttach(opts) {
1134
804
  await tunnel.dispose();
1135
805
  }
1136
806
  }
1137
-
1138
- // src/cli/commands/eval.ts
1139
- import process3 from "process";
1140
- async function handleEval(opts) {
1141
- const target = resolveTarget(opts);
1142
- const result = await withSession(target, async (session) => {
1143
- return await evaluateGlobal(session, opts.expr);
1144
- });
1145
- if (opts.json) {
1146
- writeJson(result);
1147
- if (result.exceptionDetails !== void 0) {
1148
- process3.exitCode = 1;
807
+
808
+ // src/cli/commands/eval.ts
809
+ import process3 from "process";
810
+
811
+ // src/inspector/runtime.ts
812
+ init_types();
813
+ async function resume(session) {
814
+ await session.client.send("Debugger.resume");
815
+ }
816
+ async function evaluateOnFrame(session, callFrameId, expression) {
817
+ return await session.client.send("Debugger.evaluateOnCallFrame", {
818
+ callFrameId,
819
+ expression,
820
+ returnByValue: false,
821
+ generatePreview: true,
822
+ silent: true
823
+ });
824
+ }
825
+ async function evaluateGlobal(session, expression) {
826
+ return await session.client.send("Runtime.evaluate", {
827
+ expression,
828
+ returnByValue: false,
829
+ generatePreview: true,
830
+ silent: true
831
+ });
832
+ }
833
+ function listScripts(session) {
834
+ return [...session.scripts.values()];
835
+ }
836
+ async function validateExpression(session, expression) {
837
+ const result = await session.client.send("Runtime.compileScript", {
838
+ expression,
839
+ sourceURL: "<cf-inspector-validate>",
840
+ persistScript: false
841
+ });
842
+ if (result.exceptionDetails === void 0) {
843
+ return;
844
+ }
845
+ const description = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "expression failed to compile";
846
+ throw new CfInspectorError("INVALID_EXPRESSION", description);
847
+ }
848
+ async function getProperties(session, objectId) {
849
+ const result = await session.client.send("Runtime.getProperties", {
850
+ objectId,
851
+ ownProperties: true,
852
+ accessorPropertiesOnly: false,
853
+ generatePreview: true
854
+ });
855
+ if (!Array.isArray(result.result)) {
856
+ return [];
857
+ }
858
+ return result.result;
859
+ }
860
+
861
+ // src/cli/commands/eval.ts
862
+ async function handleEval(opts) {
863
+ const target = resolveTarget(opts);
864
+ const result = await withSession(target, async (session) => {
865
+ return await evaluateGlobal(session, opts.expr);
866
+ });
867
+ if (opts.json) {
868
+ writeJson(result);
869
+ if (result.exceptionDetails !== void 0) {
870
+ process3.exitCode = 1;
871
+ }
872
+ return;
873
+ }
874
+ writeHumanEvalResult(result);
875
+ }
876
+ function writeHumanEvalResult(result) {
877
+ if (result.exceptionDetails !== void 0) {
878
+ const detail = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
879
+ process3.stderr.write(`${detail}
880
+ `);
881
+ process3.exitCode = 1;
882
+ return;
883
+ }
884
+ const inner = result.result;
885
+ if (inner === void 0) {
886
+ process3.stdout.write("\n");
887
+ return;
888
+ }
889
+ if (typeof inner.value === "string") {
890
+ process3.stdout.write(`${inner.value}
891
+ `);
892
+ return;
893
+ }
894
+ if (typeof inner.description === "string") {
895
+ process3.stdout.write(`${inner.description}
896
+ `);
897
+ return;
898
+ }
899
+ process3.stdout.write(`${JSON.stringify(inner.value)}
900
+ `);
901
+ }
902
+
903
+ // src/cli/commands/listScripts.ts
904
+ import process4 from "process";
905
+ async function handleListScripts(opts) {
906
+ const target = resolveTarget(opts);
907
+ const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
908
+ if (opts.json) {
909
+ writeJson(scripts);
910
+ return;
911
+ }
912
+ for (const script of scripts) {
913
+ process4.stdout.write(`${script.scriptId} ${script.url}
914
+ `);
915
+ }
916
+ }
917
+
918
+ // src/cli/commands/log.ts
919
+ import process6 from "process";
920
+
921
+ // src/pathMapper.ts
922
+ init_types();
923
+ var REGEX_PREFIX = "regex:";
924
+ var REGEX_FLAGS_PATTERN = /^[dgimsuvy]*$/;
925
+ var TS_JS_EXT_PATTERN = /\.(?:ts|js|mts|mjs|cts|cjs)$/i;
926
+ function parseBreakpointSpec(input) {
927
+ const idx = input.lastIndexOf(":");
928
+ if (idx <= 0 || idx === input.length - 1) {
929
+ throw new CfInspectorError(
930
+ "INVALID_BREAKPOINT",
931
+ `Breakpoint must be in 'file:line' form, received: "${input}"`
932
+ );
933
+ }
934
+ const file = input.slice(0, idx).trim();
935
+ const lineRaw = input.slice(idx + 1).trim();
936
+ const line = Number.parseInt(lineRaw, 10);
937
+ if (!Number.isInteger(line) || line <= 0 || line.toString() !== lineRaw) {
938
+ throw new CfInspectorError(
939
+ "INVALID_BREAKPOINT",
940
+ `Breakpoint line must be a positive integer, received: "${lineRaw}"`
941
+ );
942
+ }
943
+ if (file.length === 0) {
944
+ throw new CfInspectorError(
945
+ "INVALID_BREAKPOINT",
946
+ `Breakpoint file path is empty in "${input}"`
947
+ );
948
+ }
949
+ return { file, line };
950
+ }
951
+ function parseRemoteRoot(value) {
952
+ const trimmed = value?.trim();
953
+ if (trimmed === void 0 || trimmed.length === 0) {
954
+ return { kind: "none" };
955
+ }
956
+ if (trimmed.startsWith(REGEX_PREFIX)) {
957
+ return toRegex(trimmed.slice(REGEX_PREFIX.length), "");
958
+ }
959
+ const slashRegex = parseSlashDelimited(trimmed);
960
+ if (slashRegex !== void 0) {
961
+ return toRegex(slashRegex.pattern, slashRegex.flags);
962
+ }
963
+ return { kind: "literal", value: stripTrailingSlash(trimmed) };
964
+ }
965
+ function toRegex(pattern, flags) {
966
+ try {
967
+ const regex = new RegExp(pattern, flags);
968
+ return { kind: "regex", pattern, flags, regex };
969
+ } catch (err) {
970
+ const message = err instanceof Error ? err.message : String(err);
971
+ throw new CfInspectorError(
972
+ "INVALID_REMOTE_ROOT",
973
+ `Failed to compile remote-root regex "${pattern}" with flags "${flags}": ${message}`
974
+ );
975
+ }
976
+ }
977
+ function parseSlashDelimited(value) {
978
+ if (!value.startsWith("/")) {
979
+ return void 0;
980
+ }
981
+ const closing = findLastUnescapedSlash(value);
982
+ if (closing <= 0) {
983
+ return void 0;
984
+ }
985
+ const flags = value.slice(closing + 1);
986
+ if (flags.length === 0 || !REGEX_FLAGS_PATTERN.test(flags)) {
987
+ return void 0;
988
+ }
989
+ return { pattern: value.slice(1, closing), flags };
990
+ }
991
+ function findLastUnescapedSlash(value) {
992
+ for (let i = value.length - 1; i > 0; i--) {
993
+ if (value[i] === "/" && !isEscaped(value, i)) {
994
+ return i;
1149
995
  }
1150
- return;
1151
996
  }
1152
- writeHumanEvalResult(result);
997
+ return -1;
1153
998
  }
1154
- function writeHumanEvalResult(result) {
1155
- if (result.exceptionDetails !== void 0) {
1156
- const detail = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1157
- process3.stderr.write(`${detail}
1158
- `);
1159
- process3.exitCode = 1;
1160
- return;
999
+ function isEscaped(value, idx) {
1000
+ let backslashes = 0;
1001
+ for (let i = idx - 1; i >= 0; i--) {
1002
+ if (value[i] === "\\") {
1003
+ backslashes++;
1004
+ } else {
1005
+ break;
1006
+ }
1161
1007
  }
1162
- const inner = result.result;
1163
- if (inner === void 0) {
1164
- process3.stdout.write("\n");
1165
- return;
1008
+ return backslashes % 2 === 1;
1009
+ }
1010
+ function stripTrailingSlash(value) {
1011
+ if (value.length > 1 && value.endsWith("/")) {
1012
+ return value.slice(0, -1);
1166
1013
  }
1167
- if (typeof inner.value === "string") {
1168
- process3.stdout.write(`${inner.value}
1169
- `);
1170
- return;
1014
+ return value;
1015
+ }
1016
+ function normalizeRegexRootPattern(pattern) {
1017
+ const withoutStartAnchor = pattern.startsWith("^") ? pattern.slice(1) : pattern;
1018
+ const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
1019
+ return stripTrailingSlash(withoutEndAnchor);
1020
+ }
1021
+ function buildFileUrlRegex(rootPattern, tail) {
1022
+ const separator = rootPattern.endsWith("/") ? "" : "/";
1023
+ return `^file://${rootPattern}${separator}${tail}$`;
1024
+ }
1025
+ function escapeRegExp(value) {
1026
+ return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
1027
+ }
1028
+ function normalizeRelative(file) {
1029
+ return file.replaceAll(/^[./\\]+/g, "").replaceAll("\\", "/");
1030
+ }
1031
+ function dropExtension(file) {
1032
+ const match = TS_JS_EXT_PATTERN.exec(file);
1033
+ if (!match) {
1034
+ return { stem: file, matchedExt: false };
1171
1035
  }
1172
- if (typeof inner.description === "string") {
1173
- process3.stdout.write(`${inner.description}
1174
- `);
1175
- return;
1036
+ return { stem: file.slice(0, match.index), matchedExt: true };
1037
+ }
1038
+ var EXT_GROUP = String.raw`\.(?:ts|js|mts|mjs|cts|cjs)`;
1039
+ var OPTIONAL_EXT_GROUP = String.raw`(?:\.(?:ts|js|mts|mjs|cts|cjs))?`;
1040
+ function buildBreakpointUrlRegex(input) {
1041
+ const normalized = normalizeRelative(input.file);
1042
+ const { stem, matchedExt } = dropExtension(normalized);
1043
+ const escapedStem = escapeRegExp(stem);
1044
+ const tail = matchedExt ? `${escapedStem}${EXT_GROUP}` : `${escapedStem}${OPTIONAL_EXT_GROUP}`;
1045
+ switch (input.remoteRoot.kind) {
1046
+ case "none": {
1047
+ return `(?:^|/)${tail}$`;
1048
+ }
1049
+ case "literal": {
1050
+ const escapedRoot = escapeRegExp(input.remoteRoot.value);
1051
+ return buildFileUrlRegex(escapedRoot, tail);
1052
+ }
1053
+ case "regex": {
1054
+ const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
1055
+ return buildFileUrlRegex(rootPattern, tail);
1056
+ }
1176
1057
  }
1177
- process3.stdout.write(`${JSON.stringify(inner.value)}
1178
- `);
1179
1058
  }
1180
1059
 
1181
- // src/cli/commands/listScripts.ts
1182
- import process4 from "process";
1183
- async function handleListScripts(opts) {
1184
- const target = resolveTarget(opts);
1185
- const scripts = await withSession(target, (session) => Promise.resolve(listScripts(session)));
1186
- if (opts.json) {
1187
- writeJson(scripts);
1188
- return;
1060
+ // src/inspector/breakpoints.ts
1061
+ init_types();
1062
+ async function setBreakpoint(session, input) {
1063
+ const remoteRoot = input.remoteRoot ?? { kind: "none" };
1064
+ const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
1065
+ const params = {
1066
+ lineNumber: input.line - 1,
1067
+ urlRegex
1068
+ };
1069
+ if (input.condition !== void 0 && input.condition.length > 0) {
1070
+ params["condition"] = input.condition;
1189
1071
  }
1190
- for (const script of scripts) {
1191
- process4.stdout.write(`${script.scriptId} ${script.url}
1192
- `);
1072
+ const result = await session.client.send(
1073
+ "Debugger.setBreakpointByUrl",
1074
+ params
1075
+ );
1076
+ const breakpointId = asString(result.breakpointId);
1077
+ if (breakpointId.length === 0) {
1078
+ throw new CfInspectorError(
1079
+ "CDP_REQUEST_FAILED",
1080
+ `setBreakpointByUrl did not return a breakpointId for ${input.file}:${input.line.toString()}`
1081
+ );
1193
1082
  }
1083
+ return {
1084
+ breakpointId,
1085
+ file: input.file,
1086
+ line: input.line,
1087
+ urlRegex,
1088
+ resolvedLocations: toResolvedLocations(result.locations)
1089
+ };
1090
+ }
1091
+ async function removeBreakpoint(session, breakpointId) {
1092
+ await session.client.send("Debugger.removeBreakpoint", { breakpointId });
1194
1093
  }
1195
-
1196
- // src/cli/commands/log.ts
1197
- import process6 from "process";
1198
1094
 
1199
1095
  // src/logpoint/condition.ts
1200
1096
  import { randomBytes } from "crypto";
@@ -1447,6 +1343,112 @@ function writeLogSummary(stoppedReason, emitted, json) {
1447
1343
  import { performance as performance3 } from "perf_hooks";
1448
1344
  import process7 from "process";
1449
1345
 
1346
+ // src/inspector/pause.ts
1347
+ init_types();
1348
+ import { performance as performance2 } from "perf_hooks";
1349
+ function pauseMatches(pause, breakpointIds) {
1350
+ if (breakpointIds === void 0 || breakpointIds.length === 0) {
1351
+ return true;
1352
+ }
1353
+ return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
1354
+ }
1355
+ function remainingUntil(deadlineMs) {
1356
+ return Math.max(0, deadlineMs - performance2.now());
1357
+ }
1358
+ function hasResumedSincePause(session, pause) {
1359
+ const pauseAt = pause.receivedAtMs;
1360
+ const resumedAt = session.debuggerState.lastResumedAtMs;
1361
+ return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
1362
+ }
1363
+ function throwBreakpointTimeout(timeoutMs) {
1364
+ throw new CfInspectorError(
1365
+ "BREAKPOINT_NOT_HIT",
1366
+ `Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
1367
+ );
1368
+ }
1369
+ function throwUnrelatedPauseTimeout(pause, timeoutMs) {
1370
+ throw new CfInspectorError(
1371
+ "UNRELATED_PAUSE_TIMEOUT",
1372
+ `Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
1373
+ pauseDetail(pause)
1374
+ );
1375
+ }
1376
+ async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
1377
+ if (hasResumedSincePause(session, pause)) {
1378
+ return;
1379
+ }
1380
+ const remainingMs = remainingUntil(deadlineMs);
1381
+ if (remainingMs <= 0) {
1382
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
1383
+ }
1384
+ try {
1385
+ await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
1386
+ session.debuggerState.lastResumedAtMs = performance2.now();
1387
+ } catch (err) {
1388
+ if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
1389
+ throwUnrelatedPauseTimeout(pause, timeoutMs);
1390
+ }
1391
+ throw err;
1392
+ }
1393
+ }
1394
+ async function handleUnmatchedPause(session, pause, options, deadlineMs) {
1395
+ if (options.unmatchedPausePolicy === "fail") {
1396
+ throw new CfInspectorError(
1397
+ "UNRELATED_PAUSE",
1398
+ "Target paused before this command's breakpoint was reached",
1399
+ pauseDetail(pause)
1400
+ );
1401
+ }
1402
+ if (hasResumedSincePause(session, pause)) {
1403
+ return;
1404
+ }
1405
+ options.onUnmatchedPause?.(pause);
1406
+ await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
1407
+ }
1408
+ async function waitForPause(session, options) {
1409
+ const deadlineMs = performance2.now() + options.timeoutMs;
1410
+ const buffer = session.pauseBuffer;
1411
+ while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
1412
+ while (buffer.length > 0) {
1413
+ const buffered = buffer.shift();
1414
+ if (buffered === void 0) {
1415
+ continue;
1416
+ }
1417
+ if (pauseMatches(buffered, options.breakpointIds)) {
1418
+ return buffered;
1419
+ }
1420
+ await handleUnmatchedPause(session, buffered, options, deadlineMs);
1421
+ }
1422
+ const pause = await waitForLivePause(session, options, deadlineMs);
1423
+ if (pauseMatches(pause, options.breakpointIds)) {
1424
+ return pause;
1425
+ }
1426
+ await handleUnmatchedPause(session, pause, options, deadlineMs);
1427
+ }
1428
+ throwBreakpointTimeout(options.timeoutMs);
1429
+ }
1430
+ async function waitForLivePause(session, options, deadlineMs) {
1431
+ const remainingMs = remainingUntil(deadlineMs);
1432
+ if (remainingMs <= 0) {
1433
+ throwBreakpointTimeout(options.timeoutMs);
1434
+ }
1435
+ session.pauseWaitGate.active = true;
1436
+ let receivedAtMs;
1437
+ let params;
1438
+ try {
1439
+ params = await session.client.waitFor("Debugger.paused", {
1440
+ timeoutMs: remainingMs,
1441
+ predicate: () => {
1442
+ receivedAtMs = performance2.now();
1443
+ return true;
1444
+ }
1445
+ });
1446
+ } finally {
1447
+ session.pauseWaitGate.active = false;
1448
+ }
1449
+ return toPauseEvent(params, receivedAtMs ?? performance2.now());
1450
+ }
1451
+
1450
1452
  // src/snapshot/values.ts
1451
1453
  init_types();
1452
1454
  var DEFAULT_MAX_VALUE_LENGTH = 4096;