@saptools/cf-inspector 0.4.1 → 0.4.2

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 CHANGED
@@ -101,9 +101,17 @@ cf-inspector snapshot --port 9229 \
101
101
  | `--remote-root <value>` | Optional path-mapping anchor: literal path or `regex:<pattern>` / `/pattern/flags` |
102
102
  | `--include-scopes` | Include expanded paused-frame scopes under `topFrame.scopes`. Omitted by default to keep targeted captures concise |
103
103
  | `--no-json` | Print a human-readable summary instead of JSON |
104
+ | `--quiet` | Suppress snapshot progress messages on stderr |
104
105
  | `--keep-paused` | Skip `Debugger.resume` after capture |
105
106
  | `--fail-on-unmatched-pause` | Fail immediately if the target pauses somewhere else instead of waiting cooperatively |
106
107
 
108
+ Snapshot progress is printed to `stderr` by default, including Cloud Foundry
109
+ login/tunnel setup, inspector connection, breakpoint binding, the breakpoint
110
+ wait, capture, resume, and cleanup phases. The final JSON document remains the
111
+ only content written to `stdout`, so piping it to `jq` or another parser stays
112
+ safe. Pass `--quiet` to suppress these progress lines; warnings and errors still
113
+ use `stderr`.
114
+
107
115
  Snapshot JSON includes frame metadata and `captures` by default. `topFrame.scopes`
108
116
  is only present with `--include-scopes` because scope objects can be large and
109
117
  drown out targeted captures. Values are raw debugger values, so be careful when
package/dist/cli.js CHANGED
@@ -280,6 +280,10 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
280
280
 
281
281
  // src/cli/output.ts
282
282
  import process from "process";
