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 +10 -4
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +130 -13
- package/package.json +1 -1
- package/templates/quickstart/package.json +1 -1
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.
|
|
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) — `
|
|
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.
|
|
265
|
+
**Friendly demo / run / auto (0.10.5 — flows are files):**
|
|
266
266
|
|
|
267
267
|
| Want | Command |
|
|
268
268
|
|------|---------|
|
|
269
|
-
|
|
|
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` =
|
|
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` =
|
|
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,
|
|
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]
|
|
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
|
-
|
|
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>]
|
|
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(
|
|
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(
|
|
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
|
|
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