ptywright 0.1.0 → 0.2.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/README.md +459 -116
- package/dist/agent.mjs +2 -0
- package/dist/bin/ptywright.mjs +6 -0
- package/dist/cli-DIUx2w6X.mjs +3587 -0
- package/dist/cli.mjs +2 -0
- package/{src/index.ts → dist/index.mjs} +7 -9
- package/dist/mcp.mjs +2 -0
- package/dist/pty-cassette.mjs +24 -0
- package/dist/pty_like-Cpkh_O9B.mjs +404 -0
- package/dist/runner-DzZlFrt1.mjs +1897 -0
- package/dist/runner-zApMYWZx.mjs +3257 -0
- package/dist/script.mjs +2 -0
- package/dist/server-VHuEWWj_.mjs +3068 -0
- package/dist/session.mjs +2 -0
- package/dist/terminal_session-DopC7Xg6.mjs +893 -0
- package/package.json +28 -21
- package/schemas/ptywright-agent-cassette.schema.json +57 -0
- package/schemas/ptywright-agent-check.schema.json +122 -0
- package/schemas/ptywright-agent-manifest.schema.json +107 -0
- package/schemas/ptywright-agent-promote.schema.json +146 -0
- package/schemas/ptywright-agent-replay-summary.schema.json +140 -0
- package/schemas/ptywright-agent-run.schema.json +126 -0
- package/schemas/ptywright-agent.schema.json +182 -0
- package/schemas/ptywright-pty-cassette.schema.json +86 -0
- package/schemas/ptywright-script-manifest.schema.json +75 -0
- package/schemas/ptywright-script-run-summary.schema.json +114 -0
- package/schemas/ptywright-script.schema.json +55 -3
- package/skills/ptywright-testing/SKILL.md +53 -33
- package/bin/ptywright +0 -4
- package/src/cli.ts +0 -414
- package/src/generator/doc_parser.ts +0 -341
- package/src/generator/generate.ts +0 -161
- package/src/generator/index.ts +0 -10
- package/src/generator/script_generator.ts +0 -209
- package/src/generator/step_extractor.ts +0 -397
- package/src/mcp/http_server.ts +0 -174
- package/src/mcp/script_recording.ts +0 -238
- package/src/mcp/server.ts +0 -1348
- package/src/pty/bun_pty_adapter.ts +0 -34
- package/src/pty/bun_terminal_adapter.ts +0 -149
- package/src/pty/pty_adapter.ts +0 -31
- package/src/script/dsl.ts +0 -188
- package/src/script/module.ts +0 -43
- package/src/script/path.ts +0 -151
- package/src/script/run.ts +0 -108
- package/src/script/run_all.ts +0 -229
- package/src/script/runner.ts +0 -983
- package/src/script/schema.ts +0 -237
- package/src/script/steps/assert_snapshot_equals.ts +0 -21
- package/src/script/steps/index.ts +0 -2
- package/src/script/suite_report.ts +0 -626
- package/src/session/session_manager.ts +0 -145
- package/src/session/terminal_session.ts +0 -473
- package/src/terminal/ansi.ts +0 -142
- package/src/terminal/keys.ts +0 -180
- package/src/terminal/mask.ts +0 -70
- package/src/terminal/mouse.ts +0 -75
- package/src/terminal/snapshot.ts +0 -196
- package/src/terminal/style.ts +0 -121
- package/src/terminal/view.ts +0 -49
- package/src/trace/asciicast.ts +0 -20
- package/src/trace/asciinema_player_assets.ts +0 -44
- package/src/trace/cast_to_txt.ts +0 -116
- package/src/trace/recorder.ts +0 -110
- package/src/trace/report.ts +0 -2092
- package/src/types.ts +0 -86
- package/src/util/hash.ts +0 -8
- package/src/util/sleep.ts +0 -5
package/dist/cli.mjs
ADDED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
+
import { t as createPtywrightServer } from "./server-VHuEWWj_.mjs";
|
|
1
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
-
|
|
3
|
-
import { createPtywrightServer } from "./mcp/server";
|
|
4
|
-
|
|
3
|
+
//#region src/index.ts
|
|
5
4
|
const { server, sessions } = createPtywrightServer();
|
|
6
|
-
|
|
7
5
|
const transport = new StdioServerTransport();
|
|
8
6
|
await server.connect(transport);
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
void server.close();
|
|
7
|
+
function shutdown() {
|
|
8
|
+
sessions.closeAll();
|
|
9
|
+
server.close();
|
|
13
10
|
}
|
|
14
|
-
|
|
15
11
|
process.on("SIGINT", shutdown);
|
|
16
12
|
process.on("SIGTERM", shutdown);
|
|
13
|
+
//#endregion
|
|
14
|
+
export {};
|
package/dist/mcp.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { S as dataToBytes, _ as ptyCassetteSchema, a as inspectPtyCassette, b as byteLength, c as createPtyCassetteReplay, d as PTY_CASSETTE_SCHEMA_URL, f as normalizePtyCassette, g as ptyCassetteResizeEventSchema, h as ptyCassetteExitEventSchema, i as formatPtyCassetteInspectLines, l as readPtyCassettePath, m as ptyCassetteEventSchema, n as PtyCassetteRecorder, o as inspectPtyCassettePath, p as ptyCassetteDataEventSchema, r as createPtyCassetteRecorder, s as PtyCassetteReplay, t as wrapPtyLike, u as writePtyCassettePath, v as validatePtyCassette, x as dataToBase64, y as base64ToBytes } from "./pty_like-Cpkh_O9B.mjs";
|
|
2
|
+
//#region src/pty-cassette/bun_terminal.ts
|
|
3
|
+
function wrapBunTerminalOptions(options, recorder) {
|
|
4
|
+
const onData = options.data;
|
|
5
|
+
const onExit = options.exit;
|
|
6
|
+
return {
|
|
7
|
+
...options,
|
|
8
|
+
data: (terminal, data) => {
|
|
9
|
+
recorder.recordOutput(data);
|
|
10
|
+
onData?.(terminal, data);
|
|
11
|
+
},
|
|
12
|
+
exit: onExit ? (terminal) => onExit(terminal) : void 0
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function writeBunTerminalRecorded(terminal, recorder, data) {
|
|
16
|
+
recorder.recordInput(data);
|
|
17
|
+
terminal.write(data);
|
|
18
|
+
}
|
|
19
|
+
function resizeBunTerminalRecorded(terminal, recorder, cols, rows) {
|
|
20
|
+
recorder.recordResize(cols, rows);
|
|
21
|
+
terminal.resize(cols, rows);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { PTY_CASSETTE_SCHEMA_URL, PtyCassetteRecorder, PtyCassetteReplay, base64ToBytes, byteLength, createPtyCassetteRecorder, createPtyCassetteReplay, dataToBase64, dataToBytes, formatPtyCassetteInspectLines, inspectPtyCassette, inspectPtyCassettePath, normalizePtyCassette, ptyCassetteDataEventSchema, ptyCassetteEventSchema, ptyCassetteExitEventSchema, ptyCassetteResizeEventSchema, ptyCassetteSchema, readPtyCassettePath, resizeBunTerminalRecorded, validatePtyCassette, wrapBunTerminalOptions, wrapPtyLike, writeBunTerminalRecorded, writePtyCassettePath };
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { Buffer } from "node:buffer";
|
|
5
|
+
//#region src/pty-cassette/data.ts
|
|
6
|
+
function dataToBytes(data) {
|
|
7
|
+
if (typeof data === "string") return Buffer.from(data, "utf8");
|
|
8
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
function dataToBase64(data) {
|
|
12
|
+
return Buffer.from(dataToBytes(data)).toString("base64");
|
|
13
|
+
}
|
|
14
|
+
function base64ToBytes(dataBase64) {
|
|
15
|
+
return Buffer.from(dataBase64, "base64");
|
|
16
|
+
}
|
|
17
|
+
function byteLength(data) {
|
|
18
|
+
return dataToBytes(data).byteLength;
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/pty-cassette/schema.ts
|
|
22
|
+
const PTY_CASSETTE_SCHEMA_URL = "https://ptywright.local/schemas/ptywright-pty-cassette.schema.json";
|
|
23
|
+
const base64Schema = z.string().refine((value) => value.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(value), { message: "expected base64-encoded data" });
|
|
24
|
+
const ptyCassetteDataEventSchema = z.object({
|
|
25
|
+
atMs: z.number().int().nonnegative(),
|
|
26
|
+
type: z.enum(["output", "input"]),
|
|
27
|
+
dataBase64: base64Schema
|
|
28
|
+
});
|
|
29
|
+
const ptyCassetteResizeEventSchema = z.object({
|
|
30
|
+
atMs: z.number().int().nonnegative(),
|
|
31
|
+
type: z.literal("resize"),
|
|
32
|
+
cols: z.number().int().positive(),
|
|
33
|
+
rows: z.number().int().positive()
|
|
34
|
+
});
|
|
35
|
+
const ptyCassetteExitEventSchema = z.object({
|
|
36
|
+
atMs: z.number().int().nonnegative(),
|
|
37
|
+
type: z.literal("exit"),
|
|
38
|
+
exitCode: z.number().int(),
|
|
39
|
+
signal: z.union([
|
|
40
|
+
z.number().int(),
|
|
41
|
+
z.string(),
|
|
42
|
+
z.null()
|
|
43
|
+
]).optional()
|
|
44
|
+
});
|
|
45
|
+
const ptyCassetteEventSchema = z.union([
|
|
46
|
+
ptyCassetteDataEventSchema,
|
|
47
|
+
ptyCassetteResizeEventSchema,
|
|
48
|
+
ptyCassetteExitEventSchema
|
|
49
|
+
]);
|
|
50
|
+
const ptyCassetteSchema = z.object({
|
|
51
|
+
$schema: z.string().optional(),
|
|
52
|
+
version: z.literal(1),
|
|
53
|
+
createdAt: z.string().min(1),
|
|
54
|
+
durationMs: z.number().int().nonnegative(),
|
|
55
|
+
terminal: z.object({
|
|
56
|
+
cols: z.number().int().positive(),
|
|
57
|
+
rows: z.number().int().positive(),
|
|
58
|
+
term: z.string().min(1).optional()
|
|
59
|
+
}),
|
|
60
|
+
command: z.object({
|
|
61
|
+
file: z.string().min(1),
|
|
62
|
+
args: z.array(z.string()).optional(),
|
|
63
|
+
cwd: z.string().optional(),
|
|
64
|
+
env: z.record(z.string()).optional()
|
|
65
|
+
}).optional(),
|
|
66
|
+
metadata: z.record(z.union([
|
|
67
|
+
z.string(),
|
|
68
|
+
z.number(),
|
|
69
|
+
z.boolean(),
|
|
70
|
+
z.null()
|
|
71
|
+
])).optional(),
|
|
72
|
+
events: z.array(ptyCassetteEventSchema)
|
|
73
|
+
}).superRefine((value, ctx) => {
|
|
74
|
+
let last = -1;
|
|
75
|
+
for (let i = 0; i < value.events.length; i += 1) {
|
|
76
|
+
const event = value.events[i];
|
|
77
|
+
if (event.atMs < last) ctx.addIssue({
|
|
78
|
+
code: z.ZodIssueCode.custom,
|
|
79
|
+
path: [
|
|
80
|
+
"events",
|
|
81
|
+
i,
|
|
82
|
+
"atMs"
|
|
83
|
+
],
|
|
84
|
+
message: "events must be ordered by atMs"
|
|
85
|
+
});
|
|
86
|
+
last = event.atMs;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
function normalizePtyCassette(input) {
|
|
90
|
+
return ptyCassetteSchema.parse(input);
|
|
91
|
+
}
|
|
92
|
+
function validatePtyCassette(input) {
|
|
93
|
+
const result = ptyCassetteSchema.safeParse(input);
|
|
94
|
+
if (result.success) return {
|
|
95
|
+
ok: true,
|
|
96
|
+
cassette: result.data
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
errors: result.error.issues.map((issue) => {
|
|
101
|
+
return `${issue.path.length ? `${issue.path.join(".")}: ` : ""}${issue.message}`;
|
|
102
|
+
})
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/pty-cassette/io.ts
|
|
107
|
+
function readPtyCassettePath(path) {
|
|
108
|
+
return normalizePtyCassette(JSON.parse(readFileSync(path, "utf8")));
|
|
109
|
+
}
|
|
110
|
+
function writePtyCassettePath(path, cassette) {
|
|
111
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
112
|
+
writeFileSync(path, JSON.stringify(normalizePtyCassette(cassette), null, 2) + "\n", "utf8");
|
|
113
|
+
return path;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/pty-cassette/replay.ts
|
|
117
|
+
var PtyCassetteReplay = class {
|
|
118
|
+
cassette;
|
|
119
|
+
speed;
|
|
120
|
+
outputListeners = /* @__PURE__ */ new Set();
|
|
121
|
+
dataListeners = /* @__PURE__ */ new Set();
|
|
122
|
+
inputListeners = /* @__PURE__ */ new Set();
|
|
123
|
+
resizeListeners = /* @__PURE__ */ new Set();
|
|
124
|
+
exitListeners = /* @__PURE__ */ new Set();
|
|
125
|
+
stopped = false;
|
|
126
|
+
started = false;
|
|
127
|
+
constructor(cassette, options = {}) {
|
|
128
|
+
this.cassette = cassette;
|
|
129
|
+
this.speed = Math.max(0, options.speed ?? 0);
|
|
130
|
+
}
|
|
131
|
+
onOutput(listener) {
|
|
132
|
+
this.outputListeners.add(listener);
|
|
133
|
+
return disposable(this.outputListeners, listener);
|
|
134
|
+
}
|
|
135
|
+
onData(listener) {
|
|
136
|
+
this.dataListeners.add(listener);
|
|
137
|
+
return disposable(this.dataListeners, listener);
|
|
138
|
+
}
|
|
139
|
+
onInput(listener) {
|
|
140
|
+
this.inputListeners.add(listener);
|
|
141
|
+
return disposable(this.inputListeners, listener);
|
|
142
|
+
}
|
|
143
|
+
onResize(listener) {
|
|
144
|
+
this.resizeListeners.add(listener);
|
|
145
|
+
return disposable(this.resizeListeners, listener);
|
|
146
|
+
}
|
|
147
|
+
onExit(listener) {
|
|
148
|
+
this.exitListeners.add(listener);
|
|
149
|
+
return disposable(this.exitListeners, listener);
|
|
150
|
+
}
|
|
151
|
+
stop() {
|
|
152
|
+
this.stopped = true;
|
|
153
|
+
}
|
|
154
|
+
async start() {
|
|
155
|
+
if (this.started) throw new Error("pty cassette replay already started");
|
|
156
|
+
this.started = true;
|
|
157
|
+
const outputDecoder = new TextDecoder();
|
|
158
|
+
const inputDecoder = new TextDecoder();
|
|
159
|
+
let lastAtMs = 0;
|
|
160
|
+
for (const event of this.cassette.events) {
|
|
161
|
+
if (this.stopped) break;
|
|
162
|
+
if (this.speed > 0) {
|
|
163
|
+
const delayMs = Math.max(0, event.atMs - lastAtMs) / this.speed;
|
|
164
|
+
if (delayMs > 0) await sleep(delayMs);
|
|
165
|
+
}
|
|
166
|
+
lastAtMs = event.atMs;
|
|
167
|
+
if (event.type === "output") {
|
|
168
|
+
const data = decodeReplayData(event, outputDecoder);
|
|
169
|
+
for (const listener of this.outputListeners) listener(data);
|
|
170
|
+
if (data.text) for (const listener of this.dataListeners) listener(data.text);
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (event.type === "input") {
|
|
174
|
+
const data = decodeReplayData(event, inputDecoder);
|
|
175
|
+
for (const listener of this.inputListeners) listener(data);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (event.type === "resize") {
|
|
179
|
+
for (const listener of this.resizeListeners) listener(event);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
for (const listener of this.exitListeners) listener(event);
|
|
183
|
+
}
|
|
184
|
+
const tail = outputDecoder.decode();
|
|
185
|
+
if (tail) for (const listener of this.dataListeners) listener(tail);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
function createPtyCassetteReplay(cassetteOrPath, options) {
|
|
189
|
+
return new PtyCassetteReplay(typeof cassetteOrPath === "string" ? readPtyCassettePath(cassetteOrPath) : cassetteOrPath, options);
|
|
190
|
+
}
|
|
191
|
+
function decodeReplayData(event, decoder) {
|
|
192
|
+
const bytes = base64ToBytes(event.dataBase64);
|
|
193
|
+
return {
|
|
194
|
+
event,
|
|
195
|
+
bytes,
|
|
196
|
+
text: decoder.decode(bytes, { stream: true })
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function disposable(set, listener) {
|
|
200
|
+
return { dispose: () => {
|
|
201
|
+
set.delete(listener);
|
|
202
|
+
} };
|
|
203
|
+
}
|
|
204
|
+
async function sleep(ms) {
|
|
205
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/pty-cassette/inspect.ts
|
|
209
|
+
function inspectPtyCassette(cassette, path) {
|
|
210
|
+
let outputCount = 0;
|
|
211
|
+
let inputCount = 0;
|
|
212
|
+
let resizeCount = 0;
|
|
213
|
+
let exitCount = 0;
|
|
214
|
+
let outputBytes = 0;
|
|
215
|
+
let inputBytes = 0;
|
|
216
|
+
for (const event of cassette.events) if (event.type === "output") {
|
|
217
|
+
outputCount += 1;
|
|
218
|
+
outputBytes += base64ToBytes(event.dataBase64).byteLength;
|
|
219
|
+
} else if (event.type === "input") {
|
|
220
|
+
inputCount += 1;
|
|
221
|
+
inputBytes += base64ToBytes(event.dataBase64).byteLength;
|
|
222
|
+
} else if (event.type === "resize") resizeCount += 1;
|
|
223
|
+
else exitCount += 1;
|
|
224
|
+
return {
|
|
225
|
+
version: cassette.version,
|
|
226
|
+
path,
|
|
227
|
+
createdAt: cassette.createdAt,
|
|
228
|
+
durationMs: cassette.durationMs,
|
|
229
|
+
terminal: cassette.terminal,
|
|
230
|
+
command: cassette.command,
|
|
231
|
+
eventCount: cassette.events.length,
|
|
232
|
+
outputCount,
|
|
233
|
+
inputCount,
|
|
234
|
+
resizeCount,
|
|
235
|
+
exitCount,
|
|
236
|
+
outputBytes,
|
|
237
|
+
inputBytes
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
function inspectPtyCassettePath(path) {
|
|
241
|
+
return inspectPtyCassette(readPtyCassettePath(path), path);
|
|
242
|
+
}
|
|
243
|
+
function formatPtyCassetteInspectLines(result) {
|
|
244
|
+
const command = result.command ? [result.command.file, ...result.command.args ?? []].join(" ") : null;
|
|
245
|
+
return [
|
|
246
|
+
"ok pty-cassette",
|
|
247
|
+
result.path ? `path=${result.path}` : "",
|
|
248
|
+
`version=${result.version}`,
|
|
249
|
+
`createdAt=${result.createdAt}`,
|
|
250
|
+
`durationMs=${result.durationMs}`,
|
|
251
|
+
`terminal=${result.terminal.cols}x${result.terminal.rows}`,
|
|
252
|
+
result.terminal.term ? `term=${result.terminal.term}` : "",
|
|
253
|
+
command ? `command=${command}` : "",
|
|
254
|
+
`events=${result.eventCount}`,
|
|
255
|
+
`output=${result.outputCount} chunks/${result.outputBytes} bytes`,
|
|
256
|
+
`input=${result.inputCount} chunks/${result.inputBytes} bytes`,
|
|
257
|
+
`resize=${result.resizeCount}`,
|
|
258
|
+
`exit=${result.exitCount}`
|
|
259
|
+
].filter(Boolean);
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/pty-cassette/recorder.ts
|
|
263
|
+
var PtyCassetteRecorder = class {
|
|
264
|
+
startedAtMs = performance.now();
|
|
265
|
+
cassette;
|
|
266
|
+
constructor(options) {
|
|
267
|
+
this.cassette = {
|
|
268
|
+
$schema: PTY_CASSETTE_SCHEMA_URL,
|
|
269
|
+
version: 1,
|
|
270
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
271
|
+
durationMs: 0,
|
|
272
|
+
terminal: options.terminal,
|
|
273
|
+
command: options.command,
|
|
274
|
+
metadata: options.metadata,
|
|
275
|
+
events: []
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
recordOutput(data) {
|
|
279
|
+
this.recordData("output", data);
|
|
280
|
+
}
|
|
281
|
+
recordInput(data) {
|
|
282
|
+
this.recordData("input", data);
|
|
283
|
+
}
|
|
284
|
+
recordResize(cols, rows) {
|
|
285
|
+
this.pushEvent({
|
|
286
|
+
atMs: this.nowMs(),
|
|
287
|
+
type: "resize",
|
|
288
|
+
cols,
|
|
289
|
+
rows
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
recordExit(event) {
|
|
293
|
+
this.pushEvent({
|
|
294
|
+
atMs: this.nowMs(),
|
|
295
|
+
type: "exit",
|
|
296
|
+
exitCode: event.exitCode,
|
|
297
|
+
signal: event.signal ?? null
|
|
298
|
+
});
|
|
299
|
+
this.cassette.durationMs = this.nowMs();
|
|
300
|
+
}
|
|
301
|
+
snapshot() {
|
|
302
|
+
this.cassette.durationMs = this.nowMs();
|
|
303
|
+
return normalizePtyCassette({
|
|
304
|
+
...this.cassette,
|
|
305
|
+
terminal: { ...this.cassette.terminal },
|
|
306
|
+
command: this.cassette.command ? {
|
|
307
|
+
...this.cassette.command,
|
|
308
|
+
args: this.cassette.command.args ? [...this.cassette.command.args] : void 0,
|
|
309
|
+
env: this.cassette.command.env ? { ...this.cassette.command.env } : void 0
|
|
310
|
+
} : void 0,
|
|
311
|
+
metadata: this.cassette.metadata ? { ...this.cassette.metadata } : void 0,
|
|
312
|
+
events: this.cassette.events.map((event) => ({ ...event }))
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
stop() {
|
|
316
|
+
return this.snapshot();
|
|
317
|
+
}
|
|
318
|
+
writePath(path) {
|
|
319
|
+
return writePtyCassettePath(path, this.snapshot());
|
|
320
|
+
}
|
|
321
|
+
recordData(type, data) {
|
|
322
|
+
if (byteLength(data) === 0) return;
|
|
323
|
+
this.pushEvent({
|
|
324
|
+
atMs: this.nowMs(),
|
|
325
|
+
type,
|
|
326
|
+
dataBase64: dataToBase64(data)
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
pushEvent(event) {
|
|
330
|
+
this.cassette.events.push(event);
|
|
331
|
+
this.cassette.durationMs = Math.max(this.cassette.durationMs, event.atMs);
|
|
332
|
+
}
|
|
333
|
+
nowMs() {
|
|
334
|
+
return Math.max(0, Math.round(performance.now() - this.startedAtMs));
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
function createPtyCassetteRecorder(options) {
|
|
338
|
+
return new PtyCassetteRecorder(options);
|
|
339
|
+
}
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/pty-cassette/pty_like.ts
|
|
342
|
+
function wrapPtyLike(pty, options = {}) {
|
|
343
|
+
const recorder = options.recorder ?? createPtyCassetteRecorder({
|
|
344
|
+
terminal: options.terminal ?? {
|
|
345
|
+
cols: 80,
|
|
346
|
+
rows: 24,
|
|
347
|
+
term: "xterm-256color"
|
|
348
|
+
},
|
|
349
|
+
command: options.command,
|
|
350
|
+
metadata: options.metadata
|
|
351
|
+
});
|
|
352
|
+
const path = options.path;
|
|
353
|
+
const autoWriteOnExit = options.autoWriteOnExit ?? Boolean(path);
|
|
354
|
+
const disposables = [];
|
|
355
|
+
let disposed = false;
|
|
356
|
+
disposables.push(pty.onData((data) => recorder.recordOutput(data)));
|
|
357
|
+
disposables.push(pty.onExit((event) => {
|
|
358
|
+
recorder.recordExit(event);
|
|
359
|
+
if (path && autoWriteOnExit) recorder.writePath(path);
|
|
360
|
+
}));
|
|
361
|
+
return {
|
|
362
|
+
pty,
|
|
363
|
+
recorder,
|
|
364
|
+
write(data) {
|
|
365
|
+
recorder.recordInput(data);
|
|
366
|
+
return pty.write(data);
|
|
367
|
+
},
|
|
368
|
+
resize(cols, rows) {
|
|
369
|
+
recorder.recordResize(cols, rows);
|
|
370
|
+
return pty.resize?.(cols, rows);
|
|
371
|
+
},
|
|
372
|
+
kill(signal) {
|
|
373
|
+
return pty.kill?.(signal);
|
|
374
|
+
},
|
|
375
|
+
onData(listener) {
|
|
376
|
+
return pty.onData(listener);
|
|
377
|
+
},
|
|
378
|
+
onExit(listener) {
|
|
379
|
+
return pty.onExit(listener);
|
|
380
|
+
},
|
|
381
|
+
stopRecording() {
|
|
382
|
+
return recorder.stop();
|
|
383
|
+
},
|
|
384
|
+
writeCassette(nextPath = path) {
|
|
385
|
+
if (!nextPath) throw new Error("writeCassette requires a path");
|
|
386
|
+
return recorder.writePath(nextPath);
|
|
387
|
+
},
|
|
388
|
+
dispose() {
|
|
389
|
+
if (disposed) return;
|
|
390
|
+
disposed = true;
|
|
391
|
+
for (const disposable of disposables) dispose(disposable);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function dispose(disposable) {
|
|
396
|
+
if (!disposable) return;
|
|
397
|
+
if (typeof disposable === "function") {
|
|
398
|
+
disposable();
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
disposable.dispose();
|
|
402
|
+
}
|
|
403
|
+
//#endregion
|
|
404
|
+
export { dataToBytes as S, ptyCassetteSchema as _, inspectPtyCassette as a, byteLength as b, createPtyCassetteReplay as c, PTY_CASSETTE_SCHEMA_URL as d, normalizePtyCassette as f, ptyCassetteResizeEventSchema as g, ptyCassetteExitEventSchema as h, formatPtyCassetteInspectLines as i, readPtyCassettePath as l, ptyCassetteEventSchema as m, PtyCassetteRecorder as n, inspectPtyCassettePath as o, ptyCassetteDataEventSchema as p, createPtyCassetteRecorder as r, PtyCassetteReplay as s, wrapPtyLike as t, writePtyCassettePath as u, validatePtyCassette as v, dataToBase64 as x, base64ToBytes as y };
|