agent-dbg 0.1.0
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/.bin/ndbg +0 -0
- package/.claude/settings.local.json +21 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/SKILL.md +116 -0
- package/.claude/skills/ndbg-debugger/ndbg-debugger/references/commands.md +173 -0
- package/CLAUDE.md +43 -0
- package/PROGRESS.md +261 -0
- package/README.md +67 -0
- package/biome.json +41 -0
- package/ndbg-spec.md +958 -0
- package/package.json +30 -0
- package/src/cdp/client.ts +198 -0
- package/src/cdp/types.ts +16 -0
- package/src/cli/parser.ts +287 -0
- package/src/cli/registry.ts +7 -0
- package/src/cli/types.ts +24 -0
- package/src/commands/attach.ts +47 -0
- package/src/commands/blackbox-ls.ts +38 -0
- package/src/commands/blackbox-rm.ts +57 -0
- package/src/commands/blackbox.ts +48 -0
- package/src/commands/break-ls.ts +57 -0
- package/src/commands/break-rm.ts +40 -0
- package/src/commands/break-toggle.ts +42 -0
- package/src/commands/break.ts +145 -0
- package/src/commands/breakable.ts +69 -0
- package/src/commands/catch.ts +38 -0
- package/src/commands/console.ts +61 -0
- package/src/commands/continue.ts +46 -0
- package/src/commands/eval.ts +70 -0
- package/src/commands/exceptions.ts +61 -0
- package/src/commands/hotpatch.ts +67 -0
- package/src/commands/launch.ts +69 -0
- package/src/commands/logpoint.ts +78 -0
- package/src/commands/pause.ts +46 -0
- package/src/commands/props.ts +77 -0
- package/src/commands/restart-frame.ts +36 -0
- package/src/commands/run-to.ts +70 -0
- package/src/commands/scripts.ts +57 -0
- package/src/commands/search.ts +73 -0
- package/src/commands/sessions.ts +71 -0
- package/src/commands/set-return.ts +49 -0
- package/src/commands/set.ts +61 -0
- package/src/commands/source.ts +59 -0
- package/src/commands/sourcemap.ts +66 -0
- package/src/commands/stack.ts +64 -0
- package/src/commands/state.ts +124 -0
- package/src/commands/status.ts +57 -0
- package/src/commands/step.ts +50 -0
- package/src/commands/stop.ts +27 -0
- package/src/commands/vars.ts +71 -0
- package/src/daemon/client.ts +147 -0
- package/src/daemon/entry.ts +242 -0
- package/src/daemon/paths.ts +26 -0
- package/src/daemon/server.ts +185 -0
- package/src/daemon/session-blackbox.ts +41 -0
- package/src/daemon/session-breakpoints.ts +492 -0
- package/src/daemon/session-execution.ts +121 -0
- package/src/daemon/session-inspection.ts +701 -0
- package/src/daemon/session-mutation.ts +197 -0
- package/src/daemon/session-state.ts +258 -0
- package/src/daemon/session.ts +938 -0
- package/src/daemon/spawn.ts +53 -0
- package/src/formatter/errors.ts +15 -0
- package/src/formatter/source.ts +74 -0
- package/src/formatter/stack.ts +70 -0
- package/src/formatter/values.ts +269 -0
- package/src/formatter/variables.ts +20 -0
- package/src/main.ts +45 -0
- package/src/protocol/messages.ts +316 -0
- package/src/refs/ref-table.ts +120 -0
- package/src/refs/resolver.ts +24 -0
- package/src/sourcemap/resolver.ts +318 -0
- package/tests/fixtures/async-app.js +34 -0
- package/tests/fixtures/console-app.js +12 -0
- package/tests/fixtures/error-app.js +28 -0
- package/tests/fixtures/exception-app.js +6 -0
- package/tests/fixtures/inspect-app.js +10 -0
- package/tests/fixtures/mutation-app.js +9 -0
- package/tests/fixtures/simple-app.js +50 -0
- package/tests/fixtures/step-app.js +13 -0
- package/tests/fixtures/ts-app/src/app.ts +21 -0
- package/tests/fixtures/ts-app/tsconfig.json +14 -0
- package/tests/integration/blackbox.test.ts +135 -0
- package/tests/integration/break-extras.test.ts +241 -0
- package/tests/integration/breakpoint.test.ts +217 -0
- package/tests/integration/console.test.ts +275 -0
- package/tests/integration/execution.test.ts +247 -0
- package/tests/integration/inspection.test.ts +311 -0
- package/tests/integration/mutation.test.ts +178 -0
- package/tests/integration/session.test.ts +223 -0
- package/tests/integration/source.test.ts +209 -0
- package/tests/integration/sourcemap.test.ts +214 -0
- package/tests/integration/state.test.ts +208 -0
- package/tests/unit/cdp-client.test.ts +422 -0
- package/tests/unit/daemon.test.ts +286 -0
- package/tests/unit/formatter.test.ts +716 -0
- package/tests/unit/parser.test.ts +105 -0
- package/tests/unit/refs.test.ts +383 -0
- package/tests/unit/sourcemap.test.ts +236 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { dirname, resolve } from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
generatedPositionFor,
|
|
4
|
+
LEAST_UPPER_BOUND,
|
|
5
|
+
originalPositionFor,
|
|
6
|
+
TraceMap,
|
|
7
|
+
} from "@jridgewell/trace-mapping";
|
|
8
|
+
|
|
9
|
+
export interface OriginalPosition {
|
|
10
|
+
source: string;
|
|
11
|
+
line: number;
|
|
12
|
+
column: number;
|
|
13
|
+
name: string | null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GeneratedPosition {
|
|
17
|
+
scriptId: string;
|
|
18
|
+
line: number;
|
|
19
|
+
column: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SourceMapInfo {
|
|
23
|
+
scriptId: string;
|
|
24
|
+
generatedUrl: string;
|
|
25
|
+
mapUrl: string;
|
|
26
|
+
sources: string[];
|
|
27
|
+
hasSourcesContent: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface LoadedMap {
|
|
31
|
+
traceMap: TraceMap;
|
|
32
|
+
scriptId: string;
|
|
33
|
+
generatedUrl: string;
|
|
34
|
+
mapUrl: string;
|
|
35
|
+
sources: string[];
|
|
36
|
+
resolvedSources: string[];
|
|
37
|
+
hasSourcesContent: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class SourceMapResolver {
|
|
41
|
+
private maps: Map<string, LoadedMap> = new Map();
|
|
42
|
+
// Reverse lookup: resolved source path → { scriptId, sourceIndex }
|
|
43
|
+
private sourceIndex: Map<string, { scriptId: string; sourceIndex: number }> = new Map();
|
|
44
|
+
private disabled = false;
|
|
45
|
+
|
|
46
|
+
async loadSourceMap(scriptId: string, scriptUrl: string, sourceMapURL: string): Promise<boolean> {
|
|
47
|
+
if (this.disabled) return false;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
let rawMap: string;
|
|
51
|
+
|
|
52
|
+
if (sourceMapURL.startsWith("data:")) {
|
|
53
|
+
// Inline data: URI
|
|
54
|
+
const commaIndex = sourceMapURL.indexOf(",");
|
|
55
|
+
if (commaIndex === -1) return false;
|
|
56
|
+
const header = sourceMapURL.slice(0, commaIndex);
|
|
57
|
+
const data = sourceMapURL.slice(commaIndex + 1);
|
|
58
|
+
|
|
59
|
+
if (header.includes("base64")) {
|
|
60
|
+
rawMap = Buffer.from(data, "base64").toString("utf-8");
|
|
61
|
+
} else {
|
|
62
|
+
rawMap = decodeURIComponent(data);
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// File-based source map — resolve relative to the script
|
|
66
|
+
let mapPath: string;
|
|
67
|
+
const scriptPath = scriptUrl.startsWith("file://") ? scriptUrl.slice(7) : scriptUrl;
|
|
68
|
+
|
|
69
|
+
if (sourceMapURL.startsWith("/")) {
|
|
70
|
+
mapPath = sourceMapURL;
|
|
71
|
+
} else {
|
|
72
|
+
mapPath = resolve(dirname(scriptPath), sourceMapURL);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const file = Bun.file(mapPath);
|
|
76
|
+
if (!(await file.exists())) return false;
|
|
77
|
+
rawMap = await file.text();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const parsed = JSON.parse(rawMap);
|
|
81
|
+
const traceMap = new TraceMap(parsed);
|
|
82
|
+
|
|
83
|
+
const sources: string[] = (traceMap.sources as string[]) ?? [];
|
|
84
|
+
|
|
85
|
+
// Resolve source paths relative to the script location
|
|
86
|
+
const scriptPath = scriptUrl.startsWith("file://") ? scriptUrl.slice(7) : scriptUrl;
|
|
87
|
+
const scriptDir = dirname(scriptPath);
|
|
88
|
+
|
|
89
|
+
const resolvedSources = sources.map((s) => {
|
|
90
|
+
if (s.startsWith("/")) return s;
|
|
91
|
+
return resolve(scriptDir, s);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const entry: LoadedMap = {
|
|
95
|
+
traceMap,
|
|
96
|
+
scriptId,
|
|
97
|
+
generatedUrl: scriptUrl,
|
|
98
|
+
mapUrl: sourceMapURL,
|
|
99
|
+
sources,
|
|
100
|
+
resolvedSources,
|
|
101
|
+
hasSourcesContent:
|
|
102
|
+
Array.isArray(traceMap.sourcesContent) && traceMap.sourcesContent.some((c) => c != null),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
this.maps.set(scriptId, entry);
|
|
106
|
+
|
|
107
|
+
// Build reverse lookup for each source
|
|
108
|
+
for (let i = 0; i < sources.length; i++) {
|
|
109
|
+
const rawSource = sources[i];
|
|
110
|
+
const resolvedSource = resolvedSources[i];
|
|
111
|
+
|
|
112
|
+
if (rawSource) {
|
|
113
|
+
this.sourceIndex.set(rawSource, { scriptId, sourceIndex: i });
|
|
114
|
+
}
|
|
115
|
+
if (resolvedSource && resolvedSource !== rawSource) {
|
|
116
|
+
this.sourceIndex.set(resolvedSource, { scriptId, sourceIndex: i });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return true;
|
|
121
|
+
} catch {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
toOriginal(scriptId: string, line: number, column: number): OriginalPosition | null {
|
|
127
|
+
if (this.disabled) return null;
|
|
128
|
+
|
|
129
|
+
const entry = this.maps.get(scriptId);
|
|
130
|
+
if (!entry) return null;
|
|
131
|
+
|
|
132
|
+
const result = originalPositionFor(entry.traceMap, {
|
|
133
|
+
line,
|
|
134
|
+
column,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (result.source == null) return null;
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
source: result.source,
|
|
141
|
+
line: result.line ?? line,
|
|
142
|
+
column: result.column ?? column,
|
|
143
|
+
name: result.name,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
toGenerated(source: string, line: number, column: number): GeneratedPosition | null {
|
|
148
|
+
if (this.disabled) return null;
|
|
149
|
+
|
|
150
|
+
// Try direct lookup first
|
|
151
|
+
const indexEntry = this.sourceIndex.get(source);
|
|
152
|
+
if (indexEntry) {
|
|
153
|
+
const entry = this.maps.get(indexEntry.scriptId);
|
|
154
|
+
if (entry) {
|
|
155
|
+
const sourceName = entry.sources[indexEntry.sourceIndex];
|
|
156
|
+
if (sourceName) {
|
|
157
|
+
const result = this.tryGeneratedPosition(entry.traceMap, sourceName, line, column);
|
|
158
|
+
if (result) {
|
|
159
|
+
return { scriptId: indexEntry.scriptId, ...result };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Try suffix matching
|
|
166
|
+
const match = this.findScriptForSource(source);
|
|
167
|
+
if (match) {
|
|
168
|
+
const entry = this.maps.get(match.scriptId);
|
|
169
|
+
if (entry) {
|
|
170
|
+
// Find the matching source name in the map
|
|
171
|
+
for (const s of entry.sources) {
|
|
172
|
+
if (s && (s === source || s.endsWith(source) || source.endsWith(s))) {
|
|
173
|
+
const result = this.tryGeneratedPosition(entry.traceMap, s, line, column);
|
|
174
|
+
if (result) {
|
|
175
|
+
return { scriptId: match.scriptId, ...result };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private tryGeneratedPosition(
|
|
186
|
+
traceMap: TraceMap,
|
|
187
|
+
source: string,
|
|
188
|
+
line: number,
|
|
189
|
+
column: number,
|
|
190
|
+
): { line: number; column: number } | null {
|
|
191
|
+
// Try exact match first
|
|
192
|
+
const exact = generatedPositionFor(traceMap, { source, line, column });
|
|
193
|
+
if (exact.line != null) {
|
|
194
|
+
return { line: exact.line, column: exact.column ?? 0 };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Fallback: use LEAST_UPPER_BOUND to find the nearest mapping on this line
|
|
198
|
+
const approx = generatedPositionFor(traceMap, {
|
|
199
|
+
source,
|
|
200
|
+
line,
|
|
201
|
+
column,
|
|
202
|
+
bias: LEAST_UPPER_BOUND,
|
|
203
|
+
});
|
|
204
|
+
if (approx.line != null) {
|
|
205
|
+
return { line: approx.line, column: approx.column ?? 0 };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getOriginalSource(scriptId: string, sourcePath: string): string | null {
|
|
212
|
+
if (this.disabled) return null;
|
|
213
|
+
|
|
214
|
+
const entry = this.maps.get(scriptId);
|
|
215
|
+
if (!entry) return null;
|
|
216
|
+
|
|
217
|
+
const sourcesContent = entry.traceMap.sourcesContent as (string | null)[] | undefined;
|
|
218
|
+
if (!sourcesContent) return null;
|
|
219
|
+
|
|
220
|
+
// Match by raw source path or resolved path
|
|
221
|
+
for (let i = 0; i < entry.sources.length; i++) {
|
|
222
|
+
const raw = entry.sources[i];
|
|
223
|
+
const resolved = entry.resolvedSources[i];
|
|
224
|
+
if (
|
|
225
|
+
(raw && (raw === sourcePath || raw.endsWith(sourcePath) || sourcePath.endsWith(raw))) ||
|
|
226
|
+
(resolved &&
|
|
227
|
+
(resolved === sourcePath ||
|
|
228
|
+
resolved.endsWith(sourcePath) ||
|
|
229
|
+
sourcePath.endsWith(resolved)))
|
|
230
|
+
) {
|
|
231
|
+
return sourcesContent[i] ?? null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
findScriptForSource(path: string): { scriptId: string; url: string } | null {
|
|
239
|
+
if (this.disabled) return null;
|
|
240
|
+
|
|
241
|
+
// Try direct lookup first
|
|
242
|
+
const direct = this.sourceIndex.get(path);
|
|
243
|
+
if (direct) {
|
|
244
|
+
const entry = this.maps.get(direct.scriptId);
|
|
245
|
+
if (entry) {
|
|
246
|
+
return { scriptId: direct.scriptId, url: entry.generatedUrl };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Try suffix matching against all sources
|
|
251
|
+
for (const [scriptId, entry] of this.maps) {
|
|
252
|
+
for (let i = 0; i < entry.sources.length; i++) {
|
|
253
|
+
const raw = entry.sources[i];
|
|
254
|
+
const resolved = entry.resolvedSources[i];
|
|
255
|
+
if (raw && (raw.endsWith(path) || path.endsWith(raw))) {
|
|
256
|
+
return { scriptId, url: entry.generatedUrl };
|
|
257
|
+
}
|
|
258
|
+
if (resolved && (resolved.endsWith(path) || path.endsWith(resolved))) {
|
|
259
|
+
return { scriptId, url: entry.generatedUrl };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Returns the primary original source URL for a script that has a source map,
|
|
269
|
+
* regardless of whether a specific line has a mapping. Used for Option A:
|
|
270
|
+
* always show .ts path when source map exists.
|
|
271
|
+
*/
|
|
272
|
+
getScriptOriginalUrl(scriptId: string): string | null {
|
|
273
|
+
if (this.disabled) return null;
|
|
274
|
+
const entry = this.maps.get(scriptId);
|
|
275
|
+
if (!entry) return null;
|
|
276
|
+
return entry.sources[0] ?? null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
getInfo(scriptId: string): SourceMapInfo | null {
|
|
280
|
+
const entry = this.maps.get(scriptId);
|
|
281
|
+
if (!entry) return null;
|
|
282
|
+
|
|
283
|
+
return {
|
|
284
|
+
scriptId: entry.scriptId,
|
|
285
|
+
generatedUrl: entry.generatedUrl,
|
|
286
|
+
mapUrl: entry.mapUrl,
|
|
287
|
+
sources: [...entry.sources],
|
|
288
|
+
hasSourcesContent: entry.hasSourcesContent,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
getAllInfos(): SourceMapInfo[] {
|
|
293
|
+
const result: SourceMapInfo[] = [];
|
|
294
|
+
for (const entry of this.maps.values()) {
|
|
295
|
+
result.push({
|
|
296
|
+
scriptId: entry.scriptId,
|
|
297
|
+
generatedUrl: entry.generatedUrl,
|
|
298
|
+
mapUrl: entry.mapUrl,
|
|
299
|
+
sources: [...entry.sources],
|
|
300
|
+
hasSourcesContent: entry.hasSourcesContent,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
setDisabled(disabled: boolean): void {
|
|
307
|
+
this.disabled = disabled;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
isDisabled(): boolean {
|
|
311
|
+
return this.disabled;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
clear(): void {
|
|
315
|
+
this.maps.clear();
|
|
316
|
+
this.sourceIndex.clear();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Test fixture with async patterns for ndbg integration tests
|
|
2
|
+
// Launch with: node --inspect-brk tests/fixtures/async-app.js
|
|
3
|
+
|
|
4
|
+
async function delay(ms) {
|
|
5
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function processItem(item) {
|
|
9
|
+
await delay(10);
|
|
10
|
+
const result = { ...item, processed: true, timestamp: Date.now() };
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function processQueue(items) {
|
|
15
|
+
const results = [];
|
|
16
|
+
for (const item of items) {
|
|
17
|
+
const result = await processItem(item);
|
|
18
|
+
results.push(result);
|
|
19
|
+
}
|
|
20
|
+
return results;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const queue = [
|
|
24
|
+
{ id: 1, name: "alpha" },
|
|
25
|
+
{ id: 2, name: "beta" },
|
|
26
|
+
{ id: 3, name: "gamma" },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
processQueue(queue).then((results) => {
|
|
30
|
+
console.log(`Processed ${results.length} items`);
|
|
31
|
+
for (const r of results) {
|
|
32
|
+
console.log(` ${r.id}: ${r.name} (processed: ${r.processed})`);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
console.log("hello from app");
|
|
2
|
+
console.warn("warning message");
|
|
3
|
+
console.error("error message");
|
|
4
|
+
const obj = { key: "value" };
|
|
5
|
+
console.log("object:", obj);
|
|
6
|
+
try {
|
|
7
|
+
throw new Error("test error");
|
|
8
|
+
} catch (e) {
|
|
9
|
+
/* swallowed */
|
|
10
|
+
}
|
|
11
|
+
debugger;
|
|
12
|
+
console.log("after debugger");
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Test fixture with errors for ndbg integration tests
|
|
2
|
+
// Launch with: node --inspect-brk tests/fixtures/error-app.js
|
|
3
|
+
|
|
4
|
+
function riskyOperation(input) {
|
|
5
|
+
if (!input) {
|
|
6
|
+
throw new Error("Input is required");
|
|
7
|
+
}
|
|
8
|
+
return input.toUpperCase();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function handleError() {
|
|
12
|
+
try {
|
|
13
|
+
riskyOperation(null);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error("Caught:", err.message);
|
|
16
|
+
return { error: err.message };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Uncaught error path
|
|
21
|
+
const mode = process.argv[2] || "caught";
|
|
22
|
+
|
|
23
|
+
if (mode === "caught") {
|
|
24
|
+
const result = handleError();
|
|
25
|
+
console.log("Result:", result);
|
|
26
|
+
} else if (mode === "uncaught") {
|
|
27
|
+
riskyOperation(null); // This will throw uncaught
|
|
28
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const obj = { name: "test", count: 42, nested: { deep: true } };
|
|
2
|
+
const arr = [1, 2, 3];
|
|
3
|
+
const str = "hello";
|
|
4
|
+
const num = 123;
|
|
5
|
+
function greet(who) {
|
|
6
|
+
return "hi " + who;
|
|
7
|
+
}
|
|
8
|
+
const promise = Promise.resolve(42);
|
|
9
|
+
debugger; // pause here
|
|
10
|
+
console.log(obj, arr);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Simple test fixture for ndbg integration tests
|
|
2
|
+
// Launch with: node --inspect-brk tests/fixtures/simple-app.js
|
|
3
|
+
|
|
4
|
+
function greet(name) {
|
|
5
|
+
const message = `Hello, ${name}!`;
|
|
6
|
+
console.log(message);
|
|
7
|
+
return message;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function add(a, b) {
|
|
11
|
+
const result = a + b;
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function fetchData(id) {
|
|
16
|
+
const data = { id, name: "test", items: [1, 2, 3] };
|
|
17
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class Counter {
|
|
22
|
+
constructor(initial = 0) {
|
|
23
|
+
this.count = initial;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
increment() {
|
|
27
|
+
this.count++;
|
|
28
|
+
return this.count;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
decrement() {
|
|
32
|
+
this.count--;
|
|
33
|
+
return this.count;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Main execution
|
|
38
|
+
const counter = new Counter(10);
|
|
39
|
+
const greeting = greet("World");
|
|
40
|
+
const sum = add(2, 3);
|
|
41
|
+
|
|
42
|
+
counter.increment();
|
|
43
|
+
counter.increment();
|
|
44
|
+
counter.decrement();
|
|
45
|
+
|
|
46
|
+
fetchData("test-123").then((data) => {
|
|
47
|
+
console.log("Data:", JSON.stringify(data));
|
|
48
|
+
console.log("Counter:", counter.count);
|
|
49
|
+
console.log("Sum:", sum);
|
|
50
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Test fixture for execution control tests
|
|
2
|
+
// Each line is a separate statement for stepping tests
|
|
3
|
+
|
|
4
|
+
function helper(x) {
|
|
5
|
+
const doubled = x * 2;
|
|
6
|
+
return doubled;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const a = 1;
|
|
10
|
+
const b = 2;
|
|
11
|
+
const c = helper(a);
|
|
12
|
+
const d = a + b + c;
|
|
13
|
+
console.log(d);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// TypeScript test fixture for source map integration tests
|
|
2
|
+
interface Person {
|
|
3
|
+
name: string;
|
|
4
|
+
age: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function greet(person: Person): string {
|
|
8
|
+
const message: string = `Hello, ${person.name}! Age: ${person.age}`;
|
|
9
|
+
return message;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function add(a: number, b: number): number {
|
|
13
|
+
const result: number = a + b;
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const alice: Person = { name: "Alice", age: 30 };
|
|
18
|
+
const greeting: string = greet(alice);
|
|
19
|
+
const sum: number = add(2, 3);
|
|
20
|
+
console.log(greeting);
|
|
21
|
+
console.log("Sum:", sum);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"rootDir": "./src",
|
|
7
|
+
"sourceMap": true,
|
|
8
|
+
"inlineSources": true,
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"declaration": false
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*.ts"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { DebugSession } from "../../src/daemon/session.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Polls until the session reaches the expected state, or times out.
|
|
6
|
+
*/
|
|
7
|
+
async function waitForState(
|
|
8
|
+
session: DebugSession,
|
|
9
|
+
state: "idle" | "running" | "paused",
|
|
10
|
+
timeoutMs = 5000,
|
|
11
|
+
): Promise<void> {
|
|
12
|
+
const deadline = Date.now() + timeoutMs;
|
|
13
|
+
while (session.sessionState !== state && Date.now() < deadline) {
|
|
14
|
+
await Bun.sleep(50);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("Blackbox patterns", () => {
|
|
19
|
+
test("add blackbox patterns", async () => {
|
|
20
|
+
const session = new DebugSession("test-blackbox-add");
|
|
21
|
+
try {
|
|
22
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], {
|
|
23
|
+
brk: true,
|
|
24
|
+
});
|
|
25
|
+
await waitForState(session, "paused");
|
|
26
|
+
|
|
27
|
+
const result = await session.addBlackbox(["node_modules", "internal"]);
|
|
28
|
+
expect(result).toEqual(["node_modules", "internal"]);
|
|
29
|
+
expect(session.listBlackbox()).toEqual(["node_modules", "internal"]);
|
|
30
|
+
} finally {
|
|
31
|
+
await session.stop();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("list blackbox patterns", async () => {
|
|
36
|
+
const session = new DebugSession("test-blackbox-list");
|
|
37
|
+
try {
|
|
38
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], {
|
|
39
|
+
brk: true,
|
|
40
|
+
});
|
|
41
|
+
await waitForState(session, "paused");
|
|
42
|
+
|
|
43
|
+
// Initially empty
|
|
44
|
+
expect(session.listBlackbox()).toEqual([]);
|
|
45
|
+
|
|
46
|
+
// Add some patterns
|
|
47
|
+
await session.addBlackbox(["node_modules", "vendor"]);
|
|
48
|
+
|
|
49
|
+
// Verify they are listed
|
|
50
|
+
const patterns = session.listBlackbox();
|
|
51
|
+
expect(patterns).toEqual(["node_modules", "vendor"]);
|
|
52
|
+
} finally {
|
|
53
|
+
await session.stop();
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("remove specific pattern", async () => {
|
|
58
|
+
const session = new DebugSession("test-blackbox-rm-specific");
|
|
59
|
+
try {
|
|
60
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], {
|
|
61
|
+
brk: true,
|
|
62
|
+
});
|
|
63
|
+
await waitForState(session, "paused");
|
|
64
|
+
|
|
65
|
+
await session.addBlackbox(["node_modules", "vendor"]);
|
|
66
|
+
expect(session.listBlackbox()).toEqual(["node_modules", "vendor"]);
|
|
67
|
+
|
|
68
|
+
const result = await session.removeBlackbox(["node_modules"]);
|
|
69
|
+
expect(result).toEqual(["vendor"]);
|
|
70
|
+
expect(session.listBlackbox()).toEqual(["vendor"]);
|
|
71
|
+
} finally {
|
|
72
|
+
await session.stop();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("remove all patterns", async () => {
|
|
77
|
+
const session = new DebugSession("test-blackbox-rm-all");
|
|
78
|
+
try {
|
|
79
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], {
|
|
80
|
+
brk: true,
|
|
81
|
+
});
|
|
82
|
+
await waitForState(session, "paused");
|
|
83
|
+
|
|
84
|
+
await session.addBlackbox(["node_modules", "vendor", "internal"]);
|
|
85
|
+
expect(session.listBlackbox()).toHaveLength(3);
|
|
86
|
+
|
|
87
|
+
const result = await session.removeBlackbox(["all"]);
|
|
88
|
+
expect(result).toEqual([]);
|
|
89
|
+
expect(session.listBlackbox()).toEqual([]);
|
|
90
|
+
} finally {
|
|
91
|
+
await session.stop();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("blackbox persists across continue", async () => {
|
|
96
|
+
const session = new DebugSession("test-blackbox-persist");
|
|
97
|
+
try {
|
|
98
|
+
await session.launch(["node", "tests/fixtures/step-app.js"], {
|
|
99
|
+
brk: true,
|
|
100
|
+
});
|
|
101
|
+
await waitForState(session, "paused");
|
|
102
|
+
|
|
103
|
+
// Set a breakpoint further in the file so we can continue to it
|
|
104
|
+
await session.setBreakpoint("step-app.js", 12);
|
|
105
|
+
|
|
106
|
+
// Add blackbox patterns
|
|
107
|
+
await session.addBlackbox(["node_modules"]);
|
|
108
|
+
expect(session.listBlackbox()).toEqual(["node_modules"]);
|
|
109
|
+
|
|
110
|
+
// Continue to the breakpoint
|
|
111
|
+
await session.continue();
|
|
112
|
+
await waitForState(session, "paused");
|
|
113
|
+
|
|
114
|
+
// Patterns should still be present
|
|
115
|
+
expect(session.listBlackbox()).toEqual(["node_modules"]);
|
|
116
|
+
} finally {
|
|
117
|
+
await session.stop();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("blackbox throws when no session", async () => {
|
|
122
|
+
const session = new DebugSession("test-blackbox-no-session");
|
|
123
|
+
try {
|
|
124
|
+
await expect(session.addBlackbox(["node_modules"])).rejects.toThrow(
|
|
125
|
+
"No active debug session",
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
await expect(session.removeBlackbox(["node_modules"])).rejects.toThrow(
|
|
129
|
+
"No active debug session",
|
|
130
|
+
);
|
|
131
|
+
} finally {
|
|
132
|
+
await session.stop();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|