@saptools/cf-inspector 0.3.2 → 0.3.3
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 +26 -10
- package/dist/cli.js +252 -43
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +9 -2
- package/dist/index.js +136 -35
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DebuggerHandle } from '@saptools/cf-debugger';
|
|
2
2
|
|
|
3
|
-
type CfInspectorErrorCode = "INVALID_ARGUMENT" | "INVALID_BREAKPOINT" | "INVALID_REMOTE_ROOT" | "INVALID_EXPRESSION" | "BREAKPOINT_DID_NOT_BIND" | "INSPECTOR_DISCOVERY_FAILED" | "INSPECTOR_CONNECTION_FAILED" | "CDP_REQUEST_FAILED" | "BREAKPOINT_NOT_HIT" | "EVALUATION_FAILED" | "MISSING_TARGET" | "ABORTED";
|
|
3
|
+
type CfInspectorErrorCode = "INVALID_ARGUMENT" | "INVALID_BREAKPOINT" | "INVALID_REMOTE_ROOT" | "INVALID_EXPRESSION" | "BREAKPOINT_DID_NOT_BIND" | "INSPECTOR_DISCOVERY_FAILED" | "INSPECTOR_CONNECTION_FAILED" | "CDP_REQUEST_FAILED" | "BREAKPOINT_NOT_HIT" | "UNRELATED_PAUSE" | "UNRELATED_PAUSE_TIMEOUT" | "EVALUATION_FAILED" | "MISSING_TARGET" | "ABORTED";
|
|
4
4
|
declare class CfInspectorError extends Error {
|
|
5
5
|
readonly code: CfInspectorErrorCode;
|
|
6
6
|
readonly detail?: string;
|
|
@@ -54,6 +54,7 @@ interface PauseEvent {
|
|
|
54
54
|
readonly reason: string;
|
|
55
55
|
readonly hitBreakpoints: readonly string[];
|
|
56
56
|
readonly callFrames: readonly CallFrameInfo[];
|
|
57
|
+
readonly receivedAtMs?: number;
|
|
57
58
|
}
|
|
58
59
|
interface VariableSnapshot {
|
|
59
60
|
readonly name: string;
|
|
@@ -172,12 +173,16 @@ interface InspectorVersion {
|
|
|
172
173
|
interface PauseWaitGate {
|
|
173
174
|
active: boolean;
|
|
174
175
|
}
|
|
176
|
+
interface DebuggerState {
|
|
177
|
+
lastResumedAtMs?: number;
|
|
178
|
+
}
|
|
175
179
|
interface InspectorSession {
|
|
176
180
|
readonly client: CdpClient;
|
|
177
181
|
readonly target: InspectorTarget;
|
|
178
182
|
readonly scripts: ReadonlyMap<string, ScriptInfo>;
|
|
179
183
|
readonly pauseBuffer: PauseEvent[];
|
|
180
184
|
readonly pauseWaitGate: PauseWaitGate;
|
|
185
|
+
readonly debuggerState: DebuggerState;
|
|
181
186
|
dispose(): Promise<void>;
|
|
182
187
|
}
|
|
183
188
|
interface CdpEvalResult {
|
|
@@ -206,6 +211,8 @@ declare function removeBreakpoint(session: InspectorSession, breakpointId: strin
|
|
|
206
211
|
interface WaitForPauseOptions {
|
|
207
212
|
readonly timeoutMs: number;
|
|
208
213
|
readonly breakpointIds?: readonly string[];
|
|
214
|
+
readonly unmatchedPausePolicy?: "wait-for-resume" | "fail";
|
|
215
|
+
readonly onUnmatchedPause?: (pause: PauseEvent) => void;
|
|
209
216
|
}
|
|
210
217
|
declare function waitForPause(session: InspectorSession, options: WaitForPauseOptions): Promise<PauseEvent>;
|
|
211
218
|
declare function resume(session: InspectorSession): Promise<void>;
|
|
@@ -298,4 +305,4 @@ interface OpenedTunnel {
|
|
|
298
305
|
}
|
|
299
306
|
declare function openCfTunnel(target: TunnelTarget): Promise<OpenedTunnel>;
|
|
300
307
|
|
|
301
|
-
export { type BreakpointHandle, type BreakpointLocation, type CallFrameInfo, type CaptureSnapshotOptions, type CapturedExpression, CfInspectorError, type CfInspectorErrorCode, type FrameSnapshot, type InspectorConnectOptions, type InspectorSession, type InspectorTarget, type LogpointEvent, type LogpointStreamOptions, type LogpointStreamResult, type OpenedTunnel, type PauseEvent, type RemoteRootSetting, type ResolvedLocation, type ScopeInfo, type ScopeSnapshot, type ScriptInfo, type SetBreakpointInput, type SnapshotCaptureResult, type SnapshotResult, type TunnelTarget, type VariableSnapshot, type WaitForPauseOptions, buildBreakpointUrlRegex, buildLogpointCondition, captureSnapshot, connectInspector, discoverInspectorTargets, evaluateGlobal, evaluateOnFrame, fetchInspectorVersion, getProperties, listScripts, openCfTunnel, parseBreakpointSpec, parseRemoteRoot, removeBreakpoint, resume, setBreakpoint, streamLogpoint, validateExpression, waitForPause };
|
|
308
|
+
export { type BreakpointHandle, type BreakpointLocation, type CallFrameInfo, type CaptureSnapshotOptions, type CapturedExpression, CfInspectorError, type CfInspectorErrorCode, type DebuggerState, type FrameSnapshot, type InspectorConnectOptions, type InspectorSession, type InspectorTarget, type LogpointEvent, type LogpointStreamOptions, type LogpointStreamResult, type OpenedTunnel, type PauseEvent, type RemoteRootSetting, type ResolvedLocation, type ScopeInfo, type ScopeSnapshot, type ScriptInfo, type SetBreakpointInput, type SnapshotCaptureResult, type SnapshotResult, type TunnelTarget, type VariableSnapshot, type WaitForPauseOptions, buildBreakpointUrlRegex, buildLogpointCondition, captureSnapshot, connectInspector, discoverInspectorTargets, evaluateGlobal, evaluateOnFrame, fetchInspectorVersion, getProperties, listScripts, openCfTunnel, parseBreakpointSpec, parseRemoteRoot, removeBreakpoint, resume, setBreakpoint, streamLogpoint, validateExpression, waitForPause };
|
package/dist/index.js
CHANGED
|
@@ -215,6 +215,15 @@ function stripTrailingSlash(value) {
|
|
|
215
215
|
}
|
|
216
216
|
return value;
|
|
217
217
|
}
|
|
218
|
+
function normalizeRegexRootPattern(pattern) {
|
|
219
|
+
const withoutStartAnchor = pattern.startsWith("^") ? pattern.slice(1) : pattern;
|
|
220
|
+
const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
|
|
221
|
+
return stripTrailingSlash(withoutEndAnchor);
|
|
222
|
+
}
|
|
223
|
+
function buildFileUrlRegex(rootPattern, tail) {
|
|
224
|
+
const separator = rootPattern.endsWith("/") ? "" : "/";
|
|
225
|
+
return `^file://${rootPattern}${separator}${tail}$`;
|
|
226
|
+
}
|
|
218
227
|
function escapeRegExp(value) {
|
|
219
228
|
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
220
229
|
}
|
|
@@ -241,16 +250,18 @@ function buildBreakpointUrlRegex(input) {
|
|
|
241
250
|
}
|
|
242
251
|
case "literal": {
|
|
243
252
|
const escapedRoot = escapeRegExp(input.remoteRoot.value);
|
|
244
|
-
return
|
|
253
|
+
return buildFileUrlRegex(escapedRoot, tail);
|
|
245
254
|
}
|
|
246
255
|
case "regex": {
|
|
247
|
-
|
|
256
|
+
const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
|
|
257
|
+
return buildFileUrlRegex(rootPattern, tail);
|
|
248
258
|
}
|
|
249
259
|
}
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
// src/inspector.ts
|
|
253
263
|
import { request } from "http";
|
|
264
|
+
import { performance } from "perf_hooks";
|
|
254
265
|
|
|
255
266
|
// src/cdp.ts
|
|
256
267
|
init_types();
|
|
@@ -606,21 +617,21 @@ async function connectInspector(options) {
|
|
|
606
617
|
const PAUSE_BUFFER_LIMIT = 32;
|
|
607
618
|
const pauseBuffer = [];
|
|
608
619
|
const pauseWaitGate = { active: false };
|
|
620
|
+
const debuggerState = {};
|
|
609
621
|
client.on("Debugger.paused", (raw) => {
|
|
610
622
|
if (pauseWaitGate.active) {
|
|
611
623
|
return;
|
|
612
624
|
}
|
|
613
625
|
const params = raw;
|
|
614
|
-
const event =
|
|
615
|
-
reason: asString(params.reason),
|
|
616
|
-
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
617
|
-
callFrames: toCallFrames(params.callFrames)
|
|
618
|
-
};
|
|
626
|
+
const event = toPauseEvent(params, performance.now());
|
|
619
627
|
if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
|
|
620
628
|
pauseBuffer.shift();
|
|
621
629
|
}
|
|
622
630
|
pauseBuffer.push(event);
|
|
623
631
|
});
|
|
632
|
+
client.on("Debugger.resumed", () => {
|
|
633
|
+
debuggerState.lastResumedAtMs = performance.now();
|
|
634
|
+
});
|
|
624
635
|
await client.send("Runtime.enable");
|
|
625
636
|
await client.send("Debugger.enable");
|
|
626
637
|
return {
|
|
@@ -629,6 +640,7 @@ async function connectInspector(options) {
|
|
|
629
640
|
scripts,
|
|
630
641
|
pauseBuffer,
|
|
631
642
|
pauseWaitGate,
|
|
643
|
+
debuggerState,
|
|
632
644
|
dispose: async () => {
|
|
633
645
|
try {
|
|
634
646
|
await client.send("Debugger.disable");
|
|
@@ -746,36 +758,121 @@ function pauseMatches(pause, breakpointIds) {
|
|
|
746
758
|
}
|
|
747
759
|
return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
|
|
748
760
|
}
|
|
761
|
+
function toPauseEvent(params, receivedAtMs) {
|
|
762
|
+
return {
|
|
763
|
+
reason: asString(params.reason),
|
|
764
|
+
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
765
|
+
callFrames: toCallFrames(params.callFrames),
|
|
766
|
+
receivedAtMs
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function remainingUntil(deadlineMs) {
|
|
770
|
+
return Math.max(0, deadlineMs - performance.now());
|
|
771
|
+
}
|
|
772
|
+
function topFrameLocation(pause) {
|
|
773
|
+
const top = pause.callFrames[0];
|
|
774
|
+
if (top === void 0) {
|
|
775
|
+
return "(no call frame)";
|
|
776
|
+
}
|
|
777
|
+
const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
|
|
778
|
+
return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
|
|
779
|
+
}
|
|
780
|
+
function pauseDetail(pause) {
|
|
781
|
+
return JSON.stringify({
|
|
782
|
+
reason: pause.reason,
|
|
783
|
+
hitBreakpoints: pause.hitBreakpoints,
|
|
784
|
+
topFrame: topFrameLocation(pause)
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
function hasResumedSincePause(session, pause) {
|
|
788
|
+
const pauseAt = pause.receivedAtMs;
|
|
789
|
+
const resumedAt = session.debuggerState.lastResumedAtMs;
|
|
790
|
+
return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
|
|
791
|
+
}
|
|
792
|
+
function throwBreakpointTimeout(timeoutMs) {
|
|
793
|
+
throw new CfInspectorError(
|
|
794
|
+
"BREAKPOINT_NOT_HIT",
|
|
795
|
+
`Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
function throwUnrelatedPauseTimeout(pause, timeoutMs) {
|
|
799
|
+
throw new CfInspectorError(
|
|
800
|
+
"UNRELATED_PAUSE_TIMEOUT",
|
|
801
|
+
`Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
|
|
802
|
+
pauseDetail(pause)
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
|
|
806
|
+
if (hasResumedSincePause(session, pause)) {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const remainingMs = remainingUntil(deadlineMs);
|
|
810
|
+
if (remainingMs <= 0) {
|
|
811
|
+
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
812
|
+
}
|
|
813
|
+
try {
|
|
814
|
+
await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
|
|
815
|
+
session.debuggerState.lastResumedAtMs = performance.now();
|
|
816
|
+
} catch (err) {
|
|
817
|
+
if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
|
|
818
|
+
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
819
|
+
}
|
|
820
|
+
throw err;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
async function handleUnmatchedPause(session, pause, options, deadlineMs) {
|
|
824
|
+
if (options.unmatchedPausePolicy === "fail") {
|
|
825
|
+
throw new CfInspectorError(
|
|
826
|
+
"UNRELATED_PAUSE",
|
|
827
|
+
"Target paused before this command's breakpoint was reached",
|
|
828
|
+
pauseDetail(pause)
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
if (hasResumedSincePause(session, pause)) {
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
options.onUnmatchedPause?.(pause);
|
|
835
|
+
await waitForUnmatchedPauseToResume(session, pause, deadlineMs, options.timeoutMs);
|
|
836
|
+
}
|
|
749
837
|
async function waitForPause(session, options) {
|
|
838
|
+
const deadlineMs = performance.now() + options.timeoutMs;
|
|
750
839
|
const buffer = session.pauseBuffer;
|
|
751
|
-
while (buffer.length > 0) {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
840
|
+
while (buffer.length > 0 || remainingUntil(deadlineMs) > 0) {
|
|
841
|
+
while (buffer.length > 0) {
|
|
842
|
+
const buffered = buffer.shift();
|
|
843
|
+
if (buffered === void 0) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
if (pauseMatches(buffered, options.breakpointIds)) {
|
|
847
|
+
return buffered;
|
|
848
|
+
}
|
|
849
|
+
await handleUnmatchedPause(session, buffered, options, deadlineMs);
|
|
755
850
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
851
|
+
const remainingMs = remainingUntil(deadlineMs);
|
|
852
|
+
if (remainingMs <= 0) {
|
|
853
|
+
throwBreakpointTimeout(options.timeoutMs);
|
|
854
|
+
}
|
|
855
|
+
session.pauseWaitGate.active = true;
|
|
856
|
+
let receivedAtMs;
|
|
857
|
+
let params;
|
|
858
|
+
try {
|
|
859
|
+
params = await session.client.waitFor("Debugger.paused", {
|
|
860
|
+
timeoutMs: remainingMs,
|
|
861
|
+
predicate: () => {
|
|
862
|
+
receivedAtMs = performance.now();
|
|
765
863
|
return true;
|
|
766
864
|
}
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
|
|
865
|
+
});
|
|
866
|
+
} finally {
|
|
867
|
+
session.pauseWaitGate.active = false;
|
|
868
|
+
}
|
|
869
|
+
const pause = toPauseEvent(params, receivedAtMs ?? performance.now());
|
|
870
|
+
if (pauseMatches(pause, options.breakpointIds)) {
|
|
871
|
+
return pause;
|
|
872
|
+
}
|
|
873
|
+
await handleUnmatchedPause(session, pause, options, deadlineMs);
|
|
773
874
|
}
|
|
774
|
-
|
|
775
|
-
reason: asString(params.reason),
|
|
776
|
-
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
777
|
-
callFrames: toCallFrames(params.callFrames)
|
|
778
|
-
};
|
|
875
|
+
throwBreakpointTimeout(options.timeoutMs);
|
|
779
876
|
}
|
|
780
877
|
async function resume(session) {
|
|
781
878
|
await session.client.send("Debugger.resume");
|
|
@@ -831,7 +928,7 @@ var MAX_SCOPE_VARIABLES = 20;
|
|
|
831
928
|
var MAX_CHILD_VARIABLES = 8;
|
|
832
929
|
var MAX_VARIABLE_DEPTH = 2;
|
|
833
930
|
var MAX_VALUE_LENGTH = 240;
|
|
834
|
-
var SENSITIVE_NAME_REGEX = /(pass(?:word)?|token|secret|api[_-]?key|authorization|cookie|session|private[_-]?key)/i;
|
|
931
|
+
var SENSITIVE_NAME_REGEX = /(pass(?:word)?|credentials?|creds?|token|secret|api[_-]?key|authorization|cookie|session|private[_-]?key)/i;
|
|
835
932
|
var PRIORITY_BY_TYPE = {
|
|
836
933
|
local: 0,
|
|
837
934
|
arguments: 1,
|
|
@@ -892,8 +989,11 @@ function formatPrimitive(value) {
|
|
|
892
989
|
}
|
|
893
990
|
return String(value);
|
|
894
991
|
}
|
|
992
|
+
function isSensitiveName(name) {
|
|
993
|
+
return SENSITIVE_NAME_REGEX.test(name);
|
|
994
|
+
}
|
|
895
995
|
function sanitizeValue(name, raw) {
|
|
896
|
-
if (
|
|
996
|
+
if (isSensitiveName(name)) {
|
|
897
997
|
return "[REDACTED]";
|
|
898
998
|
}
|
|
899
999
|
if (raw.length <= MAX_VALUE_LENGTH) {
|
|
@@ -911,8 +1011,9 @@ async function captureProperties(session, objectId, limit, depth) {
|
|
|
911
1011
|
limited.map(async (prop) => {
|
|
912
1012
|
const name = typeof prop.name === "string" ? prop.name : "?";
|
|
913
1013
|
const described = describeProperty(prop);
|
|
1014
|
+
const sensitive = isSensitiveName(name);
|
|
914
1015
|
let children;
|
|
915
|
-
if (depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
|
|
1016
|
+
if (!sensitive && depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
|
|
916
1017
|
try {
|
|
917
1018
|
const nested = await captureProperties(
|
|
918
1019
|
session,
|
|
@@ -926,7 +1027,7 @@ async function captureProperties(session, objectId, limit, depth) {
|
|
|
926
1027
|
} catch {
|
|
927
1028
|
}
|
|
928
1029
|
}
|
|
929
|
-
const sanitizedValue = sanitizeValue(name, described.value);
|
|
1030
|
+
const sanitizedValue = sensitive ? "[REDACTED]" : sanitizeValue(name, described.value);
|
|
930
1031
|
const base = { name, value: sanitizedValue };
|
|
931
1032
|
const withType = described.type === void 0 ? base : { ...base, type: described.type };
|
|
932
1033
|
return children === void 0 ? withType : { ...withType, children };
|