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.
Files changed (68) hide show
  1. package/README.md +459 -116
  2. package/dist/agent.mjs +2 -0
  3. package/dist/bin/ptywright.mjs +6 -0
  4. package/dist/cli-DIUx2w6X.mjs +3587 -0
  5. package/dist/cli.mjs +2 -0
  6. package/{src/index.ts → dist/index.mjs} +7 -9
  7. package/dist/mcp.mjs +2 -0
  8. package/dist/pty-cassette.mjs +24 -0
  9. package/dist/pty_like-Cpkh_O9B.mjs +404 -0
  10. package/dist/runner-DzZlFrt1.mjs +1897 -0
  11. package/dist/runner-zApMYWZx.mjs +3257 -0
  12. package/dist/script.mjs +2 -0
  13. package/dist/server-VHuEWWj_.mjs +3068 -0
  14. package/dist/session.mjs +2 -0
  15. package/dist/terminal_session-DopC7Xg6.mjs +893 -0
  16. package/package.json +28 -21
  17. package/schemas/ptywright-agent-cassette.schema.json +57 -0
  18. package/schemas/ptywright-agent-check.schema.json +122 -0
  19. package/schemas/ptywright-agent-manifest.schema.json +107 -0
  20. package/schemas/ptywright-agent-promote.schema.json +146 -0
  21. package/schemas/ptywright-agent-replay-summary.schema.json +140 -0
  22. package/schemas/ptywright-agent-run.schema.json +126 -0
  23. package/schemas/ptywright-agent.schema.json +182 -0
  24. package/schemas/ptywright-pty-cassette.schema.json +86 -0
  25. package/schemas/ptywright-script-manifest.schema.json +75 -0
  26. package/schemas/ptywright-script-run-summary.schema.json +114 -0
  27. package/schemas/ptywright-script.schema.json +55 -3
  28. package/skills/ptywright-testing/SKILL.md +53 -33
  29. package/bin/ptywright +0 -4
  30. package/src/cli.ts +0 -414
  31. package/src/generator/doc_parser.ts +0 -341
  32. package/src/generator/generate.ts +0 -161
  33. package/src/generator/index.ts +0 -10
  34. package/src/generator/script_generator.ts +0 -209
  35. package/src/generator/step_extractor.ts +0 -397
  36. package/src/mcp/http_server.ts +0 -174
  37. package/src/mcp/script_recording.ts +0 -238
  38. package/src/mcp/server.ts +0 -1348
  39. package/src/pty/bun_pty_adapter.ts +0 -34
  40. package/src/pty/bun_terminal_adapter.ts +0 -149
  41. package/src/pty/pty_adapter.ts +0 -31
  42. package/src/script/dsl.ts +0 -188
  43. package/src/script/module.ts +0 -43
  44. package/src/script/path.ts +0 -151
  45. package/src/script/run.ts +0 -108
  46. package/src/script/run_all.ts +0 -229
  47. package/src/script/runner.ts +0 -983
  48. package/src/script/schema.ts +0 -237
  49. package/src/script/steps/assert_snapshot_equals.ts +0 -21
  50. package/src/script/steps/index.ts +0 -2
  51. package/src/script/suite_report.ts +0 -626
  52. package/src/session/session_manager.ts +0 -145
  53. package/src/session/terminal_session.ts +0 -473
  54. package/src/terminal/ansi.ts +0 -142
  55. package/src/terminal/keys.ts +0 -180
  56. package/src/terminal/mask.ts +0 -70
  57. package/src/terminal/mouse.ts +0 -75
  58. package/src/terminal/snapshot.ts +0 -196
  59. package/src/terminal/style.ts +0 -121
  60. package/src/terminal/view.ts +0 -49
  61. package/src/trace/asciicast.ts +0 -20
  62. package/src/trace/asciinema_player_assets.ts +0 -44
  63. package/src/trace/cast_to_txt.ts +0 -116
  64. package/src/trace/recorder.ts +0 -110
  65. package/src/trace/report.ts +0 -2092
  66. package/src/types.ts +0 -86
  67. package/src/util/hash.ts +0 -8
  68. package/src/util/sleep.ts +0 -5
