@juxtapose/core 0.8.0 → 0.10.0
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/agent/handoff.d.ts +19 -1
- package/dist/agent/handoff.js +54 -2
- package/dist/agent/handoff.js.map +1 -1
- package/dist/analysis/affected.d.ts +17 -0
- package/dist/analysis/affected.js +34 -0
- package/dist/analysis/affected.js.map +1 -1
- package/dist/browser/flags.d.ts +14 -0
- package/dist/browser/flags.js +15 -0
- package/dist/browser/flags.js.map +1 -1
- package/dist/browser/page.d.ts +19 -1
- package/dist/browser/page.js +16 -3
- package/dist/browser/page.js.map +1 -1
- package/dist/capture/capture.d.ts +10 -1
- package/dist/capture/capture.js +121 -16
- package/dist/capture/capture.js.map +1 -1
- package/dist/capture/config-errors.d.ts +10 -0
- package/dist/capture/config-errors.js +20 -0
- package/dist/capture/config-errors.js.map +1 -0
- package/dist/capture/discover.d.ts +28 -0
- package/dist/capture/discover.js +109 -0
- package/dist/capture/discover.js.map +1 -0
- package/dist/capture/idle.d.ts +88 -0
- package/dist/capture/idle.js +545 -0
- package/dist/capture/idle.js.map +1 -0
- package/dist/capture/pool.d.ts +8 -0
- package/dist/capture/pool.js +2 -0
- package/dist/capture/pool.js.map +1 -1
- package/dist/capture/settle-options.d.ts +78 -0
- package/dist/capture/settle-options.js +234 -0
- package/dist/capture/settle-options.js.map +1 -0
- package/dist/capture/settle.d.ts +0 -18
- package/dist/capture/settle.js +7 -100
- package/dist/capture/settle.js.map +1 -1
- package/dist/capture/snap.d.ts +3 -0
- package/dist/capture/snap.js +7 -1
- package/dist/capture/snap.js.map +1 -1
- package/dist/capture/variants.d.ts +44 -0
- package/dist/capture/variants.js +204 -0
- package/dist/capture/variants.js.map +1 -0
- package/dist/diff/structural/deltas.d.ts +2 -3
- package/dist/diff/structural/deltas.js +3 -3
- package/dist/diff/structural/deltas.js.map +1 -1
- package/dist/diff/structural.d.ts +4 -1
- package/dist/diff/structural.js +3 -0
- package/dist/diff/structural.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/platform.d.ts +20 -0
- package/dist/platforms/urls.js +5 -1
- package/dist/platforms/urls.js.map +1 -1
- package/dist/report/comment.js +19 -8
- package/dist/report/comment.js.map +1 -1
- package/dist/report/format.d.ts +17 -0
- package/dist/report/format.js +16 -0
- package/dist/report/format.js.map +1 -1
- package/dist/report/html.js +40 -6
- package/dist/report/html.js.map +1 -1
- package/dist/run/run.d.ts +6 -0
- package/dist/run/run.js +32 -0
- package/dist/run/run.js.map +1 -1
- package/dist/types.d.ts +107 -2
- package/package.json +1 -1
package/dist/capture/capture.js
CHANGED
|
@@ -2,13 +2,20 @@
|
|
|
2
2
|
// each step exists. Because pass/fail is a byte-exact hash, every source of
|
|
3
3
|
// nondeterminism left in this path becomes a false failure somewhere.
|
|
4
4
|
import sharp from 'sharp';
|
|
5
|
+
import { emulatedMedia } from '../browser/page.js';
|
|
5
6
|
import { assertCssSelector } from '../browser/selectors.js';
|
|
6
|
-
import {
|
|
7
|
+
import { FREEZE_ANIMATIONS_CSS, waitForPaint, waitForSelector } from './settle.js';
|
|
8
|
+
import { installIdleDetector, movedSincePaint, resetIdle, waitForIdle, } from './idle.js';
|
|
9
|
+
import { checkSettle, layerScalars, resolveSettle, } from './settle-options.js';
|
|
7
10
|
import { captureStructure } from './structure.js';
|
|
11
|
+
import { deepEqual, mergeLiveParams, platformStoryId } from './variants.js';
|
|
8
12
|
const DEFAULT_CAPTURE_TIMEOUT_MS = 2000;
|
|
9
13
|
/** Per-page memo: skip work that only needs doing once per page. */
|
|
10
14
|
const styleInjected = new WeakSet();
|
|
11
15
|
const pageViewport = new WeakMap();
|
|
16
|
+
const pageColorScheme = new WeakMap();
|
|
17
|
+
/** The capture last shown on a page: its key and its platform story id. */
|
|
18
|
+
const pageCapture = new WeakMap();
|
|
12
19
|
const warmPages = new WeakMap();
|
|
13
20
|
export async function captureStory(page, story, deps, viewport) {
|
|
14
21
|
const started = Date.now();
|
|
@@ -17,35 +24,72 @@ export async function captureStory(page, story, deps, viewport) {
|
|
|
17
24
|
const params = { ...story.params };
|
|
18
25
|
const effectiveViewport = params.viewport ?? viewport;
|
|
19
26
|
const timeoutMs = params.captureTimeout ?? DEFAULT_CAPTURE_TIMEOUT_MS;
|
|
20
|
-
|
|
27
|
+
// Every platform call gets the platform's own id; the variant capture key
|
|
28
|
+
// means nothing to Storybook or Ladle.
|
|
29
|
+
const id = platformStoryId(story);
|
|
30
|
+
const ctx = {
|
|
31
|
+
story: id === story.storyId ? story : { ...story, storyId: id },
|
|
32
|
+
params,
|
|
33
|
+
timeoutMs,
|
|
34
|
+
};
|
|
21
35
|
// Selector syntax is validated before the page is touched: a Playwright-only
|
|
22
36
|
// selector (`text=`, `>>`) would otherwise match nothing and the story would
|
|
23
37
|
// be captured unmasked, or wait its whole timeout, and the baseline would
|
|
24
38
|
// record the wrong thing. Live params are re-checked below.
|
|
25
39
|
assertSelectors(params);
|
|
40
|
+
// Before the first navigation on this page: the init script applies to
|
|
41
|
+
// documents created after it is installed.
|
|
42
|
+
const idle = await installIdleDetector(page);
|
|
26
43
|
await ensureViewport(page, effectiveViewport);
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
44
|
+
// Only a variant sets a scheme: a default capture resets to light, which is
|
|
45
|
+
// what stops a pooled page carrying a dark variant's scheme into the next
|
|
46
|
+
// story.
|
|
47
|
+
const scheme = story.variant !== undefined ? (params.colorScheme ?? 'light') : 'light';
|
|
30
48
|
// A story that wants animations running can't reuse a page whose stylesheet
|
|
31
|
-
// already froze them, so force the cold path for those.
|
|
49
|
+
// already froze them, so force the cold path for those. Likewise a scheme
|
|
50
|
+
// change: an app that reads matchMedia once at boot keeps the old scheme
|
|
51
|
+
// across a warm switch (measured: card then card__dark captured both light,
|
|
52
|
+
// the reverse both dark). Decided before the scheme is applied, against
|
|
53
|
+
// what the page booted under.
|
|
54
|
+
//
|
|
55
|
+
// And another capture of the same story (default -> mobile): the platform
|
|
56
|
+
// sees its story already rendered and "switches" without re-rendering, so
|
|
57
|
+
// the variant would be captured from the previous capture's layout.
|
|
58
|
+
const previous = pageCapture.get(page);
|
|
59
|
+
const sameStoryOtherCapture = previous !== undefined && previous.base === id && previous.key !== story.storyId;
|
|
60
|
+
const schemeChanged = pageColorScheme.has(page) && pageColorScheme.get(page) !== scheme;
|
|
32
61
|
const warm = deps.warmSwitch === true &&
|
|
33
62
|
warmPages.get(page) === deps.baseUrl &&
|
|
63
|
+
!schemeChanged &&
|
|
64
|
+
!sameStoryOtherCapture &&
|
|
34
65
|
!(params.animations === 'play' && styleInjected.has(page));
|
|
35
|
-
|
|
36
|
-
|
|
66
|
+
// Before showStory, on every capture, so the story renders in its scheme
|
|
67
|
+
// from the first frame. Memoized, so it costs nothing when unchanged.
|
|
68
|
+
await ensureColorScheme(page, scheme);
|
|
69
|
+
// A variant shares its story's URL, and a URL differing only in its
|
|
70
|
+
// fragment is a same-document navigation: the cold path below would not
|
|
71
|
+
// reload, and an app that read the scheme at boot would keep the old one,
|
|
72
|
+
// or keep the previous capture's render. Blank the page so the story boots
|
|
73
|
+
// fresh.
|
|
74
|
+
if ((schemeChanged || sameStoryOtherCapture) && warmPages.has(page)) {
|
|
75
|
+
await page.goto('about:blank');
|
|
76
|
+
warmPages.delete(page);
|
|
77
|
+
}
|
|
78
|
+
pageCapture.set(page, { key: story.storyId, base: id });
|
|
79
|
+
// Before the render is triggered, so a request or ready() call from this
|
|
80
|
+
// story's render is never mistaken for the previous one's.
|
|
81
|
+
await resetIdle(page, idle, warm);
|
|
37
82
|
const switched = warm && deps.platform.switchStory
|
|
38
|
-
? await deps.platform.switchStory(page,
|
|
83
|
+
? await deps.platform.switchStory(page, id, ctx)
|
|
39
84
|
: false;
|
|
40
85
|
if (!switched) {
|
|
41
86
|
// Delegated rather than built here: not every platform addresses a story
|
|
42
87
|
// by URL. Playwright galleries render via window.mount() on a fixed page.
|
|
43
|
-
await deps.platform.showStory(page,
|
|
88
|
+
await deps.platform.showStory(page, id, deps.baseUrl);
|
|
44
89
|
// A navigation discards injected style tags, so the freeze must be
|
|
45
90
|
// re-applied for the next story that wants it.
|
|
46
91
|
styleInjected.delete(page);
|
|
47
92
|
warmPages.set(page, deps.baseUrl);
|
|
48
|
-
await armSettle(page);
|
|
49
93
|
}
|
|
50
94
|
// Freeze BEFORE the story renders, not after.
|
|
51
95
|
//
|
|
@@ -70,16 +114,25 @@ export async function captureStory(page, story, deps, viewport) {
|
|
|
70
114
|
// waitForSelector and skip on that platform.
|
|
71
115
|
//
|
|
72
116
|
// Live params override manifest params — the live preview is the story's own
|
|
73
|
-
// declaration, and the index is a lossy projection of it
|
|
74
|
-
|
|
117
|
+
// declaration, and the index is a lossy projection of it — but merge under
|
|
118
|
+
// a variant's own fields, so the mobile variant stays mobile.
|
|
119
|
+
const live = await deps.platform.readLiveParams?.(page, id);
|
|
75
120
|
if (live) {
|
|
76
|
-
Object.assign(params, live);
|
|
121
|
+
Object.assign(params, mergeLiveParams(story, live));
|
|
77
122
|
assertSelectors(params);
|
|
123
|
+
// A story can opt out of a project variant in params only the live
|
|
124
|
+
// preview carries (Storybook). Honour it as a skip.
|
|
125
|
+
if (story.variant !== undefined && live.variants?.[story.variant] === false) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (story.variant !== undefined && liveOverrideDiffers(story, live)) {
|
|
129
|
+
deps.onWarning?.(`jux: ${story.storyId}: the story's live parameters declare variant '${story.variant}' differently from the settings it was captured with. Parameters read after render can't change which variants exist or what they set; declare variants in the story's parameters, where discovery reads them before capture.`);
|
|
130
|
+
}
|
|
78
131
|
// Viewport is the one param that cannot be applied retroactively — the
|
|
79
132
|
// story has already laid out. Re-set it and let the platform settle
|
|
80
133
|
// again, so a story declaring its own viewport is captured at that size
|
|
81
134
|
// rather than the project default.
|
|
82
|
-
const liveViewport =
|
|
135
|
+
const liveViewport = params.viewport;
|
|
83
136
|
if (liveViewport && !sameViewport(liveViewport, effectiveViewport)) {
|
|
84
137
|
await ensureViewport(page, liveViewport);
|
|
85
138
|
await deps.platform.settle(page, { ...ctx, params });
|
|
@@ -91,10 +144,18 @@ export async function captureStory(page, story, deps, viewport) {
|
|
|
91
144
|
// screenshot and would report a meaningless hash.
|
|
92
145
|
if (params.skip === true)
|
|
93
146
|
return null;
|
|
147
|
+
// Project and call layers around the story's own: waitForSelector and
|
|
148
|
+
// delay are call > story > project; settle merges field by field. Live
|
|
149
|
+
// params can carry a `settle` discovery never saw, so it is checked here;
|
|
150
|
+
// a bad one fails this story with the reason.
|
|
151
|
+
Object.assign(params, layerScalars(deps.settleLayers?.project, params, deps.settleLayers?.call));
|
|
152
|
+
assertSelectors(params);
|
|
153
|
+
checkSettle(params.settle, `story '${story.storyId}'`);
|
|
154
|
+
const settle = resolveSettle(deps.settleLayers?.project?.settle, params.settle, deps.settleLayers?.call?.settle);
|
|
94
155
|
if (params.waitForSelector) {
|
|
95
156
|
await waitForSelector(page, params.waitForSelector, timeoutMs);
|
|
96
157
|
}
|
|
97
|
-
await
|
|
158
|
+
const first = await waitForIdle(page, idle, settle);
|
|
98
159
|
// Fixed delay is the escape hatch of last resort — it costs the same on
|
|
99
160
|
// every run whether or not it was needed. Prefer waitForSelector.
|
|
100
161
|
if (params.delay && params.delay > 0) {
|
|
@@ -105,6 +166,17 @@ export async function captureStory(page, story, deps, viewport) {
|
|
|
105
166
|
// this the capture intermittently catches an intermediate frame — which a
|
|
106
167
|
// byte-exact hash reports as a failure.
|
|
107
168
|
await waitForPaint(page);
|
|
169
|
+
// Work that landed during the paint barrier (an observer callback, a
|
|
170
|
+
// request the last frame started) gets one more wait, within what is left
|
|
171
|
+
// of the cap. Once: a page that keeps moving is captured, not chased.
|
|
172
|
+
let flagged = first.status === 'idle' ? undefined : first;
|
|
173
|
+
if (first.status === 'idle' &&
|
|
174
|
+
(await movedSincePaint(page, idle, first.snap, settle))) {
|
|
175
|
+
const second = await waitForIdle(page, idle, settle, Math.max(100, settle.timeoutMs - first.waitedMs));
|
|
176
|
+
await waitForPaint(page);
|
|
177
|
+
if (second.status !== 'idle')
|
|
178
|
+
flagged = second;
|
|
179
|
+
}
|
|
108
180
|
// Structure from the same frame the screenshot is about to take: after the
|
|
109
181
|
// paint barrier, before the shutter, with nothing in between that could
|
|
110
182
|
// mutate the page.
|
|
@@ -132,6 +204,8 @@ export async function captureStory(page, story, deps, viewport) {
|
|
|
132
204
|
.toBuffer();
|
|
133
205
|
return {
|
|
134
206
|
storyId: story.storyId,
|
|
207
|
+
...(story.baseStoryId !== undefined ? { baseStoryId: story.baseStoryId } : {}),
|
|
208
|
+
...(story.variant !== undefined ? { variant: story.variant } : {}),
|
|
135
209
|
image,
|
|
136
210
|
// The live params may have widened or narrowed this after first render,
|
|
137
211
|
// so report what we actually captured at — the agent handoff computes
|
|
@@ -140,8 +214,29 @@ export async function captureStory(page, story, deps, viewport) {
|
|
|
140
214
|
capturedAt: new Date().toISOString(),
|
|
141
215
|
durationMs: Date.now() - started,
|
|
142
216
|
...(structure ? { structure } : {}),
|
|
217
|
+
...(flagged?.status === 'timeout'
|
|
218
|
+
? {
|
|
219
|
+
settle: {
|
|
220
|
+
status: 'timeout',
|
|
221
|
+
timeoutMs: settle.timeoutMs,
|
|
222
|
+
pending: flagged.pending ?? [],
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
: flagged?.status === 'fallback'
|
|
226
|
+
? { settle: { status: 'fallback', pending: flagged.pending ?? [] } }
|
|
227
|
+
: {}),
|
|
143
228
|
};
|
|
144
229
|
}
|
|
230
|
+
/** Whether the live preview declares this variant with settings other than
|
|
231
|
+
* the ones it was expanded with: Storybook keeps story-level variants out of
|
|
232
|
+
* index.json, so they only surface here, after the capture set is fixed. */
|
|
233
|
+
function liveOverrideDiffers(story, live) {
|
|
234
|
+
const spec = story.variant !== undefined ? live.variants?.[story.variant] : undefined;
|
|
235
|
+
if (spec === undefined || spec === false || typeof spec !== 'object')
|
|
236
|
+
return false;
|
|
237
|
+
const expanded = story.variantParams ?? {};
|
|
238
|
+
return Object.entries(spec).some(([k, v]) => k !== 'extends' && !deepEqual(v, expanded[k]));
|
|
239
|
+
}
|
|
145
240
|
const sameViewport = (a, b) => a.width === b.width && a.height === b.height;
|
|
146
241
|
/** Setting the viewport is a CDP round-trip; skip it when nothing changed. */
|
|
147
242
|
async function ensureViewport(page, viewport) {
|
|
@@ -151,6 +246,16 @@ async function ensureViewport(page, viewport) {
|
|
|
151
246
|
await page.setViewportSize(viewport);
|
|
152
247
|
pageViewport.set(page, key);
|
|
153
248
|
}
|
|
249
|
+
/** Media emulation is a CDP round-trip too; skip it when nothing changed. */
|
|
250
|
+
async function ensureColorScheme(page, scheme) {
|
|
251
|
+
if (pageColorScheme.get(page) === scheme)
|
|
252
|
+
return;
|
|
253
|
+
if (page.setColorScheme)
|
|
254
|
+
await page.setColorScheme(scheme);
|
|
255
|
+
else
|
|
256
|
+
await page.send('Emulation.setEmulatedMedia', emulatedMedia(scheme));
|
|
257
|
+
pageColorScheme.set(page, scheme);
|
|
258
|
+
}
|
|
154
259
|
/**
|
|
155
260
|
* Inject the freeze stylesheet, once per page.
|
|
156
261
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/capture/capture.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,4EAA4E;AAC5E,sEAAsE;AAEtE,OAAO,KAAK,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"capture.js","sourceRoot":"","sources":["../../src/capture/capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** A bad variant declaration. The CLI reports it and exits 2. */
|
|
2
|
+
export declare class VariantConfigError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** A bad settle option. Extends VariantConfigError so every caller that
|
|
6
|
+
* already reports a declaration error (run, init, the review server) reports
|
|
7
|
+
* this one the same way: exit 2 with the message. */
|
|
8
|
+
export declare class SettleConfigError extends VariantConfigError {
|
|
9
|
+
constructor(message: string);
|
|
10
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Declaration errors. Their own module because variants.ts and
|
|
2
|
+
// settle-options.ts each need the other's checks, and a class extended at
|
|
3
|
+
// module evaluation must not sit on an import cycle.
|
|
4
|
+
/** A bad variant declaration. The CLI reports it and exits 2. */
|
|
5
|
+
export class VariantConfigError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(`jux: ${message}`);
|
|
8
|
+
this.name = 'VariantConfigError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** A bad settle option. Extends VariantConfigError so every caller that
|
|
12
|
+
* already reports a declaration error (run, init, the review server) reports
|
|
13
|
+
* this one the same way: exit 2 with the message. */
|
|
14
|
+
export class SettleConfigError extends VariantConfigError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'SettleConfigError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=config-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-errors.js","sourceRoot":"","sources":["../../src/capture/config-errors.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,0EAA0E;AAC1E,qDAAqD;AAErD,iEAAiE;AACjE,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,QAAQ,OAAO,EAAE,CAAC,CAAC;QACzB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED;;sDAEsD;AACtD,MAAM,OAAO,iBAAkB,SAAQ,kBAAkB;IACvD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Browser } from '../browser/browser.js';
|
|
2
|
+
import type { EnumerateTarget, Platform } from '../platform.js';
|
|
3
|
+
import type { StoryManifest, VariantSpec, Viewport } from '../types.js';
|
|
4
|
+
export interface PrepareOptions {
|
|
5
|
+
platform: Platform;
|
|
6
|
+
browser: Browser;
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
viewport: Viewport;
|
|
9
|
+
/** `ProjectConfig.variants`. */
|
|
10
|
+
projectVariants?: Record<string, VariantSpec | false>;
|
|
11
|
+
onWarning?: (message: string) => void;
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
/** Keep discovered params on disk, keyed by the platform's
|
|
14
|
+
* `discoveryKey`, so an unchanged build skips the preview load. */
|
|
15
|
+
cache?: {
|
|
16
|
+
dir: string;
|
|
17
|
+
target: EnumerateTarget;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Merge the platform's discovered params over the enumerated ones. Never
|
|
22
|
+
* throws for discovery itself: a preview that can't be read costs the
|
|
23
|
+
* story-level settings the index lacks, which is today's behavior, and must
|
|
24
|
+
* not cost the run. Throws VariantConfigError from expansion.
|
|
25
|
+
*/
|
|
26
|
+
export declare function discoverParams(stories: readonly StoryManifest[], opts: Omit<PrepareOptions, 'projectVariants'>): Promise<StoryManifest[]>;
|
|
27
|
+
/** Discover, then expand: one StoryManifest per capture. */
|
|
28
|
+
export declare function prepareCaptures(stories: readonly StoryManifest[], opts: PrepareOptions): Promise<StoryManifest[]>;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Enumeration-time parameter discovery, then variant expansion: the step
|
|
2
|
+
// between "what stories exist" and "what captures to take".
|
|
3
|
+
//
|
|
4
|
+
// Lives in core so every caller that captures (jux run, jux init, the review
|
|
5
|
+
// server) builds the same capture set from the same inputs.
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { expandVariants } from './variants.js';
|
|
10
|
+
/** A preview that hasn't produced its parameters in this long is broken, not
|
|
11
|
+
* slow: the example's 17 stories take ~60 ms. */
|
|
12
|
+
const DISCOVER_TIMEOUT_MS = 30_000;
|
|
13
|
+
/** Bumped when the cached shape or what discovery reads changes. */
|
|
14
|
+
const CACHE_SCHEMA = 1;
|
|
15
|
+
const CACHE_FILE = 'discovered-params.json';
|
|
16
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
17
|
+
/**
|
|
18
|
+
* Merge the platform's discovered params over the enumerated ones. Never
|
|
19
|
+
* throws for discovery itself: a preview that can't be read costs the
|
|
20
|
+
* story-level settings the index lacks, which is today's behavior, and must
|
|
21
|
+
* not cost the run. Throws VariantConfigError from expansion.
|
|
22
|
+
*/
|
|
23
|
+
export async function discoverParams(stories, opts) {
|
|
24
|
+
const discover = opts.platform.discoverParams?.bind(opts.platform);
|
|
25
|
+
if (!discover || stories.length === 0)
|
|
26
|
+
return [...stories];
|
|
27
|
+
const key = await cacheKey(opts);
|
|
28
|
+
const cached = key ? await readCache(opts.cache.dir, key) : undefined;
|
|
29
|
+
const found = cached ?? (await discoverLive(discover, opts));
|
|
30
|
+
if (!found)
|
|
31
|
+
return [...stories];
|
|
32
|
+
if (key && !cached)
|
|
33
|
+
await writeCache(opts.cache.dir, key, found);
|
|
34
|
+
return stories.map((s) => {
|
|
35
|
+
const params = found.get(s.storyId);
|
|
36
|
+
return isRecord(params) ? { ...s, params: { ...s.params, ...params } } : s;
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/** One preview load; undefined (after a warning) when it can't be read. */
|
|
40
|
+
async function discoverLive(discover, opts) {
|
|
41
|
+
let found;
|
|
42
|
+
const context = await opts.browser.newContext({ viewport: opts.viewport });
|
|
43
|
+
let timer;
|
|
44
|
+
try {
|
|
45
|
+
const page = await context.newPage();
|
|
46
|
+
const timeoutMs = opts.timeoutMs ?? DISCOVER_TIMEOUT_MS;
|
|
47
|
+
found = await Promise.race([
|
|
48
|
+
discover(page, opts.baseUrl),
|
|
49
|
+
new Promise((_, reject) => {
|
|
50
|
+
timer = setTimeout(() => reject(new Error(`no answer within ${timeoutMs} ms`)), timeoutMs);
|
|
51
|
+
}),
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
opts.onWarning?.(`jux: could not read story parameters from the ${opts.platform.name} preview (${err instanceof Error ? err.message : String(err)}); story-level settings and variants come from the index only`);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
await context.close().catch(() => { });
|
|
61
|
+
}
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
/** The platform's fingerprint of what it would discover from, salted with
|
|
65
|
+
* the platform and cache schema; undefined disables the cache. */
|
|
66
|
+
async function cacheKey(opts) {
|
|
67
|
+
if (!opts.cache || !opts.platform.discoveryKey)
|
|
68
|
+
return undefined;
|
|
69
|
+
try {
|
|
70
|
+
const key = await opts.platform.discoveryKey(opts.cache.target);
|
|
71
|
+
if (key === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
return createHash('sha256')
|
|
74
|
+
.update(`${CACHE_SCHEMA}\0${opts.platform.name}\0${key}`)
|
|
75
|
+
.digest('hex');
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/** A missing, unreadable or mismatched cache is a miss, never an error. */
|
|
82
|
+
async function readCache(dir, key) {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = JSON.parse(await readFile(path.join(dir, CACHE_FILE), 'utf8'));
|
|
85
|
+
if (!isRecord(parsed) || parsed['key'] !== key || !isRecord(parsed['params']))
|
|
86
|
+
return undefined;
|
|
87
|
+
return new Map(Object.entries(parsed['params']));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/** Written atomically; a failed write costs the next run a discovery. */
|
|
94
|
+
async function writeCache(dir, key, found) {
|
|
95
|
+
try {
|
|
96
|
+
await mkdir(dir, { recursive: true });
|
|
97
|
+
const tmp = path.join(dir, `${CACHE_FILE}.${process.pid}.tmp`);
|
|
98
|
+
await writeFile(tmp, JSON.stringify({ key, params: Object.fromEntries(found) }) + '\n');
|
|
99
|
+
await rename(tmp, path.join(dir, CACHE_FILE));
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// best effort
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Discover, then expand: one StoryManifest per capture. */
|
|
106
|
+
export async function prepareCaptures(stories, opts) {
|
|
107
|
+
return expandVariants(await discoverParams(stories, opts), opts.projectVariants);
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=discover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discover.js","sourceRoot":"","sources":["../../src/capture/discover.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Page } from '../browser/page.js';
|
|
2
|
+
import { type ResolvedSettle } from './settle-options.js';
|
|
3
|
+
/** What the page reported at its last check. */
|
|
4
|
+
export interface IdleSnapshot {
|
|
5
|
+
timers: number;
|
|
6
|
+
rafs: number;
|
|
7
|
+
/** Posted messages sent and not yet delivered. */
|
|
8
|
+
msgs: number;
|
|
9
|
+
delivered: number;
|
|
10
|
+
/** Workers created within the horizon that have not answered yet. */
|
|
11
|
+
workers: number;
|
|
12
|
+
workerSrcs: string[];
|
|
13
|
+
mut: number;
|
|
14
|
+
fonts: string;
|
|
15
|
+
images: number;
|
|
16
|
+
/** The first few incomplete images, for the pending description. */
|
|
17
|
+
imageSrcs: string[];
|
|
18
|
+
ready: boolean;
|
|
19
|
+
/** The last check saw the DOM change. */
|
|
20
|
+
changing?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface IdleResult {
|
|
23
|
+
/** `fallback`: the detector could not run (not installed, or the page
|
|
24
|
+
* navigated mid-wait), so a fonts/images/quiet-DOM wait stood in. */
|
|
25
|
+
status: 'idle' | 'timeout' | 'fallback';
|
|
26
|
+
waitedMs: number;
|
|
27
|
+
snap?: IdleSnapshot;
|
|
28
|
+
/** Set on `timeout` (what was still pending) and `fallback` (why).
|
|
29
|
+
* Never empty. */
|
|
30
|
+
pending?: string[];
|
|
31
|
+
}
|
|
32
|
+
interface Request {
|
|
33
|
+
url: string;
|
|
34
|
+
type?: string;
|
|
35
|
+
/** Set on main-frame requests: the document loader that started it. */
|
|
36
|
+
mainLoader?: string;
|
|
37
|
+
}
|
|
38
|
+
/** In-flight and completed requests on one page, from CDP Network events. */
|
|
39
|
+
export declare class NetworkTracker {
|
|
40
|
+
private readonly inflight;
|
|
41
|
+
private done;
|
|
42
|
+
private readonly listeners;
|
|
43
|
+
/** In flight when the current story started on a warm switch: the last
|
|
44
|
+
* story's, never this one's. */
|
|
45
|
+
private excluded;
|
|
46
|
+
/** The committed main document's loader. */
|
|
47
|
+
private mainLoader;
|
|
48
|
+
constructor(page: Page & Required<Pick<Page, 'on'>>, mainFrameId: string | undefined);
|
|
49
|
+
/** A warm switch renders a new story in the same document: the last
|
|
50
|
+
* story's completions must not satisfy `require`, and its requests still
|
|
51
|
+
* in flight (a long poll) must not pin this one at the cap. */
|
|
52
|
+
reset(): void;
|
|
53
|
+
/** In-flight requests the wait counts: required, or not ignored. */
|
|
54
|
+
counted(s: ResolvedSettle): Request[];
|
|
55
|
+
/** `require` patterns no completed request has matched yet. */
|
|
56
|
+
missing(s: ResolvedSettle): string[];
|
|
57
|
+
busy(s: ResolvedSettle): boolean;
|
|
58
|
+
/** Resolves when the wait no longer counts anything, or after `ms`. */
|
|
59
|
+
settled(s: ResolvedSettle, ms: number): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
/** One page's settle machinery: the init script is installed, and the
|
|
62
|
+
* tracker is null when the page cannot deliver events. */
|
|
63
|
+
export interface IdleDetector {
|
|
64
|
+
net: NetworkTracker | null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Install the init script and the request tracker, once per page, before
|
|
68
|
+
* its first navigation: the script applies to every later document on the
|
|
69
|
+
* page. Network.enable is already part of page init.
|
|
70
|
+
*/
|
|
71
|
+
export declare function installIdleDetector(page: Page): Promise<IdleDetector>;
|
|
72
|
+
/** Before a story renders: forget the last story's completions, and on a
|
|
73
|
+
* warm switch (same document) its ready() call. */
|
|
74
|
+
export declare function resetIdle(page: Page, idle: IdleDetector, warm: boolean): Promise<void>;
|
|
75
|
+
/**
|
|
76
|
+
* Wait until the page reports no pending work and no counted request is in
|
|
77
|
+
* flight, or `capMs` passes. Never throws.
|
|
78
|
+
*/
|
|
79
|
+
export declare function waitForIdle(page: Page, idle: IdleDetector, s: ResolvedSettle, capMs?: number): Promise<IdleResult>;
|
|
80
|
+
/** After the paint barrier: did the DOM, fonts, images or network move? */
|
|
81
|
+
export declare function movedSincePaint(page: Page, idle: IdleDetector, before: IdleSnapshot | undefined, s: ResolvedSettle): Promise<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* What was still pending at the cap, one line per kind, so an agent reading
|
|
84
|
+
* the result can respond: ignore a request, require one, raise the cap, or
|
|
85
|
+
* mask what keeps changing. Never empty.
|
|
86
|
+
*/
|
|
87
|
+
export declare function describePending(snap: IdleSnapshot | undefined, net: NetworkTracker | null, s: ResolvedSettle): string[];
|
|
88
|
+
export {};
|