waygraph 0.10.3 → 0.10.5

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 CHANGED
@@ -3,7 +3,7 @@
3
3
  A typed graph of reusable Blocks for driving a browser through E2E flows, in place of a
4
4
  flat pile of ad hoc helper functions.
5
5
 
6
- **Current:** `0.10.2` — CLI less-is-more (`auto` / `demo` / `run` + flags). Page hubs still as in 0.9.0.
6
+ **Current:** `0.10.5` — CLI less-is-more (`auto` / `demo` / `run` + `.flow.ts` paths). Page hubs still as in 0.9.0.
7
7
 
8
8
  This repo is the `waygraph` npm package itself. The full live **Sauce Demo** example
9
9
  (Page inventory hub, Effect Add/Remove, MemNav Open details, `waygraph auto`) ships
@@ -22,7 +22,7 @@ Browser docs (quick start, **demo / run**, **auto explore**, engine handout, con
22
22
  - **Source:** [`docs/`](./docs/) — static HTML, no build step
23
23
  - **Local preview:** `npm run docs:preview` → http://127.0.0.1:4173/
24
24
  - **Deploy:** push to `main` touching `docs/**` runs [`.github/workflows/pages.yml`](./.github/workflows/pages.yml) (`workflow_dispatch` also works)
25
- - **Demo / run contract:** [`docs/demo.html`](./docs/demo.html) — `demo` / `run` / flags (`--auto-next`, `--auto-play-video`)
25
+ - **Demo / run contract:** [`docs/demo.html`](./docs/demo.html) — `list` / `.flow.ts` path / export / flags
26
26
  - **Auto explore:** [`docs/auto.html`](./docs/auto.html) — prefer `auto --cli` / `npm run auto:cli` (same menus as headed)
27
27
  - **In-repo example:** [`examples/saucedemo`](./examples/saucedemo) — full Sauce Demo project
28
28
  - **Convention showcase (Pages):** [`docs/saucedemo/`](./docs/saucedemo/) —
@@ -262,11 +262,15 @@ Deliberately **not yet implemented** (tracked on the project board):
262
262
  Mermaid) is `waygraph graph [project]` (`--mermaid`). Unattended JSON execution is
263
263
  `chain` + `WAYGRAPH_JSON=1`.
264
264
 
265
- **Friendly demo / run / auto (0.10.2 — less is more):**
265
+ **Friendly demo / run / auto (0.10.5 — flows are files):**
266
266
 
267
267
  | Want | Command |
268
268
  |------|---------|
269
- | Manual watch | `waygraph demo --blocks shopFlow` |
269
+ | List flows | `waygraph list` → `src/flows/shop.flow.ts shopFlow` |
270
+ | Run by file | `waygraph run src/flows/shop.flow.ts --data '{…}'` |
271
+ | Same via `auto` | `waygraph auto src/flows/shop.flow.ts --data '{…}'` |
272
+ | Run by export | `waygraph run --blocks shopFlow` |
273
+ | Manual watch | `waygraph demo src/flows/shop.flow.ts` |
270
274
  | Auto-advance | `waygraph demo --blocks shopFlow --auto-next` |
271
275
  | QA watch + record | `waygraph demo --blocks shopFlow --auto-play-video` |
272
276
  | Ad-hoc Blocks | `waygraph run --blocks "login then nav-cart" --data '{…}'` |
@@ -274,6 +278,8 @@ Mermaid) is `waygraph graph [project]` (`--mermaid`). Unattended JSON execution
274
278
  | Explore | `waygraph auto` / `waygraph auto --cli` |
275
279
  | Path-find | `waygraph auto --blocks LoginPage OrderComplete` |
276
280
 
281
+ `--blocks` / positional accepts a Flow export, a `.flow.ts` path, or `"a then b"`.
282
+ `auto <file.flow.ts>` **runs** that flow (same as `run`); bare `auto` still explores.
277
283
  `--auto-next` (alias `--autoplay`) = panel Auto-advance. `--auto-play-video` is **demo only**.
278
284
  `chain` remains a compat alias for `run --blocks` / `auto --blocks`.