@@ -1,34 +0,0 @@
1
- import { spawn } from "bun-pty";
2
- import type { IPty, IPtyForkOptions } from "bun-pty";
3
-
4
- import type { PtyAdapter, PtyProcess, PtySpawnOptions } from "./pty_adapter";
5
-
6
- function toForkOptions(options: PtySpawnOptions): IPtyForkOptions {
7
- return {
8
- name: options.name,
9
- cols: options.cols,
10
- rows: options.rows,
11
- cwd: options.cwd,
12
- env: options.env,
13
- };
14
- }
15
-
16
- function toPtyProcess(pty: IPty): PtyProcess {
17
- return {
18
- pid: pty.pid,
19
- cols: pty.cols,
20
- rows: pty.rows,
21
- write: (data) => pty.write(data),
22
- resize: (cols, rows) => pty.resize(cols, rows),
23
- kill: (signal) => pty.kill(signal),
24
- onData: (listener) => pty.onData(listener),
25
- onExit: (listener) => pty.onExit(listener),
26
- };
27
- }
28
-
29
- export class BunPtyAdapter implements PtyAdapter {
30
- spawn(command: string, args: string[], options: PtySpawnOptions): PtyProcess {
31
- const pty = spawn(command, args, toForkOptions(options));
32
- return toPtyProcess(pty);
33
- }
34
- }
@@ -1,149 +0,0 @@
1
- import type { Terminal, TerminalOptions } from "bun";
2
-
3
- import type {
4
- Disposable,
5
- PtyAdapter,
6
- PtyExitEvent,
7
- PtyProcess,
8
- PtySpawnOptions,
9
- } from "./pty_adapter";
10
-
11
- type Listener<T> = (arg: T) => void;
12
-
13
- function createDisposable<T>(set: Set<Listener<T>>, listener: Listener<T>): Disposable {
14
- return {
15
- dispose: () => {
16
- set.delete(listener);
17
- },
18
- };
19
- }
20
-
21
- export class BunTerminalAdapter implements PtyAdapter {
22
- spawn(command: string, args: string[], options: PtySpawnOptions): PtyProcess {
23
- if (process.platform === "win32") {
24
- throw new Error("Bun.Terminal PTY is only available on POSIX systems (Linux/macOS)");
25
- }
26
-
27
- let cols = options.cols;
28
- let rows = options.rows;
29
-
30
- const decoder = new TextDecoder();
31
- const dataListeners = new Set<Listener<string>>();
32
- const exitListeners = new Set<Listener<PtyExitEvent>>();
33
-
34
- const pendingData: string[] = [];
35
- let pendingExit: PtyExitEvent | null = null;
36
-
37
- const dispatchData = (chunk: string): void => {
38
- if (!chunk) return;
39
- if (dataListeners.size === 0) {
40
- pendingData.push(chunk);
41
- if (pendingData.length > 2000) {
42
- pendingData.splice(0, pendingData.length - 2000);
43
- }
44
- return;
45
- }
46
- for (const listener of dataListeners) listener(chunk);
47
- };
48
-
49
- const flushPendingDataTo = (listener: Listener<string>): void => {
50
- if (pendingData.length === 0) return;
51
- for (const chunk of pendingData) listener(chunk);
52
- };
53
-
54
- const dispatchExit = (event: PtyExitEvent): void => {
55
- pendingExit = event;
56
- for (const listener of exitListeners) listener(event);
57
- };
58
-
59
- const flushExitTo = (listener: Listener<PtyExitEvent>): void => {
60
- if (!pendingExit) return;
61
- listener(pendingExit);
62
- };
63
-
64
- const terminalOptions: TerminalOptions = {
65
- cols,
66
- rows,
67
- name: options.name,
68
- data: (_term, data) => {
69
- const text = decoder.decode(data, { stream: true });
70
- dispatchData(text);
71
- },
72
- exit: () => {
73
- const tail = decoder.decode();
74
- dispatchData(tail);
75
- },
76
- };
77
-
78
- let terminal: Terminal | undefined;
79
- let killed = false;
80
-
81
- const proc = Bun.spawn([command, ...args], {
82
- cwd: options.cwd,
83
- env: options.env,
84
- terminal: terminalOptions,
85
- onExit(subprocess, exitCode, _signalCode) {
86
- const tail = decoder.decode();
87
- dispatchData(tail);
88
-
89
- const signal = subprocess.signalCode ?? undefined;
90
- dispatchExit({
91
- exitCode: exitCode ?? (killed ? -1 : 0),
92
- signal,
93
- });
94
- },
95
- });
96
-
97
- terminal = proc.terminal;
98
- if (!terminal) {
99
- throw new Error("expected Bun.spawn(..., { terminal }) to attach a PTY terminal");
100
- }
101
-
102
- // Ensure input is passed through without CR->LF translation (needed for apps that
103
- // distinguish Enter (CR) from Ctrl+J (LF), e.g. Codex CLI).
104
- // Note: Bun.Terminal.setRawMode only affects PTY line discipline, not the child process.
105
- terminal.setRawMode(true);
106
-
107
- return {
108
- pid: proc.pid,
109
- get cols() {
110
- return cols;
111
- },
112
- get rows() {
113
- return rows;
114
- },
115
- write: (data) => {
116
- terminal?.write(data);
117
- },
118
- resize: (nextCols, nextRows) => {
119
- cols = nextCols;
120
- rows = nextRows;
121
- terminal?.resize(nextCols, nextRows);
122
- },
123
- kill: (signal) => {
124
- killed = true;
125
- if (signal) {
126
- proc.kill(signal as unknown as NodeJS.Signals);
127
- } else {
128
- proc.kill();
129
- }
130
- terminal?.close();
131
- },
132
- onData: (listener) => {
133
- dataListeners.add(listener);
134
- flushPendingDataTo(listener);
135
- if (dataListeners.size === 1) {
136
- queueMicrotask(() => {
137
- pendingData.length = 0;
138
- });
139
- }
140
- return createDisposable(dataListeners, listener);
141
- },
142
- onExit: (listener) => {
143
- exitListeners.add(listener);
144
- flushExitTo(listener);
145
- return createDisposable(exitListeners, listener);
146
- },
147
- };
148
- }
149
- }
@@ -1,31 +0,0 @@
1
- export type PtySpawnOptions = {
2
- cols: number;
3
- rows: number;
4
- cwd: string;
5
- env: Record<string, string>;
6
- name: string;
7
- };
8
-
9
- export type PtyExitEvent = {
10
- exitCode: number;
11
- signal?: number | string;
12
- };
13
-
14
- export type Disposable = {
15
- dispose(): void;
16
- };
17
-
18
- export type PtyProcess = {
19
- readonly pid: number;
20
- readonly cols: number;
21
- readonly rows: number;
22
- write(data: string): void;
23
- resize(cols: number, rows: number): void;
24
- kill(signal?: string): void;
25
- onData(listener: (data: string) => void): Disposable;
26
- onExit(listener: (event: PtyExitEvent) => void): Disposable;
27
- };
28
-
29
- export type PtyAdapter = {
30
- spawn(command: string, args: string[], options: PtySpawnOptions): PtyProcess;
31
- };
package/src/script/dsl.ts DELETED
@@ -1,188 +0,0 @@
1
- import { scriptSchema } from "./schema";
2
- import type { Script, ScriptStep } from "./schema";
3
-
4
- type SnapshotKey = string;
5
-
6
- type SnapshotRef<K extends SnapshotKey> = K | "last";
7
-
8
- type StepOf<T extends ScriptStep["type"]> = Extract<ScriptStep, { type: T }>;
9
-
10
- type SnapshotStep = StepOf<"snapshot">;
11
- type ExpectStep = StepOf<"expect">;
12
- type ExpectGoldenStep = StepOf<"expectGolden">;
13
- type ExpectMetaStep = StepOf<"expectMeta">;
14
- type WaitForExitStep = StepOf<"waitForExit">;
15
- type SendMouseStep = StepOf<"sendMouse">;
16
-
17
- type CustomStepMap = Record<string, unknown>;
18
-
19
- export class ScriptBuilder<K extends SnapshotKey = never, Steps extends CustomStepMap = {}> {
20
- private readonly script: Script;
21
-
22
- constructor(init: {
23
- name?: string;
24
- artifactsDir?: string;
25
- launch: Script["launch"];
26
- trace?: Script["trace"];
27
- }) {
28
- this.script = {
29
- name: init.name,
30
- artifactsDir: init.artifactsDir,
31
- launch: init.launch,
32
- trace: init.trace,
33
- steps: [],
34
- };
35
- }
36
-
37
- getName(): string | undefined {
38
- return this.script.name;
39
- }
40
-
41
- getLaunch(): Script["launch"] {
42
- return this.script.launch;
43
- }
44
-
45
- step(step: ScriptStep): this {
46
- this.script.steps.push(step);
47
- return this;
48
- }
49
-
50
- use<NextK extends SnapshotKey, NextSteps extends CustomStepMap = Steps>(
51
- fn: (s: ScriptBuilder<K, Steps>) => ScriptBuilder<NextK, NextSteps>,
52
- ): ScriptBuilder<NextK, NextSteps> {
53
- return fn(this);
54
- }
55
-
56
- custom<Name extends string>(
57
- name: Name,
58
- ...args: Name extends keyof Steps & string
59
- ? undefined extends Steps[Name]
60
- ? [payload?: Steps[Name]]
61
- : [payload: Steps[Name]]
62
- : [payload?: unknown]
63
- ): this {
64
- const payload = args[0] as unknown;
65
- if (payload === undefined) {
66
- return this.step({ type: "custom", name });
67
- }
68
- return this.step({ type: "custom", name, payload });
69
- }
70
-
71
- sendText(text: string, options?: { enter?: boolean }): this {
72
- return this.step({ type: "sendText", text, enter: options?.enter });
73
- }
74
-
75
- pasteText(text: string, options?: { bracketed?: boolean }): this {
76
- const bracketed = options?.bracketed ?? false;
77
- const payload = bracketed ? `\x1b[200~${text}\x1b[201~` : text;
78
- return this.sendText(payload);
79
- }
80
-
81
- pressKey(key: string): this {
82
- return this.step({ type: "pressKey", key });
83
- }
84
-
85
- sendMouse(step: Omit<SendMouseStep, "type">): this {
86
- return this.step({ type: "sendMouse", ...step });
87
- }
88
-
89
- resize(cols: number, rows: number): this {
90
- return this.step({ type: "resize", cols, rows });
91
- }
92
-
93
- mark(label?: string): this {
94
- return this.step({ type: "mark", label });
95
- }
96
-
97
- sleep(ms: number): this {
98
- return this.step({ type: "sleep", ms });
99
- }
100
-
101
- waitForText(step: Omit<StepOf<"waitForText">, "type">): this {
102
- return this.step({ type: "waitForText", ...step });
103
- }
104
-
105
- waitForStableScreen(step: Omit<StepOf<"waitForStableScreen">, "type"> = {}): this {
106
- return this.step({ type: "waitForStableScreen", ...step });
107
- }
108
-
109
- waitForExit(step: Omit<WaitForExitStep, "type"> = {}): this {
110
- return this.step({ type: "waitForExit", ...step });
111
- }
112
-
113
- expectMeta(step: Omit<ExpectMetaStep, "type">): this {
114
- return this.step({ type: "expectMeta", ...step });
115
- }
116
-
117
- snapshot<K2 extends string>(
118
- step: Omit<SnapshotStep, "type"> & { saveAs: K2 },
119
- ): ScriptBuilder<K | K2, Steps>;
120
- snapshot(step: Omit<SnapshotStep, "type">): ScriptBuilder<K, Steps>;
121
- snapshot(step: Omit<SnapshotStep, "type">): ScriptBuilder<any, Steps> {
122
- this.step({ type: "snapshot", ...step });
123
- return this as any;
124
- }
125
-
126
- snapshotText<K2 extends string>(
127
- step: Omit<SnapshotStep, "type" | "kind"> & { saveAs: K2 },
128
- ): ScriptBuilder<K | K2, Steps>;
129
- snapshotText(step?: Omit<SnapshotStep, "type" | "kind">): ScriptBuilder<K, Steps>;
130
- snapshotText(step: Omit<SnapshotStep, "type" | "kind"> = {}): ScriptBuilder<any, Steps> {
131
- return this.snapshot({ ...step, kind: "text" } as Omit<SnapshotStep, "type">);
132
- }
133
-
134
- snapshotView<K2 extends string>(
135
- step: Omit<SnapshotStep, "type" | "kind"> & { saveAs: K2 },
136
- ): ScriptBuilder<K | K2, Steps>;
137
- snapshotView(step?: Omit<SnapshotStep, "type" | "kind">): ScriptBuilder<K, Steps>;
138
- snapshotView(step: Omit<SnapshotStep, "type" | "kind"> = {}): ScriptBuilder<any, Steps> {
139
- return this.snapshot({ ...step, kind: "view" } as Omit<SnapshotStep, "type">);
140
- }
141
-
142
- snapshotAnsi<K2 extends string>(
143
- step: Omit<SnapshotStep, "type" | "kind"> & { saveAs: K2 },
144
- ): ScriptBuilder<K | K2, Steps>;
145
- snapshotAnsi(step?: Omit<SnapshotStep, "type" | "kind">): ScriptBuilder<K, Steps>;
146
- snapshotAnsi(step: Omit<SnapshotStep, "type" | "kind"> = {}): ScriptBuilder<any, Steps> {
147
- return this.snapshot({ ...step, kind: "ansi" } as Omit<SnapshotStep, "type">);
148
- }
149
-
150
- snapshotViewAnsi<K2 extends string>(
151
- step: Omit<SnapshotStep, "type" | "kind"> & { saveAs: K2 },
152
- ): ScriptBuilder<K | K2, Steps>;
153
- snapshotViewAnsi(step?: Omit<SnapshotStep, "type" | "kind">): ScriptBuilder<K, Steps>;
154
- snapshotViewAnsi(step: Omit<SnapshotStep, "type" | "kind"> = {}): ScriptBuilder<any, Steps> {
155
- return this.snapshot({ ...step, kind: "view_ansi" } as Omit<SnapshotStep, "type">);
156
- }
157
-
158
- snapshotGrid<K2 extends string>(
159
- step: Omit<SnapshotStep, "type" | "kind"> & { saveAs: K2 },
160
- ): ScriptBuilder<K | K2, Steps>;
161
- snapshotGrid(step?: Omit<SnapshotStep, "type" | "kind">): ScriptBuilder<K, Steps>;
162
- snapshotGrid(step: Omit<SnapshotStep, "type" | "kind"> = {}): ScriptBuilder<any, Steps> {
163
- return this.snapshot({ ...step, kind: "grid" } as Omit<SnapshotStep, "type">);
164
- }
165
-
166
- expect(step: Omit<ExpectStep, "type" | "from"> & { from?: SnapshotRef<K> }): this {
167
- return this.step({ type: "expect", ...(step as Omit<ExpectStep, "type">) });
168
- }
169
-
170
- expectGolden(step: Omit<ExpectGoldenStep, "type" | "from"> & { from?: SnapshotRef<K> }): this {
171
- return this.step({ type: "expectGolden", ...(step as Omit<ExpectGoldenStep, "type">) });
172
- }
173
-
174
- build(): Script {
175
- return scriptSchema.parse(this.script) as Script;
176
- }
177
- }
178
-
179
- export function defineScript<K extends SnapshotKey, Steps extends CustomStepMap>(
180
- build: () => ScriptBuilder<K, Steps> | Script,
181
- ): Script {
182
- const value = build();
183
- const script =
184
- typeof value === "object" && value && "build" in value
185
- ? (value as ScriptBuilder<any, any>).build()
186
- : value;
187
- return scriptSchema.parse(script) as Script;
188
- }
@@ -1,43 +0,0 @@
1
- import { resolve } from "node:path";
2
- import { pathToFileURL } from "node:url";
3
-
4
- import type { CustomStepHandler } from "./runner";
5
-
6
- function extractStepHandlers(
7
- mod: Record<string, unknown>,
8
- ): Record<string, CustomStepHandler> | undefined {
9
- return (mod.steps ?? mod.customSteps ?? mod.stepHandlers) as
10
- | Record<string, CustomStepHandler>
11
- | undefined;
12
- }
13
-
14
- export async function loadScriptModule(modulePath: string): Promise<{
15
- script: unknown;
16
- steps?: Record<string, CustomStepHandler>;
17
- }> {
18
- const absPath = resolve(process.cwd(), modulePath);
19
- const mod = (await import(pathToFileURL(absPath).href)) as Record<string, unknown>;
20
-
21
- const script = mod.default ?? mod.script;
22
- if (!script) {
23
- throw new Error(`script module must export default or 'script': ${modulePath}`);
24
- }
25
-
26
- const steps = extractStepHandlers(mod);
27
-
28
- return { script, steps };
29
- }
30
-
31
- export async function loadStepHandlersModule(modulePath: string): Promise<{
32
- steps: Record<string, CustomStepHandler>;
33
- }> {
34
- const absPath = resolve(process.cwd(), modulePath);
35
- const mod = (await import(pathToFileURL(absPath).href)) as Record<string, unknown>;
36
- const steps = extractStepHandlers(mod);
37
- if (!steps) {
38
- throw new Error(
39
- `steps module must export 'steps' (or customSteps/stepHandlers): ${modulePath}`,
40
- );
41
- }
42
- return { steps };
43
- }
@@ -1,151 +0,0 @@
1
- import { basename, extname, isAbsolute, resolve } from "node:path";
2
-
3
- import { loadScriptModule, loadStepHandlersModule } from "./module";
4
- import { runScript } from "./runner";
5
- import type { CustomStepHandler } from "./runner";
6
- import { scriptSchema } from "./schema";
7
- import type { Script } from "./schema";
8
-
9
- export type RunScriptPathOptions = {
10
- artifactsDir?: string;
11
- updateGoldens?: boolean;
12
- stepsPath?: string;
13
- };
14
-
15
- export type RunScriptPathSuccess = {
16
- ok: true;
17
- scriptName: string;
18
- artifactsDir: string;
19
- castPath?: string;
20
- reportPath?: string;
21
- };
22
-
23
- export type RunScriptPathFailure = {
24
- ok: false;
25
- error: string;
26
- scriptName?: string;
27
- artifactsDir?: string;
28
- castPath?: string;
29
- reportPath?: string;
30
- failureArtifacts?: {
31
- lastTextPath: string;
32
- lastViewPath: string;
33
- stepPath: string;
34
- errorPath: string;
35
- };
36
- };
37
-
38
- export type RunScriptPathResult = RunScriptPathSuccess | RunScriptPathFailure;
39
-
40
- export async function runScriptPath(
41
- scriptPath: string,
42
- options?: RunScriptPathOptions,
43
- ): Promise<RunScriptPathResult> {
44
- let scriptName: string | undefined;
45
- let artifactsDir: string | undefined;
46
- let castPath: string | undefined;
47
- let reportPath: string | undefined;
48
-
49
- try {
50
- const ext = extname(scriptPath).toLowerCase();
51
- const baseName = basename(scriptPath, extname(scriptPath));
52
-
53
- const extraSteps = options?.stepsPath
54
- ? (await loadStepHandlersModule(options.stepsPath)).steps
55
- : undefined;
56
-
57
- const loaded = await loadScriptInput(scriptPath, ext);
58
- const stepsFromModule = loaded.steps;
59
- const mergedSteps =
60
- stepsFromModule && extraSteps
61
- ? { ...stepsFromModule, ...extraSteps }
62
- : (stepsFromModule ?? extraSteps);
63
-
64
- const built = loaded.script;
65
- const withName =
66
- built && typeof built === "object" && !Array.isArray(built) && !("name" in built)
67
- ? { ...built, name: baseName }
68
- : built;
69
-
70
- const parsed = scriptSchema.parse(withName) as Script;
71
- scriptName = parsed.name ?? baseName;
72
- artifactsDir = resolveArtifactsDir(parsed, scriptName, options?.artifactsDir);
73
-
74
- const trace = parsed.trace ?? {};
75
- const saveCast = trace.saveCast ?? true;
76
- const saveReport = trace.saveReport ?? true;
77
- castPath = saveCast
78
- ? resolveArtifactPath(artifactsDir, trace.castPath ?? `${scriptName}.cast`)
79
- : undefined;
80
- reportPath = saveReport
81
- ? resolveArtifactPath(artifactsDir, trace.reportPath ?? `${scriptName}.report.html`)
82
- : undefined;
83
-
84
- try {
85
- await runScript(parsed, {
86
- artifactsDir,
87
- updateGoldens: options?.updateGoldens,
88
- steps: mergedSteps,
89
- });
90
-
91
- return { ok: true, scriptName, artifactsDir, castPath, reportPath };
92
- } catch (error) {
93
- return {
94
- ok: false,
95
- error: (error as Error).message,
96
- scriptName,
97
- artifactsDir,
98
- castPath,
99
- reportPath,
100
- failureArtifacts: {
101
- lastTextPath: resolveArtifactPath(artifactsDir, "failure.last.txt"),
102
- lastViewPath: resolveArtifactPath(artifactsDir, "failure.last.view.txt"),
103
- stepPath: resolveArtifactPath(artifactsDir, "failure.step.json"),
104
- errorPath: resolveArtifactPath(artifactsDir, "failure.error.txt"),
105
- },
106
- };
107
- }
108
- } catch (error) {
109
- return {
110
- ok: false,
111
- error: (error as Error).message,
112
- scriptName,
113
- artifactsDir,
114
- castPath,
115
- reportPath,
116
- };
117
- }
118
- }
119
-
120
- async function loadScriptInput(
121
- scriptPath: string,
122
- ext: string,
123
- ): Promise<{ script: unknown; steps?: Record<string, CustomStepHandler> }> {
124
- if (ext === ".json") {
125
- const raw = await Bun.file(scriptPath).text();
126
- return { script: JSON.parse(raw) as unknown };
127
- }
128
-
129
- const loaded = await loadScriptModule(scriptPath);
130
- const script = loaded.script;
131
- if (
132
- script &&
133
- typeof script === "object" &&
134
- "build" in script &&
135
- typeof (script as { build?: unknown }).build === "function"
136
- ) {
137
- return { script: (script as { build: () => unknown }).build(), steps: loaded.steps };
138
- }
139
- return { script, steps: loaded.steps };
140
- }
141
-
142
- function resolveArtifactsDir(script: Script, scriptName: string, override?: string): string {
143
- if (override?.trim()) return resolve(override.trim());
144
- if (script.artifactsDir?.trim()) return resolve(script.artifactsDir.trim());
145
- return resolve(".tmp", "runs", scriptName);
146
- }
147
-
148
- function resolveArtifactPath(artifactsDir: string, path: string): string {
149
- if (isAbsolute(path)) return path;
150
- return resolve(artifactsDir, path);
151
- }
package/src/script/run.ts DELETED
@@ -1,108 +0,0 @@
1
- import { runScriptPath } from "./path";
2
-
3
- function parseArgs(argv: string[]): {
4
- scriptPath: string;
5
- artifactsDir?: string;
6
- stepsPath?: string;
7
- updateGoldens: boolean;
8
- } {
9
- const out: {
10
- scriptPath?: string;
11
- artifactsDir?: string;
12
- stepsPath?: string;
13
- updateGoldens: boolean;
14
- } = {
15
- updateGoldens: false,
16
- };
17
-
18
- for (let i = 0; i < argv.length; i += 1) {
19
- const arg = argv[i];
20
- const next = argv[i + 1];
21
-
22
- if (!out.scriptPath && arg && !arg.startsWith("-")) {
23
- out.scriptPath = arg;
24
- continue;
25
- }
26
-
27
- if (arg === "--artifacts-dir" && next) {
28
- out.artifactsDir = next;
29
- i += 1;
30
- continue;
31
- }
32
-
33
- if (arg === "--steps" && next) {
34
- out.stepsPath = next;
35
- i += 1;
36
- continue;
37
- }
38
-
39
- if (arg === "--update-goldens") {
40
- out.updateGoldens = true;
41
- continue;
42
- }
43
-
44
- throw new Error(`unknown arg: ${arg ?? ""}`);
45
- }
46
-
47
- if (!out.scriptPath) {
48
- throw new Error(
49
- "Usage: bun run src/script/run.ts <file> [--artifacts-dir <dir>] [--steps <module.ts>] [--update-goldens]",
50
- );
51
- }
52
-
53
- return out as {
54
- scriptPath: string;
55
- artifactsDir?: string;
56
- stepsPath?: string;
57
- updateGoldens: boolean;
58
- };
59
- }
60
-
61
- function logLines(lines: Array<string | null | undefined>, stderr: boolean): void {
62
- const filtered = lines.map((l) => l?.trim()).filter(Boolean) as string[];
63
- for (const line of filtered) {
64
- // eslint-disable-next-line no-console
65
- (stderr ? console.error : console.log)(line);
66
- }
67
- }
68
-
69
- if (import.meta.main) {
70
- try {
71
- const args = parseArgs(process.argv.slice(2));
72
- const result = await runScriptPath(args.scriptPath, {
73
- artifactsDir: args.artifactsDir,
74
- updateGoldens: args.updateGoldens,
75
- stepsPath: args.stepsPath,
76
- });
77
-
78
- if (!result.ok) {
79
- logLines(
80
- [
81
- result.error,
82
- result.artifactsDir ? `artifacts=${result.artifactsDir}` : null,
83
- result.reportPath ? `report=${result.reportPath}` : null,
84
- result.castPath ? `cast=${result.castPath}` : null,
85
- result.failureArtifacts?.lastViewPath
86
- ? `last=${result.failureArtifacts.lastViewPath}`
87
- : null,
88
- result.failureArtifacts?.errorPath ? `error=${result.failureArtifacts.errorPath}` : null,
89
- ],
90
- true,
91
- );
92
- process.exitCode = 1;
93
- } else {
94
- logLines(
95
- [
96
- `ok artifacts=${result.artifactsDir}`,
97
- result.reportPath ? `report=${result.reportPath}` : null,
98
- result.castPath ? `cast=${result.castPath}` : null,
99
- ],
100
- false,
101
- );
102
- }
103
- } catch (error) {
104
- // eslint-disable-next-line no-console
105
- console.error((error as Error).message);
106
- process.exitCode = 1;
107
- }
108
- }