waygraph 0.15.23 → 0.15.24

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 (41) hide show
  1. package/dist/cli/discover.d.ts +40 -0
  2. package/dist/cli/discover.js +148 -0
  3. package/dist/cli/flags.d.ts +91 -0
  4. package/dist/cli/flags.js +442 -0
  5. package/dist/cli/run-chain.d.ts +8 -0
  6. package/dist/cli/run-chain.js +67 -0
  7. package/dist/cli/usage.d.ts +4 -0
  8. package/dist/cli/usage.js +236 -0
  9. package/dist/cli/util.d.ts +4 -0
  10. package/dist/cli/util.js +24 -0
  11. package/dist/cli.js +45 -2448
  12. package/dist/commands/agent-dive.d.ts +1 -0
  13. package/dist/commands/agent-dive.js +52 -0
  14. package/dist/commands/check.d.ts +4 -0
  15. package/dist/commands/check.js +192 -0
  16. package/dist/commands/init.d.ts +1 -0
  17. package/dist/commands/init.js +54 -0
  18. package/dist/commands/list.d.ts +2 -0
  19. package/dist/commands/list.js +67 -0
  20. package/dist/commands/map.d.ts +1 -0
  21. package/dist/commands/map.js +29 -0
  22. package/dist/commands/run.d.ts +3 -0
  23. package/dist/commands/run.js +104 -0
  24. package/dist/commands/session.d.ts +2 -0
  25. package/dist/commands/session.js +600 -0
  26. package/dist/commands/test.d.ts +1 -0
  27. package/dist/commands/test.js +33 -0
  28. package/dist/commands/traverse.d.ts +1 -0
  29. package/dist/commands/traverse.js +177 -0
  30. package/dist/commands/try.d.ts +1 -0
  31. package/dist/commands/try.js +298 -0
  32. package/examples/saucedemo/.waygraph-auto/0ac58118.log +0 -0
  33. package/examples/saucedemo/.waygraph-auto/309c107f.log +0 -0
  34. package/examples/saucedemo/.waygraph-auto/3ab7c7bd.log +44 -0
  35. package/examples/saucedemo/.waygraph-auto/45864828.log +0 -0
  36. package/examples/saucedemo/.waygraph-auto/95bd8371.log +0 -0
  37. package/examples/saucedemo/.waygraph-auto/bcb9e31a.log +0 -0
  38. package/examples/saucedemo/.waygraph-auto/cd3e528c.log +0 -0
  39. package/examples/saucedemo/.waygraph-auto/dce1535d.log +0 -0
  40. package/examples/saucedemo/.waygraph-auto/e280bf20.log +0 -0
  41. package/package.json +1 -1
