looply-cli 0.5.11 → 0.5.13
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/.src-hash +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +16 -0
- package/dist/bin.js.map +1 -1
- package/dist/branch-delivery-wiring.d.ts +7 -2
- package/dist/branch-delivery-wiring.d.ts.map +1 -1
- package/dist/branch-delivery-wiring.js +79 -12
- package/dist/branch-delivery-wiring.js.map +1 -1
- package/dist/branch-delivery.d.ts +23 -3
- package/dist/branch-delivery.d.ts.map +1 -1
- package/dist/branch-delivery.js +40 -2
- package/dist/branch-delivery.js.map +1 -1
- package/dist/coordinator-state.d.ts.map +1 -1
- package/dist/coordinator-state.js +2 -0
- package/dist/coordinator-state.js.map +1 -1
- package/dist/daemon-cmd.d.ts.map +1 -1
- package/dist/daemon-cmd.js +23 -1
- package/dist/daemon-cmd.js.map +1 -1
- package/dist/delivery-rules.d.ts +11 -0
- package/dist/delivery-rules.d.ts.map +1 -1
- package/dist/delivery-rules.js +34 -1
- package/dist/delivery-rules.js.map +1 -1
- package/dist/github-api.d.ts +2 -0
- package/dist/github-api.d.ts.map +1 -1
- package/dist/github-api.js +3 -0
- package/dist/github-api.js.map +1 -1
- package/dist/goal-gap.d.ts +9 -0
- package/dist/goal-gap.d.ts.map +1 -1
- package/dist/goal-gap.js +9 -0
- package/dist/goal-gap.js.map +1 -1
- package/dist/machine-run-slots.d.ts +10 -2
- package/dist/machine-run-slots.d.ts.map +1 -1
- package/dist/machine-run-slots.js +15 -4
- package/dist/machine-run-slots.js.map +1 -1
- package/dist/maintenance-cmd.d.ts +44 -0
- package/dist/maintenance-cmd.d.ts.map +1 -0
- package/dist/maintenance-cmd.js +132 -0
- package/dist/maintenance-cmd.js.map +1 -0
- package/dist/maintenance-facts.d.ts +52 -0
- package/dist/maintenance-facts.d.ts.map +1 -0
- package/dist/maintenance-facts.js +256 -0
- package/dist/maintenance-facts.js.map +1 -0
- package/dist/maintenance-tick.d.ts +51 -0
- package/dist/maintenance-tick.d.ts.map +1 -0
- package/dist/maintenance-tick.js +156 -0
- package/dist/maintenance-tick.js.map +1 -0
- package/dist/mcp-main.js +3 -0
- package/dist/mcp-main.js.map +1 -1
- package/dist/mcp-server.d.ts +5 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +14 -1
- package/dist/mcp-server.js.map +1 -1
- package/dist/model-transport.d.ts.map +1 -1
- package/dist/model-transport.js +4 -0
- package/dist/model-transport.js.map +1 -1
- package/dist/run-cmd.d.ts.map +1 -1
- package/dist/run-cmd.js +18 -1
- package/dist/run-cmd.js.map +1 -1
- package/dist/run-launcher.d.ts +3 -0
- package/dist/run-launcher.d.ts.map +1 -1
- package/dist/run-launcher.js +3 -2
- package/dist/run-launcher.js.map +1 -1
- package/dist/screenshot-tool.d.ts +45 -0
- package/dist/screenshot-tool.d.ts.map +1 -0
- package/dist/screenshot-tool.js +49 -0
- package/dist/screenshot-tool.js.map +1 -0
- package/dist/server-client.d.ts +39 -0
- package/dist/server-client.d.ts.map +1 -1
- package/dist/server-client.js +21 -0
- package/dist/server-client.js.map +1 -1
- package/dist/visual-proof-browser.d.ts +184 -0
- package/dist/visual-proof-browser.d.ts.map +1 -0
- package/dist/visual-proof-browser.js +267 -0
- package/dist/visual-proof-browser.js.map +1 -0
- package/dist/visual-proof-cmd.d.ts +24 -0
- package/dist/visual-proof-cmd.d.ts.map +1 -0
- package/dist/visual-proof-cmd.js +144 -0
- package/dist/visual-proof-cmd.js.map +1 -0
- package/dist/visual-proof-config.d.ts +151 -0
- package/dist/visual-proof-config.d.ts.map +1 -0
- package/dist/visual-proof-config.js +381 -0
- package/dist/visual-proof-config.js.map +1 -0
- package/dist/visual-proof-publish.d.ts +77 -0
- package/dist/visual-proof-publish.d.ts.map +1 -0
- package/dist/visual-proof-publish.js +211 -0
- package/dist/visual-proof-publish.js.map +1 -0
- package/dist/visual-proof-run.d.ts +73 -0
- package/dist/visual-proof-run.d.ts.map +1 -0
- package/dist/visual-proof-run.js +153 -0
- package/dist/visual-proof-run.js.map +1 -0
- package/dist/visual-proof-scope.d.ts +41 -0
- package/dist/visual-proof-scope.d.ts.map +1 -0
- package/dist/visual-proof-scope.js +205 -0
- package/dist/visual-proof-scope.js.map +1 -0
- package/dist/visual-proof-serve.d.ts +11 -0
- package/dist/visual-proof-serve.d.ts.map +1 -0
- package/dist/visual-proof-serve.js +100 -0
- package/dist/visual-proof-serve.js.map +1 -0
- package/dist/visual-proof-store.d.ts +76 -0
- package/dist/visual-proof-store.d.ts.map +1 -0
- package/dist/visual-proof-store.js +117 -0
- package/dist/visual-proof-store.js.map +1 -0
- package/dist/visual-proof.d.ts +87 -0
- package/dist/visual-proof.d.ts.map +1 -0
- package/dist/visual-proof.js +343 -0
- package/dist/visual-proof.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadPlaywright = loadPlaywright;
|
|
4
|
+
exports.findBrowser = findBrowser;
|
|
5
|
+
exports.offlineArgs = offlineArgs;
|
|
6
|
+
exports.decide = decide;
|
|
7
|
+
exports.launchEngine = launchEngine;
|
|
8
|
+
exports.sheetHtml = sheetHtml;
|
|
9
|
+
exports.writeJson = writeJson;
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const node_os_1 = require("node:os");
|
|
12
|
+
const node_path_1 = require("node:path");
|
|
13
|
+
/* ── finding Playwright and a browser ───────────────────────────────────────── */
|
|
14
|
+
function loadPlaywright(roots, env = process.env) {
|
|
15
|
+
const paths = [env['LOOPLY_PLAYWRIGHT'], __dirname, ...roots].filter((p) => typeof p === 'string' && p !== '');
|
|
16
|
+
for (const name of ['playwright-core', 'playwright']) {
|
|
17
|
+
let resolved;
|
|
18
|
+
try {
|
|
19
|
+
resolved = require.resolve(name, { paths });
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
|
+
const mod = require(resolved);
|
|
26
|
+
if (mod.chromium)
|
|
27
|
+
return { chromium: mod.chromium, from: (0, node_path_1.dirname)(resolved) };
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
function newestUnder(dir, prefix, rel, exists) {
|
|
32
|
+
if (!(0, node_fs_1.existsSync)(dir))
|
|
33
|
+
return null;
|
|
34
|
+
const builds = (0, node_fs_1.readdirSync)(dir)
|
|
35
|
+
.filter((d) => d.startsWith(prefix))
|
|
36
|
+
.sort((a, b) => Number(b.split('-').pop()) - Number(a.split('-').pop()));
|
|
37
|
+
for (const b of builds) {
|
|
38
|
+
for (const r of rel) {
|
|
39
|
+
const p = (0, node_path_1.join)(dir, b, r);
|
|
40
|
+
if (exists(p))
|
|
41
|
+
return p;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** A Chromium-family executable already on this machine, or null. */
|
|
47
|
+
function findBrowser(env = process.env, exists = node_fs_1.existsSync) {
|
|
48
|
+
const named = env['LOOPLY_BROWSER'];
|
|
49
|
+
if (named && exists(named))
|
|
50
|
+
return named;
|
|
51
|
+
const caches = [
|
|
52
|
+
env['PLAYWRIGHT_BROWSERS_PATH'],
|
|
53
|
+
env['LOCALAPPDATA'] ? (0, node_path_1.join)(env['LOCALAPPDATA'], 'ms-playwright') : undefined,
|
|
54
|
+
(0, node_path_1.join)((0, node_os_1.homedir)(), '.cache', 'ms-playwright'),
|
|
55
|
+
(0, node_path_1.join)((0, node_os_1.homedir)(), 'Library', 'Caches', 'ms-playwright'),
|
|
56
|
+
].filter((p) => typeof p === 'string' && p !== '' && p !== '0');
|
|
57
|
+
for (const c of caches) {
|
|
58
|
+
const shell = newestUnder(c, 'chromium_headless_shell-', [
|
|
59
|
+
'chrome-headless-shell-win64/chrome-headless-shell.exe',
|
|
60
|
+
'chrome-headless-shell-linux64/chrome-headless-shell',
|
|
61
|
+
'chrome-linux/headless_shell',
|
|
62
|
+
'chrome-headless-shell-mac-arm64/chrome-headless-shell',
|
|
63
|
+
'chrome-headless-shell-mac-x64/chrome-headless-shell',
|
|
64
|
+
], exists);
|
|
65
|
+
if (shell)
|
|
66
|
+
return shell;
|
|
67
|
+
const full = newestUnder(c, 'chromium-', ['chrome-win64/chrome.exe', 'chrome-win/chrome.exe', 'chrome-linux64/chrome', 'chrome-linux/chrome'], exists);
|
|
68
|
+
if (full)
|
|
69
|
+
return full;
|
|
70
|
+
}
|
|
71
|
+
const pf = env['ProgramFiles'] ?? 'C:\\Program Files';
|
|
72
|
+
const pf86 = env['ProgramFiles(x86)'] ?? 'C:\\Program Files (x86)';
|
|
73
|
+
const local = env['LOCALAPPDATA'];
|
|
74
|
+
const system = [
|
|
75
|
+
(0, node_path_1.join)(pf, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
76
|
+
(0, node_path_1.join)(pf86, 'Google', 'Chrome', 'Application', 'chrome.exe'),
|
|
77
|
+
...(local ? [(0, node_path_1.join)(local, 'Google', 'Chrome', 'Application', 'chrome.exe')] : []),
|
|
78
|
+
(0, node_path_1.join)(pf86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
79
|
+
(0, node_path_1.join)(pf, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),
|
|
80
|
+
'/usr/bin/google-chrome',
|
|
81
|
+
'/usr/bin/chromium',
|
|
82
|
+
'/usr/bin/chromium-browser',
|
|
83
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
84
|
+
];
|
|
85
|
+
return system.find((p) => exists(p)) ?? null;
|
|
86
|
+
}
|
|
87
|
+
/** The flags that take the network away from the browser (see the header). */
|
|
88
|
+
function offlineArgs() {
|
|
89
|
+
return [
|
|
90
|
+
'--host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE 127.0.0.1',
|
|
91
|
+
'--proxy-server=http://127.0.0.1:9',
|
|
92
|
+
'--proxy-bypass-list=127.0.0.1',
|
|
93
|
+
'--disable-background-networking',
|
|
94
|
+
'--disable-component-update',
|
|
95
|
+
'--disable-sync',
|
|
96
|
+
'--disable-domain-reliability',
|
|
97
|
+
'--no-first-run',
|
|
98
|
+
'--no-default-browser-check',
|
|
99
|
+
'--metrics-recording-only',
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
function pathMatches(pattern, path) {
|
|
103
|
+
return pattern.endsWith('*') ? path.startsWith(pattern.slice(0, -1)) : path === pattern || path === `${pattern}/`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* What to do with one request. Pure, so the offline rule is tested without a browser:
|
|
107
|
+
* an API route the project configured answers from any origin; the app's own files are
|
|
108
|
+
* served; any other path on the served origin gets the configured fallback; everything
|
|
109
|
+
* else is aborted.
|
|
110
|
+
*/
|
|
111
|
+
function decide(url, method, site, api) {
|
|
112
|
+
let u;
|
|
113
|
+
try {
|
|
114
|
+
u = new URL(url);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { kind: 'abort' };
|
|
118
|
+
}
|
|
119
|
+
if (u.protocol === 'data:' || u.protocol === 'blob:')
|
|
120
|
+
return { kind: 'static' };
|
|
121
|
+
const hit = api.routes.find((r) => (r.method === undefined || r.method === method.toUpperCase()) && pathMatches(r.path, u.pathname));
|
|
122
|
+
if (hit !== undefined)
|
|
123
|
+
return { kind: 'api', answer: { status: hit.status, body: hit.body } };
|
|
124
|
+
if (u.origin !== site.origin)
|
|
125
|
+
return { kind: 'abort' };
|
|
126
|
+
if (u.pathname.startsWith(site.mount) || `${u.pathname}/` === site.mount)
|
|
127
|
+
return { kind: 'static' };
|
|
128
|
+
return { kind: 'api', answer: api.fallback };
|
|
129
|
+
}
|
|
130
|
+
const STILL = '*,*::before,*::after{transition:none!important;animation:none!important;caret-color:transparent!important;scroll-behavior:auto!important}';
|
|
131
|
+
const SHOT_TIMEOUT_MS = 30_000;
|
|
132
|
+
const SETTLE_MS = 5_000;
|
|
133
|
+
const WAIT_FOR_MS = 10_000;
|
|
134
|
+
function storageScript(origin, storage, dir) {
|
|
135
|
+
return `(() => { try {
|
|
136
|
+
if (location.origin !== ${JSON.stringify(origin)}) return;
|
|
137
|
+
const s = ${JSON.stringify(storage)};
|
|
138
|
+
for (const k of Object.keys(s)) localStorage.setItem(k, s[k]);
|
|
139
|
+
${dir === 'rtl' ? "document.addEventListener('DOMContentLoaded', () => { if (document.documentElement.dir !== 'rtl') document.documentElement.dir = 'rtl'; });" : ''}
|
|
140
|
+
} catch (e) {} })();`;
|
|
141
|
+
}
|
|
142
|
+
/** Launch the browser. Throws, in words, when Playwright or a browser is not on this machine. */
|
|
143
|
+
async function launchEngine(roots, env = process.env) {
|
|
144
|
+
const pw = loadPlaywright(roots, env);
|
|
145
|
+
if (pw === null) {
|
|
146
|
+
throw new Error('Playwright is not installed where Looply can find it (looply-cli’s optional playwright-core, the project’s node_modules, or LOOPLY_PLAYWRIGHT)');
|
|
147
|
+
}
|
|
148
|
+
const exe = findBrowser(env);
|
|
149
|
+
if (exe === null)
|
|
150
|
+
throw new Error('no Chromium, Chrome or Edge is installed on this machine (set LOOPLY_BROWSER to one)');
|
|
151
|
+
const browser = await pw.chromium.launch({ executablePath: exe, headless: true, args: offlineArgs(), timeout: 60_000 });
|
|
152
|
+
const route = (site) => async (r) => {
|
|
153
|
+
const req = r.request();
|
|
154
|
+
const d = decide(req.url(), req.method(), site, site.api);
|
|
155
|
+
if (d.kind === 'static')
|
|
156
|
+
return r.continue();
|
|
157
|
+
if (d.kind === 'abort')
|
|
158
|
+
return r.abort('blockedbyclient');
|
|
159
|
+
return r.fulfill({ status: d.answer.status, contentType: 'application/json', body: d.answer.body });
|
|
160
|
+
};
|
|
161
|
+
return {
|
|
162
|
+
describe: `${exe} via ${pw.from}`,
|
|
163
|
+
async session(site) {
|
|
164
|
+
let state = undefined;
|
|
165
|
+
if (site.login !== null) {
|
|
166
|
+
const ctx = await browser.newContext({ serviceWorkers: 'block', viewport: { width: 1280, height: 800 } });
|
|
167
|
+
try {
|
|
168
|
+
await ctx.route('**/*', route(site));
|
|
169
|
+
await ctx.addInitScript({ content: storageScript(site.origin, site.storage, null) });
|
|
170
|
+
const page = await ctx.newPage();
|
|
171
|
+
await page.goto(`${site.origin}${site.login.path}`, { waitUntil: 'load', timeout: SHOT_TIMEOUT_MS });
|
|
172
|
+
for (const [sel, value] of Object.entries(site.login.values))
|
|
173
|
+
await page.fill(sel, value, { timeout: SHOT_TIMEOUT_MS });
|
|
174
|
+
await page.click(site.login.submit, { timeout: SHOT_TIMEOUT_MS });
|
|
175
|
+
if (site.login.waitFor)
|
|
176
|
+
await page.waitForSelector(site.login.waitFor, { timeout: SHOT_TIMEOUT_MS });
|
|
177
|
+
else
|
|
178
|
+
await page.waitForTimeout(1500);
|
|
179
|
+
state = await ctx.storageState();
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
await ctx.close();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
async shoot(spec) {
|
|
187
|
+
const ctx = await browser.newContext({
|
|
188
|
+
serviceWorkers: 'block',
|
|
189
|
+
viewport: { width: spec.viewport.width, height: spec.viewport.height },
|
|
190
|
+
deviceScaleFactor: 1,
|
|
191
|
+
colorScheme: spec.theme,
|
|
192
|
+
locale: spec.dir === 'rtl' ? 'ar' : 'en-US',
|
|
193
|
+
...(spec.viewport.width < 600 ? { isMobile: true, hasTouch: true } : {}),
|
|
194
|
+
...(state === undefined ? {} : { storageState: state }),
|
|
195
|
+
});
|
|
196
|
+
try {
|
|
197
|
+
await ctx.route('**/*', route(site));
|
|
198
|
+
await ctx.addInitScript({ content: storageScript(site.origin, spec.storage, spec.dir) });
|
|
199
|
+
const page = await ctx.newPage();
|
|
200
|
+
await page.goto(spec.url, { waitUntil: 'load', timeout: SHOT_TIMEOUT_MS });
|
|
201
|
+
// A page that polls or keeps a socket retrying never goes quiet; a few seconds is enough.
|
|
202
|
+
await page.waitForLoadState('networkidle', { timeout: SETTLE_MS }).catch(() => undefined);
|
|
203
|
+
let warning;
|
|
204
|
+
if (spec.waitFor) {
|
|
205
|
+
await page.waitForSelector(spec.waitFor, { timeout: WAIT_FOR_MS, state: 'visible' }).catch(() => {
|
|
206
|
+
// Still taken: a screen that did not draw what it should is exactly what a reviewer needs to see.
|
|
207
|
+
warning = `${spec.waitFor} did not appear within ${WAIT_FOR_MS / 1000}s`;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
await page.addStyleTag({ content: STILL });
|
|
211
|
+
await Promise.race([page.evaluate('document.fonts ? document.fonts.ready.then(() => true) : true'), page.waitForTimeout(3000)]);
|
|
212
|
+
await page.waitForTimeout(400);
|
|
213
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(spec.file), { recursive: true });
|
|
214
|
+
await page.screenshot({ path: spec.file, type: 'jpeg', quality: 72, fullPage: false });
|
|
215
|
+
return warning === undefined ? { ok: true } : { ok: true, warning };
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return { ok: false, because: e.message.split('\n')[0].slice(0, 300) };
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
await ctx.close();
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
close: async () => undefined,
|
|
225
|
+
};
|
|
226
|
+
},
|
|
227
|
+
async render(html, width, file) {
|
|
228
|
+
const ctx = await browser.newContext({ viewport: { width, height: 400 }, deviceScaleFactor: 1, serviceWorkers: 'block' });
|
|
229
|
+
try {
|
|
230
|
+
await ctx.route('**/*', (r) => (r.request().url().startsWith('data:') || r.request().url() === 'about:blank' ? r.continue() : r.abort('blockedbyclient')));
|
|
231
|
+
const page = await ctx.newPage();
|
|
232
|
+
await page.setContent(html, { waitUntil: 'load', timeout: SHOT_TIMEOUT_MS });
|
|
233
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(file), { recursive: true });
|
|
234
|
+
await page.screenshot({ path: file, type: 'jpeg', quality: 78, fullPage: true });
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
await ctx.close();
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
close: () => browser.close(),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
/* ── contact sheets ────────────────────────────────────────────────────────── */
|
|
244
|
+
const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
245
|
+
/**
|
|
246
|
+
* One page: a title, then a row per variant with BEFORE and AFTER side by side, each image
|
|
247
|
+
* inlined as a data URL (the sheet page itself can load nothing).
|
|
248
|
+
*/
|
|
249
|
+
function sheetHtml(title, subtitle, rows, cellWidth) {
|
|
250
|
+
const cell = (c) => 'file' in c
|
|
251
|
+
? `<img src="data:image/jpeg;base64,${(0, node_fs_1.readFileSync)(c.file).toString('base64')}" style="width:${cellWidth}px;display:block;border:1px solid #d0d4da">`
|
|
252
|
+
: `<div style="width:${cellWidth}px;height:${Math.round(cellWidth * 0.5)}px;display:flex;align-items:center;justify-content:center;border:1px dashed #b5bac2;color:#6b7280;font-size:14px;text-align:center;padding:8px;box-sizing:border-box">${esc(c.missing)}</div>`;
|
|
253
|
+
const body = rows
|
|
254
|
+
.map((r) => `<tr><th style="text-align:left;vertical-align:top;padding:12px 12px 4px 0;font-size:14px;color:#374151;white-space:nowrap">${esc(r.label)}</th>
|
|
255
|
+
<td style="padding:8px;vertical-align:top">${cell(r.before)}</td><td style="padding:8px;vertical-align:top">${cell(r.after)}</td></tr>`)
|
|
256
|
+
.join('\n');
|
|
257
|
+
return `<!doctype html><html><head><meta charset="utf-8"><style>body{margin:0;padding:20px;background:#fff;font-family:Segoe UI,Arial,sans-serif;color:#111827}</style></head><body>
|
|
258
|
+
<div style="font-size:20px;font-weight:600">${esc(title)}</div><div style="font-size:13px;color:#6b7280;margin:4px 0 12px">${esc(subtitle)}</div>
|
|
259
|
+
<table style="border-collapse:collapse"><tr><th></th><th style="font-size:13px;color:#6b7280;text-align:left;padding:0 8px">Before</th><th style="font-size:13px;color:#6b7280;text-align:left;padding:0 8px">After</th></tr>
|
|
260
|
+
${body}</table></body></html>`;
|
|
261
|
+
}
|
|
262
|
+
/** Write a JSON-able value next to the shots, for debugging a capture by hand. */
|
|
263
|
+
function writeJson(file, v) {
|
|
264
|
+
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(file), { recursive: true });
|
|
265
|
+
(0, node_fs_1.writeFileSync)(file, `${JSON.stringify(v, null, 2)}\n`, 'utf8');
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=visual-proof-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-proof-browser.js","sourceRoot":"","sources":["../src/visual-proof-browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ProofOutcome } from './visual-proof';
|
|
2
|
+
/**
|
|
3
|
+
* `looply visual-proof [--base <ref>] [--head <ref>] [--goal <id>] [--text "<ticket words>"] [--json]`
|
|
4
|
+
*
|
|
5
|
+
* Take the before/after screenshots for a change by hand — the same code a
|
|
6
|
+
* finished UI goal runs before it is judged. Reads `visual_proof:` from this
|
|
7
|
+
* project's `.looply/config.yaml`; builds in the sandbox; publishes nothing.
|
|
8
|
+
* Prints where the screenshots are.
|
|
9
|
+
*/
|
|
10
|
+
export interface VisualProofArgs {
|
|
11
|
+
base: string | null;
|
|
12
|
+
head: string;
|
|
13
|
+
goalId: string | null;
|
|
14
|
+
text: string;
|
|
15
|
+
json: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseVisualProofArgs(argv: readonly string[]): VisualProofArgs | {
|
|
18
|
+
error: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function describeOutcome(o: ProofOutcome): string[];
|
|
21
|
+
/** `run.carry_into_checkouts`, or what the gates imply when the file does not say. */
|
|
22
|
+
export declare function carryOf(cwd: string): string[];
|
|
23
|
+
export declare function visualProofCommand(argv: readonly string[], cwd: string, out: (line: string) => void): Promise<number>;
|
|
24
|
+
//# sourceMappingURL=visual-proof-cmd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-proof-cmd.d.ts","sourceRoot":"","sources":["../src/visual-proof-cmd.ts"],"names":[],"mappings":"AAMA,OAAO,EAAsC,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKvF;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAqBjG;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,CAazD;AAED,sFAAsF;AACtF,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAa7C;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D3H"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseVisualProofArgs = parseVisualProofArgs;
|
|
4
|
+
exports.describeOutcome = describeOutcome;
|
|
5
|
+
exports.carryOf = carryOf;
|
|
6
|
+
exports.visualProofCommand = visualProofCommand;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const config_1 = require("./config");
|
|
10
|
+
const git_driver_1 = require("./git-driver");
|
|
11
|
+
const run_base_1 = require("./run-base");
|
|
12
|
+
const untrusted_gate_1 = require("./untrusted-gate");
|
|
13
|
+
const visual_proof_1 = require("./visual-proof");
|
|
14
|
+
const visual_proof_config_1 = require("./visual-proof-config");
|
|
15
|
+
const workspace_1 = require("./workspace");
|
|
16
|
+
const yaml_1 = require("yaml");
|
|
17
|
+
function parseVisualProofArgs(argv) {
|
|
18
|
+
const out = { base: null, head: 'HEAD', goalId: null, text: '', json: false };
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const a = argv[i];
|
|
21
|
+
const next = () => {
|
|
22
|
+
const v = argv[i + 1];
|
|
23
|
+
if (v === undefined || v.startsWith('--'))
|
|
24
|
+
return null;
|
|
25
|
+
i++;
|
|
26
|
+
return v;
|
|
27
|
+
};
|
|
28
|
+
if (a === '--json')
|
|
29
|
+
out.json = true;
|
|
30
|
+
else if (a === '--base' || a === '--head' || a === '--goal' || a === '--text') {
|
|
31
|
+
const v = next();
|
|
32
|
+
if (v === null)
|
|
33
|
+
return { error: `${a} needs a value` };
|
|
34
|
+
if (a === '--base')
|
|
35
|
+
out.base = v;
|
|
36
|
+
else if (a === '--head')
|
|
37
|
+
out.head = v;
|
|
38
|
+
else if (a === '--goal')
|
|
39
|
+
out.goalId = v;
|
|
40
|
+
else
|
|
41
|
+
out.text = v;
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
return { error: `unknown argument ${a}` };
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function describeOutcome(o) {
|
|
49
|
+
if (o.kind === 'not-ui')
|
|
50
|
+
return [`no screenshots: ${o.because}`];
|
|
51
|
+
if (o.kind === 'failed')
|
|
52
|
+
return [`screenshots failed: ${o.because}`];
|
|
53
|
+
const m = o.manifest;
|
|
54
|
+
const ok = m.shots.filter((s) => s.ok).length;
|
|
55
|
+
return [
|
|
56
|
+
`screenshots: ${ok} of ${m.shots.length} taken, ${m.sheets.length} before/after sheet(s)`,
|
|
57
|
+
` before ${m.baseSha.slice(0, 12)} → after ${m.headSha.slice(0, 12)}`,
|
|
58
|
+
` folder ${o.dir}`,
|
|
59
|
+
...m.apps.flatMap((a) => [` ${a.name}: ${a.routes.map((r) => r.name || r.path || 'home').join(', ')}`, ...a.why.map((w) => ` · ${w}`)]),
|
|
60
|
+
...m.sheets.map((s) => ` sheet ${(0, node_path_1.join)(o.dir, s.file)}`),
|
|
61
|
+
...m.notes.map((n) => ` note: ${n}`),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
/** `run.carry_into_checkouts`, or what the gates imply when the file does not say. */
|
|
65
|
+
function carryOf(cwd) {
|
|
66
|
+
const file = (0, node_path_1.join)(cwd, '.looply', 'config.yaml');
|
|
67
|
+
if (!(0, node_fs_1.existsSync)(file))
|
|
68
|
+
return ['node_modules'];
|
|
69
|
+
const text = (0, node_fs_1.readFileSync)(file, 'utf8');
|
|
70
|
+
try {
|
|
71
|
+
const cfg = (0, config_1.parseConfig)(text);
|
|
72
|
+
const carry = cfg.run.carryIntoCheckouts ?? [];
|
|
73
|
+
if (carry.length > 0)
|
|
74
|
+
return [...carry];
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
/* fall through to what the gates imply */
|
|
78
|
+
}
|
|
79
|
+
const raw = (0, yaml_1.parse)(text);
|
|
80
|
+
return (0, config_1.defaultCarry)(raw?.gates);
|
|
81
|
+
}
|
|
82
|
+
async function visualProofCommand(argv, cwd, out) {
|
|
83
|
+
const args = parseVisualProofArgs(argv);
|
|
84
|
+
if ('error' in args) {
|
|
85
|
+
out(`looply visual-proof: ${args.error}`);
|
|
86
|
+
return 64;
|
|
87
|
+
}
|
|
88
|
+
let config;
|
|
89
|
+
try {
|
|
90
|
+
config = (0, visual_proof_config_1.readVisualProofConfig)(cwd);
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
out(e instanceof visual_proof_config_1.VisualProofConfigError ? e.message : `config: ${e.message}`);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
if (!config.enabled) {
|
|
97
|
+
out('visual_proof is not configured in .looply/config.yaml (see `visual-proof-config.ts` for the section)');
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
const exec = (0, git_driver_1.nodeGitExec)(cwd);
|
|
101
|
+
let base = args.base;
|
|
102
|
+
if (base === null) {
|
|
103
|
+
let configured;
|
|
104
|
+
try {
|
|
105
|
+
configured = (0, config_1.parseConfig)((0, node_fs_1.readFileSync)((0, node_path_1.join)(cwd, '.looply', 'config.yaml'), 'utf8')).run.baseBranch;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
configured = undefined;
|
|
109
|
+
}
|
|
110
|
+
base = (await (0, run_base_1.resolveBaseBranch)(new git_driver_1.GitDriver(exec), configured))?.ref ?? null;
|
|
111
|
+
if (base === null) {
|
|
112
|
+
out('looply visual-proof: the base branch could not be found; pass --base');
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const deps = {
|
|
117
|
+
git: exec,
|
|
118
|
+
buildRunner: (tree) => (0, untrusted_gate_1.makeUntrustedGateRunner)(tree),
|
|
119
|
+
now: () => Date.now(),
|
|
120
|
+
log: (line) => {
|
|
121
|
+
if (!args.json)
|
|
122
|
+
out(line);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
const outcome = await (0, visual_proof_1.captureVisualProof)({
|
|
126
|
+
checkout: cwd,
|
|
127
|
+
projectDir: cwd,
|
|
128
|
+
projectName: cwd.split(/[\\/]/).pop() ?? 'project',
|
|
129
|
+
goalId: args.goalId,
|
|
130
|
+
goalText: args.text,
|
|
131
|
+
base,
|
|
132
|
+
head: args.head,
|
|
133
|
+
config,
|
|
134
|
+
carry: carryOf(cwd),
|
|
135
|
+
storeBase: (0, workspace_1.workspacesBase)(),
|
|
136
|
+
}, deps);
|
|
137
|
+
if (args.json)
|
|
138
|
+
out(JSON.stringify(outcome.kind === 'captured' ? { kind: outcome.kind, dir: outcome.dir, manifest: outcome.manifest } : outcome, null, 2));
|
|
139
|
+
else
|
|
140
|
+
for (const line of describeOutcome(outcome))
|
|
141
|
+
out(line);
|
|
142
|
+
return outcome.kind === 'failed' ? 1 : 0;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=visual-proof-cmd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-proof-cmd.js","sourceRoot":"","sources":["../src/visual-proof-cmd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `visual_proof:` IN `.looply/config.yaml` — HOW LOOPLY SHOWS A UI CHANGE.
|
|
3
|
+
*
|
|
4
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* A ticket that changes what a screen looks like was judged by its unit tests
|
|
6
|
+
* alone (TAB-B-61, the cramped sidebar). This section tells Looply how to build
|
|
7
|
+
* the app, serve it, sign in with the project's OWN test data and which screens
|
|
8
|
+
* to capture, so a finished UI change carries before/after screenshots.
|
|
9
|
+
*
|
|
10
|
+
* visual_proof:
|
|
11
|
+
* enabled: true
|
|
12
|
+
* max_routes: 4 # screens per goal (default 4)
|
|
13
|
+
* max_mb: 2048 # all screenshots kept on this machine (default 2048)
|
|
14
|
+
* apps:
|
|
15
|
+
* - name: admin
|
|
16
|
+
* when: ["admin/**"] # changed files that make this app's proof run
|
|
17
|
+
* build: "cd admin && node node_modules/vite/bin/vite.js build" # in the sandbox, offline
|
|
18
|
+
* static: admin/dist # Looply serves this folder itself
|
|
19
|
+
* mount: /admin/ # the URL path the app expects (Vite `base`)
|
|
20
|
+
* storage: { tabsy.activeCompanyId: cmp_demo } # set before every page
|
|
21
|
+
* dark: { storage: { tabsy.theme: dark } } # else prefers-color-scheme
|
|
22
|
+
* rtl: { storage: { tabsy.lang: ar } } # absent: no RTL capture
|
|
23
|
+
* login:
|
|
24
|
+
* storage:
|
|
25
|
+
* tabsy.access: { from: admin/src/test/fixtures.ts, match: "access: '([^']+)'" }
|
|
26
|
+
* api:
|
|
27
|
+
* fallback: { status: 200, json: [] }
|
|
28
|
+
* routes:
|
|
29
|
+
* - { path: /api/v1/companies/selectable, file: admin/src/test/companies.json }
|
|
30
|
+
* router: admin/src/App.tsx # to infer screens from changed pages
|
|
31
|
+
* routes:
|
|
32
|
+
* - { path: dashboard, name: Dashboard }
|
|
33
|
+
*
|
|
34
|
+
* 🔴 NO PROJECT CODE RUNS OUTSIDE THE SANDBOX. `build` runs where every gate runs
|
|
35
|
+
* (the AppContainer, no network). Looply serves the built files with its own
|
|
36
|
+
* static server; the app's code then runs only inside the browser, and every
|
|
37
|
+
* request that is not to that server is answered from `api` or refused — the
|
|
38
|
+
* page is offline by construction. A dev-server command (`vite dev`, `next
|
|
39
|
+
* start`) is NOT supported: a contained process cannot be reached on loopback.
|
|
40
|
+
*
|
|
41
|
+
* 🔴 SIGN-IN USES THE PROJECT'S OWN TEST DATA, NEVER A REAL ACCOUNT. Every value
|
|
42
|
+
* under `login` is READ from a file in the repository whose path says it is a
|
|
43
|
+
* seed, fixture, mock or test file; a literal written here is refused, so a real
|
|
44
|
+
* password can never be typed into this file and work.
|
|
45
|
+
*/
|
|
46
|
+
export declare class VisualProofConfigError extends Error {
|
|
47
|
+
readonly key: string;
|
|
48
|
+
constructor(key: string, message: string);
|
|
49
|
+
}
|
|
50
|
+
/** A value read from a file in the repository: the whole file, a JSON key, or a regex's first group. */
|
|
51
|
+
export interface FixtureRef {
|
|
52
|
+
readonly from: string;
|
|
53
|
+
/** Dotted path into a JSON file (`users.0.password`). */
|
|
54
|
+
readonly key?: string;
|
|
55
|
+
/** A regular expression whose FIRST capture group is the value. */
|
|
56
|
+
readonly match?: string;
|
|
57
|
+
}
|
|
58
|
+
export type StorageValue = string | FixtureRef;
|
|
59
|
+
export interface ApiRoute {
|
|
60
|
+
/** A URL path on the served origin; a trailing `*` matches any rest. */
|
|
61
|
+
readonly path: string;
|
|
62
|
+
readonly method?: string;
|
|
63
|
+
readonly status: number;
|
|
64
|
+
/** A JSON body given inline. */
|
|
65
|
+
readonly json?: unknown;
|
|
66
|
+
/** A JSON body read from a file in the repository. */
|
|
67
|
+
readonly file?: string;
|
|
68
|
+
}
|
|
69
|
+
export interface FormLogin {
|
|
70
|
+
/** Path on the served origin, under the mount (`/admin/login`). */
|
|
71
|
+
readonly path: string;
|
|
72
|
+
/** CSS selector → the value to type there. */
|
|
73
|
+
readonly fields: Readonly<Record<string, FixtureRef>>;
|
|
74
|
+
readonly submit: string;
|
|
75
|
+
/** A selector that is on screen once signed in. */
|
|
76
|
+
readonly waitFor?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface VariantSetup {
|
|
79
|
+
readonly storage: Readonly<Record<string, string>>;
|
|
80
|
+
}
|
|
81
|
+
export interface ProofRoute {
|
|
82
|
+
/** Relative to the mount: `dashboard`, `orders/123`. Empty is the app's root. */
|
|
83
|
+
readonly path: string;
|
|
84
|
+
readonly name: string;
|
|
85
|
+
/** Changed files that make this route relevant. Empty: always a candidate. */
|
|
86
|
+
readonly when: readonly string[];
|
|
87
|
+
}
|
|
88
|
+
export interface ProofApp {
|
|
89
|
+
readonly name: string;
|
|
90
|
+
readonly when: readonly string[];
|
|
91
|
+
readonly build: string | null;
|
|
92
|
+
/** Folders carried into the throwaway checkout this app is built in; null: inferred from `build`. */
|
|
93
|
+
readonly carry: readonly string[] | null;
|
|
94
|
+
readonly timeoutS: number;
|
|
95
|
+
readonly static: string;
|
|
96
|
+
readonly mount: string;
|
|
97
|
+
readonly spa: boolean;
|
|
98
|
+
readonly storage: Readonly<Record<string, string>>;
|
|
99
|
+
readonly light: VariantSetup | null;
|
|
100
|
+
readonly dark: VariantSetup | null;
|
|
101
|
+
readonly rtl: VariantSetup | null;
|
|
102
|
+
readonly login: {
|
|
103
|
+
readonly storage: Readonly<Record<string, FixtureRef>>;
|
|
104
|
+
readonly form: FormLogin | null;
|
|
105
|
+
};
|
|
106
|
+
readonly api: {
|
|
107
|
+
readonly routes: readonly ApiRoute[];
|
|
108
|
+
readonly fallback: {
|
|
109
|
+
status: number;
|
|
110
|
+
json?: unknown;
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
readonly router: string | null;
|
|
114
|
+
readonly routes: readonly ProofRoute[];
|
|
115
|
+
readonly waitFor: string | null;
|
|
116
|
+
}
|
|
117
|
+
export interface Viewport {
|
|
118
|
+
readonly name: string;
|
|
119
|
+
readonly width: number;
|
|
120
|
+
readonly height: number;
|
|
121
|
+
}
|
|
122
|
+
export interface VisualProofConfig {
|
|
123
|
+
readonly enabled: boolean;
|
|
124
|
+
readonly maxRoutes: number;
|
|
125
|
+
readonly maxMb: number;
|
|
126
|
+
readonly viewports: readonly Viewport[];
|
|
127
|
+
readonly themes: readonly ('light' | 'dark')[];
|
|
128
|
+
readonly apps: readonly ProofApp[];
|
|
129
|
+
}
|
|
130
|
+
export declare const DEFAULT_VIEWPORTS: readonly Viewport[];
|
|
131
|
+
export declare const DEFAULT_MAX_ROUTES = 4;
|
|
132
|
+
export declare const DEFAULT_MAX_MB = 2048;
|
|
133
|
+
export declare const DISABLED: VisualProofConfig;
|
|
134
|
+
/** A repository-relative path that stays inside the repository. */
|
|
135
|
+
export declare function repoPath(p: string, path: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* 🔴 A SIGN-IN VALUE IS A POINTER INTO A TEST FILE, NEVER THE VALUE ITSELF.
|
|
138
|
+
*/
|
|
139
|
+
export declare function fixtureRef(raw: unknown, path: string): FixtureRef;
|
|
140
|
+
/** The `visual_proof:` section as written, or DISABLED when absent. Throws on anything malformed. */
|
|
141
|
+
export declare function parseVisualProof(raw: unknown): VisualProofConfig;
|
|
142
|
+
/** Read `visual_proof:` from the project's `.looply/config.yaml`; DISABLED when the file or section is absent. */
|
|
143
|
+
export declare function readVisualProofConfig(projectDir: string): VisualProofConfig;
|
|
144
|
+
/**
|
|
145
|
+
* Resolve a fixture pointer to its value, reading the file from the first root that has it
|
|
146
|
+
* (the checkout, then the project's main folder — a machine checkout may not carry it).
|
|
147
|
+
*/
|
|
148
|
+
export declare function readFixture(ref: FixtureRef, roots: readonly string[]): string;
|
|
149
|
+
/** A JSON body file from the repository (checkout first, then the project folder). */
|
|
150
|
+
export declare function readJsonFile(path: string, roots: readonly string[]): unknown;
|
|
151
|
+
//# sourceMappingURL=visual-proof-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"visual-proof-config.d.ts","sourceRoot":"","sources":["../src/visual-proof-config.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,qBAAa,sBAAuB,SAAQ,KAAK;aAE7B,GAAG,EAAE,MAAM;gBAAX,GAAG,EAAE,MAAM,EAC3B,OAAO,EAAE,MAAM;CAKlB;AAED,wGAAwG;AACxG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,CAAC;AAE/C,MAAM,WAAW,QAAQ;IACvB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,sDAAsD;IACtD,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mDAAmD;IACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,UAAU;IACzB,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;KAAE,CAAC;IAC5G,QAAQ,CAAC,GAAG,EAAE;QAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;QAAC,QAAQ,CAAC,QAAQ,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;KAAE,CAAC;IAC9G,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC;IAC/C,QAAQ,CAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAC;CACpC;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,QAAQ,EAGhD,CAAC;AACF,eAAO,MAAM,kBAAkB,IAAI,CAAC;AACpC,eAAO,MAAM,cAAc,OAAO,CAAC;AACnC,eAAO,MAAM,QAAQ,EAAE,iBAA+J,CAAC;AAuCvL,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMxD;AAoBD;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,CAqBjE;AA2HD,qGAAqG;AACrG,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CA4BhE;AAED,kHAAkH;AAClH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,iBAAiB,CAK3E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAmB7E;AAED,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAI5E"}
|