@tempo-ai/visual-review 0.0.2 → 0.0.108-staging.1
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/bin.js +1 -1
- package/dist/{chunk-HTDJZ32L.js → chunk-WGNWCWMJ.js} +425 -171
- package/dist/chunk-WGNWCWMJ.js.map +1 -0
- package/dist/{cli-X7OOU6H4.js → cli-DPVTULCN.js} +4 -2
- package/dist/index.d.ts +6 -12
- package/dist/index.js +1 -1
- package/package.json +2 -1
- package/templates/visual-review.yml +3 -2
- package/dist/chunk-HTDJZ32L.js.map +0 -1
- /package/dist/{cli-X7OOU6H4.js.map → cli-DPVTULCN.js.map} +0 -0
|
@@ -15,6 +15,63 @@ import path16 from "path";
|
|
|
15
15
|
|
|
16
16
|
// ../canvas-capture-headless/src/capture.ts
|
|
17
17
|
import { createHash } from "crypto";
|
|
18
|
+
|
|
19
|
+
// ../../modules/iframe-projection/combined/projection-pending.ts
|
|
20
|
+
var PROJECTION_PENDING_ATTRIBUTE = "data-tempo-projection-pending";
|
|
21
|
+
|
|
22
|
+
// ../../modules/component-render-pool/direct-render.ts
|
|
23
|
+
var DIRECT_RENDER_PARAMS = {
|
|
24
|
+
canvasPath: "canvasPath",
|
|
25
|
+
storyboardId: "storyboardId",
|
|
26
|
+
width: "width",
|
|
27
|
+
height: "height",
|
|
28
|
+
intrinsicSize: "intrinsicSize",
|
|
29
|
+
idle: "tempoCaptureIdle"
|
|
30
|
+
};
|
|
31
|
+
var STORYBOARD_RENDER_ID_ATTRIBUTE = "data-tempo-storyboard-render-id";
|
|
32
|
+
var STORYBOARD_PENDING_SELECTOR = `[${PROJECTION_PENDING_ATTRIBUTE}]`;
|
|
33
|
+
function buildCanvasStoryboardRenderId(canvasPath, storyboardId) {
|
|
34
|
+
return `canvas:${encodeURIComponent(canvasPath.trim())}#storyboard:${encodeURIComponent(
|
|
35
|
+
storyboardId.trim()
|
|
36
|
+
)}`;
|
|
37
|
+
}
|
|
38
|
+
function buildStoryboardRenderSelector(renderId) {
|
|
39
|
+
return `[${STORYBOARD_RENDER_ID_ATTRIBUTE}=${JSON.stringify(renderId)}]`;
|
|
40
|
+
}
|
|
41
|
+
function buildDirectRenderUrl(hostUrl, target) {
|
|
42
|
+
const hasWidth = target.width !== void 0;
|
|
43
|
+
const hasHeight = target.height !== void 0;
|
|
44
|
+
if (hasWidth !== hasHeight) {
|
|
45
|
+
throw new Error("Direct render width and height must be provided together");
|
|
46
|
+
}
|
|
47
|
+
const url = new URL(hostUrl);
|
|
48
|
+
url.searchParams.delete(DIRECT_RENDER_PARAMS.idle);
|
|
49
|
+
url.searchParams.set(DIRECT_RENDER_PARAMS.canvasPath, target.canvasPath);
|
|
50
|
+
url.searchParams.set(DIRECT_RENDER_PARAMS.storyboardId, target.storyboardId);
|
|
51
|
+
if (hasWidth && hasHeight) {
|
|
52
|
+
url.searchParams.set(DIRECT_RENDER_PARAMS.width, String(target.width));
|
|
53
|
+
url.searchParams.set(DIRECT_RENDER_PARAMS.height, String(target.height));
|
|
54
|
+
} else {
|
|
55
|
+
url.searchParams.delete(DIRECT_RENDER_PARAMS.width);
|
|
56
|
+
url.searchParams.delete(DIRECT_RENDER_PARAMS.height);
|
|
57
|
+
}
|
|
58
|
+
if (target.intrinsicSize) {
|
|
59
|
+
url.searchParams.set(DIRECT_RENDER_PARAMS.intrinsicSize, "1");
|
|
60
|
+
} else {
|
|
61
|
+
url.searchParams.delete(DIRECT_RENDER_PARAMS.intrinsicSize);
|
|
62
|
+
}
|
|
63
|
+
return url.toString();
|
|
64
|
+
}
|
|
65
|
+
function buildDirectRenderIdleUrl(hostUrl) {
|
|
66
|
+
const url = new URL(hostUrl);
|
|
67
|
+
for (const param of Object.values(DIRECT_RENDER_PARAMS)) {
|
|
68
|
+
url.searchParams.delete(param);
|
|
69
|
+
}
|
|
70
|
+
url.searchParams.set(DIRECT_RENDER_PARAMS.idle, "1");
|
|
71
|
+
return url.toString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ../canvas-capture-headless/src/capture.ts
|
|
18
75
|
import { PNG } from "pngjs";
|
|
19
76
|
|
|
20
77
|
// ../../modules/frame-pool/capture-determinism.ts
|
|
@@ -70,100 +127,240 @@ var FREEZE_MOTION_CSS = `
|
|
|
70
127
|
}
|
|
71
128
|
`;
|
|
72
129
|
|
|
73
|
-
//
|
|
74
|
-
var
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
130
|
+
// ../../modules/frame-pool/settle-script.ts
|
|
131
|
+
var SETTLE_STEPS = [
|
|
132
|
+
"target",
|
|
133
|
+
"fonts",
|
|
134
|
+
"suspense",
|
|
135
|
+
"content-quiet",
|
|
136
|
+
"paint"
|
|
79
137
|
];
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
onStep?.(name, Date.now() - startedAt);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
async function settlePage(page, targetSelector, options = {}) {
|
|
92
|
-
const quietMs = options.quietMs ?? 300;
|
|
93
|
-
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
94
|
-
await runSettleStep(
|
|
95
|
-
"target",
|
|
96
|
-
() => page.waitForSelector(targetSelector, {
|
|
97
|
-
state: "attached",
|
|
98
|
-
timeout: timeoutMs
|
|
99
|
-
}),
|
|
100
|
-
options.onStep
|
|
101
|
-
);
|
|
102
|
-
await runSettleStep(
|
|
103
|
-
"fonts",
|
|
104
|
-
() => page.waitForFunction(() => document.fonts.status === "loaded", {
|
|
105
|
-
timeout: timeoutMs
|
|
106
|
-
}),
|
|
107
|
-
options.onStep
|
|
138
|
+
var DEFAULT_SETTLE_QUIET_MS = 300;
|
|
139
|
+
var DEFAULT_SETTLE_STEP_TIMEOUT_MS = 15e3;
|
|
140
|
+
var DEFAULT_SETTLE_POLL_MS = 50;
|
|
141
|
+
function buildSettleWaitScript(options) {
|
|
142
|
+
const quietMs = Math.max(0, Math.round(options.quietMs ?? DEFAULT_SETTLE_QUIET_MS));
|
|
143
|
+
const stepTimeoutMs = Math.max(
|
|
144
|
+
1,
|
|
145
|
+
Math.round(options.stepTimeoutMs ?? DEFAULT_SETTLE_STEP_TIMEOUT_MS)
|
|
108
146
|
);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
(
|
|
112
|
-
(selector) => {
|
|
113
|
-
const target = document.querySelector(selector);
|
|
114
|
-
if (!target) return false;
|
|
115
|
-
return !target.querySelector("[data-tempo-projection-pending]");
|
|
116
|
-
},
|
|
117
|
-
targetSelector,
|
|
118
|
-
{ timeout: timeoutMs }
|
|
119
|
-
),
|
|
120
|
-
options.onStep
|
|
147
|
+
const quietTimeoutMs = Math.max(
|
|
148
|
+
1,
|
|
149
|
+
Math.round(options.quietTimeoutMs ?? stepTimeoutMs)
|
|
121
150
|
);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
()
|
|
125
|
-
|
|
126
|
-
|
|
151
|
+
const pollMs = Math.max(1, Math.round(options.pollMs ?? DEFAULT_SETTLE_POLL_MS));
|
|
152
|
+
return `
|
|
153
|
+
(function() {
|
|
154
|
+
var selector = ${JSON.stringify(options.selector)};
|
|
155
|
+
var pendingSelector = ${JSON.stringify(options.pendingSelector ?? null)};
|
|
156
|
+
var quietMs = ${quietMs};
|
|
157
|
+
var stepTimeoutMs = ${stepTimeoutMs};
|
|
158
|
+
var quietTimeoutMs = ${quietTimeoutMs};
|
|
159
|
+
var quietBestEffort = ${options.quietBestEffort === true ? "true" : "false"};
|
|
160
|
+
var pollMs = ${pollMs};
|
|
161
|
+
var runPaint = ${options.paint === false ? "false" : "true"};
|
|
162
|
+
var steps = {};
|
|
163
|
+
// Cleared when the content-quiet step gives up under best effort.
|
|
164
|
+
var quiet = true;
|
|
165
|
+
// Monotonic and immune to the determinism kit's frozen Date.
|
|
166
|
+
var now = typeof performance !== 'undefined' && performance && typeof performance.now === 'function'
|
|
167
|
+
? function() { return performance.now(); }
|
|
168
|
+
: function() { return Date.now(); };
|
|
169
|
+
|
|
170
|
+
function failure(step, message) {
|
|
171
|
+
return { ok: false, step: step, message: message, steps: steps };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Polls \`check\` until it answers true, or \`budgetMs\` elapses. The
|
|
175
|
+
// first check runs synchronously so an already-settled step costs no
|
|
176
|
+
// poll interval. A step run with \`giveUp\` resolves at its budget
|
|
177
|
+
// instead of failing, recording that the content never held still.
|
|
178
|
+
function until(step, check, budgetMs, giveUp) {
|
|
179
|
+
var started = now();
|
|
180
|
+
return new Promise(function(resolve, reject) {
|
|
181
|
+
function tick() {
|
|
182
|
+
var done;
|
|
183
|
+
try {
|
|
184
|
+
done = check();
|
|
185
|
+
} catch (error) {
|
|
186
|
+
steps[step] = Math.round(now() - started);
|
|
187
|
+
reject(failure(step, String((error && error.message) || error)));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (done) {
|
|
191
|
+
steps[step] = Math.round(now() - started);
|
|
192
|
+
resolve();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (now() - started >= budgetMs) {
|
|
196
|
+
steps[step] = Math.round(now() - started);
|
|
197
|
+
if (giveUp) {
|
|
198
|
+
quiet = false;
|
|
199
|
+
resolve();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
reject(failure(step, 'Timeout ' + budgetMs + 'ms exceeded'));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
setTimeout(tick, pollMs);
|
|
206
|
+
}
|
|
207
|
+
tick();
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
var quietKey = null;
|
|
212
|
+
var quietSince = 0;
|
|
213
|
+
// Images that take part in the capture: laid out and not hidden. A
|
|
214
|
+
// hidden responsive variant never starts native lazy loading and must
|
|
215
|
+
// not hold the window; a rendered lazy image never starts either,
|
|
216
|
+
// because a capture does not scroll, so it is asked for eagerly.
|
|
217
|
+
function renderedImages(target) {
|
|
218
|
+
var all = Array.prototype.slice.call(target.querySelectorAll('img'));
|
|
219
|
+
var rendered = [];
|
|
220
|
+
for (var i = 0; i < all.length; i++) {
|
|
221
|
+
var img = all[i];
|
|
222
|
+
if (img.getClientRects().length === 0) continue;
|
|
223
|
+
var visibility = getComputedStyle(img).visibility;
|
|
224
|
+
if (visibility === 'hidden' || visibility === 'collapse') continue;
|
|
225
|
+
rendered.push(img);
|
|
226
|
+
}
|
|
227
|
+
return rendered;
|
|
228
|
+
}
|
|
229
|
+
function contentQuiet() {
|
|
230
|
+
var target = document.querySelector(selector);
|
|
127
231
|
if (!target) return false;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
232
|
+
var images = renderedImages(target);
|
|
233
|
+
for (var i = 0; i < images.length; i++) {
|
|
234
|
+
if (images[i].loading === 'lazy') images[i].loading = 'eager';
|
|
235
|
+
}
|
|
236
|
+
for (var j = 0; j < images.length; j++) {
|
|
237
|
+
if (!images[j].complete) {
|
|
238
|
+
// Load/error handlers can replace content without moving its
|
|
239
|
+
// geometry: start a fresh quiet window once they have run.
|
|
240
|
+
quietKey = null;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
var rect = target.getBoundingClientRect();
|
|
245
|
+
var text = target.innerText || '';
|
|
246
|
+
var key = [
|
|
133
247
|
rect.x,
|
|
134
248
|
rect.y,
|
|
135
249
|
rect.width,
|
|
136
250
|
rect.height,
|
|
137
|
-
target.querySelectorAll(
|
|
251
|
+
target.querySelectorAll('*').length,
|
|
138
252
|
text.length,
|
|
139
253
|
images.length
|
|
140
|
-
].join(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
state.key = key2;
|
|
146
|
-
state.since = now;
|
|
254
|
+
].join(',');
|
|
255
|
+
var at = now();
|
|
256
|
+
if (quietKey !== key) {
|
|
257
|
+
quietKey = key;
|
|
258
|
+
quietSince = at;
|
|
147
259
|
return false;
|
|
148
260
|
}
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
261
|
+
return at - quietSince >= quietMs;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function paint() {
|
|
265
|
+
if (!runPaint) return Promise.resolve();
|
|
266
|
+
var started = now();
|
|
267
|
+
return new Promise(function(resolve) {
|
|
268
|
+
requestAnimationFrame(function() {
|
|
269
|
+
requestAnimationFrame(function() {
|
|
270
|
+
steps.paint = Math.round(now() - started);
|
|
271
|
+
resolve();
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return until('target', function() {
|
|
278
|
+
return document.querySelector(selector) !== null;
|
|
279
|
+
}, stepTimeoutMs, false)
|
|
280
|
+
.then(function() {
|
|
281
|
+
return until('fonts', function() {
|
|
282
|
+
var fonts = document.fonts;
|
|
283
|
+
return !fonts || fonts.status === 'loaded';
|
|
284
|
+
}, stepTimeoutMs, false);
|
|
285
|
+
})
|
|
286
|
+
.then(function() {
|
|
287
|
+
return until('suspense', function() {
|
|
288
|
+
if (!pendingSelector) return true;
|
|
289
|
+
var target = document.querySelector(selector);
|
|
290
|
+
if (!target) return false;
|
|
291
|
+
return !target.querySelector(pendingSelector);
|
|
292
|
+
}, stepTimeoutMs, false);
|
|
293
|
+
})
|
|
294
|
+
.then(function() {
|
|
295
|
+
return until('content-quiet', contentQuiet, quietTimeoutMs, quietBestEffort);
|
|
296
|
+
})
|
|
297
|
+
.then(paint)
|
|
298
|
+
.then(
|
|
299
|
+
function() {
|
|
300
|
+
return { ok: true, steps: steps, quiet: quiet };
|
|
301
|
+
},
|
|
302
|
+
function(caught) {
|
|
303
|
+
if (caught && caught.ok === false) return caught;
|
|
304
|
+
return failure('paint', String((caught && caught.message) || caught));
|
|
305
|
+
}
|
|
306
|
+
);
|
|
307
|
+
})()
|
|
308
|
+
`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ../canvas-capture-headless/src/navigation-errors.ts
|
|
312
|
+
function isNavigationInterruptedError(message) {
|
|
313
|
+
return /Execution context was destroyed|most likely because of a navigation|Cannot find context with specified id|[Ff]rame was detached|Navigation interrupted|is interrupted by another navigation|Inspected target navigated or closed|Cannot find object with id|context was destroyed/.test(
|
|
314
|
+
message
|
|
164
315
|
);
|
|
165
316
|
}
|
|
166
317
|
|
|
318
|
+
// ../canvas-capture-headless/src/determinism.ts
|
|
319
|
+
var CHROMIUM_LAUNCH_ARGS = [
|
|
320
|
+
"--force-color-profile=srgb",
|
|
321
|
+
"--force-device-scale-factor=2",
|
|
322
|
+
"--hide-scrollbars",
|
|
323
|
+
"--disable-lcd-text"
|
|
324
|
+
];
|
|
325
|
+
async function settlePage(page, targetSelector, options = {}) {
|
|
326
|
+
const quietMs = options.quietMs ?? 300;
|
|
327
|
+
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
328
|
+
const budgetMs = timeoutMs * SETTLE_STEPS.length;
|
|
329
|
+
const deadline = Date.now() + budgetMs;
|
|
330
|
+
let resumes = 0;
|
|
331
|
+
for (; ; ) {
|
|
332
|
+
let report;
|
|
333
|
+
try {
|
|
334
|
+
report = await page.evaluate(
|
|
335
|
+
buildSettleWaitScript({
|
|
336
|
+
selector: targetSelector,
|
|
337
|
+
pendingSelector: STORYBOARD_PENDING_SELECTOR,
|
|
338
|
+
quietMs,
|
|
339
|
+
stepTimeoutMs: Math.max(1, Math.min(timeoutMs, deadline - Date.now()))
|
|
340
|
+
})
|
|
341
|
+
);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
344
|
+
if (!isNavigationInterruptedError(message)) throw error;
|
|
345
|
+
resumes += 1;
|
|
346
|
+
if (Date.now() >= deadline) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`settle: Timeout ${budgetMs}ms exceeded; the document was replaced ${resumes} times while waiting`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
for (const step of SETTLE_STEPS) {
|
|
354
|
+
const ms = report.steps[step];
|
|
355
|
+
if (ms !== void 0) options.onStep?.(step, ms);
|
|
356
|
+
}
|
|
357
|
+
if (!report.ok) {
|
|
358
|
+
throw new Error(`settle.${report.step}: ${report.message}`);
|
|
359
|
+
}
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
167
364
|
// ../canvas-capture-headless/src/ensure-chromium.ts
|
|
168
365
|
import { spawn } from "child_process";
|
|
169
366
|
import { createRequire } from "module";
|
|
@@ -223,7 +420,7 @@ async function installChromium(log, spawnImpl = spawn) {
|
|
|
223
420
|
}
|
|
224
421
|
|
|
225
422
|
// ../canvas-capture-headless/src/fingerprint.ts
|
|
226
|
-
var KIT_VERSION = "
|
|
423
|
+
var KIT_VERSION = "3";
|
|
227
424
|
var FONTS_BUNDLE_VERSION = "system-1";
|
|
228
425
|
var DEVICE_SCALE_FACTOR = 2;
|
|
229
426
|
function buildEnvFingerprint(input) {
|
|
@@ -534,6 +731,9 @@ function installRenderProfileInPage() {
|
|
|
534
731
|
const renderers = /* @__PURE__ */ new Map();
|
|
535
732
|
profileWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__ = {
|
|
536
733
|
supportsFiber: true,
|
|
734
|
+
// Consumers that also load `modules/iframe-projection` see this window
|
|
735
|
+
// property through its global `DevToolsHook` augmentation, where
|
|
736
|
+
// `renderers` is typed by ReactRenderer; this stub only counts them.
|
|
537
737
|
renderers,
|
|
538
738
|
inject(renderer) {
|
|
539
739
|
profile.reactInjectedMs ??= performance.now() - profile.startedAtMs;
|
|
@@ -607,15 +807,15 @@ function readRenderProfileInPage(targetCanvasPath) {
|
|
|
607
807
|
|
|
608
808
|
// ../canvas-capture-headless/src/capture.ts
|
|
609
809
|
function storyboardRenderId(hostCanvasPath, storyboardId) {
|
|
610
|
-
return
|
|
611
|
-
}
|
|
612
|
-
function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
810
|
+
return buildCanvasStoryboardRenderId(hostCanvasPath, storyboardId);
|
|
811
|
+
}
|
|
812
|
+
function buildDirectRenderUrl2(hostUrl, target) {
|
|
813
|
+
return buildDirectRenderUrl(hostUrl, {
|
|
814
|
+
canvasPath: target.hostCanvasPath,
|
|
815
|
+
storyboardId: target.storyboardId,
|
|
816
|
+
width: target.width,
|
|
817
|
+
height: target.height
|
|
818
|
+
});
|
|
619
819
|
}
|
|
620
820
|
function sha256Hex(bytes) {
|
|
621
821
|
return createHash("sha256").update(bytes).digest("hex");
|
|
@@ -636,11 +836,6 @@ function looksBlank(pngBytes) {
|
|
|
636
836
|
}
|
|
637
837
|
return true;
|
|
638
838
|
}
|
|
639
|
-
function isNavigationInterruptedError(message) {
|
|
640
|
-
return /Execution context was destroyed|most likely because of a navigation|Cannot find context with specified id|[Ff]rame was detached|Navigation interrupted|is interrupted by another navigation|Inspected target navigated or closed|Cannot find object with id|context was destroyed/.test(
|
|
641
|
-
message
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
839
|
function isPageGoneError(message) {
|
|
645
840
|
return /Target closed|Target page, context or browser has been closed|Page closed|Target crashed/.test(
|
|
646
841
|
message
|
|
@@ -835,7 +1030,7 @@ async function startCaptureSession(options) {
|
|
|
835
1030
|
target.hostCanvasPath,
|
|
836
1031
|
target.storyboardId
|
|
837
1032
|
);
|
|
838
|
-
const selector =
|
|
1033
|
+
const selector = buildStoryboardRenderSelector(renderId);
|
|
839
1034
|
const ownsWorker = reusableWorker === void 0;
|
|
840
1035
|
const worker = reusableWorker ?? await createCaptureWorker(capture);
|
|
841
1036
|
const { page } = worker;
|
|
@@ -849,19 +1044,17 @@ async function startCaptureSession(options) {
|
|
|
849
1044
|
try {
|
|
850
1045
|
const navStart = Date.now();
|
|
851
1046
|
try {
|
|
852
|
-
const targetUrl =
|
|
1047
|
+
const targetUrl = buildDirectRenderUrl2(options.hostUrl, target);
|
|
853
1048
|
if (!worker.hasNavigated) {
|
|
854
1049
|
await page.goto(targetUrl, { waitUntil: "domcontentloaded" });
|
|
855
1050
|
await page.addStyleTag({ content: FREEZE_MOTION_CSS });
|
|
856
1051
|
worker.hasNavigated = true;
|
|
857
1052
|
} else {
|
|
858
1053
|
if (worker.currentSelector === selector) {
|
|
859
|
-
const idleUrl = new URL(options.hostUrl);
|
|
860
|
-
idleUrl.searchParams.set("tempoCaptureIdle", "1");
|
|
861
1054
|
await page.evaluate((url) => {
|
|
862
1055
|
history.replaceState(null, "", url);
|
|
863
1056
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
864
|
-
},
|
|
1057
|
+
}, buildDirectRenderIdleUrl(options.hostUrl));
|
|
865
1058
|
worker.counters.sameDocumentIssued += 1;
|
|
866
1059
|
await page.waitForSelector(worker.currentSelector, {
|
|
867
1060
|
state: "detached",
|
|
@@ -872,7 +1065,6 @@ async function startCaptureSession(options) {
|
|
|
872
1065
|
({ url, nextSelector }) => {
|
|
873
1066
|
const captureWindow = window;
|
|
874
1067
|
captureWindow.__tempoResetCaptureDeterminism?.();
|
|
875
|
-
delete captureWindow.__tempoVisualReviewQuiet;
|
|
876
1068
|
captureWindow.__tempoCaptureRenderProfile?.begin(nextSelector);
|
|
877
1069
|
history.replaceState(null, "", url);
|
|
878
1070
|
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
@@ -1437,6 +1629,7 @@ function abortableWait(cancelledMessage) {
|
|
|
1437
1629
|
import { spawn as spawn3 } from "child_process";
|
|
1438
1630
|
import path2 from "path";
|
|
1439
1631
|
var BUNDLED_NODE_BIN_DIR_ENV = "TEMPO_NODE_TOOLCHAIN_BIN_DIR";
|
|
1632
|
+
var BUNDLED_NODE_RUNTIME_ENV = "TEMPO_NODE_TOOLCHAIN_RUNTIME";
|
|
1440
1633
|
function getBundledNodeBinDir() {
|
|
1441
1634
|
const dir = process.env[BUNDLED_NODE_BIN_DIR_ENV];
|
|
1442
1635
|
return dir && dir.trim().length > 0 ? dir : null;
|
|
@@ -1455,10 +1648,13 @@ function shouldUseBundledNode(args) {
|
|
|
1455
1648
|
}
|
|
1456
1649
|
function applyBundledNodeEnv(env, binDir) {
|
|
1457
1650
|
if (!binDir) return env;
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
ELECTRON_RUN_AS_NODE
|
|
1461
|
-
}
|
|
1651
|
+
const next = prependDirToPathEnv(env, binDir);
|
|
1652
|
+
if (env[BUNDLED_NODE_RUNTIME_ENV] === "native") {
|
|
1653
|
+
delete next.ELECTRON_RUN_AS_NODE;
|
|
1654
|
+
} else {
|
|
1655
|
+
next.ELECTRON_RUN_AS_NODE = "1";
|
|
1656
|
+
}
|
|
1657
|
+
return next;
|
|
1462
1658
|
}
|
|
1463
1659
|
function prependDirToPathEnv(env, dir) {
|
|
1464
1660
|
const pathKey = Object.keys(env).find((key2) => key2.toLowerCase() === "path");
|
|
@@ -4603,7 +4799,7 @@ async function parcelEventToFileEvent(event, rootInfo) {
|
|
|
4603
4799
|
// ../tempo-sdk/package.json
|
|
4604
4800
|
var package_default = {
|
|
4605
4801
|
name: "tempo-sdk",
|
|
4606
|
-
version: "0.0.
|
|
4802
|
+
version: "0.0.42",
|
|
4607
4803
|
type: "module",
|
|
4608
4804
|
description: "Tempo SDK \u2014 Vite, Next.js, and Expo plugins for JSX annotation and shared page/storyboard types",
|
|
4609
4805
|
repository: {
|
|
@@ -12233,6 +12429,44 @@ async function readTempoDevserverConfig(tempoConfigPath) {
|
|
|
12233
12429
|
tempoHostUrl: config.urls?.tempoHost ?? null
|
|
12234
12430
|
};
|
|
12235
12431
|
}
|
|
12432
|
+
function projectRootForTempoDir(tempoDir) {
|
|
12433
|
+
const resolved = path14.resolve(tempoDir);
|
|
12434
|
+
return path14.basename(resolved) === "tempo" ? path14.dirname(resolved) : resolved;
|
|
12435
|
+
}
|
|
12436
|
+
async function resolveCanvasHostLayout(options) {
|
|
12437
|
+
const repoRoot = path14.resolve(options.repoRoot);
|
|
12438
|
+
const candidates = options.tempoDir ? [path14.join(path14.resolve(options.tempoDir), "tempo.config.json")] : [
|
|
12439
|
+
path14.join(repoRoot, "tempo", "tempo.config.json"),
|
|
12440
|
+
path14.join(repoRoot, "tempo.config.json")
|
|
12441
|
+
];
|
|
12442
|
+
for (const configPath of candidates) {
|
|
12443
|
+
let parsed;
|
|
12444
|
+
try {
|
|
12445
|
+
parsed = JSON.parse(await readFile10(configPath, "utf-8"));
|
|
12446
|
+
} catch {
|
|
12447
|
+
continue;
|
|
12448
|
+
}
|
|
12449
|
+
if (typeof parsed !== "object" || parsed === null || !("scripts" in parsed)) {
|
|
12450
|
+
continue;
|
|
12451
|
+
}
|
|
12452
|
+
const tempoDir2 = path14.dirname(configPath);
|
|
12453
|
+
const paths = parsed.paths;
|
|
12454
|
+
const canvases = typeof paths?.canvases === "string" && paths.canvases.trim().length > 0 ? paths.canvases.trim().split("\\").join("/") : "designs";
|
|
12455
|
+
return {
|
|
12456
|
+
tempoDir: tempoDir2,
|
|
12457
|
+
projectRoot: projectRootForTempoDir(tempoDir2),
|
|
12458
|
+
pagesDir: path14.resolve(tempoDir2, canvases),
|
|
12459
|
+
configPath
|
|
12460
|
+
};
|
|
12461
|
+
}
|
|
12462
|
+
const tempoDir = options.tempoDir ? path14.resolve(options.tempoDir) : path14.join(repoRoot, "tempo");
|
|
12463
|
+
return {
|
|
12464
|
+
tempoDir,
|
|
12465
|
+
projectRoot: projectRootForTempoDir(tempoDir),
|
|
12466
|
+
pagesDir: path14.join(tempoDir, "designs"),
|
|
12467
|
+
configPath: null
|
|
12468
|
+
};
|
|
12469
|
+
}
|
|
12236
12470
|
function resolveTempoDir(options) {
|
|
12237
12471
|
if (options.tempoConfigPath) return path14.dirname(options.tempoConfigPath);
|
|
12238
12472
|
if (options.tempoDir) return options.tempoDir;
|
|
@@ -12317,7 +12551,7 @@ async function startDevserverSession(options) {
|
|
|
12317
12551
|
scripts: config.scripts
|
|
12318
12552
|
});
|
|
12319
12553
|
await devServer.initialize();
|
|
12320
|
-
const projectRoot =
|
|
12554
|
+
const projectRoot = projectRootForTempoDir(tempoDir);
|
|
12321
12555
|
for (const hostCanvasPath of hostCanvasPaths) {
|
|
12322
12556
|
await devServer.ensureCanvasRegistered(
|
|
12323
12557
|
path14.join(projectRoot, hostCanvasPath)
|
|
@@ -12393,14 +12627,14 @@ async function startDevserverSession(options) {
|
|
|
12393
12627
|
}
|
|
12394
12628
|
|
|
12395
12629
|
// ../canvas-capture-headless/src/enumerate.ts
|
|
12396
|
-
import { readFile as readFile11 } from "fs/promises";
|
|
12630
|
+
import { access as access4, readFile as readFile11 } from "fs/promises";
|
|
12397
12631
|
import path15 from "path";
|
|
12398
12632
|
function toPosixPath2(value) {
|
|
12399
12633
|
return value.split(path15.sep).join("/");
|
|
12400
12634
|
}
|
|
12401
12635
|
async function enumerateCanvasStoryboards(options) {
|
|
12402
12636
|
const pagesDir = options.pagesDir ?? path15.join(options.tempoDir, "designs");
|
|
12403
|
-
const projectRoot =
|
|
12637
|
+
const projectRoot = projectRootForTempoDir(options.tempoDir);
|
|
12404
12638
|
const canvasFiles = await scanCanvasFiles(pagesDir);
|
|
12405
12639
|
const storyboards = [];
|
|
12406
12640
|
const structuralErrors = [];
|
|
@@ -12465,6 +12699,61 @@ async function enumerateCanvasStoryboards(options) {
|
|
|
12465
12699
|
vrtConfigErrors
|
|
12466
12700
|
};
|
|
12467
12701
|
}
|
|
12702
|
+
async function enumerateTrackedStoryboards(options) {
|
|
12703
|
+
try {
|
|
12704
|
+
await access4(options.tempoDir);
|
|
12705
|
+
} catch {
|
|
12706
|
+
return {
|
|
12707
|
+
tracked: [],
|
|
12708
|
+
paused: [],
|
|
12709
|
+
totalStoryboards: 0,
|
|
12710
|
+
totalCanvases: 0,
|
|
12711
|
+
errors: []
|
|
12712
|
+
};
|
|
12713
|
+
}
|
|
12714
|
+
const enumeration = await enumerateCanvasStoryboards(options);
|
|
12715
|
+
const tracked = [];
|
|
12716
|
+
const paused = [];
|
|
12717
|
+
const errors = [
|
|
12718
|
+
...enumeration.structuralErrors,
|
|
12719
|
+
...enumeration.vrtConfigErrors
|
|
12720
|
+
];
|
|
12721
|
+
for (const storyboard of enumeration.storyboards) {
|
|
12722
|
+
if (!storyboard.vrt) continue;
|
|
12723
|
+
const board = {
|
|
12724
|
+
canvasPath: storyboard.canvasPath,
|
|
12725
|
+
hostCanvasPath: storyboard.hostCanvasPath,
|
|
12726
|
+
storyboardId: storyboard.storyboardId,
|
|
12727
|
+
name: storyboard.name,
|
|
12728
|
+
layout: storyboard.layout,
|
|
12729
|
+
threshold: storyboard.vrt.threshold,
|
|
12730
|
+
ignore: storyboard.vrt.ignore
|
|
12731
|
+
};
|
|
12732
|
+
const maskedArea = (storyboard.vrt.ignore ?? []).reduce(
|
|
12733
|
+
(total, region) => total + Math.max(0, Math.min(region.w, board.layout.width)) * Math.max(0, Math.min(region.h, board.layout.height)),
|
|
12734
|
+
0
|
|
12735
|
+
);
|
|
12736
|
+
if (maskedArea >= 0.9 * board.layout.width * board.layout.height) {
|
|
12737
|
+
errors.push({
|
|
12738
|
+
filePath: storyboard.filePath,
|
|
12739
|
+
message: `<Storyboard id="${storyboard.storyboardId}"> vrt.ignore masks \u226590% of the board \u2014 untrack it instead`
|
|
12740
|
+
});
|
|
12741
|
+
continue;
|
|
12742
|
+
}
|
|
12743
|
+
if (storyboard.vrt.skip) {
|
|
12744
|
+
paused.push(board);
|
|
12745
|
+
continue;
|
|
12746
|
+
}
|
|
12747
|
+
tracked.push(board);
|
|
12748
|
+
}
|
|
12749
|
+
return {
|
|
12750
|
+
tracked,
|
|
12751
|
+
paused,
|
|
12752
|
+
totalStoryboards: enumeration.totalStoryboards,
|
|
12753
|
+
totalCanvases: enumeration.totalCanvases,
|
|
12754
|
+
errors
|
|
12755
|
+
};
|
|
12756
|
+
}
|
|
12468
12757
|
|
|
12469
12758
|
// ../canvas-capture-headless/src/timing.ts
|
|
12470
12759
|
function percentile(sortedValues, p) {
|
|
@@ -12544,64 +12833,8 @@ var TimingReport = class {
|
|
|
12544
12833
|
};
|
|
12545
12834
|
|
|
12546
12835
|
// src/enumerate.ts
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
try {
|
|
12550
|
-
await access4(options.tempoDir);
|
|
12551
|
-
} catch {
|
|
12552
|
-
return {
|
|
12553
|
-
tracked: [],
|
|
12554
|
-
paused: [],
|
|
12555
|
-
totalStoryboards: 0,
|
|
12556
|
-
totalCanvases: 0,
|
|
12557
|
-
errors: []
|
|
12558
|
-
};
|
|
12559
|
-
}
|
|
12560
|
-
const enumeration = await enumerateCanvasStoryboards({
|
|
12561
|
-
tempoDir: options.tempoDir,
|
|
12562
|
-
pagesDir: options.pagesDir
|
|
12563
|
-
});
|
|
12564
|
-
const tracked = [];
|
|
12565
|
-
const paused = [];
|
|
12566
|
-
const errors = [
|
|
12567
|
-
...enumeration.structuralErrors,
|
|
12568
|
-
...enumeration.vrtConfigErrors
|
|
12569
|
-
];
|
|
12570
|
-
for (const storyboard of enumeration.storyboards) {
|
|
12571
|
-
if (!storyboard.vrt) continue;
|
|
12572
|
-
const board = {
|
|
12573
|
-
canvasPath: storyboard.canvasPath,
|
|
12574
|
-
hostCanvasPath: storyboard.hostCanvasPath,
|
|
12575
|
-
storyboardId: storyboard.storyboardId,
|
|
12576
|
-
name: storyboard.name,
|
|
12577
|
-
layout: storyboard.layout,
|
|
12578
|
-
threshold: storyboard.vrt.threshold,
|
|
12579
|
-
ignore: storyboard.vrt.ignore
|
|
12580
|
-
};
|
|
12581
|
-
const maskedArea = (storyboard.vrt.ignore ?? []).reduce(
|
|
12582
|
-
(total, region) => total + Math.max(0, Math.min(region.w, board.layout.width)) * Math.max(0, Math.min(region.h, board.layout.height)),
|
|
12583
|
-
0
|
|
12584
|
-
);
|
|
12585
|
-
if (maskedArea >= 0.9 * board.layout.width * board.layout.height) {
|
|
12586
|
-
errors.push({
|
|
12587
|
-
filePath: storyboard.filePath,
|
|
12588
|
-
message: `<Storyboard id="${storyboard.storyboardId}"> vrt.ignore masks \u226590% of the board \u2014 untrack it instead`
|
|
12589
|
-
});
|
|
12590
|
-
continue;
|
|
12591
|
-
}
|
|
12592
|
-
if (storyboard.vrt.skip) {
|
|
12593
|
-
paused.push(board);
|
|
12594
|
-
continue;
|
|
12595
|
-
}
|
|
12596
|
-
tracked.push(board);
|
|
12597
|
-
}
|
|
12598
|
-
return {
|
|
12599
|
-
tracked,
|
|
12600
|
-
paused,
|
|
12601
|
-
totalStoryboards: enumeration.totalStoryboards,
|
|
12602
|
-
totalCanvases: enumeration.totalCanvases,
|
|
12603
|
-
errors
|
|
12604
|
-
};
|
|
12836
|
+
async function enumerateTrackedStoryboards2(options) {
|
|
12837
|
+
return await enumerateTrackedStoryboards(options);
|
|
12605
12838
|
}
|
|
12606
12839
|
|
|
12607
12840
|
// src/fingerprint.ts
|
|
@@ -12824,6 +13057,7 @@ function parseArgs(argv) {
|
|
|
12824
13057
|
const args = {
|
|
12825
13058
|
repoRoot: inferRepoRoot(),
|
|
12826
13059
|
tempoDir: "",
|
|
13060
|
+
tempoDirExplicit: false,
|
|
12827
13061
|
// The published CLI is intentionally near-argumentless in Actions. Local
|
|
12828
13062
|
// capture remains Convex-free unless --upload is explicit.
|
|
12829
13063
|
upload: process.env.GITHUB_ACTIONS === "true" && inferredTrigger !== "manual",
|
|
@@ -12854,6 +13088,7 @@ function parseArgs(argv) {
|
|
|
12854
13088
|
break;
|
|
12855
13089
|
case "--tempo-dir":
|
|
12856
13090
|
args.tempoDir = path16.resolve(next());
|
|
13091
|
+
args.tempoDirExplicit = true;
|
|
12857
13092
|
break;
|
|
12858
13093
|
case "--out":
|
|
12859
13094
|
args.out = path16.resolve(next());
|
|
@@ -13046,6 +13281,12 @@ function startClaimHeartbeat(options) {
|
|
|
13046
13281
|
}
|
|
13047
13282
|
};
|
|
13048
13283
|
}
|
|
13284
|
+
function explicitTempoDirRelative(args) {
|
|
13285
|
+
if (!args.tempoDirExplicit) return void 0;
|
|
13286
|
+
const relative2 = path16.relative(args.repoRoot, args.tempoDir);
|
|
13287
|
+
if (relative2.startsWith("..") || path16.isAbsolute(relative2)) return void 0;
|
|
13288
|
+
return relative2;
|
|
13289
|
+
}
|
|
13049
13290
|
function buildBaselineCaptureArgs(input) {
|
|
13050
13291
|
const args = [
|
|
13051
13292
|
"capture",
|
|
@@ -13067,6 +13308,12 @@ function buildBaselineCaptureArgs(input) {
|
|
|
13067
13308
|
];
|
|
13068
13309
|
if (input.url) args.push("--url", input.url);
|
|
13069
13310
|
if (input.githubRepo) args.push("--github-repo", input.githubRepo);
|
|
13311
|
+
if (input.tempoDirRelative !== void 0) {
|
|
13312
|
+
args.push(
|
|
13313
|
+
"--tempo-dir",
|
|
13314
|
+
path16.join(input.worktreeRoot, input.tempoDirRelative || ".")
|
|
13315
|
+
);
|
|
13316
|
+
}
|
|
13070
13317
|
if (input.repoKey) args.push("--repo-key", input.repoKey);
|
|
13071
13318
|
if (input.settleTimeoutMs !== void 0) {
|
|
13072
13319
|
args.push("--settle-timeout", String(input.settleTimeoutMs));
|
|
@@ -13101,7 +13348,8 @@ async function runBaselineCaptureInWorktree(options) {
|
|
|
13101
13348
|
url: options.args.url,
|
|
13102
13349
|
githubRepo: options.args.githubRepo,
|
|
13103
13350
|
repoKey: options.args.repoKey,
|
|
13104
|
-
settleTimeoutMs: options.args.settleTimeoutMs
|
|
13351
|
+
settleTimeoutMs: options.args.settleTimeoutMs,
|
|
13352
|
+
tempoDirRelative: explicitTempoDirRelative(options.args)
|
|
13105
13353
|
})
|
|
13106
13354
|
];
|
|
13107
13355
|
await new Promise((resolve2, reject) => {
|
|
@@ -13152,10 +13400,15 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
13152
13400
|
if (args.trigger === "pr" && baseCandidates.length === 0) {
|
|
13153
13401
|
throw new Error("--trigger pr requires --base <merge-base sha>");
|
|
13154
13402
|
}
|
|
13155
|
-
const
|
|
13403
|
+
const layout = await resolveCanvasHostLayout({
|
|
13404
|
+
repoRoot: args.repoRoot,
|
|
13405
|
+
tempoDir: args.tempoDirExplicit ? args.tempoDir : void 0
|
|
13406
|
+
});
|
|
13407
|
+
args.tempoDir = layout.tempoDir;
|
|
13408
|
+
const pagesDir = layout.pagesDir;
|
|
13156
13409
|
const enumeration = await timing.phase(
|
|
13157
13410
|
"enumerate",
|
|
13158
|
-
() =>
|
|
13411
|
+
() => enumerateTrackedStoryboards2({ tempoDir: args.tempoDir, pagesDir })
|
|
13159
13412
|
);
|
|
13160
13413
|
if (enumeration.errors.length > 0) {
|
|
13161
13414
|
for (const error of enumeration.errors) {
|
|
@@ -13608,13 +13861,14 @@ export {
|
|
|
13608
13861
|
percentile,
|
|
13609
13862
|
summarize,
|
|
13610
13863
|
TimingReport,
|
|
13611
|
-
enumerateTrackedStoryboards,
|
|
13864
|
+
enumerateTrackedStoryboards2 as enumerateTrackedStoryboards,
|
|
13612
13865
|
scaleIgnoreRegionsToDevicePx,
|
|
13613
13866
|
VisualReviewHttpError,
|
|
13614
13867
|
VisualReviewIngestClient,
|
|
13615
13868
|
resolveOrClaimBaseline,
|
|
13869
|
+
explicitTempoDirRelative,
|
|
13616
13870
|
buildBaselineCaptureArgs,
|
|
13617
13871
|
main,
|
|
13618
13872
|
VISUAL_REVIEW_OIDC_AUDIENCE
|
|
13619
13873
|
};
|
|
13620
|
-
//# sourceMappingURL=chunk-
|
|
13874
|
+
//# sourceMappingURL=chunk-WGNWCWMJ.js.map
|