@flighthq/tool-capture 0.1.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/baselineStore.d.ts +5 -0
- package/dist/baselineStore.d.ts.map +1 -0
- package/dist/baselineStore.js +54 -0
- package/dist/baselineStore.js.map +1 -0
- package/dist/captureBrowser.d.ts +9 -0
- package/dist/captureBrowser.d.ts.map +1 -0
- package/dist/captureBrowser.js +83 -0
- package/dist/captureBrowser.js.map +1 -0
- package/dist/captureEntries.d.ts +11 -0
- package/dist/captureEntries.d.ts.map +1 -0
- package/dist/captureEntries.js +44 -0
- package/dist/captureEntries.js.map +1 -0
- package/dist/captureEntry.d.ts +83 -0
- package/dist/captureEntry.d.ts.map +1 -0
- package/dist/captureEntry.js +334 -0
- package/dist/captureEntry.js.map +1 -0
- package/dist/captureFormat.d.ts +6 -0
- package/dist/captureFormat.d.ts.map +1 -0
- package/dist/captureFormat.js +48 -0
- package/dist/captureFormat.js.map +1 -0
- package/dist/captureInterrupt.d.ts +3 -0
- package/dist/captureInterrupt.d.ts.map +1 -0
- package/dist/captureInterrupt.js +29 -0
- package/dist/captureInterrupt.js.map +1 -0
- package/dist/captureRenderTarget.d.ts +29 -0
- package/dist/captureRenderTarget.d.ts.map +1 -0
- package/dist/captureRenderTarget.js +27 -0
- package/dist/captureRenderTarget.js.map +1 -0
- package/dist/captureServer.d.ts +16 -0
- package/dist/captureServer.d.ts.map +1 -0
- package/dist/captureServer.js +142 -0
- package/dist/captureServer.js.map +1 -0
- package/dist/functionalScenes.d.ts +9 -0
- package/dist/functionalScenes.d.ts.map +1 -0
- package/dist/functionalScenes.js +64 -0
- package/dist/functionalScenes.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
- package/src/baselineStore.test.ts +59 -0
- package/src/captureBrowser.test.ts +12 -0
- package/src/captureEntries.test.ts +55 -0
- package/src/captureEntry.test.ts +32 -0
- package/src/captureFormat.test.ts +45 -0
- package/src/captureInterrupt.test.ts +29 -0
- package/src/captureRenderTarget.test.ts +11 -0
- package/src/captureServer.test.ts +19 -0
- package/src/functionalScenes.test.ts +55 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// The core capture pass: load one (entry, renderer) page, synchronize to a presented frame, screenshot
|
|
2
|
+
// the render output, drain structured logs, and write the screenshot.png / logs.jsonl / status.json
|
|
3
|
+
// trio — comparing the screenshot hash against a committed baseline. captureEntry runs a whole entry's
|
|
4
|
+
// renderer set sequentially; captureParallel fans an entries × renderers matrix across N pages.
|
|
5
|
+
//
|
|
6
|
+
// The Node-side present-frame sync lives here: the two-rAF wait (or the --frames halt's
|
|
7
|
+
// __captureFramesReached poll) before screenshotting, coordinated with the page-side
|
|
8
|
+
// waitForPresentedFrame + gl.finish() in the functional verifier via the window contract that
|
|
9
|
+
// launchBrowser's init script establishes (__ftRealRequestAnimationFrame, __ftRenderImage).
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { mkdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { join, resolve } from 'node:path';
|
|
13
|
+
import pc from 'picocolors';
|
|
14
|
+
import { getBaselineField, setBaselineField } from './baselineStore.js';
|
|
15
|
+
import { BACKEND_UNAVAILABLE, rendererMatchesFilter, routeSegment } from './captureEntries.js';
|
|
16
|
+
import { formatDetailLine, formatStatusLine } from './captureFormat.js';
|
|
17
|
+
import { isBrowserClosedError } from './captureInterrupt.js';
|
|
18
|
+
export async function captureEntry(opts) {
|
|
19
|
+
const { context, entry, renderers, baseUrl, tool, outBase, root, updateBaseline = false, extraWait = 0, captureFrames = 0, failOnError = false, isAborted = () => false, } = opts;
|
|
20
|
+
const { displayLabel } = opts;
|
|
21
|
+
let anyFailed = false;
|
|
22
|
+
let anyChanged = false;
|
|
23
|
+
// Detail lines below the caller's [N/M] header drop the entry name (the header carries it) and
|
|
24
|
+
// column-align on the renderer label, matching compare-render.ts. The renderer name carries the
|
|
25
|
+
// verdict color; routine confirmations dim (see formatStatusLine).
|
|
26
|
+
// displayLabel overrides the renderer name when set (used by captureParallel).
|
|
27
|
+
const labelWidth = displayLabel ? Math.max(6, displayLabel.length) : Math.max(6, ...renderers.map((r) => r.length));
|
|
28
|
+
const label = (renderer) => displayLabel ?? renderer;
|
|
29
|
+
const statusLine = (tone, renderer, message) => formatStatusLine(tone, label(renderer), labelWidth, message);
|
|
30
|
+
for (const renderer of renderers) {
|
|
31
|
+
// Stop launching pages once an interrupt has begun; the partial result is reported by the caller.
|
|
32
|
+
if (isAborted())
|
|
33
|
+
break;
|
|
34
|
+
const urlPath = tool === 'examples'
|
|
35
|
+
? `examples/${entry.name}/${routeSegment(renderer)}/`
|
|
36
|
+
: tool === 'functional'
|
|
37
|
+
? `tests/${entry.name}/${routeSegment(renderer)}/`
|
|
38
|
+
: ''; // landing: the single page is served at the server root
|
|
39
|
+
const url = `${baseUrl}/${urlPath}`;
|
|
40
|
+
const { outDir, tmpScreenshot, finalScreenshot, tmpLogs, finalLogs, statusPath } = getCaptureOutputPaths(outBase, tool, entry.name, renderer);
|
|
41
|
+
mkdirSync(outDir, { recursive: true });
|
|
42
|
+
const logs = [];
|
|
43
|
+
const page = await context.newPage();
|
|
44
|
+
page.on('console', (msg) => {
|
|
45
|
+
const text = msg.text();
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(text);
|
|
48
|
+
if (parsed !== null && typeof parsed === 'object' && '__flight' in parsed) {
|
|
49
|
+
logs.push(parsed);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// not a flight log entry — fall through to raw-console handling below
|
|
55
|
+
}
|
|
56
|
+
// Surface raw browser console errors/warnings that are not flight logs (e.g. a library or the
|
|
57
|
+
// SDK calling console.error directly). Without this, such messages are dropped and logs.jsonl
|
|
58
|
+
// looks clean even though DevTools shows the error. Other console levels are intentionally
|
|
59
|
+
// ignored to avoid HMR/info noise.
|
|
60
|
+
const type = msg.type();
|
|
61
|
+
if (type === 'error' || type === 'warning') {
|
|
62
|
+
logs.push({
|
|
63
|
+
__flight: true,
|
|
64
|
+
t: -1,
|
|
65
|
+
level: type === 'error' ? 'error' : 'warn',
|
|
66
|
+
channel: 'console',
|
|
67
|
+
data: { msg: text },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
page.on('pageerror', (err) => {
|
|
72
|
+
logs.push({ __flight: true, t: -1, level: 'pageerror', data: { msg: err.message } });
|
|
73
|
+
});
|
|
74
|
+
// A failed asset/network request never throws in-page, so it would otherwise be invisible here.
|
|
75
|
+
page.on('requestfailed', (req) => {
|
|
76
|
+
logs.push({
|
|
77
|
+
__flight: true,
|
|
78
|
+
t: -1,
|
|
79
|
+
level: 'error',
|
|
80
|
+
channel: 'network',
|
|
81
|
+
data: { msg: `request failed: ${req.url()} (${req.failure()?.errorText ?? 'unknown'})` },
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
try {
|
|
85
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15_000 });
|
|
86
|
+
await page.waitForSelector('canvas', { timeout: 8_000 }).catch(() => { });
|
|
87
|
+
if (captureFrames && captureFrames > 0) {
|
|
88
|
+
// --frames=N mode: wait until the page has rendered N frames and the halt has frozen it
|
|
89
|
+
// (see launchBrowser). waitForFunction polls until the page reaches N — no fixed short
|
|
90
|
+
// timeout that could shoot a varying earlier frame — so frame N is captured deterministically.
|
|
91
|
+
await page
|
|
92
|
+
.waitForFunction(() => window.__captureFramesReached === true, null, {
|
|
93
|
+
timeout: 15_000,
|
|
94
|
+
})
|
|
95
|
+
.catch(() => { });
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Default: two animation frames so the page's own rAF callback has run and drawn before the
|
|
99
|
+
// screenshot. A frozen page (the landing backgrounds in capture mode) renders the same frame
|
|
100
|
+
// each tick, so this is enough for a stable shot; the optional --wait covers slower loads.
|
|
101
|
+
await page.evaluate(() => new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(() => r()))));
|
|
102
|
+
}
|
|
103
|
+
if (extraWait > 0)
|
|
104
|
+
await page.waitForTimeout(extraWait);
|
|
105
|
+
const waitsForVerification = tool === 'functional';
|
|
106
|
+
if (waitsForVerification)
|
|
107
|
+
await waitForRenderVerification(page);
|
|
108
|
+
// Screenshot the render output only — not the full viewport — so all renderers produce the same
|
|
109
|
+
// frame size and the gallery blink comparator has something meaningful to compare.
|
|
110
|
+
//
|
|
111
|
+
// - webgpu: SwiftShader can't present to the swapchain, so the canvas is blank in a Playwright
|
|
112
|
+
// screenshot. The functional verifier reads the frame back from the GPU and exposes it as a PNG
|
|
113
|
+
// data URL (window.__ftRenderImage). Fall back to a full page screenshot if unavailable (e.g.
|
|
114
|
+
// examples, which don't run the verifier).
|
|
115
|
+
// - dom: no canvas; the renderer appends a sized <div> directly to <body>.
|
|
116
|
+
// - canvas / webgl: a <canvas> is appended directly to <body>.
|
|
117
|
+
// - fallback: full page screenshot when neither canvas nor div is found (unknown layout).
|
|
118
|
+
let screenshotBuffer;
|
|
119
|
+
const backend = rendererBackend(renderer);
|
|
120
|
+
if (backend === 'webgpu') {
|
|
121
|
+
const dataUrl = await getRenderImageDataUrl(page);
|
|
122
|
+
if (dataUrl) {
|
|
123
|
+
screenshotBuffer = Buffer.from(dataUrl.split(',')[1], 'base64');
|
|
124
|
+
}
|
|
125
|
+
else if (waitsForVerification) {
|
|
126
|
+
throw new Error('WebGPU verifier did not produce a render image');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
screenshotBuffer = await page.screenshot();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (backend === 'dom') {
|
|
133
|
+
screenshotBuffer = await page
|
|
134
|
+
.locator('body > div')
|
|
135
|
+
.first()
|
|
136
|
+
.screenshot()
|
|
137
|
+
.catch(() => page.screenshot());
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
screenshotBuffer = await page
|
|
141
|
+
.locator('canvas')
|
|
142
|
+
.first()
|
|
143
|
+
.screenshot()
|
|
144
|
+
.catch(() => page.screenshot());
|
|
145
|
+
}
|
|
146
|
+
const hash = createHash('sha256').update(screenshotBuffer).digest('hex');
|
|
147
|
+
// Atomic write: tmp files renamed into place, status.json written last.
|
|
148
|
+
writeFileSync(tmpScreenshot, screenshotBuffer);
|
|
149
|
+
writeFileSync(tmpLogs, logs.map((l) => JSON.stringify(l)).join('\n'));
|
|
150
|
+
renameSync(tmpScreenshot, finalScreenshot);
|
|
151
|
+
renameSync(tmpLogs, finalLogs);
|
|
152
|
+
// Baseline update or comparison. The baseline is a committed sha256 hash, not a screenshot:
|
|
153
|
+
// capture mode renders a deterministic frame (see launchBrowser), so the hash alone detects
|
|
154
|
+
// change and keeps the tracked baseline a small text file instead of a binary PNG. The fresh
|
|
155
|
+
// screenshot is still written to the output dir above for human inspection on a mismatch.
|
|
156
|
+
let baselineHash = null;
|
|
157
|
+
let changed = null;
|
|
158
|
+
if (updateBaseline) {
|
|
159
|
+
setBaselineField(root, tool, entry.name, renderer, 'sha256', hash);
|
|
160
|
+
baselineHash = hash;
|
|
161
|
+
changed = false;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
baselineHash = getBaselineField(root, tool, entry.name, renderer, 'sha256');
|
|
165
|
+
if (baselineHash !== null)
|
|
166
|
+
changed = hash !== baselineHash;
|
|
167
|
+
}
|
|
168
|
+
if (changed === true)
|
|
169
|
+
anyChanged = true;
|
|
170
|
+
// A successful capture whose hash drifted is still a pass (it rendered), but the drift is the one
|
|
171
|
+
// thing worth seeing — green renderer, yellow note — so it does not use the dimmed pass message.
|
|
172
|
+
if (changed === true) {
|
|
173
|
+
console.log(formatDetailLine(pc.green('✓'), label(renderer), labelWidth, pc.yellow('changed (hash differs from baseline)'), pc.green));
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
console.log(statusLine('pass', renderer, ''));
|
|
177
|
+
}
|
|
178
|
+
const status = {
|
|
179
|
+
state: 'ready',
|
|
180
|
+
capturedAt: Date.now(),
|
|
181
|
+
error: null,
|
|
182
|
+
hash,
|
|
183
|
+
baselineHash,
|
|
184
|
+
changed,
|
|
185
|
+
};
|
|
186
|
+
writeFileSync(statusPath, JSON.stringify(status, null, 2));
|
|
187
|
+
if (failOnError) {
|
|
188
|
+
// Let any in-flight error events (a verification throw surfaces as a page error / console
|
|
189
|
+
// error) flush to the listeners, then fail the entry if the page reported any error.
|
|
190
|
+
await page.waitForTimeout(120);
|
|
191
|
+
const errorLog = logs.find((l) => {
|
|
192
|
+
const level = l.level;
|
|
193
|
+
return level === 'pageerror' || level === 'error';
|
|
194
|
+
});
|
|
195
|
+
if (errorLog) {
|
|
196
|
+
const detailMsg = errorLog.data?.msg ?? 'error logged';
|
|
197
|
+
// A backend the environment cannot provide or sustain (Wgpu with no adapter/device, or a
|
|
198
|
+
// swiftshader device lost mid-run) is skipped, not failed — see BACKEND_UNAVAILABLE.
|
|
199
|
+
if (BACKEND_UNAVAILABLE.test(detailMsg)) {
|
|
200
|
+
console.log(statusLine('skip', renderer, `skipped — backend unavailable (${detailMsg})`));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.error(statusLine('fail', renderer, detailMsg));
|
|
204
|
+
anyFailed = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch (err) {
|
|
210
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
211
|
+
// An interrupt tears the browser down mid-capture; the resulting "page closed" reject is the
|
|
212
|
+
// shutdown, not a render failure, so swallow it and let the caller report the partial run. The
|
|
213
|
+
// finally below still closes the page.
|
|
214
|
+
if (isAborted() || isBrowserClosedError(err))
|
|
215
|
+
break;
|
|
216
|
+
logs.push({ __flight: true, t: -1, level: 'capture-error', data: { msg: message } });
|
|
217
|
+
writeFileSync(finalLogs, logs.map((l) => JSON.stringify(l)).join('\n'));
|
|
218
|
+
const errorStatus = {
|
|
219
|
+
state: 'error',
|
|
220
|
+
capturedAt: Date.now(),
|
|
221
|
+
error: message,
|
|
222
|
+
hash: null,
|
|
223
|
+
baselineHash: null,
|
|
224
|
+
changed: null,
|
|
225
|
+
};
|
|
226
|
+
writeFileSync(statusPath, JSON.stringify(errorStatus, null, 2));
|
|
227
|
+
console.error(statusLine('fail', renderer, message));
|
|
228
|
+
anyFailed = true;
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
await page.close().catch(() => { });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (anyFailed)
|
|
235
|
+
return 'error';
|
|
236
|
+
if (anyChanged)
|
|
237
|
+
return 'changed';
|
|
238
|
+
return 'ok';
|
|
239
|
+
}
|
|
240
|
+
async function getRenderImageDataUrl(page) {
|
|
241
|
+
return page
|
|
242
|
+
.evaluate(() => window.__ftRenderImage ?? null)
|
|
243
|
+
.catch(() => null);
|
|
244
|
+
}
|
|
245
|
+
function rendererBackend(renderer) {
|
|
246
|
+
const i = renderer.indexOf(':');
|
|
247
|
+
return i === -1 ? renderer : renderer.slice(i + 1);
|
|
248
|
+
}
|
|
249
|
+
async function waitForRenderVerification(page) {
|
|
250
|
+
await page
|
|
251
|
+
.waitForFunction(() => {
|
|
252
|
+
const w = window;
|
|
253
|
+
const verification = w.__ftVerification;
|
|
254
|
+
if (document.getElementById('ft-error') !== null)
|
|
255
|
+
return true;
|
|
256
|
+
if (verification === undefined)
|
|
257
|
+
return false;
|
|
258
|
+
if (verification.render === 'dom')
|
|
259
|
+
return true;
|
|
260
|
+
if (verification.render === 'webgpu')
|
|
261
|
+
return typeof w.__ftRenderImage === 'string' && w.__ftRenderImage !== '';
|
|
262
|
+
return verification.fingerprint !== null;
|
|
263
|
+
}, null, { polling: 100, timeout: 15_000 })
|
|
264
|
+
.catch(() => { });
|
|
265
|
+
return page
|
|
266
|
+
.evaluate(() => window.__ftVerification ?? null)
|
|
267
|
+
.catch(() => null);
|
|
268
|
+
}
|
|
269
|
+
// Flattens entries × renderers into a shared job queue and processes them with workerCount
|
|
270
|
+
// concurrent Playwright pages on the same browser context. Each worker pulls one (entry, renderer)
|
|
271
|
+
// pair at a time, runs the full captureEntry pipeline on it, and returns it to the queue for the
|
|
272
|
+
// next job. Single-threaded JS makes the queue safe without locks: shift() is synchronous and
|
|
273
|
+
// cannot interleave with another worker's shift().
|
|
274
|
+
export async function captureParallel(opts) {
|
|
275
|
+
const { context, entries, rendererFilter, workerCount = 6, isAborted = () => false } = opts;
|
|
276
|
+
const jobs = [];
|
|
277
|
+
for (const entry of entries) {
|
|
278
|
+
const renderers = entry.renderers.filter((r) => rendererMatchesFilter(r, rendererFilter));
|
|
279
|
+
for (const renderer of renderers) {
|
|
280
|
+
jobs.push({ entry, renderer });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
let captured = 0;
|
|
284
|
+
let changed = 0;
|
|
285
|
+
let failed = 0;
|
|
286
|
+
const activeWorkers = Math.min(workerCount, jobs.length);
|
|
287
|
+
const workers = Array.from({ length: activeWorkers }, async () => {
|
|
288
|
+
while (true) {
|
|
289
|
+
if (isAborted())
|
|
290
|
+
break;
|
|
291
|
+
const job = jobs.shift();
|
|
292
|
+
if (!job)
|
|
293
|
+
break;
|
|
294
|
+
const result = await captureEntry({
|
|
295
|
+
context,
|
|
296
|
+
entry: job.entry,
|
|
297
|
+
renderers: [job.renderer],
|
|
298
|
+
displayLabel: `${job.entry.name}/${job.renderer}`,
|
|
299
|
+
baseUrl: opts.baseUrl,
|
|
300
|
+
tool: opts.tool,
|
|
301
|
+
outBase: opts.outBase,
|
|
302
|
+
root: opts.root,
|
|
303
|
+
updateBaseline: opts.updateBaseline,
|
|
304
|
+
extraWait: opts.extraWait,
|
|
305
|
+
captureFrames: opts.captureFrames,
|
|
306
|
+
failOnError: opts.failOnError,
|
|
307
|
+
isAborted: () => isAborted(),
|
|
308
|
+
});
|
|
309
|
+
if (result === 'ok')
|
|
310
|
+
captured++;
|
|
311
|
+
else if (result === 'changed')
|
|
312
|
+
changed++;
|
|
313
|
+
else
|
|
314
|
+
failed++;
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
await Promise.all(workers);
|
|
318
|
+
return { captured, changed, failed };
|
|
319
|
+
}
|
|
320
|
+
// The artifact paths for one capture, derived purely from the output base, tool, entry name, and
|
|
321
|
+
// renderer. Shared by captureEntry (which writes them) and captureRenderTarget (which reports them),
|
|
322
|
+
// so the on-disk layout is defined in exactly one place: {outBase}/{tool}/{name}/{routeSegment}/…
|
|
323
|
+
export function getCaptureOutputPaths(outBase, tool, name, renderer) {
|
|
324
|
+
const outDir = join(resolve(outBase), tool, name, routeSegment(renderer));
|
|
325
|
+
return {
|
|
326
|
+
outDir,
|
|
327
|
+
tmpScreenshot: join(outDir, 'screenshot.tmp.png'),
|
|
328
|
+
finalScreenshot: join(outDir, 'screenshot.png'),
|
|
329
|
+
tmpLogs: join(outDir, 'logs.tmp.jsonl'),
|
|
330
|
+
finalLogs: join(outDir, 'logs.jsonl'),
|
|
331
|
+
statusPath: join(outDir, 'status.json'),
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=captureEntry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureEntry.js","sourceRoot":"","sources":["../src/captureEntry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type DetailTone = 'pass' | 'fail' | 'skip' | 'muted';
|
|
2
|
+
export declare function formatDetailLine(glyph: string, label: string, labelWidth: number, message: string, paint?: (s: string) => string): string;
|
|
3
|
+
export declare function formatStatusLine(tone: DetailTone, label: string, labelWidth: number, message: string): string;
|
|
4
|
+
export declare function formatSummaryCount(value: number, label: string, tone: 'pass' | 'fail' | 'warn'): string;
|
|
5
|
+
export declare function formatSummaryLine(failed: boolean, counts: readonly string[]): string;
|
|
6
|
+
//# sourceMappingURL=captureFormat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureFormat.d.ts","sourceRoot":"","sources":["../src/captureFormat.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAM5D,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,KAAK,GAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAiB,GACtC,MAAM,CAKR;AAMD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAI7G;AAID,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAMvG;AAID,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAGpF"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Terminal output formatting shared by the capture.ts / compare-render.ts CLIs so their progress and
|
|
2
|
+
// summary lines stay aligned.
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
// An indented detail line under an entry's [N/M] header: a status glyph, the renderer/check label
|
|
5
|
+
// padded to a shared column width, then an optional message. Low-level layout — the caller has already
|
|
6
|
+
// colored `glyph` and `message`; `paint` colors the padded label (padding happens before color so the
|
|
7
|
+
// invisible ANSI codes do not throw off the column width). Most callers want formatStatusLine instead.
|
|
8
|
+
export function formatDetailLine(glyph, label, labelWidth, message, paint = (s) => s) {
|
|
9
|
+
// Pad only when a message follows; an unpadded label avoids a trailing space (and trimEnd cannot
|
|
10
|
+
// strip it once color codes wrap the padding).
|
|
11
|
+
const paintedLabel = paint(message ? label.padEnd(labelWidth) : label);
|
|
12
|
+
return message ? ` ${glyph} ${paintedLabel} ${message}` : ` ${glyph} ${paintedLabel}`;
|
|
13
|
+
}
|
|
14
|
+
// The common detail line: the glyph AND the renderer/check label carry the verdict color, so the eye
|
|
15
|
+
// lands on *what* passed or failed rather than on a tiny glyph in a field of white. A routine
|
|
16
|
+
// confirmation (pass / muted) dims its message so it recedes; a fail/skip keeps the tone color on the
|
|
17
|
+
// message because the reason is the point.
|
|
18
|
+
export function formatStatusLine(tone, label, labelWidth, message) {
|
|
19
|
+
const paint = TONE_PAINT[tone];
|
|
20
|
+
const body = message ? (tone === 'pass' || tone === 'muted' ? pc.dim(message) : paint(message)) : '';
|
|
21
|
+
return formatDetailLine(paint(TONE_GLYPH[tone]), label, labelWidth, body, paint);
|
|
22
|
+
}
|
|
23
|
+
// Color a "<value> <label>" summary count: dim at zero (a zero is never alarming, whatever its tone),
|
|
24
|
+
// else green (pass) / red (fail) / yellow (warn).
|
|
25
|
+
export function formatSummaryCount(value, label, tone) {
|
|
26
|
+
const text = `${value} ${label}`;
|
|
27
|
+
if (value === 0)
|
|
28
|
+
return pc.dim(text);
|
|
29
|
+
if (tone === 'fail')
|
|
30
|
+
return pc.red(text);
|
|
31
|
+
if (tone === 'warn')
|
|
32
|
+
return pc.yellow(text);
|
|
33
|
+
return pc.green(text);
|
|
34
|
+
}
|
|
35
|
+
// A run's final summary: a ✓/✗ verdict followed by count segments. `failed` drives the verdict, so a
|
|
36
|
+
// run that exits non-zero always leads with ✗ even if its failing count sits later in the line.
|
|
37
|
+
export function formatSummaryLine(failed, counts) {
|
|
38
|
+
const verdict = failed ? pc.red('✗ FAILED') : pc.green('✓ ok');
|
|
39
|
+
return `${verdict} ${counts.join(' ')}`;
|
|
40
|
+
}
|
|
41
|
+
const TONE_GLYPH = { pass: '✓', fail: '✗', skip: '⊘', muted: '·' };
|
|
42
|
+
const TONE_PAINT = {
|
|
43
|
+
pass: pc.green,
|
|
44
|
+
fail: pc.red,
|
|
45
|
+
skip: pc.yellow,
|
|
46
|
+
muted: pc.dim,
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=captureFormat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureFormat.js","sourceRoot":"","sources":["../src/captureFormat.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,8BAA8B;AAE9B,OAAO,EAAE,MAAM,YAAY,CAAC;AAI5B,kGAAkG;AAClG,uGAAuG;AACvG,sGAAsG;AACtG,uGAAuG;AACvG,MAAM,UAAU,gBAAgB,CAC9B,KAAa,EACb,KAAa,EACb,UAAkB,EAClB,OAAe,EACf,QAA+B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEvC,iGAAiG;IACjG,+CAA+C;IAC/C,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,YAAY,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,YAAY,EAAE,CAAC;AAC3F,CAAC;AAED,qGAAqG;AACrG,8FAA8F;AAC9F,sGAAsG;AACtG,2CAA2C;AAC3C,MAAM,UAAU,gBAAgB,CAAC,IAAgB,EAAE,KAAa,EAAE,UAAkB,EAAE,OAAe;IACnG,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACrG,OAAO,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AACnF,CAAC;AAED,sGAAsG;AACtG,kDAAkD;AAClD,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,KAAa,EAAE,IAA8B;IAC7F,MAAM,IAAI,GAAG,GAAG,KAAK,IAAI,KAAK,EAAE,CAAC;IACjC,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC5C,OAAO,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACxB,CAAC;AAED,qGAAqG;AACrG,gGAAgG;AAChG,MAAM,UAAU,iBAAiB,CAAC,MAAe,EAAE,MAAyB;IAC1E,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC/D,OAAO,GAAG,OAAO,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,GAA+B,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AAC/F,MAAM,UAAU,GAA8C;IAC5D,IAAI,EAAE,EAAE,CAAC,KAAK;IACd,IAAI,EAAE,EAAE,CAAC,GAAG;IACZ,IAAI,EAAE,EAAE,CAAC,MAAM;IACf,KAAK,EAAE,EAAE,CAAC,GAAG;CACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureInterrupt.d.ts","sourceRoot":"","sources":["../src/captureInterrupt.ts"],"names":[],"mappings":"AAUA,wBAAgB,mBAAmB,IAAI,MAAM,OAAO,CAUnD;AAID,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAG1D"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Graceful-interrupt support for a long capture run. Playwright installs its own SIGINT/SIGTERM
|
|
2
|
+
// handlers that close the browser, after which in-flight page operations reject; these helpers let the
|
|
3
|
+
// capture loop distinguish that torn-down state from a real render failure.
|
|
4
|
+
// Graceful interrupt. Playwright installs its own SIGINT/SIGTERM handlers that close the browser, after
|
|
5
|
+
// which any in-flight page operation rejects with "Target page, context or browser has been closed". A
|
|
6
|
+
// long render run is routinely Ctrl+C'd, so rather than let that reject crash with a raw stack trace —
|
|
7
|
+
// and rather than let the loop keep going and report the torn-down page as a spurious failure — callers
|
|
8
|
+
// poll this flag: break the entry/renderer loop once it is set, then print a partial summary and exit.
|
|
9
|
+
// Returns a getter so the flag stays private. Idempotent across calls.
|
|
10
|
+
export function installAbortHandler() {
|
|
11
|
+
if (!abortHandlerInstalled) {
|
|
12
|
+
abortHandlerInstalled = true;
|
|
13
|
+
const onSignal = () => {
|
|
14
|
+
runAborted = true;
|
|
15
|
+
};
|
|
16
|
+
process.on('SIGINT', onSignal);
|
|
17
|
+
process.on('SIGTERM', onSignal);
|
|
18
|
+
}
|
|
19
|
+
return () => runAborted;
|
|
20
|
+
}
|
|
21
|
+
// True for the "browser/context/page was closed" rejection Playwright raises once it has torn the
|
|
22
|
+
// browser down on signal — expected during a graceful interrupt, not a real test failure.
|
|
23
|
+
export function isBrowserClosedError(err) {
|
|
24
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
25
|
+
return /Target (page|closed)|has been closed|Browser has been closed|Target crashed/i.test(message);
|
|
26
|
+
}
|
|
27
|
+
let runAborted = false;
|
|
28
|
+
let abortHandlerInstalled = false;
|
|
29
|
+
//# sourceMappingURL=captureInterrupt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureInterrupt.js","sourceRoot":"","sources":["../src/captureInterrupt.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,uGAAuG;AACvG,4EAA4E;AAE5E,wGAAwG;AACxG,uGAAuG;AACvG,uGAAuG;AACvG,wGAAwG;AACxG,uGAAuG;AACvG,uEAAuE;AACvE,MAAM,UAAU,mBAAmB;IACjC,IAAI,CAAC,qBAAqB,EAAE,CAAC;QAC3B,qBAAqB,GAAG,IAAI,CAAC;QAC7B,MAAM,QAAQ,GAAG,GAAS,EAAE;YAC1B,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC,CAAC;QACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC/B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,GAAG,EAAE,CAAC,UAAU,CAAC;AAC1B,CAAC;AAED,kGAAkG;AAClG,0FAA0F;AAC1F,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC/C,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,8EAA8E,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACtG,CAAC;AAED,IAAI,UAAU,GAAG,KAAK,CAAC;AACvB,IAAI,qBAAqB,GAAG,KAAK,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BrowserContext } from '@playwright/test';
|
|
2
|
+
import type { Tool } from './captureEntries.js';
|
|
3
|
+
import type { CaptureStatus } from './captureEntry.js';
|
|
4
|
+
export interface CaptureRenderTargetOptions {
|
|
5
|
+
context: BrowserContext;
|
|
6
|
+
/** The running server's base URL (from resolveServer / resolveStaticServer, or an external URL). */
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
/** Output base directory; artifacts land at {outBase}/{tool}/{name}/{renderer}/. */
|
|
9
|
+
outBase: string;
|
|
10
|
+
/** Repo root — committed baselines live at <tool>/baselines/<name>.json. */
|
|
11
|
+
root: string;
|
|
12
|
+
updateBaseline?: boolean;
|
|
13
|
+
extraWait?: number;
|
|
14
|
+
captureFrames?: number;
|
|
15
|
+
failOnError?: boolean;
|
|
16
|
+
isAborted?: () => boolean;
|
|
17
|
+
}
|
|
18
|
+
/** The artifacts and verdict of one captured render target. */
|
|
19
|
+
export interface CaptureRenderTargetResult {
|
|
20
|
+
screenshotPath: string;
|
|
21
|
+
logsPath: string;
|
|
22
|
+
statusPath: string;
|
|
23
|
+
/** The parsed status.json, or null when the capture was interrupted before writing one. */
|
|
24
|
+
status: CaptureStatus | null;
|
|
25
|
+
/** captureEntry's coarse verdict for the single renderer. */
|
|
26
|
+
result: 'ok' | 'changed' | 'error';
|
|
27
|
+
}
|
|
28
|
+
export declare function captureRenderTarget(tool: Tool, name: string, renderer: string, options: CaptureRenderTargetOptions): Promise<CaptureRenderTargetResult>;
|
|
29
|
+
//# sourceMappingURL=captureRenderTarget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureRenderTarget.d.ts","sourceRoot":"","sources":["../src/captureRenderTarget.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGvD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,cAAc,CAAC;IACxB,oGAAoG;IACpG,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;CAC3B;AAED,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,2FAA2F;IAC3F,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,6DAA6D;IAC7D,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC;CACpC;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,yBAAyB,CAAC,CAsBpC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// The programmatic single-target capture entry (the charter North Star): drive one page for one
|
|
2
|
+
// (tool, name, renderer), synchronize to a presented frame, and return the artifact paths plus the
|
|
3
|
+
// parsed status.json verdict. It composes captureEntry over a single renderer so watch mode, the CLI
|
|
4
|
+
// sweep, and other repos share one hardened capture path rather than copied harness scripts.
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { captureEntry, getCaptureOutputPaths } from './captureEntry.js';
|
|
7
|
+
export async function captureRenderTarget(tool, name, renderer, options) {
|
|
8
|
+
const { context, baseUrl, outBase, root } = options;
|
|
9
|
+
const result = await captureEntry({
|
|
10
|
+
context,
|
|
11
|
+
entry: { name, renderers: [renderer] },
|
|
12
|
+
renderers: [renderer],
|
|
13
|
+
baseUrl,
|
|
14
|
+
tool,
|
|
15
|
+
outBase,
|
|
16
|
+
root,
|
|
17
|
+
updateBaseline: options.updateBaseline,
|
|
18
|
+
extraWait: options.extraWait,
|
|
19
|
+
captureFrames: options.captureFrames,
|
|
20
|
+
failOnError: options.failOnError,
|
|
21
|
+
isAborted: options.isAborted,
|
|
22
|
+
});
|
|
23
|
+
const { finalScreenshot, finalLogs, statusPath } = getCaptureOutputPaths(outBase, tool, name, renderer);
|
|
24
|
+
const status = existsSync(statusPath) ? JSON.parse(readFileSync(statusPath, 'utf8')) : null;
|
|
25
|
+
return { screenshotPath: finalScreenshot, logsPath: finalLogs, statusPath, status, result };
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=captureRenderTarget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureRenderTarget.js","sourceRoot":"","sources":["../src/captureRenderTarget.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,mGAAmG;AACnG,qGAAqG;AACrG,6FAA6F;AAE7F,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAMnD,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AA4BxE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAU,EACV,IAAY,EACZ,QAAgB,EAChB,OAAmC;IAEnC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAEpD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC;QAChC,OAAO;QACP,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE;QACtC,SAAS,EAAE,CAAC,QAAQ,CAAC;QACrB,OAAO;QACP,IAAI;QACJ,OAAO;QACP,IAAI;QACJ,cAAc,EAAE,OAAO,CAAC,cAAc;QACtC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,WAAW,EAAE,OAAO,CAAC,WAAW;QAChC,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC,CAAC;IAEH,MAAM,EAAE,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,qBAAqB,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IACxG,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;IAE/G,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC9F,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Tool } from './captureEntries.js';
|
|
2
|
+
export interface Server {
|
|
3
|
+
url: string;
|
|
4
|
+
kill(): void;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveServer(opts: {
|
|
7
|
+
tool: Tool;
|
|
8
|
+
root: string;
|
|
9
|
+
externalUrl?: string;
|
|
10
|
+
}): Promise<Server>;
|
|
11
|
+
export declare function resolveStaticServer(opts: {
|
|
12
|
+
tool: Tool;
|
|
13
|
+
root: string;
|
|
14
|
+
forceBuild?: boolean;
|
|
15
|
+
}): Promise<Server>;
|
|
16
|
+
//# sourceMappingURL=captureServer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captureServer.d.ts","sourceRoot":"","sources":["../src/captureServer.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAEhD,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAiEvG;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgF7G"}
|