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
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
-
import { extname } from "node:path";
|
|
3
|
-
|
|
4
|
-
export type DocumentSource = {
|
|
5
|
-
type: "local" | "url" | "raw";
|
|
6
|
-
path?: string;
|
|
7
|
-
url?: string;
|
|
8
|
-
content?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type CodeBlock = {
|
|
12
|
-
language: string;
|
|
13
|
-
code: string;
|
|
14
|
-
lineNumber: number;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type ParsedDocument = {
|
|
18
|
-
title?: string;
|
|
19
|
-
description?: string;
|
|
20
|
-
codeBlocks: CodeBlock[];
|
|
21
|
-
steps: string[];
|
|
22
|
-
rawContent: string;
|
|
23
|
-
format: "markdown" | "html" | "json" | "yaml" | "text";
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export async function parseDocument(source: DocumentSource): Promise<ParsedDocument> {
|
|
27
|
-
const content = await fetchContent(source);
|
|
28
|
-
const format = detectFormat(source, content);
|
|
29
|
-
|
|
30
|
-
switch (format) {
|
|
31
|
-
case "markdown":
|
|
32
|
-
return parseMarkdown(content);
|
|
33
|
-
case "json":
|
|
34
|
-
return parseJson(content);
|
|
35
|
-
case "yaml":
|
|
36
|
-
return parseYaml(content);
|
|
37
|
-
case "html":
|
|
38
|
-
return parseHtml(content);
|
|
39
|
-
default:
|
|
40
|
-
return parsePlainText(content);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function fetchContent(source: DocumentSource): Promise<string> {
|
|
45
|
-
if (source.content) {
|
|
46
|
-
return source.content;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (source.type === "local" && source.path) {
|
|
50
|
-
if (!existsSync(source.path)) {
|
|
51
|
-
throw new Error(`File not found: ${source.path}`);
|
|
52
|
-
}
|
|
53
|
-
return readFileSync(source.path, "utf8");
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (source.type === "url" && source.url) {
|
|
57
|
-
const response = await fetch(source.url);
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
throw new Error(`Failed to fetch URL: ${source.url} (${response.status})`);
|
|
60
|
-
}
|
|
61
|
-
return response.text();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
throw new Error("Invalid document source: must provide path, url, or content");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function detectFormat(
|
|
68
|
-
source: DocumentSource,
|
|
69
|
-
content: string,
|
|
70
|
-
): "markdown" | "html" | "json" | "yaml" | "text" {
|
|
71
|
-
if (source.path) {
|
|
72
|
-
const ext = extname(source.path).toLowerCase();
|
|
73
|
-
if (ext === ".md" || ext === ".markdown") return "markdown";
|
|
74
|
-
if (ext === ".html" || ext === ".htm") return "html";
|
|
75
|
-
if (ext === ".json") return "json";
|
|
76
|
-
if (ext === ".yaml" || ext === ".yml") return "yaml";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const trimmed = content.trim();
|
|
80
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return "json";
|
|
81
|
-
if (trimmed.startsWith("<!DOCTYPE") || trimmed.startsWith("<html")) return "html";
|
|
82
|
-
if (/^#\s/.test(trimmed) || /\n##\s/.test(content) || /```/.test(content)) return "markdown";
|
|
83
|
-
|
|
84
|
-
return "text";
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function parseMarkdown(content: string): ParsedDocument {
|
|
88
|
-
const lines = content.split("\n");
|
|
89
|
-
const codeBlocks: CodeBlock[] = [];
|
|
90
|
-
const steps: string[] = [];
|
|
91
|
-
let title: string | undefined;
|
|
92
|
-
let description: string | undefined;
|
|
93
|
-
|
|
94
|
-
let inCodeBlock = false;
|
|
95
|
-
let currentLang = "";
|
|
96
|
-
let currentCode: string[] = [];
|
|
97
|
-
let codeBlockStart = 0;
|
|
98
|
-
|
|
99
|
-
for (let i = 0; i < lines.length; i++) {
|
|
100
|
-
const line = lines[i] ?? "";
|
|
101
|
-
|
|
102
|
-
// Extract title from first h1
|
|
103
|
-
if (!title && /^#\s+(.+)$/.test(line)) {
|
|
104
|
-
title = line.replace(/^#\s+/, "").trim();
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Extract description from first paragraph after title
|
|
109
|
-
if (title && !description && !inCodeBlock && line.trim() && !/^[#-]/.test(line)) {
|
|
110
|
-
description = line.trim();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Handle code blocks
|
|
114
|
-
if (line.startsWith("```")) {
|
|
115
|
-
if (!inCodeBlock) {
|
|
116
|
-
inCodeBlock = true;
|
|
117
|
-
currentLang = line.slice(3).trim().toLowerCase();
|
|
118
|
-
currentCode = [];
|
|
119
|
-
codeBlockStart = i + 1;
|
|
120
|
-
} else {
|
|
121
|
-
inCodeBlock = false;
|
|
122
|
-
if (currentCode.length > 0) {
|
|
123
|
-
codeBlocks.push({
|
|
124
|
-
language: currentLang || "text",
|
|
125
|
-
code: currentCode.join("\n"),
|
|
126
|
-
lineNumber: codeBlockStart,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (inCodeBlock) {
|
|
134
|
-
currentCode.push(line);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Extract numbered steps
|
|
139
|
-
const stepMatch = line.match(/^\s*(\d+)\.\s+(.+)$/);
|
|
140
|
-
if (stepMatch && stepMatch[2]) {
|
|
141
|
-
steps.push(stepMatch[2].trim());
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Extract bullet points that look like steps
|
|
145
|
-
const bulletMatch = line.match(/^\s*[-*]\s+(.+)$/);
|
|
146
|
-
if (bulletMatch && bulletMatch[1]) {
|
|
147
|
-
const text = bulletMatch[1].trim();
|
|
148
|
-
if (isLikelyStep(text)) {
|
|
149
|
-
steps.push(text);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
title,
|
|
156
|
-
description,
|
|
157
|
-
codeBlocks,
|
|
158
|
-
steps,
|
|
159
|
-
rawContent: content,
|
|
160
|
-
format: "markdown",
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function parseJson(content: string): ParsedDocument {
|
|
165
|
-
const parsed = JSON.parse(content) as unknown;
|
|
166
|
-
const codeBlocks: CodeBlock[] = [];
|
|
167
|
-
const steps: string[] = [];
|
|
168
|
-
|
|
169
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
170
|
-
const obj = parsed as Record<string, unknown>;
|
|
171
|
-
|
|
172
|
-
// Check if it's a ptywright script format
|
|
173
|
-
if ("launch" in obj && "steps" in obj) {
|
|
174
|
-
return {
|
|
175
|
-
title: (obj.name as string) ?? "Imported Script",
|
|
176
|
-
description: "Parsed from JSON script",
|
|
177
|
-
codeBlocks: [],
|
|
178
|
-
steps: [],
|
|
179
|
-
rawContent: content,
|
|
180
|
-
format: "json",
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Extract commands from various JSON structures
|
|
185
|
-
if (Array.isArray(obj.commands)) {
|
|
186
|
-
for (const cmd of obj.commands) {
|
|
187
|
-
if (typeof cmd === "string") steps.push(cmd);
|
|
188
|
-
else if (typeof cmd === "object" && cmd && "command" in cmd) {
|
|
189
|
-
steps.push(String((cmd as Record<string, unknown>).command));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (Array.isArray(obj.steps)) {
|
|
195
|
-
for (const step of obj.steps) {
|
|
196
|
-
if (typeof step === "string") steps.push(step);
|
|
197
|
-
else if (typeof step === "object" && step) {
|
|
198
|
-
const s = step as Record<string, unknown>;
|
|
199
|
-
if (typeof s.description === "string") steps.push(s.description);
|
|
200
|
-
else if (typeof s.command === "string") steps.push(s.command);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return {
|
|
207
|
-
codeBlocks,
|
|
208
|
-
steps,
|
|
209
|
-
rawContent: content,
|
|
210
|
-
format: "json",
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function parseYaml(content: string): ParsedDocument {
|
|
215
|
-
// Simple YAML parsing for common patterns
|
|
216
|
-
const steps: string[] = [];
|
|
217
|
-
const lines = content.split("\n");
|
|
218
|
-
|
|
219
|
-
for (const line of lines) {
|
|
220
|
-
// Match YAML list items
|
|
221
|
-
const match = line.match(/^\s*-\s+(.+)$/);
|
|
222
|
-
if (match && match[1]) {
|
|
223
|
-
const value = match[1].trim();
|
|
224
|
-
if (!value.startsWith("{") && !value.includes(":")) {
|
|
225
|
-
steps.push(value);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
codeBlocks: [],
|
|
232
|
-
steps,
|
|
233
|
-
rawContent: content,
|
|
234
|
-
format: "yaml",
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function parseHtml(content: string): ParsedDocument {
|
|
239
|
-
const codeBlocks: CodeBlock[] = [];
|
|
240
|
-
const steps: string[] = [];
|
|
241
|
-
let title: string | undefined;
|
|
242
|
-
|
|
243
|
-
// Extract title
|
|
244
|
-
const titleMatch = content.match(/<title>([^<]+)<\/title>/i);
|
|
245
|
-
if (titleMatch && titleMatch[1]) {
|
|
246
|
-
title = titleMatch[1].trim();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Extract code blocks from <pre><code> or <code>
|
|
250
|
-
const codeRegex = /<code[^>]*(?:class="[^"]*language-(\w+)[^"]*")?[^>]*>([\s\S]*?)<\/code>/gi;
|
|
251
|
-
let match;
|
|
252
|
-
while ((match = codeRegex.exec(content)) !== null) {
|
|
253
|
-
const lang = match[1] ?? "text";
|
|
254
|
-
const code = decodeHtmlEntities(match[2] ?? "");
|
|
255
|
-
if (code.trim()) {
|
|
256
|
-
codeBlocks.push({
|
|
257
|
-
language: lang,
|
|
258
|
-
code: code.trim(),
|
|
259
|
-
lineNumber: 0,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Extract list items
|
|
265
|
-
const liRegex = /<li[^>]*>([\s\S]*?)<\/li>/gi;
|
|
266
|
-
while ((match = liRegex.exec(content)) !== null) {
|
|
267
|
-
const text = stripHtmlTags(match[1] ?? "").trim();
|
|
268
|
-
if (text && isLikelyStep(text)) {
|
|
269
|
-
steps.push(text);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return {
|
|
274
|
-
title,
|
|
275
|
-
codeBlocks,
|
|
276
|
-
steps,
|
|
277
|
-
rawContent: content,
|
|
278
|
-
format: "html",
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function parsePlainText(content: string): ParsedDocument {
|
|
283
|
-
const lines = content.split("\n");
|
|
284
|
-
const steps: string[] = [];
|
|
285
|
-
|
|
286
|
-
for (const line of lines) {
|
|
287
|
-
const trimmed = line.trim();
|
|
288
|
-
if (!trimmed) continue;
|
|
289
|
-
|
|
290
|
-
// Extract numbered steps (e.g., "1. do something")
|
|
291
|
-
const numberedMatch = trimmed.match(/^\d+\.\s+(.+)$/);
|
|
292
|
-
if (numberedMatch && numberedMatch[1]) {
|
|
293
|
-
steps.push(numberedMatch[1].trim());
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Extract bullet points
|
|
298
|
-
const bulletMatch = trimmed.match(/^[-*]\s+(.+)$/);
|
|
299
|
-
if (bulletMatch && bulletMatch[1]) {
|
|
300
|
-
steps.push(bulletMatch[1].trim());
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// Check if it looks like a step
|
|
305
|
-
if (isLikelyStep(trimmed)) {
|
|
306
|
-
steps.push(trimmed);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return {
|
|
311
|
-
codeBlocks: [],
|
|
312
|
-
steps,
|
|
313
|
-
rawContent: content,
|
|
314
|
-
format: "text",
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function isLikelyStep(text: string): boolean {
|
|
319
|
-
const stepPatterns = [
|
|
320
|
-
/^(run|execute|type|enter|press|click|open|start|stop|wait|check|verify|assert)/i,
|
|
321
|
-
/^(输入|执行|运行|点击|打开|启动|停止|等待|检查|验证)/,
|
|
322
|
-
/\$\s+\w+/, // Shell prompt pattern
|
|
323
|
-
/^>\s+\w+/, // Alternative prompt
|
|
324
|
-
];
|
|
325
|
-
|
|
326
|
-
return stepPatterns.some((pattern) => pattern.test(text));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function decodeHtmlEntities(text: string): string {
|
|
330
|
-
return text
|
|
331
|
-
.replace(/</g, "<")
|
|
332
|
-
.replace(/>/g, ">")
|
|
333
|
-
.replace(/&/g, "&")
|
|
334
|
-
.replace(/"/g, '"')
|
|
335
|
-
.replace(/'/g, "'")
|
|
336
|
-
.replace(/ /g, " ");
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function stripHtmlTags(html: string): string {
|
|
340
|
-
return html.replace(/<[^>]+>/g, "");
|
|
341
|
-
}
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { resolve, basename, extname } from "node:path";
|
|
2
|
-
|
|
3
|
-
import { parseDocument, type DocumentSource, type ParsedDocument } from "./doc_parser";
|
|
4
|
-
import { extractSteps } from "./step_extractor";
|
|
5
|
-
import { generateScript } from "./script_generator";
|
|
6
|
-
|
|
7
|
-
export type GenerateTestOptions = {
|
|
8
|
-
source: string;
|
|
9
|
-
sourceType?: "local" | "url" | "auto";
|
|
10
|
-
outputDir?: string;
|
|
11
|
-
outputFormat?: "json" | "ts" | "both";
|
|
12
|
-
targetCommand?: string;
|
|
13
|
-
targetArgs?: string[];
|
|
14
|
-
name?: string;
|
|
15
|
-
cols?: number;
|
|
16
|
-
rows?: number;
|
|
17
|
-
env?: Record<string, string>;
|
|
18
|
-
trace?: {
|
|
19
|
-
saveCast?: boolean;
|
|
20
|
-
saveReport?: boolean;
|
|
21
|
-
};
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export type GenerateTestResult = {
|
|
25
|
-
ok: boolean;
|
|
26
|
-
name: string;
|
|
27
|
-
jsonPath?: string;
|
|
28
|
-
tsPath?: string;
|
|
29
|
-
stepCount: number;
|
|
30
|
-
warnings: string[];
|
|
31
|
-
error?: string;
|
|
32
|
-
parsed?: {
|
|
33
|
-
title?: string;
|
|
34
|
-
format: string;
|
|
35
|
-
codeBlockCount: number;
|
|
36
|
-
textStepCount: number;
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export async function generateTestFromDoc(
|
|
41
|
-
options: GenerateTestOptions,
|
|
42
|
-
): Promise<GenerateTestResult> {
|
|
43
|
-
const warnings: string[] = [];
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
// 1. Resolve document source
|
|
47
|
-
const docSource = resolveDocumentSource(options.source, options.sourceType);
|
|
48
|
-
|
|
49
|
-
// 2. Parse document
|
|
50
|
-
const parsed = await parseDocument(docSource);
|
|
51
|
-
|
|
52
|
-
// 3. Extract steps
|
|
53
|
-
const extraction = extractSteps(parsed);
|
|
54
|
-
warnings.push(...extraction.warnings);
|
|
55
|
-
|
|
56
|
-
if (extraction.steps.length === 0) {
|
|
57
|
-
return {
|
|
58
|
-
ok: false,
|
|
59
|
-
name: options.name ?? "unknown",
|
|
60
|
-
stepCount: 0,
|
|
61
|
-
warnings,
|
|
62
|
-
error: "No test steps could be extracted from the document",
|
|
63
|
-
parsed: {
|
|
64
|
-
title: parsed.title,
|
|
65
|
-
format: parsed.format,
|
|
66
|
-
codeBlockCount: parsed.codeBlocks.length,
|
|
67
|
-
textStepCount: parsed.steps.length,
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 4. Generate script
|
|
73
|
-
const scriptName = resolveScriptName(options.name, options.source, parsed);
|
|
74
|
-
const outputDir = options.outputDir ?? resolve(".tmp", "generated");
|
|
75
|
-
|
|
76
|
-
const generated = generateScript(extraction.steps, {
|
|
77
|
-
name: scriptName,
|
|
78
|
-
launch: extraction.launch,
|
|
79
|
-
targetCommand: options.targetCommand,
|
|
80
|
-
targetArgs: options.targetArgs,
|
|
81
|
-
outputDir,
|
|
82
|
-
format: options.outputFormat ?? "both",
|
|
83
|
-
cols: options.cols,
|
|
84
|
-
rows: options.rows,
|
|
85
|
-
env: options.env,
|
|
86
|
-
trace: options.trace,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
ok: true,
|
|
91
|
-
name: generated.name,
|
|
92
|
-
jsonPath: generated.jsonPath,
|
|
93
|
-
tsPath: generated.tsPath,
|
|
94
|
-
stepCount: generated.stepCount,
|
|
95
|
-
warnings,
|
|
96
|
-
parsed: {
|
|
97
|
-
title: parsed.title,
|
|
98
|
-
format: parsed.format,
|
|
99
|
-
codeBlockCount: parsed.codeBlocks.length,
|
|
100
|
-
textStepCount: parsed.steps.length,
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
} catch (error) {
|
|
104
|
-
return {
|
|
105
|
-
ok: false,
|
|
106
|
-
name: options.name ?? "unknown",
|
|
107
|
-
stepCount: 0,
|
|
108
|
-
warnings,
|
|
109
|
-
error: error instanceof Error ? error.message : String(error),
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function resolveDocumentSource(
|
|
115
|
-
source: string,
|
|
116
|
-
sourceType?: "local" | "url" | "auto",
|
|
117
|
-
): DocumentSource {
|
|
118
|
-
const type = !sourceType || sourceType === "auto" ? detectSourceType(source) : sourceType;
|
|
119
|
-
|
|
120
|
-
if (type === "url") {
|
|
121
|
-
return { type: "url", url: source };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return { type: "local", path: resolve(source) };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function detectSourceType(source: string): "local" | "url" {
|
|
128
|
-
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
129
|
-
return "url";
|
|
130
|
-
}
|
|
131
|
-
return "local";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function resolveScriptName(
|
|
135
|
-
explicitName: string | undefined,
|
|
136
|
-
source: string,
|
|
137
|
-
parsed: ParsedDocument,
|
|
138
|
-
): string {
|
|
139
|
-
if (explicitName) {
|
|
140
|
-
return sanitizeName(explicitName);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (parsed.title) {
|
|
144
|
-
return sanitizeName(parsed.title);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Extract from file path
|
|
148
|
-
const base = basename(source, extname(source));
|
|
149
|
-
return sanitizeName(base);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function sanitizeName(name: string): string {
|
|
153
|
-
return (
|
|
154
|
-
name
|
|
155
|
-
.toLowerCase()
|
|
156
|
-
.replace(/[^a-z0-9_-]/g, "_")
|
|
157
|
-
.replace(/_+/g, "_")
|
|
158
|
-
.replace(/^_|_$/g, "")
|
|
159
|
-
.slice(0, 64) || "generated_test"
|
|
160
|
-
);
|
|
161
|
-
}
|
package/src/generator/index.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export { parseDocument, type ParsedDocument, type DocumentSource } from "./doc_parser";
|
|
2
|
-
export { extractSteps, type ExtractedStep } from "./step_extractor";
|
|
3
|
-
export {
|
|
4
|
-
generateScript,
|
|
5
|
-
generateJsonScript,
|
|
6
|
-
generateTypeScriptScript,
|
|
7
|
-
type GenerateOptions,
|
|
8
|
-
type GeneratedScript,
|
|
9
|
-
} from "./script_generator";
|
|
10
|
-
export { generateTestFromDoc, type GenerateTestOptions } from "./generate";
|