283
+ function writeProgress(message) {
284
+ process.stderr.write(`[cf-inspector] ${message}
285
+ `);
286
+ }
283
287
  function writeJson(value) {
284
288
  process.stdout.write(`${JSON.stringify(value, null, 2)}
285
289
  `);
@@ -394,7 +398,8 @@ async function openCfTunnel(target) {
394
398
  ...target.tunnelReadyTimeoutMs === void 0 ? {} : { tunnelReadyTimeoutMs: target.tunnelReadyTimeoutMs },
395
399
  ...target.preferredPort === void 0 ? {} : { preferredPort: target.preferredPort },
396
400
  ...target.verbose === void 0 ? {} : { verbose: target.verbose },
397
- ...target.signal === void 0 ? {} : { signal: target.signal }
401
+ ...target.signal === void 0 ? {} : { signal: target.signal },
402
+ ...target.onStatus === void 0 ? {} : { onStatus: target.onStatus }
398
403
  };
399
404
  const handle = await startDebugger(opts);
400
405
  return {
@@ -843,6 +848,22 @@ var DEFAULT_CF_TIMEOUT_SEC = 60;
843
848
  var DEFAULT_EXCEPTION_TIMEOUT_SEC = 30;
844
849
 
845
850
  // src/cli/target.ts
851
+ var CF_TUNNEL_STATUS_MESSAGES = {
852
+ starting: "Preparing the Cloud Foundry debugger...",
853
+ "logging-in": "Logging in to Cloud Foundry...",
854
+ targeting: "Targeting the Cloud Foundry org and space...",
855
+ "ssh-enabling": "Enabling SSH for the Cloud Foundry app...",
856
+ "ssh-restarting": "Restarting the Cloud Foundry app to activate SSH...",
857
+ signaling: "Starting the remote Node.js inspector...",
858
+ tunneling: "Opening the SSH inspector tunnel...",
859
+ ready: "Cloud Foundry inspector tunnel is ready.",
860
+ stopping: "Closing the Cloud Foundry inspector tunnel...",
861
+ stopped: "Cloud Foundry inspector tunnel closed.",
862
+ error: "Cloud Foundry inspector tunnel failed."
863
+ };
864
+ function formatCfTunnelStatus(status) {
865
+ return CF_TUNNEL_STATUS_MESSAGES[status];
866
+ }
846
867
  function parsePositiveInt(raw, label) {
847
868
  if (raw === void 0) {
848
869
  return void 0;
@@ -877,20 +898,26 @@ function resolveTarget(opts) {
877
898
  function hasCfTarget(opts) {
878
899
  return opts.region !== void 0 && opts.org !== void 0 && opts.space !== void 0 && opts.app !== void 0;
879
900
  }
880
- async function withSession(target, fn) {
881
- const tunnel = await openTarget(target);
901
+ async function withSession(target, fn, reportProgress) {
902
+ const tunnel = await openTarget(target, reportProgress);
882
903
  let session;
883
904
  try {
905
+ reportProgress?.(
906
+ `Connecting to the Node.js inspector at ${tunnel.host}:${tunnel.port.toString()}...`
907
+ );
884
908
  session = await connectInspector({ port: tunnel.port, host: tunnel.host });
909
+ reportProgress?.("Inspector session is ready.");
885
910
  return await fn(session, tunnel.port);
886
911
  } finally {
887
912
  if (session) {
913
+ reportProgress?.("Closing the inspector session...");
888
914
  await session.dispose();
915
+ reportProgress?.("Inspector session closed.");
889
916
  }
890
917
  await tunnel.dispose();
891
918
  }
892
919
  }
893
- async function openTarget(target) {
920
+ async function openTarget(target, reportProgress) {
894
921
  if (target.kind === "port") {
895
922
  return {
896
923
  port: target.port,
@@ -898,12 +925,18 @@ async function openTarget(target) {
898
925
  dispose: () => Promise.resolve()
899
926
  };
900
927
  }
928
+ reportProgress?.(formatCfTunnelStatus("starting"));
901
929
  const tunnel = await openCfTunnel({
902
930
  region: target.region,
903
931
  org: target.org,
904
932
  space: target.space,
905
933
  app: target.app,
906
- tunnelReadyTimeoutMs: target.cfTimeoutMs
934
+ tunnelReadyTimeoutMs: target.cfTimeoutMs,
935
+ ...reportProgress === void 0 ? {} : {
936
+ onStatus: (status) => {
937
+ reportProgress(formatCfTunnelStatus(status));
938
+ }
939
+ }
907
940
  });
908
941
  return {
909
942
  port: tunnel.localPort,
@@ -2449,12 +2482,14 @@ import process9 from "process";
2449
2482
  init_types();
2450
2483
  async function handleSnapshot(opts) {
2451
2484
  const prepared = prepareSnapshotCommand(opts);
2452
- const result = await runSnapshotCommand(prepared, opts);
2485
+ const reportProgress = opts.quiet === true ? void 0 : writeProgress;
2486
+ const result = await runSnapshotCommand(prepared, opts, reportProgress);
2453
2487
  if (opts.json) {
2454
2488
  writeJson(result);
2455
2489
  } else {
2456
2490
  writeHumanSnapshot(result);
2457
2491
  }
2492
+ reportProgress?.("Snapshot complete.");
2458
2493
  }
2459
2494
  function prepareSnapshotCommand(opts) {
2460
2495
  const target = resolveTarget(opts);
@@ -2482,24 +2517,34 @@ function prepareSnapshotCommand(opts) {
2482
2517
  stackCaptures: parseCaptureList(opts.stackCaptures)
2483
2518
  };
2484
2519
  }
2485
- async function runSnapshotCommand(command, opts) {
2520
+ async function runSnapshotCommand(command, opts, reportProgress) {
2486
2521
  return await withSession(command.target, async (session) => {
2487
2522
  if (command.condition !== void 0) {
2523
+ reportProgress?.("Validating the breakpoint condition...");
2488
2524
  await validateExpression(session, command.condition);
2525
+ reportProgress?.("Breakpoint condition is valid.");
2489
2526
  }
2490
- const handles = await Promise.all(
2491
- command.breakpoints.map(
2492
- (bp) => setBreakpoint(session, {
2493
- file: bp.file,
2494
- line: bp.line,
2495
- remoteRoot: command.remoteRoot,
2496
- ...command.condition === void 0 ? {} : { condition: command.condition },
2497
- ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
2498
- })
2499
- )
2527
+ const breakpointCount = command.breakpoints.length;
2528
+ reportProgress?.(
2529
+ `Setting ${breakpointCount.toString()} ${breakpointCount === 1 ? "breakpoint" : "breakpoints"}...`
2530
+ );
2531
+ const handles = await setCommandBreakpoints(session, command);
2532
+ const resolvedCount = handles.reduce(
2533
+ (total, handle) => total + handle.resolvedLocations.length,
2534
+ 0
2535
+ );
2536
+ reportProgress?.(
2537
+ `Breakpoint setup complete: ${resolvedCount.toString()} resolved ${resolvedCount === 1 ? "location" : "locations"}.`
2500
2538
  );
2501
2539
  warnOnUnboundBreakpoints(handles);
2540
+ reportProgress?.(
2541
+ `Waiting up to ${(command.timeoutMs / 1e3).toString()}s for a breakpoint hit...`
2542
+ );
2502
2543
  const pause = await waitForCommandPause(session, opts, handles, command.timeoutMs);
2544
+ const captureCount = command.captures.length;
2545
+ reportProgress?.(
2546
+ `Breakpoint hit; capturing ${captureCount.toString()} ${captureCount === 1 ? "expression" : "expressions"}...`
2547
+ );
2503
2548
  const pausedStartedAt = pause.receivedAtMs ?? performance4.now();
2504
2549
  const snapshot = await captureSnapshot(session, pause, {
2505
2550
  captures: command.captures,
@@ -2509,10 +2554,25 @@ async function runSnapshotCommand(command, opts) {
2509
2554
  stackCaptures: command.stackCaptures
2510
2555
  });
2511
2556
  if (opts.keepPaused === true) {
2557
+ reportProgress?.("Snapshot captured; leaving the target paused as requested.");
2512
2558
  return withPausedDuration(snapshot, null);
2513
2559
  }
2514
- return await resumeAfterSnapshot(session, snapshot, pausedStartedAt);
2515
- });
2560
+ reportProgress?.("Snapshot captured; resuming the target...");
2561
+ return await resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress);
2562
+ }, reportProgress);
2563
+ }
2564
+ async function setCommandBreakpoints(session, command) {
2565
+ return await Promise.all(
2566
+ command.breakpoints.map(
2567
+ (bp) => setBreakpoint(session, {
2568
+ file: bp.file,
2569
+ line: bp.line,
2570
+ remoteRoot: command.remoteRoot,
2571
+ ...command.condition === void 0 ? {} : { condition: command.condition },
2572
+ ...command.hitCount === void 0 ? {} : { hitCount: command.hitCount }
2573
+ })
2574
+ )
2575
+ );
2516
2576
  }
2517
2577
  async function waitForCommandPause(session, opts, handles, timeoutMs) {
2518
2578
  let warnedUnmatchedPause = false;
@@ -2529,9 +2589,10 @@ async function waitForCommandPause(session, opts, handles, timeoutMs) {
2529
2589
  }
2530
2590
  });
2531
2591
  }
2532
- async function resumeAfterSnapshot(session, snapshot, pausedStartedAt) {
2592
+ async function resumeAfterSnapshot(session, snapshot, pausedStartedAt, reportProgress) {
2533
2593
  try {
2534
2594
  await resume(session);
2595
+ reportProgress?.("Target resumed.");
2535
2596
  return withPausedDuration(snapshot, roundDurationMs(performance4.now() - pausedStartedAt));
2536
2597
  } catch {
2537
2598
  process9.stderr.write(
@@ -2786,7 +2847,7 @@ async function main(argv) {
2786
2847
  function registerSnapshot(program) {
2787
2848
  applyTargetOptions(
2788
2849
  program.command("snapshot").description("Set a breakpoint, wait for it to hit, capture expressions, and resume")
2789
- ).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("--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) => {
2850
+ ).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) => {
2790
2851
  await handleSnapshot(opts);
2791
2852
  });
2792
2853
  }