@@ -0,0 +1,442 @@
1
+ // Split out of the former 8,700-line cli.ts (see src/ARCHITECTURE.md). Behavior unchanged.
2
+ import { basename, join, relative, resolve } from "node:path";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { walkDir } from "./discover.js";
5
+ function takeFlagValue(argv, i, a, flag) {
6
+ if (a.startsWith(flag + "=")) {
7
+ return { value: a.slice(flag.length + 1), nextI: i };
8
+ }
9
+ const v = argv[i + 1];
10
+ if (v === undefined || (v.startsWith("-") && !a.includes("="))) {
11
+ console.error(`waygraph: ${flag} needs a value`);
12
+ process.exit(1);
13
+ }
14
+ return { value: v, nextI: i + 1 };
15
+ }
16
+ /**
17
+ * Looks like a Checkpoint tag for auto --blocks From To (PascalCase).
18
+ * Kebab-case Block names must not trigger path-find mode.
19
+ */
20
+ function looksLikeCheckpointArg(s) {
21
+ if (!s || s.startsWith("-"))
22
+ return false;
23
+ if (s.includes("then") || s.includes("(") || s.includes("{") || s.includes("/"))
24
+ return false;
25
+ if (s === "." || s.includes("."))
26
+ return false;
27
+ return /^[A-Z][A-Za-z0-9_]*$/.test(s);
28
+ }
29
+ /**
30
+ * Pulls run/demo/auto flags out of argv. Unknown bare `--*` that are not
31
+ * recognized stay as positionals only when they are project paths; known
32
+ * explore flags (--cli/--mermaid/--map) are parsed here.
33
+ */
34
+ export function parseRunFlags(argv) {
35
+ const out = { positionals: [] };
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const a = argv[i];
38
+ if (a === "--step") {
39
+ out.step = true;
40
+ }
41
+ else if (a === "--no-step") {
42
+ out.step = false;
43
+ }
44
+ else if (a === "--auto-next" || a === "--autoplay") {
45
+ out.autoplay = true;
46
+ }
47
+ else if (a === "--no-auto-next" || a === "--no-autoplay") {
48
+ out.autoplay = false;
49
+ }
50
+ else if (a === "--auto-play-video") {
51
+ out.autoPlayVideo = true;
52
+ }
53
+ else if (a === "--auto-play-video-head") {
54
+ out.autoPlayVideo = true;
55
+ out.nonHeadless = true;
56
+ }
57
+ else if (a === "--fast") {
58
+ out.fast = true;
59
+ }
60
+ else if (a === "--full") {
61
+ out.fullStepper = true;
62
+ }
63
+ else if (a === "--mini" || a === "--stepper-mini") {
64
+ out.miniStepper = true;
65
+ }
66
+ else if (a === "--todo-left") {
67
+ out.todoPos = "left";
68
+ }
69
+ else if (a === "--todo-right") {
70
+ out.todoPos = "right";
71
+ }
72
+ else if (a === "--todo-full" || a === "--no-todo-smart") {
73
+ out.todoUiFull = true;
74
+ }
75
+ else if (a === "--todo-smart") {
76
+ out.todoUiSmart = true;
77
+ }
78
+ else if (a === "--ff-expand") {
79
+ out.ffExpand = true;
80
+ }
81
+ else if (a === "--ff-disabled" || a === "--no-ff") {
82
+ out.ffDisabled = true;
83
+ out.ffExpand = true;
84
+ }
85
+ else if (a === "--non-headless") {
86
+ out.nonHeadless = true;
87
+ }
88
+ else if (a === "--headless") {
89
+ out.headlessBrowser = true;
90
+ }
91
+ else if (a === "--json") {
92
+ out.json = true;
93
+ }
94
+ else if (a === "--no-practices") {
95
+ out.noPractices = true;
96
+ }
97
+ else if (a === "--blank") {
98
+ out.blank = true;
99
+ }
100
+ else if (a === "--goto" || a.startsWith("--goto=")) {
101
+ const t = takeFlagValue(argv, i, a, "--goto");
102
+ out.goto = t.value;
103
+ i = t.nextI;
104
+ }
105
+ else if (a === "--inject" || a.startsWith("--inject=")) {
106
+ const t = takeFlagValue(argv, i, a, "--inject");
107
+ out.inject ??= [];
108
+ out.inject.push(t.value);
109
+ i = t.nextI;
110
+ }
111
+ else if (a === "--cli") {
112
+ out.cli = true;
113
+ }
114
+ else if (a === "--detach") {
115
+ out.detach = true;
116
+ }
117
+ else if (a === "--headed") {
118
+ out.headed = true;
119
+ }
120
+ else if (a === "--mermaid") {
121
+ out.mermaid = true;
122
+ }
123
+ else if (a === "--map") {
124
+ out.map = true;
125
+ }
126
+ else if (a === "--base-url" || a.startsWith("--base-url=")) {
127
+ const t = takeFlagValue(argv, i, a, "--base-url");
128
+ out.baseUrl = t.value;
129
+ i = t.nextI;
130
+ }
131
+ else if (a === "--title" || a.startsWith("--title=")) {
132
+ const t = takeFlagValue(argv, i, a, "--title");
133
+ out.title = t.value;
134
+ i = t.nextI;
135
+ }
136
+ else if (a === "--data" || a.startsWith("--data=")) {
137
+ const t = takeFlagValue(argv, i, a, "--data");
138
+ out.data = t.value;
139
+ i = t.nextI;
140
+ }
141
+ else if (a === "--mem-stub") {
142
+ out.memStub = true;
143
+ }
144
+ else if (a === "--video" || a.startsWith("--video=")) {
145
+ if (a.startsWith("--video=")) {
146
+ out.video = a.slice("--video=".length);
147
+ }
148
+ else {
149
+ const v = argv[i + 1];
150
+ if (v === undefined || v.startsWith("-")) {
151
+ out.video = "";
152
+ }
153
+ else {
154
+ out.video = v;
155
+ i++;
156
+ }
157
+ }
158
+ }
159
+ else if (a === "--video-viewport" || a.startsWith("--video-viewport=")) {
160
+ const t = takeFlagValue(argv, i, a, "--video-viewport");
161
+ out.videoViewport = t.value;
162
+ i = t.nextI;
163
+ }
164
+ else if (a === "--blocks" || a.startsWith("--blocks=")) {
165
+ if (a.startsWith("--blocks=")) {
166
+ out.blocks = a.slice("--blocks=".length);
167
+ }
168
+ else {
169
+ const v1 = argv[++i];
170
+ if (!v1 || v1.startsWith("-")) {
171
+ console.error("waygraph: --blocks needs a flow/spec or <from> <to> checkpoints");
172
+ process.exit(1);
173
+ }
174
+ const v2 = argv[i + 1];
175
+ if (v2 && looksLikeCheckpointArg(v1) && looksLikeCheckpointArg(v2)) {
176
+ out.blocksFromTo = [v1, v2];
177
+ i++;
178
+ }
179
+ else {
180
+ out.blocks = v1;
181
+ }
182
+ }
183
+ }
184
+ else {
185
+ out.positionals.push(a);
186
+ }
187
+ }
188
+ return out;
189
+ }
190
+ /** Parses WAYGRAPH_VIDEO_VIEWPORT / --video-viewport: "1920x1080" or "1920,1080". */
191
+ function parseVideoViewportFlag(raw) {
192
+ if (!raw?.trim())
193
+ return null;
194
+ const m = raw.trim().match(/^(\d{3,5})[xX,](\d{3,5})$/);
195
+ if (!m)
196
+ return null;
197
+ const width = Number(m[1]);
198
+ const height = Number(m[2]);
199
+ if (!Number.isFinite(width) || !Number.isFinite(height))
200
+ return null;
201
+ if (width < 320 || height < 240 || width > 7680 || height > 4320)
202
+ return null;
203
+ return { width, height };
204
+ }
205
+ /** Writes flag values into process.env so the chain child inherits them. Flags beat prior env. */
206
+ export function applyRunFlags(flags, opts) {
207
+ if (flags.autoPlayVideo) {
208
+ if (!opts?.allowAutoPlayVideo) {
209
+ console.error("waygraph: --auto-play-video is a demo-only flag (QA watch + record)");
210
+ process.exit(1);
211
+ }
212
+ if (flags.autoplay === undefined)
213
+ flags.autoplay = true;
214
+ if (flags.video === undefined)
215
+ flags.video = "";
216
+ // Overlay narration (captions/rings/slides) needs step mode to run, but
217
+ // --auto-play-video is unattended-and-recorded by design - no human
218
+ // has to watch a live window for Playwright's own recordVideo to
219
+ // capture the right thing. Headless by default; --non-headless (or the
220
+ // --auto-play-video-head alias, parsed above) opts back into a
221
+ // visible browser for someone who wants to watch it live too.
222
+ process.env.WAYGRAPH_STEP = "1";
223
+ if (!flags.nonHeadless && process.env.WAYGRAPH_HEADED === undefined) {
224
+ process.env.WAYGRAPH_HEADED = "0";
225
+ }
226
+ }
227
+ if ((flags.fast || flags.fullStepper || flags.miniStepper || flags.todoPos || flags.todoUiFull || flags.todoUiSmart) &&
228
+ !opts?.allowDemoUi) {
229
+ console.error("waygraph: --fast / --full / --mini / --todo-left|--todo-right / --todo-full|--todo-smart are demo-only flags");
230
+ process.exit(1);
231
+ }
232
+ if (flags.step === true) {
233
+ process.env.WAYGRAPH_STEP = "1";
234
+ process.env.WAYGRAPH_HEADED = "1";
235
+ }
236
+ else if (flags.step === false) {
237
+ process.env.WAYGRAPH_STEP = "0";
238
+ }
239
+ if (flags.autoplay === true) {
240
+ process.env.WAYGRAPH_AUTOPLAY = "1";
241
+ }
242
+ else if (flags.autoplay === false) {
243
+ process.env.WAYGRAPH_AUTOPLAY = "0";
244
+ }
245
+ if (flags.nonHeadless) {
246
+ process.env.WAYGRAPH_HEADED = "1";
247
+ }
248
+ // Any --video on demo/run: headless unless --non-headless already won.
249
+ if (flags.video !== undefined && !flags.nonHeadless && process.env.WAYGRAPH_HEADED === undefined) {
250
+ process.env.WAYGRAPH_HEADED = "0";
251
+ }
252
+ if (flags.baseUrl !== undefined) {
253
+ process.env.WAYGRAPH_BASE_URL = flags.baseUrl;
254
+ }
255
+ if (flags.title !== undefined) {
256
+ process.env.WAYGRAPH_TITLE = flags.title;
257
+ }
258
+ if (flags.video !== undefined) {
259
+ process.env.WAYGRAPH_VIDEO = flags.video || "1";
260
+ }
261
+ if (flags.videoViewport !== undefined) {
262
+ if (!parseVideoViewportFlag(flags.videoViewport)) {
263
+ console.error('waygraph: --video-viewport must look like "1920x1080"');
264
+ process.exit(1);
265
+ }
266
+ process.env.WAYGRAPH_VIDEO_VIEWPORT = flags.videoViewport;
267
+ }
268
+ if (flags.data !== undefined) {
269
+ process.env.WAYGRAPH_DATA = flags.data;
270
+ }
271
+ if (flags.memStub) {
272
+ process.env.WAYGRAPH_MEM_STUB = "1";
273
+ }
274
+ if (flags.fast) {
275
+ process.env.WAYGRAPH_DEMO_FAST = "1";
276
+ }
277
+ if (flags.fullStepper) {
278
+ process.env.WAYGRAPH_STEPPER = "full";
279
+ }
280
+ if (flags.miniStepper) {
281
+ process.env.WAYGRAPH_MINI = "1";
282
+ }
283
+ if (flags.todoPos === "left" || flags.todoPos === "right") {
284
+ process.env.WAYGRAPH_TODO_POS = flags.todoPos;
285
+ }
286
+ if (flags.todoUiFull) {
287
+ process.env.WAYGRAPH_TODO_UI = "full";
288
+ }
289
+ else if (flags.todoUiSmart) {
290
+ process.env.WAYGRAPH_TODO_UI = "smart";
291
+ }
292
+ if (flags.ffExpand) {
293
+ process.env.WAYGRAPH_FF_EXPAND = "1";
294
+ }
295
+ if (flags.ffDisabled) {
296
+ process.env.WAYGRAPH_FF_DISABLED = "1";
297
+ process.env.WAYGRAPH_FF_EXPAND = "1";
298
+ }
299
+ }
300
+ /**
301
+ * BASE_URL when neither --base-url nor WAYGRAPH_BASE_URL is set:
302
+ * package.json `waygraph.baseUrl` (or `baseURL`), then playwright.config `baseURL`.
303
+ */
304
+ export function resolveBaseUrl(projectDir) {
305
+ const pkgPath = join(projectDir, "package.json");
306
+ if (existsSync(pkgPath)) {
307
+ try {
308
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
309
+ const fromPkg = pkg.waygraph?.baseUrl ??
310
+ pkg.waygraph?.baseURL ??
311
+ pkg.config?.waygraph?.baseUrl ??
312
+ pkg.config?.waygraph?.baseURL;
313
+ if (typeof fromPkg === "string" && fromPkg.trim())
314
+ return fromPkg.trim();
315
+ }
316
+ catch {
317
+ // ignore malformed package.json - fall through to playwright
318
+ }
319
+ }
320
+ for (const name of ["playwright.config.ts", "playwright.config.mts", "playwright.config.js", "playwright.config.mjs"]) {
321
+ const p = join(projectDir, name);
322
+ if (!existsSync(p))
323
+ continue;
324
+ const src = readFileSync(p, "utf-8");
325
+ const m = src.match(/baseURL\s*:\s*["']([^"']+)["']/);
326
+ if (m?.[1])
327
+ return m[1];
328
+ }
329
+ return undefined;
330
+ }
331
+ /**
332
+ * Friendly demo defaults: STEP on; headed only for live watch (no --video).
333
+ * --video / --auto-play-video => headless unless --non-headless.
334
+ * Call after applyRunFlags so explicit flags already won.
335
+ */
336
+ export function applyDemoDefaults(projectDir) {
337
+ if (process.env.WAYGRAPH_STEP === undefined)
338
+ process.env.WAYGRAPH_STEP = "1";
339
+ if (process.env.WAYGRAPH_STEP === "1" && process.env.WAYGRAPH_HEADED === undefined) {
340
+ // Recording locks headless - a live window fights fixed video viewport /
341
+ // device shell centering. Watch live only with --non-headless.
342
+ process.env.WAYGRAPH_HEADED = process.env.WAYGRAPH_VIDEO ? "0" : "1";
343
+ }
344
+ // Manual Next by default. --auto-next / --auto-play-video flip this.
345
+ if (process.env.WAYGRAPH_AUTOPLAY === undefined) {
346
+ if (process.env.WAYGRAPH_VIDEO && process.env.WAYGRAPH_STEP === "0") {
347
+ process.env.WAYGRAPH_AUTOPLAY = "1";
348
+ }
349
+ else {
350
+ process.env.WAYGRAPH_AUTOPLAY = "0";
351
+ }
352
+ }
353
+ if (!process.env.WAYGRAPH_BASE_URL) {
354
+ const resolved = resolveBaseUrl(projectDir);
355
+ if (resolved)
356
+ process.env.WAYGRAPH_BASE_URL = resolved;
357
+ }
358
+ }
359
+ export function resolveProjectDir(flags, fallbackIndex = 0) {
360
+ const cand = flags.positionals[fallbackIndex] ?? process.cwd();
361
+ return resolve(cand);
362
+ }
363
+ export function resolveSpec(flags) {
364
+ return flags.blocks ?? flags.positionals[0];
365
+ }
366
+ /** `shop.flow.ts` / `src/flows/cart-bulk.flow.ts` / absolute path — not a Checkpoint or export id. */
367
+ export function looksLikeFlowFileRef(ref) {
368
+ if (!ref || ref.includes("then") || ref.includes("("))
369
+ return false;
370
+ return /\.flow\.ts$/i.test(ref) || (/\.ts$/i.test(ref) && (ref.includes("/") || ref.includes("\\")));
371
+ }
372
+ function flowExportNameFromBasename(filePath) {
373
+ const base = basename(filePath).replace(/\.flow\.ts$/i, "").replace(/\.ts$/i, "");
374
+ const camel = base.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
375
+ return /Flow$/i.test(camel) ? camel : `${camel}Flow`;
376
+ }
377
+ /**
378
+ * Map a `.flow.ts` path (relative to project or absolute) to its Flow export
379
+ * name so the chain runner can `findFlow(exportName)`. Prefers
380
+ * `shop.flow.ts` → `shopFlow`. Reads source only (no import) so the outer CLI
381
+ * does not dual-load `@playwright/test` against the project tree.
382
+ */
383
+ async function resolveFlowFileToExport(projectDir, fileArg) {
384
+ let abs = resolve(projectDir, fileArg);
385
+ if (!existsSync(abs)) {
386
+ const want = basename(fileArg);
387
+ const hits = walkDir(projectDir, new RegExp(`${want.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`));
388
+ if (hits.length === 1)
389
+ abs = hits[0];
390
+ else if (hits.length === 0) {
391
+ throw new Error(`waygraph: no flow file "${fileArg}" under ${projectDir}`);
392
+ }
393
+ else {
394
+ throw new Error(`waygraph: "${fileArg}" matches ${hits.length} files - use a path from \`waygraph list\``);
395
+ }
396
+ }
397
+ const expected = flowExportNameFromBasename(abs);
398
+ const src = readFileSync(abs, "utf-8");
399
+ if (new RegExp(`\\bexport\\s+(?:const|let|var|async\\s+function|function)\\s+${expected}\\b`).test(src)) {
400
+ return expected;
401
+ }
402
+ if (new RegExp(`\\bexport\\s*\\{[^}]*\\b${expected}\\b`).test(src)) {
403
+ return expected;
404
+ }
405
+ const flowNames = [
406
+ ...src.matchAll(/\bexport\s+(?:const|let|var|async\s+function|function)\s+(\w*Flow)\b/g),
407
+ ].map((m) => m[1]);
408
+ const unique = [...new Set(flowNames)];
409
+ if (unique.length === 1)
410
+ return unique[0];
411
+ if (unique.includes(expected))
412
+ return expected;
413
+ if (unique.length === 0) {
414
+ throw new Error(`waygraph: ${relative(projectDir, abs)} has no exported *Flow (expected ${expected})`);
415
+ }
416
+ throw new Error(`waygraph: ${relative(projectDir, abs)} exports ${unique.join(", ")} - pass the export name or rename to ${expected}`);
417
+ }
418
+ /**
419
+ * Expand `.flow.ts` path segments in a run/demo spec to export names.
420
+ * `src/flows/shop.flow.ts` → `shopFlow`; leaves `loginFlow then nav-cart` alone.
421
+ */
422
+ export async function expandSpecFlowFiles(projectDir, spec) {
423
+ const parts = spec.split(/\bthen\b/).map((s) => s.trim()).filter((s) => s.length > 0);
424
+ const out = [];
425
+ for (const part of parts) {
426
+ const m = /^([^\s(]+)(\s*\([\s\S]*\))?$/.exec(part);
427
+ if (!m) {
428
+ out.push(part);
429
+ continue;
430
+ }
431
+ const ref = m[1];
432
+ const payload = m[2] ?? "";
433
+ if (looksLikeFlowFileRef(ref)) {
434
+ const exportName = await resolveFlowFileToExport(projectDir, ref);
435
+ out.push(exportName + payload);
436
+ }
437
+ else {
438
+ out.push(part);
439
+ }
440
+ }
441
+ return out.join(" then ");
442
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Runs the chain runner script as a child process rooted at `projectDir` -
3
+ * see {@link CHAIN_RUNNER_BOOTSTRAP} for why this can't just be imported
4
+ * in-process. `WAYGRAPH_BASE_URL` (relative `page.goto()` targets, like
5
+ * every real zsign-all Block uses), `WAYGRAPH_HEADED=1`, and
6
+ * `WAYGRAPH_SLOWMO=<ms>` pass straight through from this process's own env.
7
+ */
8
+ export declare function runChain(projectDir: string, spec: string): Promise<void>;
@@ -0,0 +1,67 @@
1
+ // Split out of the former 8,700-line cli.ts (see src/ARCHITECTURE.md). Behavior unchanged.
2
+ import { expandSpecFlowFiles, resolveBaseUrl } from "./flags.js";
3
+ import { join } from "node:path";
4
+ import { rmSync, writeFileSync } from "node:fs";
5
+ import { spawn } from "node:child_process";
6
+ // ---------------------------------------------------------------------------
7
+ // `chain`
8
+ // ---------------------------------------------------------------------------
9
+ /**
10
+ * `chain` cannot resolve Blocks by importing them straight into THIS process: this CLI's own
11
+ * `waygraph` (this same package) already loaded - and its engine imports `@playwright/test`.
12
+ * A target project installs its OWN copy of both `waygraph` and `@playwright/test`, so importing
13
+ * one of its `.block.ts` files pulls in a second, physically different Playwright - which
14
+ * Playwright's own runtime refuses outright ("Requiring @playwright/test second time").
15
+ *
16
+ * The fix: never let the two copies share a process. The runner (real, typed-by-review code in
17
+ * `src/runner/`, shipped as the `waygraph/runner` export) is started from a 2-line bootstrap
18
+ * written into the TARGET project directory and run as its own child process, so
19
+ * `waygraph/runner` - and therefore the engine, highlights and Playwright it uses - all resolve
20
+ * from the target project's own install, exclusively. This CLI's own copy never loads there.
21
+ */
22
+ const CHAIN_RUNNER_BOOTSTRAP = `let mod;
23
+ try {
24
+ mod = await import("waygraph/runner");
25
+ } catch (err) {
26
+ console.error(
27
+ "waygraph: the waygraph installed in this project has no runner export (needs >= 0.15.23). " +
28
+ "Upgrade it: npm install waygraph@latest\\n" + (err && err.message ? err.message : String(err)),
29
+ );
30
+ process.exit(1);
31
+ }
32
+ mod.startRunner();
33
+ `;
34
+ /**
35
+ * Runs the chain runner script as a child process rooted at `projectDir` -
36
+ * see {@link CHAIN_RUNNER_BOOTSTRAP} for why this can't just be imported
37
+ * in-process. `WAYGRAPH_BASE_URL` (relative `page.goto()` targets, like
38
+ * every real zsign-all Block uses), `WAYGRAPH_HEADED=1`, and
39
+ * `WAYGRAPH_SLOWMO=<ms>` pass straight through from this process's own env.
40
+ */
41
+ export async function runChain(projectDir, spec) {
42
+ const expanded = await expandSpecFlowFiles(projectDir, spec);
43
+ if (process.env.WAYGRAPH_VIDEO && !process.env.WAYGRAPH_BASE_URL) {
44
+ const resolved = resolveBaseUrl(projectDir);
45
+ if (resolved)
46
+ process.env.WAYGRAPH_BASE_URL = resolved;
47
+ }
48
+ const tsxEsm = import.meta.resolve("tsx/esm");
49
+ const scriptPath = join(projectDir, `.waygraph-chain-${process.pid}.mjs`);
50
+ writeFileSync(scriptPath, CHAIN_RUNNER_BOOTSTRAP);
51
+ try {
52
+ const code = await new Promise((res, rej) => {
53
+ const child = spawn(process.execPath, ["--import", tsxEsm, scriptPath, projectDir, expanded], {
54
+ stdio: "inherit",
55
+ env: process.env,
56
+ });
57
+ child.on("error", rej);
58
+ child.on("exit", (code) => res(code ?? 1));
59
+ });
60
+ if (code !== 0) {
61
+ process.exitCode = code;
62
+ }
63
+ }
64
+ finally {
65
+ rmSync(scriptPath, { force: true });
66
+ }
67
+ }
@@ -0,0 +1,4 @@
1
+ import type { SessionMeta } from "../browser.js";
2
+ export declare function printBrowserUsage(): void;
3
+ export declare function printBrowserSessionsList(sessions: SessionMeta[], projectDir: string): void;
4
+ export declare function usage(): void;