waygraph 0.15.51 → 0.15.52
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/dist/analysis/graph.d.ts +1 -1
- package/dist/analysis/graph.js +10 -15
- package/dist/analysis/practices-check.d.ts +0 -1
- package/dist/analysis/practices-check.js +0 -13
- package/dist/auto-session/session.d.ts +1 -1
- package/dist/auto-session/session.js +13 -8
- package/dist/blocks-select.d.ts +7 -4
- package/dist/blocks-select.js +0 -14
- package/dist/browser.d.ts +27 -0
- package/dist/browser.js +62 -0
- package/dist/cli/run-chain.js +4 -0
- package/dist/cli/usage.js +14 -1
- package/dist/cli.js +10 -1
- package/dist/commands/agent-dive-scaffold.d.ts +5 -0
- package/dist/commands/agent-dive-scaffold.js +7 -0
- package/dist/commands/extensions.d.ts +1 -0
- package/dist/commands/extensions.js +22 -0
- package/dist/commands/init.js +42 -4
- package/dist/commands/test.js +16 -2
- package/dist/commands/try.js +2 -1
- package/dist/commands/upgrade.d.ts +6 -0
- package/dist/commands/upgrade.js +107 -0
- package/dist/config.d.ts +47 -3
- package/dist/config.js +45 -12
- package/dist/core/block.d.ts +12 -0
- package/dist/core/block.js +36 -0
- package/dist/core/blocks/nav.d.ts +11 -0
- package/dist/core/blocks/nav.js +8 -0
- package/dist/core/config.d.ts +5 -0
- package/dist/core/engine.d.ts +17 -16
- package/dist/core/engine.js +3 -3
- package/dist/core/flow.d.ts +17 -0
- package/dist/core/flow.js +44 -21
- package/dist/core/map-builder.d.ts +3 -3
- package/dist/core/map-builder.js +3 -3
- package/dist/env-file.d.ts +7 -7
- package/dist/env-file.js +29 -16
- package/dist/extensions.d.ts +21 -0
- package/dist/extensions.js +178 -0
- package/dist/index.d.ts +6 -4
- package/dist/index.js +3 -2
- package/dist/pilot-overlay/overlay-fixtures.d.ts +0 -7
- package/dist/pilot-overlay/overlay-fixtures.js +0 -45
- package/dist/playwright-config.d.ts +4 -0
- package/dist/playwright-config.js +15 -0
- package/dist/playwright.config.d.ts +2 -0
- package/dist/playwright.config.js +6 -0
- package/dist/runner/demo-log.js +1 -7
- package/dist/runner/inpage/core.d.ts +1 -1
- package/dist/runner/inpage/core.js +8 -7
- package/dist/runner/main.js +18 -10
- package/dist/runner/rings.js +0 -49
- package/dist/session/auto-explore-run.js +9 -5
- package/dist/session/auto-explore.js +6 -4
- package/dist/session/step-overlay.js +3 -1
- package/dist/traverse/run.js +3 -1
- package/dist/ui/components/ring.js +1 -0
- package/dist/ui/overlay.css +1 -0
- package/dist/ui/shadow.d.ts +1 -3
- package/dist/ui/shadow.js +0 -4
- package/dist/ui/tokens.d.ts +0 -11
- package/dist/ui/tokens.js +0 -5
- package/dist/video-dir.d.ts +8 -0
- package/dist/video-dir.js +60 -0
- package/dist/webserver.js +5 -1
- package/package.json +1 -1
- package/templates/scaffold/README.md +4 -2
- package/templates/scaffold/STRUCTURE.md +5 -2
- package/templates/scaffold/config/waygraph.config.ts +26 -0
- package/templates/scaffold/package.json +1 -1
- package/templates/scaffold/src/flows/README.md +5 -5
- package/templates/scaffold/src/flows/example.flow.ts +31 -17
- package/templates/scaffold/src/flows/hoppscotch-auth.flow.ts +35 -0
- package/templates/scaffold/src/flows/hoppscotch-get.flow.ts +23 -0
- package/templates/scaffold/src/flows/hoppscotch-post.flow.ts +33 -0
- package/templates/scaffold/src/flows/mail-verify.flow.ts +36 -75
- package/templates/scaffold/src/flows/shop.flow.ts +41 -65
- package/templates/scaffold/src/map/(app_base)/docs/_nav.block.ts +0 -9
- package/templates/scaffold/src/map/(app_base)/docs/_page.block.ts +0 -5
- package/templates/scaffold/src/map/(app_base)/docs/_sel.ts +1 -1
- package/templates/scaffold/src/map/(app_base)/home/_methods/add-item.effect.block.ts +3 -19
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-hello.method.block.ts +2 -25
- package/templates/scaffold/src/map/(app_base)/home/_methods/assert-item-added.method.block.ts +2 -19
- package/templates/scaffold/src/map/(app_base)/home/_methods/clear-cart.method.block.ts +1 -17
- package/templates/scaffold/src/map/(app_base)/home/_methods/remove-item.effect.block.ts +1 -10
- package/templates/scaffold/src/map/(app_base)/home/_nav.block.ts +1 -12
- package/templates/scaffold/src/map/(app_base)/home/_page.block.ts +0 -9
- package/templates/scaffold/src/map/(app_base)/home/_sel.ts +3 -1
- package/templates/scaffold/src/map/(app_base)/home/nav-verification-link.block.ts +1 -23
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-body.method.block.ts +21 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-response-header.method.block.ts +19 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/assert-status.method.block.ts +20 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/pick-extension-interceptor.method.block.ts +18 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-body.method.block.ts +21 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/read-response-headers.method.block.ts +21 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/send-request.method.block.ts +15 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-bearer-token.method.block.ts +20 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-headers.method.block.ts +23 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-json-body.method.block.ts +36 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-method.method.block.ts +17 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-query-params.method.block.ts +22 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_methods/set-url.method.block.ts +18 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_nav.block.ts +11 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_page.block.ts +36 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/_sel.ts +27 -0
- package/templates/scaffold/src/map/(external)/hoppscotch/settings/_nav.block.ts +10 -0
- package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-content.method.block.ts +2 -27
- package/templates/scaffold/src/map/(external)/mailpit/_methods/assert-email-received.method.block.ts +2 -21
- package/templates/scaffold/src/map/(external)/mailpit/_methods/extract-email-link.method.block.ts +1 -19
- package/templates/scaffold/src/map/(external)/mailpit/_methods/open-message.method.block.ts +1 -11
- package/templates/scaffold/src/map/(external)/mailpit/_nav.block.ts +1 -9
- package/templates/scaffold/src/map/(external)/mailpit/_page.block.ts +0 -11
- package/templates/scaffold/src/states/demo.mem-keys.ts +20 -19
- package/templates/scaffold/src/states/demo.mem-stubs.ts +4 -0
- package/templates/scaffold/src/states/demo.states.ts +5 -1
- package/templates/scaffold/tests/example.spec.ts +2 -3
- package/templates/scaffold/tests/hoppscotch.spec.ts +97 -0
- package/templates/scaffold/tests/mail-verify.spec.ts +2 -11
- package/templates/scaffold/tsconfig.json +1 -1
- package/templates/skills/waygraph-convention.skill.md +32 -3
- package/templates/skills/waygraph-pilot.skill.md +4 -5
- package/dist/browser-path.d.ts +0 -5
- package/dist/browser-path.js +0 -18
- package/dist/runner/overlay-present.d.ts +0 -8
- package/dist/runner/overlay-present.js +0 -57
- package/dist/ui/components/banner.d.ts +0 -2
- package/dist/ui/components/banner.js +0 -10
- package/dist/ui/components/cursor.d.ts +0 -2
- package/dist/ui/components/cursor.js +0 -19
- package/templates/scaffold/playwright.config.ts +0 -12
package/dist/core/flow.js
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
// Split out of the former 2,900-line engine.ts (see src/ARCHITECTURE.md). Behavior unchanged.
|
|
2
2
|
import { connect } from "../types.js";
|
|
3
|
-
import { chromiumExecutablePath } from "../browser
|
|
3
|
+
import { chromiumExecutablePath, extensionPathsFromEnv, launchContext } from "../browser.js";
|
|
4
|
+
import { resolveExtensions } from "../extensions.js";
|
|
4
5
|
import { WaygraphError, MSG_CHAINFLOW_BLOCK_NOT_FOUND_BY_INDEX, MSG_CHAINFLOW_BLOCK_NOT_FOUND_BY_NAME, MSG_CHAINFLOW_EMPTY, } from "../errors.js";
|
|
5
6
|
import { preflight, runGraph } from "./run-graph.js";
|
|
6
7
|
import { resolveLauncher } from "./config.js";
|
|
7
8
|
import { MemPage } from "../mem-page.js";
|
|
8
9
|
import { findBlockIndex, modVerify, withVerify } from "./block.js";
|
|
9
10
|
import { normalizeHighlightSize, normalizeHighlightTone, normalizeHighlightWeight } from "../highlights.js";
|
|
11
|
+
/** Launches the browser and context an Engine uses when the caller gave none, from its config. */
|
|
12
|
+
async function openOwnContext(config) {
|
|
13
|
+
const browserName = config?.browserName ?? "chromium";
|
|
14
|
+
const executablePath = browserName === "chromium" ? chromiumExecutablePath() : undefined;
|
|
15
|
+
const baseURL = config?.baseUrl ?? process.env.WAYGRAPH_BASE_URL;
|
|
16
|
+
return launchContext({
|
|
17
|
+
launcher: resolveLauncher(browserName, config),
|
|
18
|
+
launch: {
|
|
19
|
+
headless: config?.headless ?? true,
|
|
20
|
+
...(config?.slowMo !== undefined ? { slowMo: config.slowMo } : {}),
|
|
21
|
+
...(executablePath ? { executablePath } : {}),
|
|
22
|
+
},
|
|
23
|
+
context: {
|
|
24
|
+
...(baseURL ? { baseURL } : {}),
|
|
25
|
+
...(config?.recordVideo ? { recordVideo: config.recordVideo } : {}),
|
|
26
|
+
},
|
|
27
|
+
extensions: await resolveExtensions(config?.extensions ?? extensionPathsFromEnv(), process.cwd()),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
10
30
|
export function buildFlow(middle, engineConfig) {
|
|
11
31
|
// resetSession/title start false/unset here - defineFlow itself carries
|
|
12
32
|
// no such options anymore. They're set by wrapping the result in
|
|
@@ -32,16 +52,8 @@ export function buildFlow(middle, engineConfig) {
|
|
|
32
52
|
// before a page opens - the same "fail loud, cheaply" guarantee
|
|
33
53
|
// run(context, mem) already has via an existing context.
|
|
34
54
|
preflight(mem, chain);
|
|
35
|
-
const
|
|
36
|
-
const launch = resolveLauncher(browserName, config);
|
|
37
|
-
const executablePath = browserName === "chromium" ? chromiumExecutablePath() : undefined;
|
|
38
|
-
const browser = await launch.launch({
|
|
39
|
-
headless: config.headless ?? true,
|
|
40
|
-
...(config.slowMo !== undefined ? { slowMo: config.slowMo } : {}),
|
|
41
|
-
...(executablePath ? { executablePath } : {}),
|
|
42
|
-
});
|
|
55
|
+
const { context, close } = await openOwnContext(config);
|
|
43
56
|
try {
|
|
44
|
-
const context = await browser.newContext(config.recordVideo ? { recordVideo: config.recordVideo } : {});
|
|
45
57
|
try {
|
|
46
58
|
return await runGraph(chain, undefined, context, mem, 5000, {
|
|
47
59
|
...(config.layouts !== undefined ? { layouts: config.layouts } : {}),
|
|
@@ -56,7 +68,7 @@ export function buildFlow(middle, engineConfig) {
|
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
70
|
finally {
|
|
59
|
-
await
|
|
71
|
+
await close();
|
|
60
72
|
}
|
|
61
73
|
}
|
|
62
74
|
const context = contextOrMem;
|
|
@@ -256,6 +268,25 @@ export function withHighlightFixtures(flow, fixtures) {
|
|
|
256
268
|
modBlockVerify: (block, nameOrIndex, newCheck) => withHighlightFixtures(flow.modBlockVerify(block, nameOrIndex, newCheck), fixtures),
|
|
257
269
|
};
|
|
258
270
|
}
|
|
271
|
+
/** Applies each set option through its `with*` wrapper, so both spellings produce the same Flow. */
|
|
272
|
+
export function applyFlowOptions(flow, options) {
|
|
273
|
+
if (!options)
|
|
274
|
+
return flow;
|
|
275
|
+
let f = flow;
|
|
276
|
+
if (options.sessionReset)
|
|
277
|
+
f = withSessionReset(f);
|
|
278
|
+
if (options.title !== undefined)
|
|
279
|
+
f = withTitle(f, options.title);
|
|
280
|
+
if (options.demoPace)
|
|
281
|
+
f = withDemoPace(f, options.demoPace);
|
|
282
|
+
if (options.highlightStyle)
|
|
283
|
+
f = withHighlightStyle(f, options.highlightStyle);
|
|
284
|
+
if (options.expectedFailure !== undefined)
|
|
285
|
+
f = withExpectedFailure(f, options.expectedFailure);
|
|
286
|
+
if (options.memStub)
|
|
287
|
+
f = withMemStub(f);
|
|
288
|
+
return f;
|
|
289
|
+
}
|
|
259
290
|
function locateInChain(flows, block) {
|
|
260
291
|
if (typeof block === "number") {
|
|
261
292
|
let remaining = block;
|
|
@@ -319,16 +350,8 @@ export function chainFlow(...flows) {
|
|
|
319
350
|
// config) would otherwise each launch a separate browser, defeating
|
|
320
351
|
// the point of sequencing them (a fresh browser is itself a full
|
|
321
352
|
// session reset, silently making withSessionReset a no-op).
|
|
322
|
-
const
|
|
323
|
-
const launch = resolveLauncher(browserName, config);
|
|
324
|
-
const executablePath = browserName === "chromium" ? chromiumExecutablePath() : undefined;
|
|
325
|
-
const browser = await launch.launch({
|
|
326
|
-
headless: config?.headless ?? true,
|
|
327
|
-
...(config?.slowMo !== undefined ? { slowMo: config.slowMo } : {}),
|
|
328
|
-
...(executablePath ? { executablePath } : {}),
|
|
329
|
-
});
|
|
353
|
+
const { context, close } = await openOwnContext(config);
|
|
330
354
|
try {
|
|
331
|
-
const context = await browser.newContext(config?.recordVideo ? { recordVideo: config.recordVideo } : {});
|
|
332
355
|
try {
|
|
333
356
|
return await run(context, mem, { closeOnFinish: true });
|
|
334
357
|
}
|
|
@@ -337,7 +360,7 @@ export function chainFlow(...flows) {
|
|
|
337
360
|
}
|
|
338
361
|
}
|
|
339
362
|
finally {
|
|
340
|
-
await
|
|
363
|
+
await close();
|
|
341
364
|
}
|
|
342
365
|
}
|
|
343
366
|
const context = contextOrMem;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Block, Checkpoint } from "../types.js";
|
|
2
2
|
import type { EndMarker, StartMarker } from "./run-graph.js";
|
|
3
|
-
import type { Flow } from "./flow.js";
|
|
3
|
+
import type { Flow, FlowOptions } from "./flow.js";
|
|
4
4
|
import type { NavBlock } from "./blocks/nav.js";
|
|
5
5
|
import type { PageBlock } from "./blocks/page.js";
|
|
6
6
|
import type { AssertBlock, MethodBlock } from "./blocks/method.js";
|
|
@@ -12,7 +12,7 @@ type S = Checkpoint<"__start__">;
|
|
|
12
12
|
* import each other (Engine.map() builds a MapBuilder; MapBuilder.end() calls back into
|
|
13
13
|
* defineFlow) - a real cycle check:structure forbids. This type is how that's avoided.
|
|
14
14
|
*/
|
|
15
|
-
export type DefineFlowFn = (blocks: readonly [StartMarker, ...Block<any, any>[], EndMarker]) => Flow<any>;
|
|
15
|
+
export type DefineFlowFn = (blocks: readonly [StartMarker, ...Block<any, any>[], EndMarker], options?: FlowOptions) => Flow<any>;
|
|
16
16
|
/** Per-step options every `MapBuilder` step method (`.gotoPage()`/`.method()`/`.assert()`) takes. */
|
|
17
17
|
export interface MapStepOpts {
|
|
18
18
|
/**
|
|
@@ -166,6 +166,6 @@ export declare class MapBuilder<Out extends Checkpoint<string>> {
|
|
|
166
166
|
* left open at `.end()` closes silently; an EXPLICIT `.ffStart()` left open
|
|
167
167
|
* still throws - the author opened that one and needs to close it themselves.
|
|
168
168
|
*/
|
|
169
|
-
end(): Flow<Out>;
|
|
169
|
+
end(options?: FlowOptions): Flow<Out>;
|
|
170
170
|
}
|
|
171
171
|
export {};
|
package/dist/core/map-builder.js
CHANGED
|
@@ -329,17 +329,17 @@ export class MapBuilder {
|
|
|
329
329
|
* left open at `.end()` closes silently; an EXPLICIT `.ffStart()` left open
|
|
330
330
|
* still throws - the author opened that one and needs to close it themselves.
|
|
331
331
|
*/
|
|
332
|
-
end() {
|
|
332
|
+
end(options) {
|
|
333
333
|
if (this.ff && !this.ff.auto) {
|
|
334
334
|
throw new WaygraphError("WG_MAP_FF_NOT_CLOSED", MSG_MAP_FF_NOT_CLOSED(this.ff.name));
|
|
335
335
|
}
|
|
336
336
|
if (this.ff && this.ff.auto) {
|
|
337
|
-
return this.closeFf().end();
|
|
337
|
+
return this.closeFf().end(options);
|
|
338
338
|
}
|
|
339
339
|
if (this.steps.length === 0) {
|
|
340
340
|
throw new WaygraphError("WG_MAP_EMPTY", MSG_MAP_EMPTY);
|
|
341
341
|
}
|
|
342
342
|
const chain = [start, ...this.steps, end];
|
|
343
|
-
return this.defineFlow(chain);
|
|
343
|
+
return this.defineFlow(chain, options);
|
|
344
344
|
}
|
|
345
345
|
}
|
package/dist/env-file.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
/** The env mode: `--env <mode>` / `WAYGRAPH_ENV`, else `NODE_ENV`, else "development". */
|
|
2
|
+
export declare function envMode(): string;
|
|
3
|
+
/** Removes `--env <mode>` / `--env=<mode>` from argv and records it as `WAYGRAPH_ENV`, so it never reaches a forwarded command. */
|
|
4
|
+
export declare function takeEnvFlag(argv: string[]): string[];
|
|
1
5
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* whatever the shell actually exported - always wins over every file; a `.env` file only fills
|
|
6
|
-
* a gap, it never silently overrides something the caller explicitly set. Safe to call more
|
|
7
|
-
* than once (e.g. once eagerly at `cwd`, again once the real project dir is known) - loading
|
|
8
|
-
* twice is a no-op the second time for any key already filled.
|
|
6
|
+
* Loads `.env` / `.env.local` / `.env.<mode>` / `.env.<mode>.local` from a project's root, the layering
|
|
7
|
+
* Vite and Next use: each later file overrides the earlier ones (`.env.<mode>.local` wins). A value the
|
|
8
|
+
* shell already exported always beats every file, so a file only fills gaps. Calling it twice is harmless.
|
|
9
9
|
*/
|
|
10
10
|
export declare function loadEnvFiles(projectDir: string, mode?: string): void;
|
package/dist/env-file.js
CHANGED
|
@@ -28,31 +28,44 @@ function parseEnvFile(content) {
|
|
|
28
28
|
}
|
|
29
29
|
return out;
|
|
30
30
|
}
|
|
31
|
+
/** The env mode: `--env <mode>` / `WAYGRAPH_ENV`, else `NODE_ENV`, else "development". */
|
|
32
|
+
export function envMode() {
|
|
33
|
+
return process.env.WAYGRAPH_ENV || process.env.NODE_ENV || "development";
|
|
34
|
+
}
|
|
35
|
+
/** Removes `--env <mode>` / `--env=<mode>` from argv and records it as `WAYGRAPH_ENV`, so it never reaches a forwarded command. */
|
|
36
|
+
export function takeEnvFlag(argv) {
|
|
37
|
+
const out = [];
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const a = argv[i];
|
|
40
|
+
if (a === "--env" && argv[i + 1] !== undefined)
|
|
41
|
+
process.env.WAYGRAPH_ENV = argv[++i];
|
|
42
|
+
else if (a.startsWith("--env="))
|
|
43
|
+
process.env.WAYGRAPH_ENV = a.slice("--env=".length);
|
|
44
|
+
else
|
|
45
|
+
out.push(a);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
31
49
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* whatever the shell actually exported - always wins over every file; a `.env` file only fills
|
|
36
|
-
* a gap, it never silently overrides something the caller explicitly set. Safe to call more
|
|
37
|
-
* than once (e.g. once eagerly at `cwd`, again once the real project dir is known) - loading
|
|
38
|
-
* twice is a no-op the second time for any key already filled.
|
|
50
|
+
* Loads `.env` / `.env.local` / `.env.<mode>` / `.env.<mode>.local` from a project's root, the layering
|
|
51
|
+
* Vite and Next use: each later file overrides the earlier ones (`.env.<mode>.local` wins). A value the
|
|
52
|
+
* shell already exported always beats every file, so a file only fills gaps. Calling it twice is harmless.
|
|
39
53
|
*/
|
|
40
|
-
export function loadEnvFiles(projectDir, mode =
|
|
41
|
-
const
|
|
42
|
-
for (const name of
|
|
54
|
+
export function loadEnvFiles(projectDir, mode = envMode()) {
|
|
55
|
+
const merged = {};
|
|
56
|
+
for (const name of [".env", ".env.local", `.env.${mode}`, `.env.${mode}.local`]) {
|
|
43
57
|
const p = join(projectDir, name);
|
|
44
58
|
if (!existsSync(p))
|
|
45
59
|
continue;
|
|
46
|
-
let parsed;
|
|
47
60
|
try {
|
|
48
|
-
|
|
61
|
+
Object.assign(merged, parseEnvFile(readFileSync(p, "utf-8")));
|
|
49
62
|
}
|
|
50
63
|
catch {
|
|
51
64
|
continue; // unreadable file - never fail a run over an optional .env
|
|
52
65
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
66
|
+
}
|
|
67
|
+
for (const [key, value] of Object.entries(merged)) {
|
|
68
|
+
if (process.env[key] === undefined)
|
|
69
|
+
process.env[key] = value;
|
|
57
70
|
}
|
|
58
71
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** A known name (`"hoppscotch"`), a Chrome Web Store id, a path to an unpacked extension, or `{ id, sha256 }`. */
|
|
2
|
+
export type ExtensionSpec = string | {
|
|
3
|
+
id?: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
sha256?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare const KNOWN_EXTENSIONS: Readonly<Record<string, string>>;
|
|
8
|
+
/** Unpacks a .crx and pins the manifest `key`, so the extension keeps its store id (its origin is then stable, e.g. for an API allowlist). */
|
|
9
|
+
export declare function unpackCrx(crx: Buffer, dest: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* Returns an unpacked directory for one config entry, downloading it on first use. An installed
|
|
12
|
+
* extension is never silently replaced - pass `update: true` (`waygraph extensions install --update`) to refetch.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveExtension(spec: ExtensionSpec, projectDir: string, opts?: {
|
|
15
|
+
update?: boolean;
|
|
16
|
+
}): Promise<string>;
|
|
17
|
+
export declare function resolveExtensions(specs: readonly ExtensionSpec[] | undefined, projectDir: string): Promise<string[]>;
|
|
18
|
+
export declare function extensionInfo(dir: string): {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Browser extensions for waygraph runs: resolve a config entry to an unpacked directory Chromium can load.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
6
|
+
import { inflateRawSync } from "node:zlib";
|
|
7
|
+
export const KNOWN_EXTENSIONS = {
|
|
8
|
+
hoppscotch: "amknoiejhlmhancpahfcfcfhllgkpbld",
|
|
9
|
+
};
|
|
10
|
+
const STORE_ID = /^[a-p]{32}$/;
|
|
11
|
+
function cacheDir() {
|
|
12
|
+
const base = process.env.WAYGRAPH_CACHE_DIR ?? join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "waygraph");
|
|
13
|
+
return join(base, "extensions");
|
|
14
|
+
}
|
|
15
|
+
/** Reads the end-of-central-directory record and extracts every entry; rejects paths that escape `dest`. */
|
|
16
|
+
function unzipTo(zip, dest) {
|
|
17
|
+
let eocd = zip.length - 22;
|
|
18
|
+
while (eocd >= 0 && zip.readUInt32LE(eocd) !== 0x06054b50)
|
|
19
|
+
eocd--;
|
|
20
|
+
if (eocd < 0)
|
|
21
|
+
throw new Error("not a zip archive");
|
|
22
|
+
const count = zip.readUInt16LE(eocd + 10);
|
|
23
|
+
let p = zip.readUInt32LE(eocd + 16);
|
|
24
|
+
const root = resolve(dest);
|
|
25
|
+
for (let i = 0; i < count; i++) {
|
|
26
|
+
if (zip.readUInt32LE(p) !== 0x02014b50)
|
|
27
|
+
throw new Error("corrupt zip directory");
|
|
28
|
+
const method = zip.readUInt16LE(p + 10);
|
|
29
|
+
const size = zip.readUInt32LE(p + 20);
|
|
30
|
+
const nameLen = zip.readUInt16LE(p + 28);
|
|
31
|
+
const extraLen = zip.readUInt16LE(p + 30);
|
|
32
|
+
const commentLen = zip.readUInt16LE(p + 32);
|
|
33
|
+
const local = zip.readUInt32LE(p + 42);
|
|
34
|
+
const name = zip.toString("utf8", p + 46, p + 46 + nameLen);
|
|
35
|
+
p += 46 + nameLen + extraLen + commentLen;
|
|
36
|
+
const target = resolve(root, name);
|
|
37
|
+
if (target !== root && !target.startsWith(root + sep))
|
|
38
|
+
throw new Error(`unsafe path in archive: ${name}`);
|
|
39
|
+
if (name.endsWith("/")) {
|
|
40
|
+
mkdirSync(target, { recursive: true });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const dataStart = local + 30 + zip.readUInt16LE(local + 26) + zip.readUInt16LE(local + 28);
|
|
44
|
+
const raw = zip.subarray(dataStart, dataStart + size);
|
|
45
|
+
if (method !== 0 && method !== 8)
|
|
46
|
+
throw new Error(`unsupported zip compression (${method}) for ${name}`);
|
|
47
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
48
|
+
writeFileSync(target, method === 0 ? raw : inflateRawSync(raw));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** A .crx is a signed header followed by a normal zip. The signature is not checked here - pin `sha256` to do that. */
|
|
52
|
+
function crxToZip(crx) {
|
|
53
|
+
if (crx.toString("latin1", 0, 4) !== "Cr24")
|
|
54
|
+
throw new Error("not a Chrome extension (.crx) file");
|
|
55
|
+
const version = crx.readUInt32LE(4);
|
|
56
|
+
if (version === 3)
|
|
57
|
+
return crx.subarray(12 + crx.readUInt32LE(8));
|
|
58
|
+
if (version === 2)
|
|
59
|
+
return crx.subarray(16 + crx.readUInt32LE(8) + crx.readUInt32LE(12));
|
|
60
|
+
throw new Error(`unsupported .crx version ${version}`);
|
|
61
|
+
}
|
|
62
|
+
/** Reads the length-delimited fields of one protobuf message as [fieldNumber, bytes] pairs; other wire types are skipped. */
|
|
63
|
+
function protoFields(buf) {
|
|
64
|
+
const out = [];
|
|
65
|
+
let p = 0;
|
|
66
|
+
const varint = () => {
|
|
67
|
+
let n = 0;
|
|
68
|
+
for (let shift = 0;; shift += 7) {
|
|
69
|
+
const b = buf[p++];
|
|
70
|
+
n += (b & 0x7f) * 2 ** shift;
|
|
71
|
+
if (!(b & 0x80))
|
|
72
|
+
return n;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
while (p < buf.length) {
|
|
76
|
+
const tag = varint();
|
|
77
|
+
const wire = tag & 7;
|
|
78
|
+
if (wire === 2) {
|
|
79
|
+
const len = varint();
|
|
80
|
+
out.push([tag >>> 3, buf.subarray(p, p + len)]);
|
|
81
|
+
p += len;
|
|
82
|
+
}
|
|
83
|
+
else if (wire === 0)
|
|
84
|
+
varint();
|
|
85
|
+
else if (wire === 1)
|
|
86
|
+
p += 8;
|
|
87
|
+
else if (wire === 5)
|
|
88
|
+
p += 4;
|
|
89
|
+
else
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
/** The extension's public key (DER), from which Chrome derives its id: with it in the manifest the id equals the store's. */
|
|
95
|
+
function crxPublicKey(crx) {
|
|
96
|
+
const version = crx.readUInt32LE(4);
|
|
97
|
+
if (version === 2)
|
|
98
|
+
return crx.subarray(16, 16 + crx.readUInt32LE(8));
|
|
99
|
+
const header = protoFields(crx.subarray(12, 12 + crx.readUInt32LE(8)));
|
|
100
|
+
const signed = header.find(([n]) => n === 10000)?.[1];
|
|
101
|
+
const crxId = signed ? protoFields(signed).find(([n]) => n === 1)?.[1] : undefined;
|
|
102
|
+
for (const [n, proof] of header) {
|
|
103
|
+
if (n !== 2 && n !== 3)
|
|
104
|
+
continue;
|
|
105
|
+
const key = protoFields(proof).find(([f]) => f === 1)?.[1];
|
|
106
|
+
if (key && crxId && createHash("sha256").update(key).digest().subarray(0, 16).equals(crxId))
|
|
107
|
+
return key;
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
/** Unpacks a .crx and pins the manifest `key`, so the extension keeps its store id (its origin is then stable, e.g. for an API allowlist). */
|
|
112
|
+
export function unpackCrx(crx, dest) {
|
|
113
|
+
unzipTo(crxToZip(crx), dest);
|
|
114
|
+
const key = crxPublicKey(crx);
|
|
115
|
+
if (!key)
|
|
116
|
+
return;
|
|
117
|
+
const file = join(dest, "manifest.json");
|
|
118
|
+
const manifest = JSON.parse(readFileSync(file, "utf-8"));
|
|
119
|
+
manifest.key = key.toString("base64");
|
|
120
|
+
writeFileSync(file, JSON.stringify(manifest, null, 2));
|
|
121
|
+
}
|
|
122
|
+
function readManifest(dir) {
|
|
123
|
+
const file = join(dir, "manifest.json");
|
|
124
|
+
if (!existsSync(file))
|
|
125
|
+
throw new Error(`no manifest.json in ${dir}`);
|
|
126
|
+
return JSON.parse(readFileSync(file, "utf-8"));
|
|
127
|
+
}
|
|
128
|
+
async function download(id, sha256) {
|
|
129
|
+
const url = "https://clients2.google.com/service/update2/crx?response=redirect&prodversion=130.0.0.0" +
|
|
130
|
+
`&acceptformat=crx2,crx3&x=id%3D${id}%26uc`;
|
|
131
|
+
const res = await fetch(url);
|
|
132
|
+
const body = Buffer.from(await res.arrayBuffer());
|
|
133
|
+
if (!res.ok || body.length < 16 || body.toString("latin1", 0, 4) !== "Cr24") {
|
|
134
|
+
throw new Error(`extension ${id} was not found in the Chrome Web Store (HTTP ${res.status})`);
|
|
135
|
+
}
|
|
136
|
+
if (sha256 && createHash("sha256").update(body).digest("hex") !== sha256.toLowerCase()) {
|
|
137
|
+
throw new Error(`extension ${id}: downloaded file does not match the pinned sha256`);
|
|
138
|
+
}
|
|
139
|
+
return body;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns an unpacked directory for one config entry, downloading it on first use. An installed
|
|
143
|
+
* extension is never silently replaced - pass `update: true` (`waygraph extensions install --update`) to refetch.
|
|
144
|
+
*/
|
|
145
|
+
export async function resolveExtension(spec, projectDir, opts = {}) {
|
|
146
|
+
const entry = typeof spec === "string" ? (spec.startsWith(".") || spec.includes("/") ? { path: spec } : { id: spec }) : spec;
|
|
147
|
+
if (entry.path) {
|
|
148
|
+
const dir = resolve(projectDir, entry.path);
|
|
149
|
+
readManifest(dir);
|
|
150
|
+
return dir;
|
|
151
|
+
}
|
|
152
|
+
const id = KNOWN_EXTENSIONS[entry.id ?? ""] ?? entry.id ?? "";
|
|
153
|
+
if (!STORE_ID.test(id)) {
|
|
154
|
+
throw new Error(`unknown extension "${entry.id}" - use a known name (${Object.keys(KNOWN_EXTENSIONS).join(", ")}), a Chrome Web Store id, or { path }`);
|
|
155
|
+
}
|
|
156
|
+
const dir = join(cacheDir(), id);
|
|
157
|
+
if (!opts.update && existsSync(join(dir, "manifest.json")))
|
|
158
|
+
return dir;
|
|
159
|
+
const tmp = `${dir}.tmp-${process.pid}`;
|
|
160
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
161
|
+
try {
|
|
162
|
+
unpackCrx(await download(id, entry.sha256), tmp);
|
|
163
|
+
readManifest(tmp);
|
|
164
|
+
rmSync(dir, { recursive: true, force: true });
|
|
165
|
+
renameSync(tmp, dir);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
169
|
+
}
|
|
170
|
+
return dir;
|
|
171
|
+
}
|
|
172
|
+
export async function resolveExtensions(specs, projectDir) {
|
|
173
|
+
return Promise.all((specs ?? []).map((s) => resolveExtension(s, projectDir)));
|
|
174
|
+
}
|
|
175
|
+
export function extensionInfo(dir) {
|
|
176
|
+
const m = readManifest(dir);
|
|
177
|
+
return { name: m.name ?? dir, version: m.version ?? "?" };
|
|
178
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,13 +6,13 @@ export { css, byText, byRole } from "./selector.js";
|
|
|
6
6
|
export { WaygraphError, WAYGRAPH_ERRORS } from "./errors.js";
|
|
7
7
|
export type { WaygraphErrorCode } from "./errors.js";
|
|
8
8
|
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
|
|
9
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.js";
|
|
9
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, withDialog, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.js";
|
|
10
10
|
export type { BranchEntry, BranchRunResult, RunBranchRegressionOptions } from "./core/branch-regression.js";
|
|
11
11
|
export type { ApiBlockOptions, ApiCallResult } from "./core/blocks/api.js";
|
|
12
12
|
export type { MapStepOpts } from "./core/map-builder.js";
|
|
13
13
|
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, resolveTodoDockUi, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, WAYGRAPH_RING_CSS, } from "./highlights.js";
|
|
14
14
|
export type { WaygraphHighlightStub, WaygraphHighlightFixture, WaygraphSlide, WaygraphTodoItem, WaygraphTodoInput, WaygraphTodoGroup, WaygraphTodoGroupInput, WaygraphTodosOpts, TodoDockUiOpts, BannerPos, BannerUiOpts, TodoDockUiResolved, TodoDockState, TodoListStyle, FixtureDurationFields, HighlightStubPhase, HighlightStubPhaseName, HighlightFixtureMap, ResolvedHighlight, WaygraphHighlight, HighlightTone, HighlightSize, HighlightWeight, HighlightStyleDefaults, DemoPace, DemoPaceName, StubCtx, StubLifecycleFn, StubPhaseResult, StubPhaseFixtures, DevicePreset, DeviceViewport, DeviceState, DeviceOrientation, } from "./highlights.js";
|
|
15
|
-
export type { Flow, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, AssertBlock, AssertBlockOptions, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, Layout, LayoutOptions, MapBuilderOptions, } from "./core.js";
|
|
15
|
+
export type { Flow, FlowOptions, EngineConfig, ComposedBlock, FastForwardComposedBlock, RunGraphOptions, BlockInfo, NavBlock, NavBlockOptions, NavClickBlock, NavClickBlockOptions, MethodBlock, ActionBlock, AssertBlock, AssertBlockOptions, PageBlock, PageBlockOptions, PageMethodEntry, EffectBlock, MemEffectBlock, MemNavBlock, Layout, LayoutOptions, MapBuilderOptions, } from "./core.js";
|
|
16
16
|
export type { ActionPage } from "./types.js";
|
|
17
17
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./analysis/graph.js";
|
|
18
18
|
export type { WaygraphGraph, WaygraphNode, WaygraphEdge, SkippedBlock, OrphanBlock } from "./analysis/graph.js";
|
|
@@ -30,7 +30,9 @@ export type { AutoSessionInit, SessionSnapshot, SessionSnapshotEdge, SessionSnap
|
|
|
30
30
|
export { pilotStart } from "./session/pilot.js";
|
|
31
31
|
export type { PilotStartResult } from "./session/pilot.js";
|
|
32
32
|
export { defineConfig, resolveBaseUrl } from "./config.js";
|
|
33
|
-
export type { WaygraphConfig } from "./config.js";
|
|
33
|
+
export type { WaygraphConfig, ConfigContext } from "./config.js";
|
|
34
34
|
export { withWebServer } from "./webserver.js";
|
|
35
|
-
export { chromiumExecutablePath } from "./browser
|
|
35
|
+
export { chromiumExecutablePath } from "./browser.js";
|
|
36
|
+
export { resolveExtension, KNOWN_EXTENSIONS } from "./extensions.js";
|
|
37
|
+
export type { ExtensionSpec } from "./extensions.js";
|
|
36
38
|
export type { WebServerConfig } from "./webserver.js";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { registerMemStub, seedMemStub, getMemStub } from "./mem-stub.js";
|
|
|
4
4
|
export { css, byText, byRole } from "./selector.js";
|
|
5
5
|
export { WaygraphError, WAYGRAPH_ERRORS } from "./errors.js";
|
|
6
6
|
export { Trait, urlMatches, textEquals, visible, disabled, enabled, frameVisible, frameTextEquals, frameContainsText, } from "./trait.js";
|
|
7
|
-
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.js";
|
|
7
|
+
export { runGraph, spawnTab, narrate, Engine, start, end, branch, withVerify, modVerify, modVerifyAll, defineBlock, defineNavBlock, defineNavClickBlock, defineMethodBlock, defineActionBlock, defineAssertBlock, definePageBlock, defineEffectBlock, defineMemEffectBlock, defineMemNavBlock, defineApiBlock, locate, composeBlock, fastForwardComposeBlock, isFastForwardBlock, preflight, withSessionReset, withTitle, withMemStub, withDemoPace, withBlockPace, withHighlightStyle, withExpectedFailure, withHighlightFixtures, withStubBefore, withStubAfter, withStubOnError, withDialog, chainFlow, defineLayout, map, MapBuilder, branchRoutes, collectBranchFlows, runBranchRegression, } from "./core.js";
|
|
8
8
|
export { resolveHighlightSlots, resolveSlides, runStubPhase, resolveFixtureDwellMs, resolveSlideDwellMs, formatHighlightCaption, hasAuthoredStubAfter, hasAuthoredStubOnError, normalizeHighlightTone, normalizeDemoPace, normalizeHighlightSize, normalizeHighlightWeight, applyHighlightStyleDefaults, toneIconPrefix, resolveStepDemoPace, demoPaceScale, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, demoPaceGateMs, formatDemoPaceBadge, formatDemoPaceLabel, normalizeTodos, normalizeTodoPos, normalizeTodoStyle, buildTodoDock, applyTodoPhase, advanceTodoDock, completeSequentialTodoDock, applyDevicePhase, resolveDeviceState, normalizeDevicePreset, normalizeDeviceOrientation, applyOrientation, orientationFromViewport, DEVICE_PRESETS, resolveTodoDockUi, FIXTURE_DURATION_MS_DEFAULT, FIXTURE_DURATION_FAST_MS_DEFAULT, FIXTURE_DURATION_SLOW_MS_DEFAULT, SLIDE_DURATION_MS_DEFAULT, SLIDE_DURATION_FAST_MS_DEFAULT, WAYGRAPH_RING_CSS, } from "./highlights.js";
|
|
9
9
|
export { discoverGraph, toMermaid, findOrphanBlocks, findBlockPath } from "./analysis/graph.js";
|
|
10
10
|
export { isFileSelectToken, parseBlocksSelect, matchBlocksSelect, globToRegExp, } from "./blocks-select.js";
|
|
@@ -16,4 +16,5 @@ export { AutoSession, stubFilePath } from "./auto-session.js";
|
|
|
16
16
|
export { pilotStart } from "./session/pilot.js";
|
|
17
17
|
export { defineConfig, resolveBaseUrl } from "./config.js";
|
|
18
18
|
export { withWebServer } from "./webserver.js";
|
|
19
|
-
export { chromiumExecutablePath } from "./browser
|
|
19
|
+
export { chromiumExecutablePath } from "./browser.js";
|
|
20
|
+
export { resolveExtension, KNOWN_EXTENSIONS } from "./extensions.js";
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import type { StubPhaseResult } from "../highlights.js";
|
|
2
2
|
import type { PilotHighlightFixtures } from "./fixtures.js";
|
|
3
|
-
/**
|
|
4
|
-
* Resolve agent-sent "waygraph overlay fixtures" (`auto highlight` JSON / shorthand) through the SAME
|
|
5
|
-
* stub-ctx path a Block's `stubBefore(ctx)` uses, so ctx.todos / ctx.device / ctx.title / ctx.bannerUi semantics
|
|
6
|
-
* (keep vs set vs clear, defaults, normalisation) cannot diverge between `waygraph demo` and pilot/auto/browser.
|
|
7
|
-
* Not Playwright test fixtures - these describe what the waygraph overlay paints on the page under test.
|
|
8
|
-
*/
|
|
9
|
-
export declare function overlayFixturesToPhase(f: PilotHighlightFixtures): Promise<StubPhaseResult>;
|
|
10
3
|
/**
|
|
11
4
|
* The reverse: a resolved stub phase (what `runStubPhase` returns for a Block's stubBefore/After/OnError)
|
|
12
5
|
* -> what the Pilot painter takes. Lets `auto/browser/pilot send` paint a Block's authored waygraph overlay
|
|
@@ -1,48 +1,3 @@
|
|
|
1
|
-
import { runStubPhase } from "../highlights.js";
|
|
2
|
-
/**
|
|
3
|
-
* Resolve agent-sent "waygraph overlay fixtures" (`auto highlight` JSON / shorthand) through the SAME
|
|
4
|
-
* stub-ctx path a Block's `stubBefore(ctx)` uses, so ctx.todos / ctx.device / ctx.title / ctx.bannerUi semantics
|
|
5
|
-
* (keep vs set vs clear, defaults, normalisation) cannot diverge between `waygraph demo` and pilot/auto/browser.
|
|
6
|
-
* Not Playwright test fixtures - these describe what the waygraph overlay paints on the page under test.
|
|
7
|
-
*/
|
|
8
|
-
export async function overlayFixturesToPhase(f) {
|
|
9
|
-
const lifecycle = (ctx) => {
|
|
10
|
-
if (f.clear) {
|
|
11
|
-
ctx.hideTodos();
|
|
12
|
-
ctx.hideDevice();
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
if (f.title !== undefined)
|
|
16
|
-
ctx.title(f.title, f.titlePos ? { pos: f.titlePos } : undefined);
|
|
17
|
-
else if (f.titlePos)
|
|
18
|
-
ctx.titlePos(f.titlePos);
|
|
19
|
-
if (f.bannerUi)
|
|
20
|
-
ctx.bannerUi(f.bannerUi);
|
|
21
|
-
if (f.todos !== undefined) {
|
|
22
|
-
ctx.todos("pilot", f.todos, {
|
|
23
|
-
title: f.todoTitle ?? "Plan",
|
|
24
|
-
index: f.todoIndex ?? 0,
|
|
25
|
-
pos: f.todoPos === "left" ? "left" : "right",
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
const ui = f.todoDockUi ?? f.todoUi;
|
|
29
|
-
if (ui)
|
|
30
|
-
ctx.todoDockUi(ui);
|
|
31
|
-
if (f.device)
|
|
32
|
-
ctx.device(f.device);
|
|
33
|
-
if (typeof f.zoom === "number" && Number.isFinite(f.zoom) && f.zoom > 0)
|
|
34
|
-
ctx.zoom(f.zoom);
|
|
35
|
-
if (f.zoomOut !== undefined)
|
|
36
|
-
ctx.zoomOut(f.zoomOut);
|
|
37
|
-
if (f.rings?.length) {
|
|
38
|
-
f.rings.forEach((r, i) => {
|
|
39
|
-
const { selector, label, ...rest } = r;
|
|
40
|
-
ctx.ring(`ring${i + 1}`, { selector, label, ...rest });
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
return runStubPhase({ name: "overlay-fixtures", stubBefore: lifecycle }, "stubBefore");
|
|
45
|
-
}
|
|
46
1
|
/**
|
|
47
2
|
* The reverse: a resolved stub phase (what `runStubPhase` returns for a Block's stubBefore/After/OnError)
|
|
48
3
|
* -> what the Pilot painter takes. Lets `auto/browser/pilot send` paint a Block's authored waygraph overlay
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PlaywrightTestConfig } from "@playwright/test";
|
|
2
|
+
import type { WaygraphConfig } from "./config.js";
|
|
3
|
+
/** The test runner's config for a project, built from its waygraph config. `waygraph test` uses this when there is no playwright.config. */
|
|
4
|
+
export declare function playwrightConfigFrom(config: WaygraphConfig, projectDir: string, baseURL: string | undefined): PlaywrightTestConfig;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
/** The test runner's config for a project, built from its waygraph config. `waygraph test` uses this when there is no playwright.config. */
|
|
3
|
+
export function playwrightConfigFrom(config, projectDir, baseURL) {
|
|
4
|
+
const { use, ...rest } = config.playwright ?? {};
|
|
5
|
+
const webServers = [config.webServer ?? []].flat().map((s) => ({ ...s, cwd: join(projectDir, s.cwd ?? ".") }));
|
|
6
|
+
return {
|
|
7
|
+
testDir: join(projectDir, config.testDir ?? "tests"),
|
|
8
|
+
outputDir: join(projectDir, "test-results"),
|
|
9
|
+
fullyParallel: true,
|
|
10
|
+
reporter: [["list"], ["html", { open: "never", outputFolder: join(projectDir, "playwright-report") }]],
|
|
11
|
+
...(webServers.length > 0 ? { webServer: webServers } : {}),
|
|
12
|
+
...rest,
|
|
13
|
+
use: { baseURL, trace: "retain-on-failure", ...use },
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Loaded by `waygraph test` (playwright test -c) for projects that keep their settings in config/waygraph.config.ts.
|
|
2
|
+
import { defineConfig } from "@playwright/test";
|
|
3
|
+
import { loadWaygraphConfig, resolveBaseUrl } from "./config.js";
|
|
4
|
+
import { playwrightConfigFrom } from "./playwright-config.js";
|
|
5
|
+
const projectDir = process.env.WAYGRAPH_PROJECT_DIR ?? process.cwd();
|
|
6
|
+
export default defineConfig(playwrightConfigFrom((await loadWaygraphConfig(projectDir)) ?? {}, projectDir, await resolveBaseUrl(projectDir)));
|
package/dist/runner/demo-log.js
CHANGED
|
@@ -109,14 +109,8 @@ function demoTs() {
|
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
let logSurface = "demo";
|
|
113
|
-
/** Which surface is talking (demo | auto | browser | pilot) - same log format everywhere, only the tag differs. */
|
|
114
|
-
export function setDemoLogSurface(name) {
|
|
115
|
-
logSurface = String(name || "demo");
|
|
116
|
-
}
|
|
117
|
-
|
|
118
112
|
function demoPrefix() {
|
|
119
|
-
return ansiPaint(ANSI.dim, "[" + demoTs() + "]") + " waygraph
|
|
113
|
+
return ansiPaint(ANSI.dim, "[" + demoTs() + "]") + " waygraph demo: ";
|
|
120
114
|
}
|
|
121
115
|
|
|
122
116
|
export function demoLog(msg) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** In-page installer (page.evaluate target) - see core.js's own header. Installs the shared ring
|
|
2
|
-
* primitive (__wgEnsureRingEl/__wgPaintRingAt
|
|
2
|
+
* primitive (__wgEnsureRingEl/__wgPaintRingAt), cursor, click-pulse, focus veil,
|
|
3
3
|
* and the #wg-ring singleton every surface's "current action" ring uses. */
|
|
4
4
|
export function installCore(args: Record<string, unknown>): void;
|