@ian-pascoe/pi-dap 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/LICENSE +21 -0
- package/README.md +164 -0
- package/package.json +57 -0
- package/src/dap-observer-ui.ts +337 -0
- package/src/dap-protocol-client.ts +1103 -0
- package/src/dap-session-files.ts +110 -0
- package/src/dap-session.ts +1231 -0
- package/src/dap-tool-contract.ts +263 -0
- package/src/dap-tool-rendering.ts +512 -0
- package/src/dap-tool.ts +420 -0
- package/src/index.ts +1 -0
- package/src/pi-dap-extension.ts +110 -0
- package/src/pi-dap-settings.ts +406 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { chmod, mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Maximum unread Debuggee output retained in bytes. */
|
|
5
|
+
export const MAX_DAP_RETAINED_BYTES = 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
/** One drain of unread Debuggee output. */
|
|
8
|
+
export interface DapOutputDrain {
|
|
9
|
+
readonly discardedBytes: number;
|
|
10
|
+
readonly text: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Retains only bounded unread Debuggee output until a tool operation drains it. */
|
|
14
|
+
export interface DapOutputBuffer {
|
|
15
|
+
/** Append Debuggee output in protocol event order. */
|
|
16
|
+
append(output: string): void;
|
|
17
|
+
/** Return and clear all currently unread Debuggee output. */
|
|
18
|
+
drain(): DapOutputDrain;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Owns private Result Spill and adapter stderr paths for one Pi session. */
|
|
22
|
+
export interface DapSessionFiles {
|
|
23
|
+
/** Private directory removed when the Pi session shuts down. */
|
|
24
|
+
readonly directoryPath: string;
|
|
25
|
+
/** Write complete truncated tool output to a Result Spill file. */
|
|
26
|
+
writeResultSpill(output: string): Promise<string>;
|
|
27
|
+
/** Create a private stderr file path for one Debug Adapter launch. */
|
|
28
|
+
getAdapterStderrPath(adapterId: string): Promise<string>;
|
|
29
|
+
/** Remove all session files after queued writes finish. */
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class RetainedDapOutput implements DapOutputBuffer {
|
|
34
|
+
private content = Buffer.alloc(0);
|
|
35
|
+
private discardedBytes = 0;
|
|
36
|
+
|
|
37
|
+
append(output: string): void {
|
|
38
|
+
const combined = Buffer.concat([this.content, Buffer.from(output)]);
|
|
39
|
+
const overflow = Math.max(0, combined.length - MAX_DAP_RETAINED_BYTES);
|
|
40
|
+
this.discardedBytes += overflow;
|
|
41
|
+
this.content = overflow === 0 ? combined : Buffer.from(combined.subarray(overflow));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
drain(): DapOutputDrain {
|
|
45
|
+
const drained = { discardedBytes: this.discardedBytes, text: this.content.toString("utf8") };
|
|
46
|
+
this.content = Buffer.alloc(0);
|
|
47
|
+
this.discardedBytes = 0;
|
|
48
|
+
return drained;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class DapSessionFileStore implements DapSessionFiles {
|
|
53
|
+
private closed = false;
|
|
54
|
+
private closePromise: Promise<void> | undefined;
|
|
55
|
+
private nextFileIndex = 0;
|
|
56
|
+
private writeQueue: Promise<void> = Promise.resolve();
|
|
57
|
+
|
|
58
|
+
constructor(readonly directoryPath: string) {}
|
|
59
|
+
|
|
60
|
+
writeResultSpill(output: string): Promise<string> {
|
|
61
|
+
const path = join(this.directoryPath, `result-spill-${this.nextFileIndex++}.txt`);
|
|
62
|
+
return this.enqueueWrite(async () => {
|
|
63
|
+
await writeFile(path, output, { encoding: "utf8", mode: 0o600 });
|
|
64
|
+
await chmod(path, 0o600);
|
|
65
|
+
return path;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getAdapterStderrPath(_adapterId: string): Promise<string> {
|
|
70
|
+
const path = join(this.directoryPath, `adapter-stderr-${this.nextFileIndex++}.log`);
|
|
71
|
+
return this.enqueueWrite(async () => {
|
|
72
|
+
await writeFile(path, "", { mode: 0o600 });
|
|
73
|
+
await chmod(path, 0o600);
|
|
74
|
+
return path;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
close(): Promise<void> {
|
|
79
|
+
if (this.closePromise !== undefined) return this.closePromise;
|
|
80
|
+
this.closed = true;
|
|
81
|
+
this.closePromise = this.writeQueue.then(
|
|
82
|
+
() => rm(this.directoryPath, { force: true, recursive: true }),
|
|
83
|
+
() => rm(this.directoryPath, { force: true, recursive: true }),
|
|
84
|
+
);
|
|
85
|
+
return this.closePromise;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private enqueueWrite<T>(write: () => Promise<T>): Promise<T> {
|
|
89
|
+
if (this.closed) return Promise.reject(new Error("Pi DAP: session files are closed"));
|
|
90
|
+
const result = this.writeQueue.then(write);
|
|
91
|
+
this.writeQueue = result.then(
|
|
92
|
+
() => undefined,
|
|
93
|
+
() => undefined,
|
|
94
|
+
);
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Create a bounded unread Debuggee output buffer. */
|
|
100
|
+
export function createDapOutputBuffer(): DapOutputBuffer {
|
|
101
|
+
return new RetainedDapOutput();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Create a private directory for Result Spills and Debug Adapter stderr. */
|
|
105
|
+
export async function createDapSessionFiles(sessionDirectory: string): Promise<DapSessionFiles> {
|
|
106
|
+
await mkdir(sessionDirectory, { mode: 0o700, recursive: true });
|
|
107
|
+
const directoryPath = await mkdtemp(join(sessionDirectory, "pi-dap-"));
|
|
108
|
+
await chmod(directoryPath, 0o700);
|
|
109
|
+
return new DapSessionFileStore(directoryPath);
|
|
110
|
+
}
|