waygraph 0.15.51 → 0.15.53
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 +8 -1
- package/dist/analysis/graph.d.ts +1 -1
- package/dist/analysis/graph.js +10 -15
- package/dist/analysis/map-check.d.ts +3 -2
- 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/flags.js +3 -1
- package/dist/cli/run-chain.js +4 -0
- package/dist/cli/usage.js +17 -1
- package/dist/cli.js +13 -1
- package/dist/commands/agent-dive-scaffold.d.ts +5 -20
- package/dist/commands/agent-dive-scaffold.js +10 -3
- package/dist/commands/extensions.d.ts +1 -0
- package/dist/commands/extensions.js +22 -0
- package/dist/commands/init.js +43 -5
- 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 +113 -0
- package/dist/config.d.ts +81 -3
- package/dist/config.js +91 -16
- package/dist/core/block.d.ts +19 -2
- package/dist/core/block.js +43 -3
- 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 +19 -16
- package/dist/core/engine.js +6 -4
- package/dist/core/flow.d.ts +27 -0
- package/dist/core/flow.js +75 -28
- package/dist/core/map-builder.d.ts +30 -4
- package/dist/core/map-builder.js +45 -7
- 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/highlights/demo-pace.d.ts +0 -1
- package/dist/highlights/demo-pace.js +24 -3
- package/dist/index.d.ts +7 -5
- package/dist/index.js +3 -2
- package/dist/pilot-overlay/action-ring.d.ts +0 -9
- package/dist/pilot-overlay/action-ring.js +1 -1
- package/dist/pilot-overlay/overlay-fixtures.d.ts +0 -7
- package/dist/pilot-overlay/overlay-fixtures.js +0 -45
- package/dist/pilot-overlay/panel.d.ts +2 -1
- 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/interaction-patch.js +32 -1
- package/dist/runner/main.js +22 -11
- package/dist/runner/rings.js +0 -49
- package/dist/runner/step-mode.js +10 -4
- package/dist/session/auto-explore-run.js +9 -5
- package/dist/session/auto-explore.d.ts +2 -1
- package/dist/session/auto-explore.js +6 -4
- package/dist/session/auto-session-ipc.d.ts +1 -2
- package/dist/session/auto-session-ipc.js +1 -1
- package/dist/session/block-inject.d.ts +0 -13
- package/dist/session/block-inject.js +3 -3
- package/dist/session/pilot.d.ts +0 -2
- package/dist/session/pilot.js +1 -2
- package/dist/session/step-overlay.js +3 -1
- package/dist/traverse/coverage.d.ts +0 -4
- package/dist/traverse/coverage.js +1 -1
- package/dist/traverse/run.js +3 -1
- package/dist/types.d.ts +1 -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 +11 -5
- package/templates/scaffold/README.md +5 -3
- package/templates/scaffold/STRUCTURE.md +10 -4
- package/templates/scaffold/config/waygraph.config.ts +45 -0
- package/templates/scaffold/package.json +7 -7
- package/templates/scaffold/src/flows/(external)/hoppscotch/auth.flow.ts +36 -0
- package/templates/scaffold/src/flows/(external)/hoppscotch/get.flow.ts +24 -0
- package/templates/scaffold/src/flows/(external)/hoppscotch/post.flow.ts +34 -0
- package/templates/scaffold/src/flows/(user-stories)/email-verification.flow.ts +47 -0
- package/templates/scaffold/src/flows/README.md +35 -14
- package/templates/scaffold/src/flows/home/cart.flow.ts +51 -0
- package/templates/scaffold/src/flows/home/hello.flow.ts +42 -0
- 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 +6 -7
- package/templates/scaffold/tests/hoppscotch.spec.ts +97 -0
- package/templates/scaffold/tests/mail-verify.spec.ts +8 -17
- package/templates/scaffold/tsconfig.json +1 -1
- package/templates/skills/waygraph-convention.skill.md +40 -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/templates/scaffold/src/flows/example.flow.ts +0 -29
- package/templates/scaffold/src/flows/mail-verify.flow.ts +0 -85
- package/templates/scaffold/src/flows/shop.flow.ts +0 -74
package/dist/runner/main.js
CHANGED
|
@@ -10,7 +10,8 @@ import { connect } from "../types.js";
|
|
|
10
10
|
import { MemPage } from "../mem-page.js";
|
|
11
11
|
import { Engine, start, end, chainFlow, isFastForwardBlock, branchRoutes, runBranchRegression } from "../core.js";
|
|
12
12
|
import { withWebServer } from "../webserver.js";
|
|
13
|
-
import {
|
|
13
|
+
import { resolveVideoDir } from "../video-dir.js";
|
|
14
|
+
import { chromiumExecutablePath, extensionPathsFromEnv, launchContext } from "../browser.js";
|
|
14
15
|
|
|
15
16
|
async function runMain() {
|
|
16
17
|
const projectDir = process.argv[2];
|
|
@@ -230,6 +231,9 @@ async function runMain() {
|
|
|
230
231
|
: "waygraph demo: opaque FFCompose in flow -> Playwright slowMo=0 (blitz); set WAYGRAPH_SLOWMO to override",
|
|
231
232
|
);
|
|
232
233
|
}
|
|
234
|
+
// Step (demo) mode: slowMo is waygraph's own beat after each action, not Playwright's per-operation slowMo, so a
|
|
235
|
+
// step's pace can remove it and typing is not taxed per keystroke. The browser launches at 0.
|
|
236
|
+
const launchSlowMo = step ? 0 : slowMo;
|
|
233
237
|
const baseURL = process.env.WAYGRAPH_BASE_URL;
|
|
234
238
|
const title = process.env.WAYGRAPH_TITLE;
|
|
235
239
|
// Comma-separated Block names (their real .name, e.g. "login") that
|
|
@@ -252,7 +256,7 @@ async function runMain() {
|
|
|
252
256
|
const videoEnv = process.env.WAYGRAPH_VIDEO;
|
|
253
257
|
let videoDir = null;
|
|
254
258
|
if (videoEnv) {
|
|
255
|
-
videoDir = videoEnv === "1" ?
|
|
259
|
+
videoDir = videoEnv === "1" ? await resolveVideoDir(projectDir) : videoEnv;
|
|
256
260
|
mkdirSync(videoDir, { recursive: true });
|
|
257
261
|
}
|
|
258
262
|
/** Fixed 16:9 capture for --video (demo QA and headless run). Step+video no longer maximizes. */
|
|
@@ -262,7 +266,7 @@ async function runMain() {
|
|
|
262
266
|
const fixedVideoViewport = videoDir
|
|
263
267
|
? (videoViewportParsed ?? (step ? DEFAULT_VIDEO_VIEWPORT : DEFAULT_RUN_VIDEO_VIEWPORT))
|
|
264
268
|
: null;
|
|
265
|
-
const engine = new Engine({ headless: !headed, slowMo });
|
|
269
|
+
const engine = new Engine({ headless: !headed, slowMo: launchSlowMo });
|
|
266
270
|
let result;
|
|
267
271
|
// Non-interactive execution (no --step) runs the whole chain as one
|
|
268
272
|
// composed flow with no per-block loop of its own to defer seeding
|
|
@@ -290,12 +294,6 @@ async function runMain() {
|
|
|
290
294
|
const executablePath = chromiumExecutablePath();
|
|
291
295
|
// Maximized for interactive step, and for fixed video viewport so the
|
|
292
296
|
// locked record size can actually fit (otherwise .webm gets grey pad).
|
|
293
|
-
const browser = await chromium.launch({
|
|
294
|
-
headless: !headed,
|
|
295
|
-
slowMo,
|
|
296
|
-
args: step || fixedVideoViewport ? ["--start-maximized"] : [],
|
|
297
|
-
...(executablePath ? { executablePath } : {}),
|
|
298
|
-
});
|
|
299
297
|
const contextOpts = fixedVideoViewport
|
|
300
298
|
? {
|
|
301
299
|
baseURL,
|
|
@@ -313,7 +311,17 @@ async function runMain() {
|
|
|
313
311
|
? { dir: videoDir, size: fixedVideoViewport }
|
|
314
312
|
: { dir: videoDir };
|
|
315
313
|
}
|
|
316
|
-
const context = await
|
|
314
|
+
const { context, close: closeBrowser } = await launchContext({
|
|
315
|
+
launcher: chromium,
|
|
316
|
+
launch: {
|
|
317
|
+
headless: !headed,
|
|
318
|
+
slowMo: launchSlowMo,
|
|
319
|
+
args: step || fixedVideoViewport ? ["--start-maximized"] : [],
|
|
320
|
+
...(executablePath ? { executablePath } : {}),
|
|
321
|
+
},
|
|
322
|
+
context: contextOpts,
|
|
323
|
+
extensions: extensionPathsFromEnv(),
|
|
324
|
+
});
|
|
317
325
|
let pageForVideo = null;
|
|
318
326
|
try {
|
|
319
327
|
if (step) {
|
|
@@ -369,7 +377,7 @@ async function runMain() {
|
|
|
369
377
|
if (savedVideo) {
|
|
370
378
|
console.log("waygraph: video saved to " + savedVideo);
|
|
371
379
|
}
|
|
372
|
-
await
|
|
380
|
+
await closeBrowser();
|
|
373
381
|
}
|
|
374
382
|
} else {
|
|
375
383
|
if (chainFlows && chainFlows.length > 0) {
|
|
@@ -421,6 +429,9 @@ async function runAllBranchesMode(chainFlows, mem, baseURL, headed, engine) {
|
|
|
421
429
|
process.exitCode = 1;
|
|
422
430
|
return;
|
|
423
431
|
}
|
|
432
|
+
if (extensionPathsFromEnv().length > 0) {
|
|
433
|
+
console.warn("waygraph run --all-branches: extensions are not loaded (branches run in cloned contexts)");
|
|
434
|
+
}
|
|
424
435
|
const { chromium } = await import("@playwright/test");
|
|
425
436
|
const browser = await chromium.launch({ headless: !headed });
|
|
426
437
|
const cloneSession = process.env.WAYGRAPH_SHARED_SESSION !== "1";
|
package/dist/runner/rings.js
CHANGED
|
@@ -381,55 +381,6 @@ export async function cycleHighlightRings(page, highlights, gatesFast, opts) {
|
|
|
381
381
|
}
|
|
382
382
|
|
|
383
383
|
|
|
384
|
-
/**
|
|
385
|
-
* Paint a NAMED ring instance (ui/css/ring.css's shared `.wg-ring-el`/`.wg-ring-el-label`) at a
|
|
386
|
-
* known viewport box - the primitive both the demo/auto singleton (`showRing`, always "wg-ring")
|
|
387
|
-
* and Pilot's several simultaneous fixture rings (their own ids) paint through, so there is one
|
|
388
|
-
* ring-rendering implementation, not two. No collision/follow/focus - those are `#wg-ring`'s own
|
|
389
|
-
* extras (see `showRing`/`inpage/core.js`'s `__wgPositionRing`), meaningless for an independent
|
|
390
|
-
* fixture ring that isn't "the current action."
|
|
391
|
-
*/
|
|
392
|
-
export async function showRingAt(page, ringId, labelId, box, label, tone, style) {
|
|
393
|
-
const size = normalizeHighlightSize(style && style.size);
|
|
394
|
-
const weight = normalizeHighlightWeight(style && style.weight);
|
|
395
|
-
const color = style && style.color ? String(style.color) : "";
|
|
396
|
-
await page
|
|
397
|
-
.evaluate(
|
|
398
|
-
({ ringId, labelId, box, label, tone, size, weight, color }) => {
|
|
399
|
-
if (window.__wgPaintRingAt) {
|
|
400
|
-
window.__wgPaintRingAt(ringId, labelId, box, label, tone, { size, weight, color });
|
|
401
|
-
}
|
|
402
|
-
},
|
|
403
|
-
{ ringId, labelId, box, label, tone: tone || "planned", size, weight, color },
|
|
404
|
-
)
|
|
405
|
-
.catch(() => {});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/** Hide (not remove) a named ring instance painted via {@link showRingAt}. */
|
|
409
|
-
export async function hideRingAt(page, ringId, labelId) {
|
|
410
|
-
await page
|
|
411
|
-
.evaluate(
|
|
412
|
-
({ ringId, labelId }) => {
|
|
413
|
-
if (window.__wgHideRingAt) window.__wgHideRingAt(ringId, labelId);
|
|
414
|
-
},
|
|
415
|
-
{ ringId, labelId },
|
|
416
|
-
)
|
|
417
|
-
.catch(() => {});
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/** Remove a named ring instance entirely (Pilot's fixture rings are short-lived, not reused). */
|
|
421
|
-
export async function removeRingAt(page, ringId, labelId) {
|
|
422
|
-
await page
|
|
423
|
-
.evaluate(
|
|
424
|
-
({ ringId, labelId }) => {
|
|
425
|
-
__wgById(ringId)?.remove();
|
|
426
|
-
__wgById(labelId)?.remove();
|
|
427
|
-
},
|
|
428
|
-
{ ringId, labelId },
|
|
429
|
-
)
|
|
430
|
-
.catch(() => {});
|
|
431
|
-
}
|
|
432
|
-
|
|
433
384
|
export async function showRing(page, box, label, tone, style) {
|
|
434
385
|
const size = normalizeHighlightSize(style && style.size);
|
|
435
386
|
const weight = normalizeHighlightWeight(style && style.weight);
|
package/dist/runner/step-mode.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Moved verbatim from the former CHAIN_RUNNER_SCRIPT template string in cli.ts (see src/ARCHITECTURE.md).
|
|
2
2
|
// Runs inside the target project's own waygraph copy - keep it dependency-light and self-contained.
|
|
3
3
|
import { instrumentInteractionHighlighting } from "./interaction-patch.js";
|
|
4
|
-
import { applyHighlightStyleDefaults, demoPaceGateMs, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, formatHighlightCaption, hasAuthoredStubAfter, resolveSlides, resolveStepDemoPace, resolveTodoDockUi, runStubPhase } from "../highlights.js";
|
|
4
|
+
import { applyHighlightStyleDefaults, normalizeDemoPace, demoPaceGateMs, demoPaceIsBlitz, demoPaceIsFast, demoPaceIsSlow, formatHighlightCaption, hasAuthoredStubAfter, resolveSlides, resolveStepDemoPace, resolveTodoDockUi, runStubPhase } from "../highlights.js";
|
|
5
5
|
import { markStepRunning, resetPageState, teardownOverlay } from "./teardown.js";
|
|
6
6
|
import { demoLog, logStubPhaseFixtures, logTodoDockFull, paceSpeakFields, summarizeDevice } from "./demo-log.js";
|
|
7
7
|
import { extractVerifyHighlights, presentFailPanel, renderAfterStep, renderBeforeStep } from "./step-panels.js";
|
|
@@ -26,6 +26,8 @@ export async function runStepMode(engine, start, end, context, page, mem, resolv
|
|
|
26
26
|
const demoFast = process.env.WAYGRAPH_DEMO_FAST === "1";
|
|
27
27
|
const stepperMode = process.env.WAYGRAPH_STEPPER === "full" ? "full" : "carousel";
|
|
28
28
|
const pacing = { gatesFast: demoFast, gatesSlow: false, skipTheater: false, demoPace: "normal" };
|
|
29
|
+
// A title set by a stub (`ctx.title`) names the flow from that step on, until another stub sets a new one.
|
|
30
|
+
let stickyTitle;
|
|
29
31
|
const stubBeforeRef = { current: [] };
|
|
30
32
|
/** Live todo dock for Method fill/click advance (same carry as stage.state.lastTodoDock). */
|
|
31
33
|
const todoDockRef = { current: undefined };
|
|
@@ -213,15 +215,18 @@ export async function runStepMode(engine, start, end, context, page, mem, resolv
|
|
|
213
215
|
}
|
|
214
216
|
// Episode pace: block > flow > CLI --fast (only when no authored pace).
|
|
215
217
|
// Authoring wins: withDemoPace("slow"|2|4500) keeps slow dwell even under --fast.
|
|
218
|
+
// `demo.pace` in the config (WAYGRAPH_DEMO_PACE) is the default for flows and steps that set none.
|
|
219
|
+
const defaultPace = process.env.WAYGRAPH_DEMO_PACE ? normalizeDemoPace(process.env.WAYGRAPH_DEMO_PACE) : undefined;
|
|
216
220
|
const stepPace = resolveStepDemoPace({
|
|
217
221
|
blockPace: r.block.demoPace,
|
|
218
|
-
flowPace: r.demoPace,
|
|
222
|
+
flowPace: r.demoPace ?? defaultPace,
|
|
219
223
|
fastForward: !!r.block.fastForward,
|
|
220
224
|
wasFastForward: !!r.wasFastForward,
|
|
221
225
|
});
|
|
222
226
|
const authoredPace =
|
|
223
227
|
(r.block.demoPace != null && r.block.demoPace !== "") ||
|
|
224
|
-
(r.demoPace != null && r.demoPace !== "")
|
|
228
|
+
(r.demoPace != null && r.demoPace !== "") ||
|
|
229
|
+
defaultPace != null;
|
|
225
230
|
const cliFast = demoFast || fastBlockNames.has(r.block.name);
|
|
226
231
|
pacing.demoPace =
|
|
227
232
|
stepPace === "normal" && cliFast && !authoredPace
|
|
@@ -266,8 +271,9 @@ export async function runStepMode(engine, start, end, context, page, mem, resolv
|
|
|
266
271
|
(stage.state.lastTodoDock && stage.state.lastTodoDock.groups[0] && stage.state.lastTodoDock.groups[0].items) ||
|
|
267
272
|
stubBeforePhase.todos ||
|
|
268
273
|
[];
|
|
274
|
+
if (stubBeforePhase.title && String(stubBeforePhase.title).trim()) stickyTitle = String(stubBeforePhase.title).trim();
|
|
269
275
|
const overlayTitle =
|
|
270
|
-
|
|
276
|
+
stickyTitle ||
|
|
271
277
|
(r.episodeTitle && String(r.episodeTitle).trim()) ||
|
|
272
278
|
title ||
|
|
273
279
|
"waygraph demo";
|
|
@@ -5,9 +5,9 @@ import { Engine, start, end, locate } from "../core.js";
|
|
|
5
5
|
import { MemPage } from "../mem-page.js";
|
|
6
6
|
import { buildExploreContext, buildExploreMenu, } from "./auto-explore.js";
|
|
7
7
|
import { installDemoChrome, instrumentInteractionHighlighting } from "./step-overlay.js";
|
|
8
|
-
import { resolveBaseUrl } from "../config.js";
|
|
8
|
+
import { configuredExtensions, resolveBaseUrl } from "../config.js";
|
|
9
9
|
import { withWebServer } from "../webserver.js";
|
|
10
|
-
import { chromiumExecutablePath } from "../browser
|
|
10
|
+
import { chromiumExecutablePath, launchContext } from "../browser.js";
|
|
11
11
|
/** See `../config.js`'s own `resolveBaseUrl` - re-exported so existing importers of this module
|
|
12
12
|
* don't need to change their import path. */
|
|
13
13
|
export { resolveBaseUrl };
|
|
@@ -587,13 +587,17 @@ async function runAutoExploreCore(projectDir, options = {}) {
|
|
|
587
587
|
launchOpts.slowMo = 250;
|
|
588
588
|
if (executablePath)
|
|
589
589
|
launchOpts.executablePath = executablePath;
|
|
590
|
-
const browser = await chromium.launch(launchOpts);
|
|
591
590
|
const contextOpts = cli
|
|
592
591
|
? { viewport: { width: 1280, height: 720 } }
|
|
593
592
|
: { viewport: null };
|
|
594
593
|
if (baseURL)
|
|
595
594
|
contextOpts.baseURL = baseURL;
|
|
596
|
-
const context = await
|
|
595
|
+
const { close: closeBrowser, context } = await launchContext({
|
|
596
|
+
launcher: chromium,
|
|
597
|
+
launch: launchOpts,
|
|
598
|
+
context: contextOpts,
|
|
599
|
+
extensions: await configuredExtensions(projectDir),
|
|
600
|
+
});
|
|
597
601
|
let page = await context.newPage();
|
|
598
602
|
if (startUrl) {
|
|
599
603
|
await page.goto(startUrl, { waitUntil: "domcontentloaded" }).catch(() => { });
|
|
@@ -692,6 +696,6 @@ async function runAutoExploreCore(projectDir, options = {}) {
|
|
|
692
696
|
finally {
|
|
693
697
|
cliRl?.close();
|
|
694
698
|
await context.close();
|
|
695
|
-
await
|
|
699
|
+
await closeBrowser();
|
|
696
700
|
}
|
|
697
701
|
}
|
|
@@ -24,7 +24,7 @@ export interface ExploreEdge extends WaygraphEdge {
|
|
|
24
24
|
label?: string;
|
|
25
25
|
instanceOption?: WaygraphInstanceOption;
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
interface ExploreSection {
|
|
28
28
|
title: string;
|
|
29
29
|
edges: ExploreEdge[];
|
|
30
30
|
}
|
|
@@ -55,3 +55,4 @@ export declare function buildExploreContext(projectDir: string, opts?: ExploreCo
|
|
|
55
55
|
graph: WaygraphGraph;
|
|
56
56
|
library: Awaited<ReturnType<typeof loadBlockLibrary>>;
|
|
57
57
|
}>;
|
|
58
|
+
export {};
|
|
@@ -169,7 +169,7 @@ export function exploreChoices(graph, here) {
|
|
|
169
169
|
const out = [];
|
|
170
170
|
for (const edge of graph.edges) {
|
|
171
171
|
if (here === null) {
|
|
172
|
-
if (edge.kind !== "nav")
|
|
172
|
+
if (edge.kind !== "nav" || edge.from !== "*")
|
|
173
173
|
continue;
|
|
174
174
|
}
|
|
175
175
|
else if (edge.from !== here && edge.from !== "*") {
|
|
@@ -283,8 +283,9 @@ export async function buildExploreMenu(page, graph, library, here) {
|
|
|
283
283
|
continue;
|
|
284
284
|
if (here === null) {
|
|
285
285
|
// Bootstrap only: URL deep-links as "Start here". Click-nav needs a live
|
|
286
|
-
// control so it cannot seed an unknown page
|
|
287
|
-
|
|
286
|
+
// control so it cannot seed an unknown page, and a Nav with `from` needs
|
|
287
|
+
// that state first (e.g. signed in) so it cannot start one either.
|
|
288
|
+
if (!isUrlNav(entry) || edge.from !== "*")
|
|
288
289
|
continue;
|
|
289
290
|
push(navigate, edge);
|
|
290
291
|
continue;
|
|
@@ -295,7 +296,8 @@ export async function buildExploreMenu(page, graph, library, here) {
|
|
|
295
296
|
// Graph still marks NavBlocks from:"*" for pathfinding/traverse; auto menu
|
|
296
297
|
// only offers click-nav whose control is visible on this page (navRunnable).
|
|
297
298
|
// Otherwise LoginForm lists every app route + mailpit as if legal moves.
|
|
298
|
-
|
|
299
|
+
// A url Nav that declares `from` is only an edge from those pages, so it IS a real move here.
|
|
300
|
+
if (isUrlNav(entry) && edge.from === "*")
|
|
299
301
|
continue;
|
|
300
302
|
if (isBackEdge(edge) && (edge.from === here || edge.from === "*")) {
|
|
301
303
|
push(back, edge);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AutoSession, type AutoSessionInit, type SessionSnapshot, type InspectDomOptions, type DomSnapshot, type TraceStep, type ApplyPathResult, type ConsoleLogEntry, type StorageSnapshot, type StubFileKind } from "../auto-session.js";
|
|
2
2
|
import type { PilotHighlightFixtures } from "../pilot-overlay.js";
|
|
3
|
-
export type { PilotHighlightFixtures
|
|
3
|
+
export type { PilotHighlightFixtures } from "../pilot-overlay.js";
|
|
4
4
|
export interface SessionMeta {
|
|
5
5
|
sessionId: string;
|
|
6
6
|
socketPath: string;
|
|
@@ -54,7 +54,6 @@ export type HighlightResponse = {
|
|
|
54
54
|
ok: false;
|
|
55
55
|
error: string;
|
|
56
56
|
};
|
|
57
|
-
export declare function readSessionMeta(projectDir: string, sessionId: string): SessionMeta | null;
|
|
58
57
|
/**
|
|
59
58
|
* Resolve session metadata from a project hint (cwd) or the global registry.
|
|
60
59
|
* Fixes "session unreachable" when the agent calls status/send from a different
|
|
@@ -58,7 +58,7 @@ function logPathFor(projectDir, sessionId) {
|
|
|
58
58
|
function generateSessionId() {
|
|
59
59
|
return randomBytes(4).toString("hex");
|
|
60
60
|
}
|
|
61
|
-
|
|
61
|
+
function readSessionMeta(projectDir, sessionId) {
|
|
62
62
|
const p = metaPath(projectDir, sessionId);
|
|
63
63
|
if (!existsSync(p))
|
|
64
64
|
return null;
|
|
@@ -1,21 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This package's own installed root (contains `templates/`/`examples/`, sibling to `dist/`) -
|
|
3
|
-
* this file compiles to `dist/session/block-inject.js`, two levels down, not one. Same class of
|
|
4
|
-
* bug as `cli/util.ts`'s own `packageRoot()` (see its comment) - self-inflicted here by moving
|
|
5
|
-
* this file into `src/session/` without updating the path math to match the new depth.
|
|
6
|
-
*/
|
|
7
|
-
export declare function packageRoot(): string;
|
|
8
|
-
/** Human label for an injected root (used in merged `file` paths). */
|
|
9
|
-
export declare function injectLabel(root: string): string;
|
|
10
1
|
/**
|
|
11
2
|
* Display path for a Block discovered under an inject root (not the host cwd).
|
|
12
3
|
* Primary project files stay relative as-is; injected ones are tagged `@label/...`.
|
|
13
4
|
*/
|
|
14
5
|
export declare function injectTaggedFile(hostDir: string, root: string, relFile: string): string;
|
|
15
|
-
/**
|
|
16
|
-
* Resolve one `--inject` token to an absolute directory (preset name or path).
|
|
17
|
-
*/
|
|
18
|
-
export declare function resolveInjectRoot(token: string, cwd?: string): string;
|
|
19
6
|
export declare function resolveInjectRoots(tokens: string[], cwd?: string): string[];
|
|
20
7
|
/** Roots to scan: host project first, then each inject path (deduped). */
|
|
21
8
|
export declare function exploreRoots(projectDir: string, inject?: string[]): string[];
|
|
@@ -12,11 +12,11 @@ import { homedir } from "node:os";
|
|
|
12
12
|
* bug as `cli/util.ts`'s own `packageRoot()` (see its comment) - self-inflicted here by moving
|
|
13
13
|
* this file into `src/session/` without updating the path math to match the new depth.
|
|
14
14
|
*/
|
|
15
|
-
|
|
15
|
+
function packageRoot() {
|
|
16
16
|
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
17
17
|
}
|
|
18
18
|
/** Human label for an injected root (used in merged `file` paths). */
|
|
19
|
-
|
|
19
|
+
function injectLabel(root) {
|
|
20
20
|
return basename(root.replace(/\/$/, ""));
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
@@ -50,7 +50,7 @@ const PRESETS = {
|
|
|
50
50
|
/**
|
|
51
51
|
* Resolve one `--inject` token to an absolute directory (preset name or path).
|
|
52
52
|
*/
|
|
53
|
-
|
|
53
|
+
function resolveInjectRoot(token, cwd = process.cwd()) {
|
|
54
54
|
const raw = token.trim();
|
|
55
55
|
if (!raw)
|
|
56
56
|
throw new Error("waygraph inject: empty --inject value");
|
package/dist/session/pilot.d.ts
CHANGED
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
* new execution primitive.
|
|
30
30
|
*/
|
|
31
31
|
import { type WaygraphGraph } from "../analysis/graph.js";
|
|
32
|
-
import { listBrowserSessions, type SessionMeta } from "./auto-session-ipc.js";
|
|
33
32
|
import { type BrowserStartInit } from "./browser.js";
|
|
34
33
|
import type { SessionSnapshot } from "../auto-session.js";
|
|
35
34
|
export interface PilotStartResult {
|
|
@@ -51,4 +50,3 @@ export interface PilotStartResult {
|
|
|
51
50
|
export declare function pilotStart(init: BrowserStartInit): Promise<PilotStartResult>;
|
|
52
51
|
/** Reattach to an existing browser session — does not spawn a new browser. */
|
|
53
52
|
export declare function pilotAttach(projectDir: string, sessionId: string, inject?: string[]): Promise<PilotStartResult>;
|
|
54
|
-
export { listBrowserSessions, type SessionMeta };
|
package/dist/session/pilot.js
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* new execution primitive.
|
|
30
30
|
*/
|
|
31
31
|
import { discoverGraph } from "../analysis/graph.js";
|
|
32
|
-
import { requestSession,
|
|
32
|
+
import { requestSession, resolveSessionMeta, } from "./auto-session-ipc.js";
|
|
33
33
|
import { browserStart } from "./browser.js";
|
|
34
34
|
/**
|
|
35
35
|
* Starts a detached session and reads back its own graph + starting
|
|
@@ -77,4 +77,3 @@ export async function pilotAttach(projectDir, sessionId, inject) {
|
|
|
77
77
|
snapshot: status.snapshot,
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
-
export { listBrowserSessions };
|
|
@@ -86,7 +86,9 @@ export async function installDemoChrome(page, title = "waygraph auto", options =
|
|
|
86
86
|
ring.style.width = box.width + pad * 2 + "px";
|
|
87
87
|
ring.style.height = box.height + pad * 2 + "px";
|
|
88
88
|
ring.style.opacity = "1";
|
|
89
|
-
|
|
89
|
+
// Drawn from data-label (CSS ::after), not DOM text, so the page's own text selectors never match it.
|
|
90
|
+
ringLabel.dataset.label = label;
|
|
91
|
+
ringLabel.textContent = "";
|
|
90
92
|
ringLabel.style.left = box.x + "px";
|
|
91
93
|
ringLabel.style.top = box.y + box.height + 8 + "px";
|
|
92
94
|
ringLabel.style.opacity = "1";
|
|
@@ -27,10 +27,6 @@ export interface TraverseCoverageReport {
|
|
|
27
27
|
leaf?: string;
|
|
28
28
|
}>;
|
|
29
29
|
}
|
|
30
|
-
/** Static graph edge key = Block runtime name. */
|
|
31
|
-
export declare function edgeKeyFromGraphEdge(edge: {
|
|
32
|
-
block: string;
|
|
33
|
-
}): string;
|
|
34
30
|
/** Collect unique edge keys from a WaygraphGraph-like edges list. */
|
|
35
31
|
export declare function collectGraphEdgeKeys(edges: ReadonlyArray<{
|
|
36
32
|
block: string;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
/** Static graph edge key = Block runtime name. */
|
|
13
|
-
|
|
13
|
+
function edgeKeyFromGraphEdge(edge) {
|
|
14
14
|
return edge.block;
|
|
15
15
|
}
|
|
16
16
|
/** Collect unique edge keys from a WaygraphGraph-like edges list. */
|
package/dist/traverse/run.js
CHANGED
|
@@ -5,7 +5,7 @@ import { defaultMemValueForKey } from "../session/auto-explore-run.js";
|
|
|
5
5
|
import { resolveBaseUrl } from "../config.js";
|
|
6
6
|
import { EdgeLeaseCoordinator } from "./lease.js";
|
|
7
7
|
import { buildCoverageReport, collectGraphEdgeKeys, defaultCoverageOutPath, formatCoverageLine, writeCoverageReport, } from "./coverage.js";
|
|
8
|
-
import { chromiumExecutablePath } from "../browser
|
|
8
|
+
import { chromiumExecutablePath } from "../browser.js";
|
|
9
9
|
function seedMem(library, mem, dataJson) {
|
|
10
10
|
const raw = dataJson ?? process.env.WAYGRAPH_DATA ?? process.env.WAYGRAPH_AUTO_MEM;
|
|
11
11
|
let envParsed = {};
|
|
@@ -253,6 +253,8 @@ export async function runTraverse(projectDir, options = {}) {
|
|
|
253
253
|
const allEdgeKeys = collectGraphEdgeKeys(graph.edges);
|
|
254
254
|
const leases = new EdgeLeaseCoordinator({ projectDir, persist: parallel > 1 });
|
|
255
255
|
const engine = new Engine({ headless: !headed, slowMo: headed ? 100 : 0 });
|
|
256
|
+
if (process.env.WAYGRAPH_EXTENSIONS)
|
|
257
|
+
console.error("waygraph traverse: extensions are not loaded (workers use plain contexts)");
|
|
256
258
|
const { chromium } = await import("@playwright/test");
|
|
257
259
|
const executablePath = chromiumExecutablePath();
|
|
258
260
|
const launchOpts = {
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Page } from "@playwright/test";
|
|
|
2
2
|
import type { MemPage, MemKey } from "./mem-page.js";
|
|
3
3
|
import type { Trait } from "./trait.js";
|
|
4
4
|
import type { HighlightStubPhaseOrFn, WaygraphHighlight, WaygraphSlidesOrFn } from "./highlights.js";
|
|
5
|
-
export type { WaygraphHighlight
|
|
5
|
+
export type { WaygraphHighlight } from "./highlights.js";
|
|
6
6
|
/**
|
|
7
7
|
* A Checkpoint is identified solely by its string tag - a phantom marker with no
|
|
8
8
|
* embedded data. Two Checkpoints with the same tag are the same state everywhere.
|
|
@@ -20,6 +20,7 @@ export function ringCss() {
|
|
|
20
20
|
"#wg-ring-label{position:fixed;z-index:2147483646;pointer-events:none;opacity:0;" +
|
|
21
21
|
`max-width:min(360px,70vw);white-space:normal;padding:6px 10px;border-radius:7px;background:${p.labelBg};color:${p.labelFg};` +
|
|
22
22
|
"font:600 12px/1.35 system-ui,sans-serif;transition:opacity .3s ease;}" +
|
|
23
|
+
"#wg-ring-label::after{content:attr(data-label);}" +
|
|
23
24
|
toneRules("#wg-ring-label", (t) => `background:${t.labelBg};color:${t.labelFg};`) +
|
|
24
25
|
// Gray = a real Playwright-driven instruction: mark it with a robot.
|
|
25
26
|
`#wg-ring-label[data-tone=auto]::before{${ROBOT}}` +
|
package/dist/ui/overlay.css
CHANGED
|
@@ -86,6 +86,7 @@
|
|
|
86
86
|
font: 600 12px/1.35 var(--wg-font); transition: opacity .3s ease;
|
|
87
87
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,.25);
|
|
88
88
|
}
|
|
89
|
+
#wg-ring-label::after { content: attr(data-label); }
|
|
89
90
|
.wg-ring-el-label[data-tone=planned] { --wg-tone-bg: var(--wg-tone-planned-bg); --wg-tone-fg: var(--wg-tone-planned-fg); }
|
|
90
91
|
.wg-ring-el-label[data-tone=auto] { --wg-tone-bg: var(--wg-tone-auto-bg); --wg-tone-fg: var(--wg-tone-auto-fg); }
|
|
91
92
|
.wg-ring-el-label[data-tone=info] { --wg-tone-bg: var(--wg-tone-info-bg); --wg-tone-fg: var(--wg-tone-info-fg); }
|
package/dist/ui/shadow.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* lookups check the shadow root first and fall back to the light DOM (the device stage shell and
|
|
8
8
|
* host-page markers such as `[data-wg-zoomed]` intentionally stay in the light DOM).
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
10
|
+
import type { Page } from "@playwright/test";
|
|
11
11
|
/**
|
|
12
12
|
* `overlaySheet` is the ONE real-CSS bundle (`ui/stylesheet.ts` -> `dist/ui/overlay.css`, built on
|
|
13
13
|
* Open Props - see `src/ui/css/README.md`) every surface shares; passed in from Node since in-page
|
|
@@ -16,5 +16,3 @@ import type { BrowserContext, Page } from "@playwright/test";
|
|
|
16
16
|
export declare function installShadowRoot(overlaySheet?: string): void;
|
|
17
17
|
/** Idempotent; call before any overlay install on a page. Installs the shared stylesheet bundle. */
|
|
18
18
|
export declare function ensureShadowRoot(page: Page): Promise<void>;
|
|
19
|
-
/** Also define the helpers on every future document (pilot overlay persists across navigations). */
|
|
20
|
-
export declare function installShadowRootEverywhere(context: BrowserContext): Promise<void>;
|
package/dist/ui/shadow.js
CHANGED
|
@@ -44,7 +44,3 @@ export function installShadowRoot(overlaySheet) {
|
|
|
44
44
|
export async function ensureShadowRoot(page) {
|
|
45
45
|
await page.evaluate(installShadowRoot, getOverlaySheet()).catch(() => { });
|
|
46
46
|
}
|
|
47
|
-
/** Also define the helpers on every future document (pilot overlay persists across navigations). */
|
|
48
|
-
export async function installShadowRootEverywhere(context) {
|
|
49
|
-
await context.addInitScript(installShadowRoot, getOverlaySheet());
|
|
50
|
-
}
|
package/dist/ui/tokens.d.ts
CHANGED
|
@@ -18,14 +18,3 @@ export interface Tone {
|
|
|
18
18
|
export declare const TONES: Record<ToneName, Tone>;
|
|
19
19
|
/** Dark surface shared by panels, banner, docks, HUD chips. */
|
|
20
20
|
export declare const SURFACE = "rgba(20,10,40,.94)";
|
|
21
|
-
export declare const ACCENT = "#7C3AED";
|
|
22
|
-
/** Secondary accent used on the dark surface (section titles, kinds). */
|
|
23
|
-
export declare const ACCENT_SOFT = "#c9a6ff";
|
|
24
|
-
/** Stacking order: one scale instead of magic numbers. */
|
|
25
|
-
export declare const Z: {
|
|
26
|
-
readonly top: 2147483647;
|
|
27
|
-
readonly ring: 2147483646;
|
|
28
|
-
readonly fx: 2147483645;
|
|
29
|
-
readonly focus: 2147483644;
|
|
30
|
-
readonly dock: 2147483000;
|
|
31
|
-
};
|
package/dist/ui/tokens.js
CHANGED
|
@@ -14,8 +14,3 @@ export const TONES = {
|
|
|
14
14
|
};
|
|
15
15
|
/** Dark surface shared by panels, banner, docks, HUD chips. */
|
|
16
16
|
export const SURFACE = "rgba(20,10,40,.94)";
|
|
17
|
-
export const ACCENT = "#7C3AED";
|
|
18
|
-
/** Secondary accent used on the dark surface (section titles, kinds). */
|
|
19
|
-
export const ACCENT_SOFT = "#c9a6ff";
|
|
20
|
-
/** Stacking order: one scale instead of magic numbers. */
|
|
21
|
-
export const Z = { top: 2147483647, ring: 2147483646, fx: 2147483645, focus: 2147483644, dock: 2147483000 };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The user's Downloads folder, or undefined when there is none (a server, a container). */
|
|
2
|
+
export declare function downloadsDir(): string | undefined;
|
|
3
|
+
/**
|
|
4
|
+
* Where `--video` recordings go: `WAYGRAPH_VIDEO_DIR`, else `video.dir` from the config (absolute, `~/…`, or relative to
|
|
5
|
+
* the project), else `<Downloads>/waygraph-videos/<project>`. With no usable Downloads folder, or on CI, it is
|
|
6
|
+
* `<project>/.waygraph-videos`. An explicit `--video <dir>` never reaches this.
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveVideoDir(projectDir: string): Promise<string>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
4
|
+
import { loadWaygraphConfig } from "./config.js";
|
|
5
|
+
/** The user's Downloads folder, or undefined when there is none (a server, a container). */
|
|
6
|
+
export function downloadsDir() {
|
|
7
|
+
const home = homedir();
|
|
8
|
+
let dir = process.env.XDG_DOWNLOAD_DIR;
|
|
9
|
+
if (!dir && process.platform === "linux") {
|
|
10
|
+
try {
|
|
11
|
+
const line = readFileSync(join(process.env.XDG_CONFIG_HOME ?? join(home, ".config"), "user-dirs.dirs"), "utf-8")
|
|
12
|
+
.split("\n")
|
|
13
|
+
.find((l) => l.startsWith("XDG_DOWNLOAD_DIR="));
|
|
14
|
+
dir = line?.slice("XDG_DOWNLOAD_DIR=".length).replace(/^"|"$/g, "").replace("$HOME", home);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
/* no user-dirs file: use the conventional name */
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
dir ??= join(home, "Downloads");
|
|
21
|
+
return existsSync(dir) ? dir : undefined;
|
|
22
|
+
}
|
|
23
|
+
const expandHome = (p) => (p === "~" || p.startsWith("~/") ? join(homedir(), p.slice(1)) : p);
|
|
24
|
+
/** True if a file can be created in `dir` (macOS asks permission for Downloads the first time; a denial surfaces here). */
|
|
25
|
+
function writable(dir) {
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
const probe = join(dir, `.waygraph-probe-${process.pid}`);
|
|
29
|
+
writeFileSync(probe, "");
|
|
30
|
+
rmSync(probe);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Where `--video` recordings go: `WAYGRAPH_VIDEO_DIR`, else `video.dir` from the config (absolute, `~/…`, or relative to
|
|
39
|
+
* the project), else `<Downloads>/waygraph-videos/<project>`. With no usable Downloads folder, or on CI, it is
|
|
40
|
+
* `<project>/.waygraph-videos`. An explicit `--video <dir>` never reaches this.
|
|
41
|
+
*/
|
|
42
|
+
export async function resolveVideoDir(projectDir) {
|
|
43
|
+
const fallback = join(projectDir, ".waygraph-videos");
|
|
44
|
+
const configured = process.env.WAYGRAPH_VIDEO_DIR || (await loadWaygraphConfig(projectDir))?.video?.dir;
|
|
45
|
+
if (configured) {
|
|
46
|
+
const dir = isAbsolute(expandHome(configured)) ? expandHome(configured) : resolve(projectDir, configured);
|
|
47
|
+
if (writable(dir))
|
|
48
|
+
return dir;
|
|
49
|
+
console.error(`waygraph: cannot write videos to ${dir} - using ${fallback}`);
|
|
50
|
+
return fallback;
|
|
51
|
+
}
|
|
52
|
+
const downloads = process.env.CI ? undefined : downloadsDir();
|
|
53
|
+
if (!downloads)
|
|
54
|
+
return fallback;
|
|
55
|
+
const dir = join(downloads, "waygraph-videos", basename(resolve(projectDir)));
|
|
56
|
+
if (writable(dir))
|
|
57
|
+
return dir;
|
|
58
|
+
console.error(`waygraph: cannot write to ${downloads} (denied?) - saving videos in ${fallback}`);
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
package/dist/webserver.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// Starts/stops a project's
|
|
1
|
+
// Starts/stops a project's `webServer` (waygraph.config, or a legacy playwright.config) around `waygraph run`/`demo`/`auto`,
|
|
2
2
|
// the same block `waygraph test` already honors.
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { pathToFileURL } from "node:url";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
+
import { loadWaygraphConfig } from "./config.js";
|
|
7
8
|
const CONFIG_FILES = [
|
|
8
9
|
"playwright.config.ts",
|
|
9
10
|
"playwright.config.mts",
|
|
@@ -12,6 +13,9 @@ const CONFIG_FILES = [
|
|
|
12
13
|
"playwright.config.cjs",
|
|
13
14
|
];
|
|
14
15
|
async function loadWebServerConfigs(projectDir) {
|
|
16
|
+
const fromWaygraph = (await loadWaygraphConfig(projectDir))?.webServer;
|
|
17
|
+
if (fromWaygraph)
|
|
18
|
+
return Array.isArray(fromWaygraph) ? fromWaygraph : [fromWaygraph];
|
|
15
19
|
for (const name of CONFIG_FILES) {
|
|
16
20
|
const p = join(projectDir, name);
|
|
17
21
|
if (!existsSync(p))
|