279
285
 
package/dist/cli.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * waygraph CLI -- tooling around this same package's engine.
4
4
  *
5
5
  * Primary verbs (less is more):
6
- * auto Explore picker; `--blocks From To` = graph path-find + run
6
+ * auto Explore picker; `.flow.ts` / `--blocks From To` = run that path
7
7
  * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--auto-play-video`
8
8
  * run Execute; `--blocks` `--data` `--non-headless` `--video`
9
9
  *
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * waygraph CLI -- tooling around this same package's engine.
4
4
  *
5
5
  * Primary verbs (less is more):
6
- * auto Explore picker; `--blocks From To` = graph path-find + run
6
+ * auto Explore picker; `.flow.ts` / `--blocks From To` = run that path
7
7
  * demo Watch (step overlay); `--blocks` `--data` `--auto-next` `--auto-play-video`
8
8
  * run Execute; `--blocks` `--data` `--non-headless` `--video`
9
9
  *
@@ -12,7 +12,7 @@
12
12
  *
13
13
  * "project" defaults to cwd. Flags beat WAYGRAPH_* env.
14
14
  */
15
- import { existsSync, readdirSync, readFileSync, writeFileSync, rmSync, cpSync, mkdirSync, mkdtempSync } from "node:fs";
15
+ import { existsSync, readdirSync, readFileSync, writeFileSync, rmSync, cpSync, mkdirSync, mkdtempSync, statSync } from "node:fs";
16
16
  import { resolve, relative, join, basename, dirname } from "node:path";
17
17
  import { tmpdir } from "node:os";
18
18
  import { fileURLToPath } from "node:url";
@@ -2020,9 +2020,14 @@ async function main() {
2020
2020
  const flow = await findFlow(projectDir, bareRef);
2021
2021
  if (flow && typeof flow.blocks === "function") {
2022
2022
  const blockInfos = flow.blocks();
2023
+ // Same --data / WAYGRAPH_DATA seeding as the multi-segment path.
2024
+ // Without this, \`waygraph run shop.flow.ts --data '{...}'\` (and bare
2025
+ // export names) hit preflight with an empty MemPage.
2026
+ const seedMem = () => seedMemForFlow(mem, blockInfos, undefined, bareRef);
2023
2027
  resolved = blockInfos.map((bi, idx) => ({
2024
2028
  block: bi.block,
2025
2029
  exportName: bi.name,
2030
+ seedMem: idx === 0 ? seedMem : undefined,
2026
2031
  expectedFailureReason:
2027
2032
  flow.expectedFailureReason && idx === blockInfos.length - 1
2028
2033
  ? flow.expectedFailureReason
@@ -2291,6 +2296,7 @@ main().catch((err) => {
2291
2296
  * `WAYGRAPH_SLOWMO=<ms>` pass straight through from this process's own env.
2292
2297
  */
2293
2298
  async function runChain(projectDir, spec) {
2299
+ const expanded = await expandSpecFlowFiles(projectDir, spec);
2294
2300
  if (process.env.WAYGRAPH_VIDEO && !process.env.WAYGRAPH_BASE_URL) {
2295
2301
  const resolved = resolveBaseUrl(projectDir);
2296
2302
  if (resolved)
@@ -2301,7 +2307,7 @@ async function runChain(projectDir, spec) {
2301
2307
  writeFileSync(scriptPath, CHAIN_RUNNER_SCRIPT);
2302
2308
  try {
2303
2309
  const code = await new Promise((res, rej) => {
2304
- const child = spawn(process.execPath, ["--import", tsxEsm, scriptPath, projectDir, spec], {
2310
+ const child = spawn(process.execPath, ["--import", tsxEsm, scriptPath, projectDir, expanded], {
2305
2311
  stdio: "inherit",
2306
2312
  env: process.env,
2307
2313
  });
@@ -2969,22 +2975,100 @@ function resolveProjectDir(flags, fallbackIndex = 0) {
2969
2975
  function resolveSpec(flags) {
2970
2976
  return flags.blocks ?? flags.positionals[0];
2971
2977
  }
2978
+ /** `shop.flow.ts` / `src/flows/cart-bulk.flow.ts` / absolute path — not a Checkpoint or export id. */
2979
+ function looksLikeFlowFileRef(ref) {
2980
+ if (!ref || ref.includes("then") || ref.includes("("))
2981
+ return false;
2982
+ return /\.flow\.ts$/i.test(ref) || (/\.ts$/i.test(ref) && (ref.includes("/") || ref.includes("\\")));
2983
+ }
2984
+ function flowExportNameFromBasename(filePath) {
2985
+ const base = basename(filePath).replace(/\.flow\.ts$/i, "").replace(/\.ts$/i, "");
2986
+ const camel = base.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
2987
+ return /Flow$/i.test(camel) ? camel : `${camel}Flow`;
2988
+ }
2989
+ /**
2990
+ * Map a `.flow.ts` path (relative to project or absolute) to its Flow export
2991
+ * name so the chain runner can `findFlow(exportName)`. Prefers
2992
+ * `shop.flow.ts` → `shopFlow`. Reads source only (no import) so the outer CLI
2993
+ * does not dual-load `@playwright/test` against the project tree.
2994
+ */
2995
+ async function resolveFlowFileToExport(projectDir, fileArg) {
2996
+ let abs = resolve(projectDir, fileArg);
2997
+ if (!existsSync(abs)) {
2998
+ const want = basename(fileArg);
2999
+ const hits = walkDir(projectDir, new RegExp(`${want.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`));
3000
+ if (hits.length === 1)
3001
+ abs = hits[0];
3002
+ else if (hits.length === 0) {
3003
+ throw new Error(`waygraph: no flow file "${fileArg}" under ${projectDir}`);
3004
+ }
3005
+ else {
3006
+ throw new Error(`waygraph: "${fileArg}" matches ${hits.length} files - use a path from \`waygraph list\``);
3007
+ }
3008
+ }
3009
+ const expected = flowExportNameFromBasename(abs);
3010
+ const src = readFileSync(abs, "utf-8");
3011
+ if (new RegExp(`\\bexport\\s+(?:const|let|var|async\\s+function|function)\\s+${expected}\\b`).test(src)) {
3012
+ return expected;
3013
+ }
3014
+ if (new RegExp(`\\bexport\\s*\\{[^}]*\\b${expected}\\b`).test(src)) {
3015
+ return expected;
3016
+ }
3017
+ const flowNames = [
3018
+ ...src.matchAll(/\bexport\s+(?:const|let|var|async\s+function|function)\s+(\w*Flow)\b/g),
3019
+ ].map((m) => m[1]);
3020
+ const unique = [...new Set(flowNames)];
3021
+ if (unique.length === 1)
3022
+ return unique[0];
3023
+ if (unique.includes(expected))
3024
+ return expected;
3025
+ if (unique.length === 0) {
3026
+ throw new Error(`waygraph: ${relative(projectDir, abs)} has no exported *Flow (expected ${expected})`);
3027
+ }
3028
+ throw new Error(`waygraph: ${relative(projectDir, abs)} exports ${unique.join(", ")} - pass the export name or rename to ${expected}`);
3029
+ }
3030
+ /**
3031
+ * Expand `.flow.ts` path segments in a run/demo spec to export names.
3032
+ * `src/flows/shop.flow.ts` → `shopFlow`; leaves `loginFlow then nav-cart` alone.
3033
+ */
3034
+ async function expandSpecFlowFiles(projectDir, spec) {
3035
+ const parts = spec.split(/\bthen\b/).map((s) => s.trim()).filter((s) => s.length > 0);
3036
+ const out = [];
3037
+ for (const part of parts) {
3038
+ const m = /^([^\s(]+)(\s*\([\s\S]*\))?$/.exec(part);
3039
+ if (!m) {
3040
+ out.push(part);
3041
+ continue;
3042
+ }
3043
+ const ref = m[1];
3044
+ const payload = m[2] ?? "";
3045
+ if (looksLikeFlowFileRef(ref)) {
3046
+ const exportName = await resolveFlowFileToExport(projectDir, ref);
3047
+ out.push(exportName + payload);
3048
+ }
3049
+ else {
3050
+ out.push(part);
3051
+ }
3052
+ }
3053
+ return out.join(" then ");
3054
+ }
2972
3055
  const args = process.argv.slice(2);
2973
3056
  const command = args[0];
2974
3057
  function usage() {
2975
3058
  console.log(`waygraph -- graph project tool + engine CLI
2976
3059
 
2977
3060
  Primary (less is more):
2978
- waygraph auto [project] Interactive explore (picker)
3061
+ waygraph auto [project|.flow.ts] Interactive explore (picker)
2979
3062
  --cli Terminal menu instead of browser panel
2980
3063
  --blocks <From> <To> Graph path-find From->To, then run
2981
3064
  --data '{...}' Mem seed (same as demo/run)
2982
- waygraph demo [--blocks <flow|spec>] Watch with step overlay (QA path)
3065
+ (pass a .flow.ts to run that flow - same as run)
3066
+ waygraph demo [--blocks <flow|file|spec>] Watch with step overlay (QA path)
2983
3067
  --data '{...}' Mem seed JSON (or inline flow({...}))
2984
3068
  --auto-next Auto-advance steps (alias: --autoplay)
2985
3069
  --auto-play-video QA: --auto-next + --video (+ step)
2986
3070
  --title / --base-url
2987
- waygraph run [--blocks <flow|spec>] Execute (no overlay unless --step)
3071
+ waygraph run [--blocks <flow|file|spec>] Execute (no overlay unless --step)
2988
3072
  --data '{...}'
2989
3073
  --non-headless Show browser
2990
3074
  --video [dir] Record .webm
@@ -2995,18 +3079,21 @@ Also:
2995
3079
  try auto --headed Browser panel instead of CLI
2996
3080
 
2997
3081
  Examples:
3082
+ waygraph list # file → export map
3083
+ waygraph run src/flows/shop.flow.ts --data '{...}'
3084
+ waygraph run --blocks shopFlow --non-headless --video
3085
+ waygraph demo --blocks src/flows/cart-bulk.flow.ts --auto-next
2998
3086
  waygraph try auto:cli
3087
+ waygraph auto src/flows/shop.flow.ts --data '{...}' # run by file (same as run)
2999
3088
  waygraph auto --cli --data '{"saucedemo.credentials":{...}}'
3000
3089
  waygraph auto --blocks LoginPage OrderComplete
3001
- waygraph demo --blocks shopFlow --auto-next
3002
- waygraph demo --blocks cartBulkFlow --data '{"saucedemo.credentials":{...}}' --auto-play-video
3003
3090
  waygraph run --blocks "loginFlow then add-all-to-cart" --data '{...}' --video
3004
- waygraph run --blocks shopFlow --non-headless --video
3005
3091
 
3006
3092
  Aliases (compat): \`chain <spec>\` -> run --blocks; \`chain auto A B\` -> auto --blocks A B;
3007
3093
  --autoplay -> --auto-next. Prefer the primary verbs above.
3008
3094
 
3009
3095
  Project path optional (defaults to cwd). Flags beat WAYGRAPH_* env.
3096
+ \`run\`/\`demo\`/\`auto\`: Flow export, .flow.ts path, or "a then b" chain (auto also explores).
3010
3097
  `);
3011
3098
  process.exit(0);
3012
3099
  }
@@ -3053,7 +3140,10 @@ async function main() {
3053
3140
  applyRunFlags(flags);
3054
3141
  const spec = resolveSpec(flags);
3055
3142
  if (!spec) {
3056
- console.error('waygraph run: missing flow/spec — e.g. waygraph run --blocks shopFlow\n' +
3143
+ console.error("waygraph run: missing flow/spec — e.g.\n" +
3144
+ " waygraph run src/flows/shop.flow.ts\n" +
3145
+ " waygraph run --blocks shopFlow\n" +
3146
+ " waygraph list # file → export map\n" +
3057
3147
  " Flags: --blocks --data --non-headless --video [dir] --step/--no-step");
3058
3148
  process.exit(1);
3059
3149
  }
@@ -3121,7 +3211,9 @@ async function main() {
3121
3211
  applyRunFlags(flags, { allowAutoPlayVideo: true });
3122
3212
  const spec = resolveSpec(flags);
3123
3213
  if (!spec) {
3124
- console.error('waygraph demo: missing flow/spec — e.g. waygraph demo --blocks shopFlow\n' +
3214
+ console.error("waygraph demo: missing flow/spec — e.g.\n" +
3215
+ " waygraph demo src/flows/shop.flow.ts\n" +
3216
+ " waygraph demo --blocks shopFlow\n" +
3125
3217
  " Flags: --blocks --data --auto-next --auto-play-video --title --base-url --video");
3126
3218
  process.exit(1);
3127
3219
  }
@@ -3151,11 +3243,35 @@ async function main() {
3151
3243
  const mermaid = flags.mermaid === true;
3152
3244
  const mapOnly = command === "graph" || flags.map === true;
3153
3245
  const cliPicker = flags.cli === true;
3154
- const proj = resolve(flags.positionals[0] ?? process.cwd());
3246
+ const firstPos = flags.positionals[0];
3247
+ // auto src/flows/shop.flow.ts → run that flow (same as `run`). Do not
3248
+ // treat a .flow.ts path as a project directory (existsSync is true for files).
3249
+ if (command === "auto" && firstPos && looksLikeFlowFileRef(firstPos) && !flags.blocksFromTo) {
3250
+ const proj = resolve(process.cwd());
3251
+ if (!existsSync(proj) || !statSync(proj).isDirectory()) {
3252
+ console.error(`waygraph: no such directory: ${proj}`);
3253
+ process.exit(1);
3254
+ }
3255
+ if (!process.env.WAYGRAPH_BASE_URL) {
3256
+ const resolved = resolveBaseUrl(proj);
3257
+ if (resolved)
3258
+ process.env.WAYGRAPH_BASE_URL = resolved;
3259
+ }
3260
+ await runChain(proj, firstPos);
3261
+ break;
3262
+ }
3263
+ const proj = resolve(firstPos ?? process.cwd());
3155
3264
  if (!existsSync(proj)) {
3156
3265
  console.error(`waygraph: no such directory: ${proj}`);
3157
3266
  process.exit(1);
3158
3267
  }
3268
+ if (!statSync(proj).isDirectory()) {
3269
+ console.error(`waygraph ${command}: "${firstPos}" is a file, not a project directory\n` +
3270
+ " run a flow: waygraph auto src/flows/shop.flow.ts\n" +
3271
+ " or explore: waygraph auto\n" +
3272
+ " or path-find: waygraph auto --blocks LoginPage OrderComplete");
3273
+ process.exit(1);
3274
+ }
3159
3275
  // auto --blocks From To (graph path-find + run)
3160
3276
  if (command === "auto" && (flags.blocksFromTo || flags.blocks)) {
3161
3277
  if (flags.blocksFromTo) {
@@ -3165,7 +3281,8 @@ async function main() {
3165
3281
  }
3166
3282
  console.error("waygraph auto --blocks expects <fromCheckpoint> <toCheckpoint>\n" +
3167
3283
  ' e.g. waygraph auto --blocks LoginPage OrderComplete\n' +
3168
- ' for a hand-named chain use: waygraph run --blocks "a then b"');
3284
+ ' for a hand-named chain use: waygraph run --blocks "a then b"\n' +
3285
+ " or: waygraph auto src/flows/shop.flow.ts");
3169
3286
  process.exit(1);
3170
3287
  }
3171
3288
  if (mapOnly || mermaid) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waygraph",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "type": "module",
5
5
  "description": "A typed graph of reusable Blocks for driving a browser through E2E flows.",
6
6
  "license": "MIT",
@@ -23,7 +23,7 @@
23
23
  "baseUrl": "https://www.saucedemo.com"
24
24
  },
25
25
  "dependencies": {
26
- "waygraph": "^0.10.2"
26
+ "waygraph": "^0.10.5"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@playwright/test": "^1.55.0",