rerender-lens 0.2.0 → 0.4.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +110 -1
  2. package/README.md +159 -9
  3. package/dist/budget-COBu7jBU.d.cts +64 -0
  4. package/dist/budget-LkNjGtRc.d.ts +64 -0
  5. package/dist/cli.cjs +458 -0
  6. package/dist/cli.cjs.map +1 -0
  7. package/dist/cli.d.cts +4 -0
  8. package/dist/cli.d.ts +4 -0
  9. package/dist/cli.js +452 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/devtools-BkCct3cJ.d.cts +252 -0
  12. package/dist/devtools-CZebzpF6.d.ts +252 -0
  13. package/dist/index.cjs +832 -75
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +40 -336
  16. package/dist/index.d.ts +40 -336
  17. package/dist/index.js +816 -76
  18. package/dist/index.js.map +1 -1
  19. package/dist/notifiers-BGQtWKfX.d.cts +90 -0
  20. package/dist/notifiers-BjjGSHpp.d.ts +90 -0
  21. package/dist/playwright.cjs +199 -0
  22. package/dist/playwright.cjs.map +1 -0
  23. package/dist/playwright.d.cts +37 -0
  24. package/dist/playwright.d.ts +37 -0
  25. package/dist/playwright.js +186 -0
  26. package/dist/playwright.js.map +1 -0
  27. package/dist/relay.cjs +166 -0
  28. package/dist/relay.cjs.map +1 -0
  29. package/dist/relay.d.cts +41 -0
  30. package/dist/relay.d.ts +41 -0
  31. package/dist/relay.js +161 -0
  32. package/dist/relay.js.map +1 -0
  33. package/dist/rerender-lens.iife.js +2178 -0
  34. package/dist/setup.cjs +1638 -0
  35. package/dist/setup.cjs.map +1 -0
  36. package/dist/setup.d.cts +2 -0
  37. package/dist/setup.d.ts +2 -0
  38. package/dist/setup.js +1636 -0
  39. package/dist/setup.js.map +1 -0
  40. package/dist/types-BzUEVkxJ.d.cts +177 -0
  41. package/dist/types-BzUEVkxJ.d.ts +177 -0
  42. package/dist/vite.cjs +113 -0
  43. package/dist/vite.cjs.map +1 -0
  44. package/dist/vite.d.cts +84 -0
  45. package/dist/vite.d.ts +84 -0
  46. package/dist/vite.js +103 -0
  47. package/dist/vite.js.map +1 -0
  48. package/dist/vitest-setup.cjs +1368 -0
  49. package/dist/vitest-setup.cjs.map +1 -0
  50. package/dist/vitest-setup.d.cts +8 -0
  51. package/dist/vitest-setup.d.ts +8 -0
  52. package/dist/vitest-setup.js +1366 -0
  53. package/dist/vitest-setup.js.map +1 -0
  54. package/dist/vitest.cjs +1429 -0
  55. package/dist/vitest.cjs.map +1 -0
  56. package/dist/vitest.d.cts +52 -0
  57. package/dist/vitest.d.ts +52 -0
  58. package/dist/vitest.js +1420 -0
  59. package/dist/vitest.js.map +1 -0
  60. package/package.json +84 -4
  61. package/panel/panel.css +384 -0
  62. package/panel/panel.html +12 -0
  63. package/panel/panel.js +3287 -0
package/dist/cli.js ADDED
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env node
2
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
3
+ import { createServer } from 'http';
4
+ import { join, dirname } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ // src/report.ts
8
+ function storeAdvice(custom) {
9
+ const chain = custom ?? [];
10
+ if (chain.some((n) => /^useSelector$/.test(n)))
11
+ return "the Redux selector returns a new object on every call: return a stored slice, pass shallowEqual as the equality function, or memoize it with createSelector";
12
+ if (chain.some((n) => /^useAppSelector$/.test(n)))
13
+ return "the selector returns a new object on every call: return a stored slice, pass shallowEqual, or memoize it with createSelector";
14
+ if (chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore"))
15
+ return "the store selector returns a new object on every call: select a primitive, or wrap the selector with useShallow (Zustand) / an equality function";
16
+ return "getSnapshot returns a new reference with the same contents: cache the snapshot in the store and return the same object while the data is unchanged";
17
+ }
18
+
19
+ // src/fixes.ts
20
+ var AVOIDABLE = /* @__PURE__ */ new Set(["deep-equal", "function", "element"]);
21
+ var rootOf = (path) => path.split(/[.[]/)[0] || path;
22
+ function fixesFor(r) {
23
+ const out = [];
24
+ const owner = r.owner || r.parent && r.parent.name || "?";
25
+ const changes = [...r.propChanges, ...r.stateChanges, ...r.hookChanges];
26
+ if (r.avoidable && (changes.length === 0 || r.memoized === false)) {
27
+ out.push({
28
+ kind: "memo",
29
+ owner: r.component,
30
+ target: r.component,
31
+ prop: null,
32
+ label: `Wrap <${r.component}> in React.memo`,
33
+ detail: changes.length === 0 ? `<${r.component}> re-rendered with identical props because <${r.parent && r.parent.name || "its parent"}> re-rendered.` : `<${r.component}> is not memoized: fixing its props alone will not stop the re-render.`
34
+ });
35
+ }
36
+ for (const c of r.propChanges) {
37
+ if (!AVOIDABLE.has(c.kind)) continue;
38
+ const root = rootOf(c.path);
39
+ if (root === "children" && (c.kind === "element" || c.kind === "deep-equal")) {
40
+ out.push({ kind: "children", owner, target: r.component, prop: "children", label: `memoize children of <${r.component}> in <${owner}>`, detail: `<${owner}> re-creates the children of <${r.component}> on every render.` });
41
+ } else if (c.kind === "function") {
42
+ out.push({ kind: "useCallback", owner, target: r.component, prop: root, label: `useCallback(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new function on every render of <${owner}>.` });
43
+ } else if (c.kind === "element") {
44
+ out.push({ kind: "useMemoElement", owner, target: r.component, prop: root, label: `memoize element prop ${root} in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new element with the same type and props on every render of <${owner}>.` });
45
+ } else {
46
+ out.push({ kind: "useMemo", owner, target: r.component, prop: root, label: `useMemo(${root}) in <${owner}>`, detail: `prop "${c.path}" of <${r.component}> is a new ${Array.isArray(c.next) ? "array" : "object"} with the same contents on every render of <${owner}>.` });
47
+ }
48
+ }
49
+ for (const c of [...r.stateChanges, ...r.hookChanges]) {
50
+ const isContext = c.hook === "useContext";
51
+ const ctxName = isContext ? (/useContext\((.*)\)/.exec(c.path) || [])[1] || "Context" : "";
52
+ const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;
53
+ if (isContext && c.kind === "different" && c.changedKeys && typeof c.totalKeys === "number" && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys) {
54
+ out.push({
55
+ kind: "splitContext",
56
+ owner: providerOwner || `${ctxName}.Provider`,
57
+ target: r.component,
58
+ prop: ctxName,
59
+ label: `split ${ctxName}${providerOwner ? ` in <${providerOwner}>` : ""}: only ${c.changedKeys.join(", ")} changed`,
60
+ detail: `${c.changedKeys.length} of ${c.totalKeys} keys changed in ${ctxName}, yet every consumer re-rendered.`
61
+ });
62
+ continue;
63
+ }
64
+ if (!AVOIDABLE.has(c.kind)) continue;
65
+ if (isContext) {
66
+ out.push({ kind: "contextValue", owner: providerOwner || `${ctxName}.Provider`, target: r.component, prop: ctxName, label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ""}`, detail: `${ctxName} produced a new value that is deep-equal to the previous one.` });
67
+ } else if (c.hook === "useSyncExternalStore") {
68
+ const chain = c.custom || [];
69
+ const redux = chain.some((n) => /^use(App)?Selector$/.test(n));
70
+ const zustand = !redux && chain.some((n) => /^use[A-Z]\w*Store$/.test(n) || n === "useStore" || n === "useBoundStore");
71
+ out.push({ kind: "storeSnapshot", owner: r.component, target: r.component, prop: c.path, label: redux ? `memoize the selector in <${r.component}>` : zustand ? `useShallow in <${r.component}>` : `stable getSnapshot in <${r.component}>`, detail: storeAdvice(c.custom) });
72
+ } else {
73
+ out.push({ kind: "bailout", owner: r.component, target: r.component, prop: c.path, label: `bail out before setting ${c.path} in <${r.component}>`, detail: `${c.path} was set to a value deep-equal to the current one (new reference).` });
74
+ }
75
+ }
76
+ return out;
77
+ }
78
+ var fixKey = (f) => `${f.kind}|${f.owner}|${f.prop || f.target}`;
79
+ function rankFixes(reports) {
80
+ const byKey = /* @__PURE__ */ new Map();
81
+ for (const r of reports) {
82
+ if (!r.avoidable) continue;
83
+ for (const f of fixesFor(r)) {
84
+ const key = fixKey(f);
85
+ let agg = byKey.get(key);
86
+ if (!agg) {
87
+ agg = { ...f, key, count: 0, components: {} };
88
+ byKey.set(key, agg);
89
+ }
90
+ agg.count++;
91
+ agg.components[r.component] = (agg.components[r.component] || 0) + 1;
92
+ }
93
+ }
94
+ return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));
95
+ }
96
+ function formatFixes(reports, limit = 10) {
97
+ const ranked = rankFixes(reports);
98
+ if (!ranked.length) return "No avoidable re-renders.";
99
+ const lines = ranked.slice(0, limit).map((f, i) => `${String(i + 1).padStart(2)}. ${f.label} (removes ${f.count}: ${Object.entries(f.components).map(([c, n]) => `<${c}>${n > 1 ? " x" + n : ""}`).join(", ")})`);
100
+ if (ranked.length > limit) lines.push(` \u2026 ${ranked.length - limit} more`);
101
+ return lines.join("\n");
102
+ }
103
+
104
+ // src/sessions.ts
105
+ function summarizeReports(reports, meta = {}) {
106
+ var _a;
107
+ const byComponent = {};
108
+ let avoidable = 0;
109
+ let wasted = 0;
110
+ for (const r of reports) {
111
+ const c = byComponent[_a = r.component] || (byComponent[_a] = { total: 0, avoidable: 0, wasted: 0 });
112
+ c.total++;
113
+ if (r.avoidable) {
114
+ c.avoidable++;
115
+ avoidable++;
116
+ if (typeof r.selfDuration === "number") {
117
+ c.wasted += r.selfDuration;
118
+ wasted += r.selfDuration;
119
+ }
120
+ }
121
+ }
122
+ return {
123
+ id: meta.id ?? `s${Date.now().toString(36)}`,
124
+ name: meta.name ?? "session",
125
+ startedAt: meta.startedAt ?? (reports[0]?.time ?? 0),
126
+ endedAt: meta.endedAt ?? (reports[reports.length - 1]?.time ?? null),
127
+ total: reports.length,
128
+ avoidable,
129
+ wasted,
130
+ byComponent,
131
+ fixes: rankFixes(reports).map((f) => ({ key: f.key, label: f.label, count: f.count }))
132
+ };
133
+ }
134
+ function compareSummaries(before, after) {
135
+ const names = /* @__PURE__ */ new Set([...Object.keys(before.byComponent), ...Object.keys(after.byComponent)]);
136
+ const rows = [];
137
+ for (const component of names) {
138
+ const b = before.byComponent[component]?.avoidable || 0;
139
+ const a = after.byComponent[component]?.avoidable || 0;
140
+ if (b || a) rows.push({ component, before: b, after: a, delta: a - b });
141
+ }
142
+ rows.sort((x, y) => x.delta - y.delta || y.before - x.before || x.component.localeCompare(y.component));
143
+ const afterKeys = new Set(after.fixes.map((f) => f.key));
144
+ const beforeKeys = new Set(before.fixes.map((f) => f.key));
145
+ return {
146
+ before,
147
+ after,
148
+ rows,
149
+ total: { before: before.total, after: after.total, delta: after.total - before.total },
150
+ avoidable: { before: before.avoidable, after: after.avoidable, delta: after.avoidable - before.avoidable },
151
+ wasted: { before: before.wasted, after: after.wasted, delta: after.wasted - before.wasted },
152
+ resolvedFixes: before.fixes.filter((f) => !afterKeys.has(f.key)),
153
+ newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key)),
154
+ regressions: rows.filter((r) => r.delta > 0)
155
+ };
156
+ }
157
+ function formatComparison(c) {
158
+ const w = Math.max(14, ...c.rows.map((r) => r.component.length + 2));
159
+ const line = (label, b, a, d) => `${label.padEnd(w)} ${String(b).padStart(8)} ${String(a).padStart(8)} ${d.padStart(8)}`;
160
+ const delta = (n, suffix = "") => n === 0 ? "\xB10" : `${n > 0 ? "+" : ""}${Number.isInteger(n) ? n : n.toFixed(1)}${suffix}`;
161
+ const out = [line("avoidable", c.before.name.slice(0, 8), c.after.name.slice(0, 8), "\u0394"), line("all components", c.avoidable.before, c.avoidable.after, delta(c.avoidable.delta))];
162
+ if (c.wasted.before || c.wasted.after) out.push(line("wasted ms", c.wasted.before.toFixed(1), c.wasted.after.toFixed(1), delta(Number(c.wasted.delta.toFixed(1)))));
163
+ for (const r of c.rows) out.push(line(`<${r.component}>`, r.before, r.after, delta(r.delta)));
164
+ if (c.resolvedFixes.length) out.push("", "no longer needed:", ...c.resolvedFixes.map((f) => ` - ${f.label}`));
165
+ if (c.newFixes.length) out.push("", "new:", ...c.newFixes.map((f) => ` - ${f.label}`));
166
+ return out.join("\n");
167
+ }
168
+ function parseExport(data) {
169
+ if (Array.isArray(data)) return { reports: data, sessions: [], summary: null };
170
+ if (data && typeof data === "object") {
171
+ const d = data;
172
+ if (Array.isArray(d.reports)) return { reports: d.reports, sessions: Array.isArray(d.sessions) ? d.sessions : [], summary: null };
173
+ if (d.byComponent && typeof d.byComponent === "object") return { reports: [], sessions: [], summary: data };
174
+ }
175
+ throw new Error("not a rerender-lens export or session summary");
176
+ }
177
+
178
+ // src/budget.ts
179
+ function avoidableCounts(reports) {
180
+ const counts = {};
181
+ for (const r of reports) if (r.avoidable) counts[r.component] = (counts[r.component] || 0) + 1;
182
+ return counts;
183
+ }
184
+ function checkBudget(reports, budget) {
185
+ const b = typeof budget === "number" ? { "*": budget } : budget;
186
+ const fallback = typeof b["*"] === "number" ? b["*"] : 0;
187
+ const counts = avoidableCounts(reports);
188
+ const violations = [];
189
+ const slack = [];
190
+ const names = /* @__PURE__ */ new Set([...Object.keys(counts), ...Object.keys(b).filter((k) => k !== "*")]);
191
+ for (const component of names) {
192
+ const allowed = typeof b[component] === "number" ? b[component] : fallback;
193
+ const avoidable = counts[component] || 0;
194
+ if (avoidable > allowed) violations.push({ component, avoidable, allowed });
195
+ else if (avoidable < allowed) slack.push({ component, avoidable, allowed });
196
+ }
197
+ violations.sort((x, y) => y.avoidable - y.allowed - (x.avoidable - x.allowed));
198
+ return { ok: violations.length === 0, violations, slack, counts };
199
+ }
200
+ function toBudget(reports) {
201
+ return { "*": 0, ...avoidableCounts(reports) };
202
+ }
203
+ var MIME = { ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".css": "text/css; charset=utf-8" };
204
+ function findPanelDir() {
205
+ const here = dirname(fileURLToPath(import.meta.url));
206
+ for (const dir of [join(here, "..", "panel"), join(here, "..", "extension"), join(here, "..", "..", "extension")]) {
207
+ if (existsSync(join(dir, "panel.html")) && existsSync(join(dir, "panel.js"))) return dir;
208
+ }
209
+ return null;
210
+ }
211
+ var cors = (res) => {
212
+ res.setHeader("Access-Control-Allow-Origin", "*");
213
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
214
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
215
+ };
216
+ function readBody(req, limit = 8 * 1024 * 1024) {
217
+ return new Promise((resolve, reject) => {
218
+ const chunks = [];
219
+ let size = 0;
220
+ req.on("data", (c) => {
221
+ size += c.length;
222
+ if (size > limit) {
223
+ reject(new Error("body too large"));
224
+ req.destroy();
225
+ return;
226
+ }
227
+ chunks.push(c);
228
+ });
229
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
230
+ req.on("error", reject);
231
+ });
232
+ }
233
+ function createRelayServer(options = {}) {
234
+ const host = options.host ?? "127.0.0.1";
235
+ const panelDir = options.panelDir === void 0 ? findPanelDir() : options.panelDir;
236
+ const keepAliveMs = options.keepAliveMs ?? 15e3;
237
+ const apps = /* @__PURE__ */ new Set();
238
+ const panels = /* @__PURE__ */ new Set();
239
+ let url = "";
240
+ const send = (targets, message) => {
241
+ const line = `data: ${JSON.stringify(message)}
242
+
243
+ `;
244
+ for (const res of targets) {
245
+ try {
246
+ res.write(line);
247
+ } catch {
248
+ targets.delete(res);
249
+ }
250
+ }
251
+ };
252
+ const route = (message) => {
253
+ if (!message || typeof message !== "object") return;
254
+ const m = message;
255
+ if (m.__rerenderLensCmd === true) send(apps, m);
256
+ else if (m.__rerenderLens === true || m.__rerenderLensReply === true) send(panels, m);
257
+ };
258
+ const server = createServer(async (req, res) => {
259
+ cors(res);
260
+ const u = new URL(req.url || "/", "http://relay");
261
+ if (req.method === "OPTIONS") {
262
+ res.statusCode = 204;
263
+ res.end();
264
+ return;
265
+ }
266
+ if (u.pathname === "/" && req.method === "GET") {
267
+ res.statusCode = 302;
268
+ res.setHeader("Location", `/panel.html?relay=${encodeURIComponent(url)}`);
269
+ res.end();
270
+ return;
271
+ }
272
+ if (u.pathname === "/events" && req.method === "GET") {
273
+ const role = u.searchParams.get("role") === "app" ? apps : panels;
274
+ res.statusCode = 200;
275
+ res.setHeader("Content-Type", "text/event-stream");
276
+ res.setHeader("Cache-Control", "no-store");
277
+ res.setHeader("Connection", "keep-alive");
278
+ res.write(": connected\n\n");
279
+ role.add(res);
280
+ const ping = setInterval(() => {
281
+ try {
282
+ res.write(": ping\n\n");
283
+ } catch {
284
+ }
285
+ }, keepAliveMs);
286
+ req.on("close", () => {
287
+ clearInterval(ping);
288
+ role.delete(res);
289
+ });
290
+ if (role === apps) {
291
+ send(panels, { __rerenderLens: true, version: 2, type: "relay", payload: { apps: apps.size } });
292
+ req.on("close", () => send(panels, { __rerenderLens: true, version: 2, type: "relay", payload: { apps: apps.size } }));
293
+ }
294
+ return;
295
+ }
296
+ if (u.pathname === "/message" && req.method === "POST") {
297
+ try {
298
+ const parsed = JSON.parse(await readBody(req) || "null");
299
+ for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m);
300
+ res.statusCode = 204;
301
+ res.end();
302
+ } catch (e) {
303
+ res.statusCode = 400;
304
+ res.setHeader("Content-Type", "text/plain");
305
+ res.end(String(e.message || e));
306
+ }
307
+ return;
308
+ }
309
+ if (u.pathname === "/status" && req.method === "GET") {
310
+ res.statusCode = 200;
311
+ res.setHeader("Content-Type", "application/json");
312
+ res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size }));
313
+ return;
314
+ }
315
+ const file = u.pathname.replace(/^\//, "");
316
+ if (/^panel\.(html|js|css)$/.test(file) && req.method === "GET") {
317
+ if (!panelDir) {
318
+ res.statusCode = 500;
319
+ res.setHeader("Content-Type", "text/plain");
320
+ res.end("rerender-lens: panel files not found (is the package built?)");
321
+ return;
322
+ }
323
+ res.statusCode = 200;
324
+ res.setHeader("Content-Type", MIME[file.slice(file.lastIndexOf("."))] || "application/octet-stream");
325
+ res.setHeader("Cache-Control", "no-store");
326
+ res.end(readFileSync(join(panelDir, file)));
327
+ return;
328
+ }
329
+ res.statusCode = 404;
330
+ res.setHeader("Content-Type", "text/plain");
331
+ res.end("not found");
332
+ });
333
+ return new Promise((resolve, reject) => {
334
+ server.once("error", reject);
335
+ server.listen(options.port ?? 4141, host, () => {
336
+ const addr = server.address();
337
+ const port = typeof addr === "object" && addr ? addr.port : options.port ?? 4141;
338
+ url = `http://${host === "0.0.0.0" || host === "::" ? "localhost" : host}:${port}`;
339
+ resolve({
340
+ server,
341
+ url,
342
+ port,
343
+ counts: () => ({ apps: apps.size, panels: panels.size }),
344
+ close: () => new Promise((done) => {
345
+ for (const res of [...apps, ...panels]) res.end();
346
+ apps.clear();
347
+ panels.clear();
348
+ server.close(() => done());
349
+ })
350
+ });
351
+ });
352
+ });
353
+ }
354
+
355
+ // src/cli.ts
356
+ var read = (file) => JSON.parse(readFileSync(file, "utf8"));
357
+ function summaryOf(file) {
358
+ const parsed = parseExport(read(file));
359
+ if (parsed.summary) return parsed.summary;
360
+ if (parsed.sessions.length && !parsed.reports.length) return parsed.sessions[parsed.sessions.length - 1];
361
+ return summarizeReports(parsed.reports, { name: file.replace(/^.*[\\/]/, "").replace(/\.json$/, "") });
362
+ }
363
+ async function panelCommand(args) {
364
+ const flag = (name) => {
365
+ const i = args.indexOf(name);
366
+ return i >= 0 ? args[i + 1] ?? "" : null;
367
+ };
368
+ const relay = await createRelayServer({ port: Number(flag("--port") || 4141), host: flag("--host") || "127.0.0.1" });
369
+ console.log(`rerender-lens panel: ${relay.url}/`);
370
+ console.log(`in the app: createDevtoolsNotifier({ relay: '${relay.url}' }) or window.__RERENDER_LENS_RELAY__ = '${relay.url}'`);
371
+ console.log("Ctrl+C to stop.");
372
+ return new Promise((resolve) => {
373
+ const stop = () => void relay.close().then(() => resolve(0));
374
+ process.once("SIGINT", stop);
375
+ process.once("SIGTERM", stop);
376
+ relay.server.once("close", () => resolve(0));
377
+ });
378
+ }
379
+ function main(argv) {
380
+ const [cmd, ...args] = argv;
381
+ if (cmd === "panel") return panelCommand(args).catch((e) => (console.error(e.message), 1));
382
+ const flag = (name) => {
383
+ const i = args.indexOf(name);
384
+ return i >= 0 ? args[i + 1] ?? "" : null;
385
+ };
386
+ const positional = args.filter((a, i) => !a.startsWith("--") && !(i > 0 && args[i - 1].startsWith("--") && args[i - 1] !== "--init"));
387
+ try {
388
+ switch (cmd) {
389
+ case "fixes": {
390
+ const file = positional[0];
391
+ if (!file) throw new Error("usage: rerender-lens fixes <export.json>");
392
+ const { reports } = parseExport(read(file));
393
+ console.log(formatFixes(reports, Number(flag("--limit") || 20)));
394
+ return 0;
395
+ }
396
+ case "summary": {
397
+ const file = positional[0];
398
+ if (!file) throw new Error("usage: rerender-lens summary <export.json> [--out summary.json]");
399
+ const summary = summaryOf(file);
400
+ const out = flag("--out");
401
+ if (out) {
402
+ writeFileSync(out, JSON.stringify(summary, null, 2) + "\n");
403
+ console.log(`${out}: ${summary.total} renders, ${summary.avoidable} avoidable`);
404
+ } else console.log(JSON.stringify(summary, null, 2));
405
+ return 0;
406
+ }
407
+ case "compare": {
408
+ const [a, b] = positional;
409
+ if (!a || !b) throw new Error("usage: rerender-lens compare <before.json> <after.json>");
410
+ const cmp = compareSummaries(summaryOf(a), summaryOf(b));
411
+ console.log(formatComparison(cmp));
412
+ if (cmp.regressions.length) {
413
+ console.error(`
414
+ ${cmp.regressions.length} component${cmp.regressions.length === 1 ? "" : "s"} regressed.`);
415
+ return 1;
416
+ }
417
+ return 0;
418
+ }
419
+ case "budget": {
420
+ const file = positional[0];
421
+ if (!file) throw new Error("usage: rerender-lens budget <export.json> <budget.json> | --init");
422
+ const { reports } = parseExport(read(file));
423
+ if (args.includes("--init")) {
424
+ console.log(JSON.stringify(toBudget(reports), null, 2));
425
+ return 0;
426
+ }
427
+ const budgetFile = positional[1];
428
+ if (!budgetFile) throw new Error("usage: rerender-lens budget <export.json> <budget.json>");
429
+ const result = checkBudget(reports, read(budgetFile));
430
+ if (result.ok) {
431
+ console.log(`Within budget (${Object.keys(result.counts).length} components with avoidable re-renders).`);
432
+ return 0;
433
+ }
434
+ for (const v of result.violations) console.error(`<${v.component}>: ${v.avoidable} avoidable (budget ${v.allowed})`);
435
+ console.error("\n" + formatFixes(reports));
436
+ return 1;
437
+ }
438
+ default:
439
+ console.error("usage: rerender-lens <panel|fixes|summary|compare|budget> ...");
440
+ return cmd ? 1 : 0;
441
+ }
442
+ } catch (e) {
443
+ console.error(e.message);
444
+ return 1;
445
+ }
446
+ }
447
+ var isMain = typeof process !== "undefined" && Array.isArray(process.argv) && /rerender-lens(\.c?js)?$|cli\.c?js$/.test(process.argv[1] || "");
448
+ if (isMain) void Promise.resolve(main(process.argv.slice(2))).then((code) => process.exit(code));
449
+
450
+ export { main };
451
+ //# sourceMappingURL=cli.js.map
452
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/report.ts","../src/fixes.ts","../src/sessions.ts","../src/budget.ts","../src/relay.ts","../src/cli.ts"],"names":["readFileSync"],"mappings":";;;;;;;AAsCO,SAAS,YAAY,MAAA,EAAsC;AAChE,EAAA,MAAM,KAAA,GAAQ,UAAU,EAAC;AACzB,EAAA,IAAI,MAAM,IAAA,CAAK,CAAC,MAAM,eAAA,CAAgB,IAAA,CAAK,CAAC,CAAC,CAAA;AAC3C,IAAA,OAAO,6JAAA;AACT,EAAA,IAAI,MAAM,IAAA,CAAK,CAAC,MAAM,kBAAA,CAAmB,IAAA,CAAK,CAAC,CAAC,CAAA;AAC9C,IAAA,OAAO,8HAAA;AACT,EAAA,IAAI,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,oBAAA,CAAqB,IAAA,CAAK,CAAC,CAAA,IAAK,CAAA,KAAM,UAAA,IAAc,CAAA,KAAM,eAAe,CAAA;AAC7F,IAAA,OAAO,kJAAA;AACT,EAAA,OAAO,oJAAA;AACT;;;ACnBA,IAAM,4BAAY,IAAI,GAAA,CAAY,CAAC,YAAA,EAAc,UAAA,EAAY,SAAS,CAAC,CAAA;AACvE,IAAM,MAAA,GAAS,CAAC,IAAA,KAAyB,IAAA,CAAK,MAAM,MAAM,CAAA,CAAE,CAAC,CAAA,IAAK,IAAA;AAE3D,SAAS,SAAS,CAAA,EAAwB;AAC/C,EAAA,MAAM,MAAa,EAAC;AACpB,EAAA,MAAM,QAAQ,CAAA,CAAE,KAAA,IAAU,EAAE,MAAA,IAAU,CAAA,CAAE,OAAO,IAAA,IAAS,GAAA;AACxD,EAAA,MAAM,OAAA,GAAoB,CAAC,GAAG,CAAA,CAAE,WAAA,EAAa,GAAG,CAAA,CAAE,YAAA,EAAc,GAAG,CAAA,CAAE,WAAW,CAAA;AAChF,EAAA,IAAI,EAAE,SAAA,KAAc,OAAA,CAAQ,WAAW,CAAA,IAAK,CAAA,CAAE,aAAa,KAAA,CAAA,EAAQ;AACjE,IAAA,GAAA,CAAI,IAAA,CAAK;AAAA,MACP,IAAA,EAAM,MAAA;AAAA,MACN,OAAO,CAAA,CAAE,SAAA;AAAA,MACT,QAAQ,CAAA,CAAE,SAAA;AAAA,MACV,IAAA,EAAM,IAAA;AAAA,MACN,KAAA,EAAO,CAAA,MAAA,EAAS,CAAA,CAAE,SAAS,CAAA,eAAA,CAAA;AAAA,MAC3B,QAAQ,OAAA,CAAQ,MAAA,KAAW,CAAA,GAAI,CAAA,CAAA,EAAI,EAAE,SAAS,CAAA,4CAAA,EAAgD,CAAA,CAAE,MAAA,IAAU,EAAE,MAAA,CAAO,IAAA,IAAS,YAAY,CAAA,cAAA,CAAA,GAAmB,CAAA,CAAA,EAAI,EAAE,SAAS,CAAA,sEAAA;AAAA,KAC3K,CAAA;AAAA,EACH;AACA,EAAA,KAAA,MAAW,CAAA,IAAK,EAAE,WAAA,EAAa;AAC7B,IAAA,IAAI,CAAC,SAAA,CAAU,GAAA,CAAI,CAAA,CAAE,IAAI,CAAA,EAAG;AAC5B,IAAA,MAAM,IAAA,GAAO,MAAA,CAAO,CAAA,CAAE,IAAI,CAAA;AAC1B,IAAA,IAAI,SAAS,UAAA,KAAe,CAAA,CAAE,SAAS,SAAA,IAAa,CAAA,CAAE,SAAS,YAAA,CAAA,EAAe;AAC5E,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,UAAA,EAAY,KAAA,EAAO,QAAQ,CAAA,CAAE,SAAA,EAAW,IAAA,EAAM,UAAA,EAAY,KAAA,EAAO,CAAA,qBAAA,EAAwB,EAAE,SAAS,CAAA,MAAA,EAAS,KAAK,CAAA,CAAA,CAAA,EAAK,MAAA,EAAQ,CAAA,CAAA,EAAI,KAAK,CAAA,8BAAA,EAAiC,CAAA,CAAE,SAAS,CAAA,kBAAA,CAAA,EAAsB,CAAA;AAAA,IAC7N,CAAA,MAAA,IAAW,CAAA,CAAE,IAAA,KAAS,UAAA,EAAY;AAChC,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,aAAA,EAAe,KAAA,EAAO,MAAA,EAAQ,CAAA,CAAE,SAAA,EAAW,IAAA,EAAM,IAAA,EAAM,KAAA,EAAO,CAAA,YAAA,EAAe,IAAI,SAAS,KAAK,CAAA,CAAA,CAAA,EAAK,MAAA,EAAQ,CAAA,MAAA,EAAS,CAAA,CAAE,IAAI,CAAA,MAAA,EAAS,CAAA,CAAE,SAAS,CAAA,wCAAA,EAA2C,KAAK,CAAA,EAAA,CAAA,EAAM,CAAA;AAAA,IACxN,CAAA,MAAA,IAAW,CAAA,CAAE,IAAA,KAAS,SAAA,EAAW;AAC/B,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,gBAAA,EAAkB,KAAA,EAAO,MAAA,EAAQ,CAAA,CAAE,SAAA,EAAW,IAAA,EAAM,IAAA,EAAM,KAAA,EAAO,CAAA,qBAAA,EAAwB,IAAI,QAAQ,KAAK,CAAA,CAAA,CAAA,EAAK,MAAA,EAAQ,CAAA,MAAA,EAAS,CAAA,CAAE,IAAI,CAAA,MAAA,EAAS,CAAA,CAAE,SAAS,CAAA,oEAAA,EAAuE,KAAK,CAAA,EAAA,CAAA,EAAM,CAAA;AAAA,IAC/P,CAAA,MAAO;AACL,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,EAAW,OAAO,MAAA,EAAQ,CAAA,CAAE,SAAA,EAAW,IAAA,EAAM,MAAM,KAAA,EAAO,CAAA,QAAA,EAAW,IAAI,CAAA,MAAA,EAAS,KAAK,CAAA,CAAA,CAAA,EAAK,MAAA,EAAQ,CAAA,MAAA,EAAS,CAAA,CAAE,IAAI,CAAA,MAAA,EAAS,CAAA,CAAE,SAAS,cAAc,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,IAAI,IAAI,OAAA,GAAU,QAAQ,CAAA,4CAAA,EAA+C,KAAK,MAAM,CAAA;AAAA,IAC5Q;AAAA,EACF;AACA,EAAA,KAAA,MAAW,CAAA,IAAK,CAAC,GAAG,CAAA,CAAE,cAAc,GAAG,CAAA,CAAE,WAAW,CAAA,EAAuC;AACzF,IAAA,MAAM,SAAA,GAAY,EAAE,IAAA,KAAS,YAAA;AAC7B,IAAA,MAAM,OAAA,GAAU,SAAA,GAAA,CAAa,oBAAA,CAAqB,IAAA,CAAK,CAAA,CAAE,IAAI,CAAA,IAAK,EAAC,EAAG,CAAC,CAAA,IAAK,SAAA,GAAY,EAAA;AACxF,IAAA,MAAM,aAAA,GAAgB,aAAa,CAAA,CAAE,QAAA,IAAY,EAAE,QAAA,CAAS,SAAA,GAAY,CAAA,CAAE,QAAA,CAAS,SAAA,GAAY,IAAA;AAC/F,IAAA,IAAI,aAAa,CAAA,CAAE,IAAA,KAAS,eAAe,CAAA,CAAE,WAAA,IAAe,OAAO,CAAA,CAAE,SAAA,KAAc,QAAA,IAAY,CAAA,CAAE,YAAY,MAAA,GAAS,CAAA,IAAK,EAAE,WAAA,CAAY,MAAA,GAAS,EAAE,SAAA,EAAW;AAC7J,MAAA,GAAA,CAAI,IAAA,CAAK;AAAA,QACP,IAAA,EAAM,cAAA;AAAA,QACN,KAAA,EAAO,aAAA,IAAiB,CAAA,EAAG,OAAO,CAAA,SAAA,CAAA;AAAA,QAClC,QAAQ,CAAA,CAAE,SAAA;AAAA,QACV,IAAA,EAAM,OAAA;AAAA,QACN,KAAA,EAAO,CAAA,MAAA,EAAS,OAAO,CAAA,EAAG,gBAAgB,CAAA,KAAA,EAAQ,aAAa,CAAA,CAAA,CAAA,GAAM,EAAE,CAAA,OAAA,EAAU,CAAA,CAAE,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA,QAAA,CAAA;AAAA,QACzG,MAAA,EAAQ,GAAG,CAAA,CAAE,WAAA,CAAY,MAAM,CAAA,IAAA,EAAO,CAAA,CAAE,SAAS,CAAA,iBAAA,EAAoB,OAAO,CAAA,iCAAA;AAAA,OAC7E,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,SAAA,CAAU,GAAA,CAAI,CAAA,CAAE,IAAI,CAAA,EAAG;AAC5B,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,cAAA,EAAgB,KAAA,EAAO,aAAA,IAAiB,CAAA,EAAG,OAAO,CAAA,SAAA,CAAA,EAAa,MAAA,EAAQ,CAAA,CAAE,SAAA,EAAW,IAAA,EAAM,OAAA,EAAS,KAAA,EAAO,CAAA,YAAA,EAAe,OAAO,CAAA,eAAA,EAAkB,aAAA,GAAgB,CAAA,KAAA,EAAQ,aAAa,CAAA,CAAA,CAAA,GAAM,EAAE,CAAA,CAAA,EAAI,MAAA,EAAQ,CAAA,EAAG,OAAO,CAAA,6DAAA,CAAA,EAAiE,CAAA;AAAA,IACzS,CAAA,MAAA,IAAW,CAAA,CAAE,IAAA,KAAS,sBAAA,EAAwB;AAC5C,MAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,MAAA,IAAU,EAAC;AAC3B,MAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,CAAC,MAAM,qBAAA,CAAsB,IAAA,CAAK,CAAC,CAAC,CAAA;AAC7D,MAAA,MAAM,OAAA,GAAU,CAAC,KAAA,IAAS,KAAA,CAAM,KAAK,CAAC,CAAA,KAAM,oBAAA,CAAqB,IAAA,CAAK,CAAC,CAAA,IAAK,CAAA,KAAM,UAAA,IAAc,MAAM,eAAe,CAAA;AACrH,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAO,CAAA,CAAE,SAAA,EAAW,MAAA,EAAQ,CAAA,CAAE,SAAA,EAAW,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,OAAO,KAAA,GAAQ,CAAA,yBAAA,EAA4B,CAAA,CAAE,SAAS,CAAA,CAAA,CAAA,GAAM,OAAA,GAAU,CAAA,eAAA,EAAkB,CAAA,CAAE,SAAS,CAAA,CAAA,CAAA,GAAM,CAAA,uBAAA,EAA0B,CAAA,CAAE,SAAS,KAAK,MAAA,EAAQ,WAAA,CAAY,CAAA,CAAE,MAAM,GAAG,CAAA;AAAA,IAC7Q,CAAA,MAAO;AACL,MAAA,GAAA,CAAI,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,EAAW,KAAA,EAAO,CAAA,CAAE,SAAA,EAAW,MAAA,EAAQ,CAAA,CAAE,SAAA,EAAW,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,OAAO,CAAA,wBAAA,EAA2B,CAAA,CAAE,IAAI,CAAA,KAAA,EAAQ,CAAA,CAAE,SAAS,CAAA,CAAA,CAAA,EAAK,MAAA,EAAQ,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,kEAAA,CAAA,EAAsE,CAAA;AAAA,IAC5O;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AAEO,IAAM,MAAA,GAAS,CAAC,CAAA,KAAmB,CAAA,EAAG,CAAA,CAAE,IAAI,CAAA,CAAA,EAAI,CAAA,CAAE,KAAK,CAAA,CAAA,EAAI,CAAA,CAAE,IAAA,IAAQ,EAAE,MAAM,CAAA,CAAA;AAG7E,SAAS,UAAU,OAAA,EAAsC;AAC9D,EAAA,MAAM,KAAA,uBAAY,GAAA,EAAuB;AACzC,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,IAAA,IAAI,CAAC,EAAE,SAAA,EAAW;AAClB,IAAA,KAAA,MAAW,CAAA,IAAK,QAAA,CAAS,CAAC,CAAA,EAAG;AAC3B,MAAA,MAAM,GAAA,GAAM,OAAO,CAAC,CAAA;AACpB,MAAA,IAAI,GAAA,GAAM,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AACvB,MAAA,IAAI,CAAC,GAAA,EAAK;AACR,QAAA,GAAA,GAAM,EAAE,GAAG,CAAA,EAAG,GAAA,EAAK,OAAO,CAAA,EAAG,UAAA,EAAY,EAAC,EAAE;AAC5C,QAAA,KAAA,CAAM,GAAA,CAAI,KAAK,GAAG,CAAA;AAAA,MACpB;AACA,MAAA,GAAA,CAAI,KAAA,EAAA;AACJ,MAAA,GAAA,CAAI,UAAA,CAAW,EAAE,SAAS,CAAA,GAAA,CAAK,IAAI,UAAA,CAAW,CAAA,CAAE,SAAS,CAAA,IAAK,CAAA,IAAK,CAAA;AAAA,IACrE;AAAA,EACF;AACA,EAAA,OAAO,CAAC,GAAG,KAAA,CAAM,QAAQ,CAAA,CAAE,KAAK,CAAC,CAAA,EAAG,MAAM,CAAA,CAAE,KAAA,GAAQ,EAAE,KAAA,IAAS,CAAA,CAAE,MAAM,aAAA,CAAc,CAAA,CAAE,KAAK,CAAC,CAAA;AAC/F;AAGO,SAAS,WAAA,CAAY,OAAA,EAAyB,KAAA,GAAQ,EAAA,EAAY;AACvE,EAAA,MAAM,MAAA,GAAS,UAAU,OAAO,CAAA;AAChC,EAAA,IAAI,CAAC,MAAA,CAAO,MAAA,EAAQ,OAAO,0BAAA;AAC3B,EAAA,MAAM,KAAA,GAAQ,OAAO,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,EAAG,OAAO,CAAA,GAAI,CAAC,EAAE,QAAA,CAAS,CAAC,CAAC,CAAA,EAAA,EAAK,CAAA,CAAE,KAAK,CAAA,WAAA,EAAc,CAAA,CAAE,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,EAAE,UAAU,CAAA,CAAE,IAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,KAAM,CAAA,CAAA,EAAI,CAAC,CAAA,CAAA,EAAI,CAAA,GAAI,CAAA,GAAI,IAAA,GAAO,CAAA,GAAI,EAAE,EAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAG,CAAA;AACjN,EAAA,IAAI,MAAA,CAAO,SAAS,KAAA,EAAO,KAAA,CAAM,KAAK,CAAA,WAAA,EAAS,MAAA,CAAO,MAAA,GAAS,KAAK,CAAA,KAAA,CAAO,CAAA;AAC3E,EAAA,OAAO,KAAA,CAAM,KAAK,IAAI,CAAA;AACxB;;;AC7EO,SAAS,gBAAA,CAAiB,OAAA,EAAyB,IAAA,GAA+E,EAAC,EAAmB;AAvC7J,EAAA,IAAA,EAAA;AAwCE,EAAA,MAAM,cAA6C,EAAC;AACpD,EAAA,IAAI,SAAA,GAAY,CAAA;AAChB,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,KAAA,MAAW,KAAK,OAAA,EAAS;AACvB,IAAA,MAAM,CAAA,GAAK,WAAA,CAAA,EAAA,GAAY,CAAA,CAAE,SAAA,CAAA,KAAd,WAAA,CAAA,EAAA,CAAA,GAA6B,EAAE,KAAA,EAAO,CAAA,EAAG,SAAA,EAAW,CAAA,EAAG,MAAA,EAAQ,CAAA,EAAE,CAAA;AAC5E,IAAA,CAAA,CAAE,KAAA,EAAA;AACF,IAAA,IAAI,EAAE,SAAA,EAAW;AACf,MAAA,CAAA,CAAE,SAAA,EAAA;AACF,MAAA,SAAA,EAAA;AACA,MAAA,IAAI,OAAO,CAAA,CAAE,YAAA,KAAiB,QAAA,EAAU;AACtC,QAAA,CAAA,CAAE,UAAU,CAAA,CAAE,YAAA;AACd,QAAA,MAAA,IAAU,CAAA,CAAE,YAAA;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACA,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,KAAK,EAAA,IAAM,CAAA,CAAA,EAAI,KAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAC,CAAA,CAAA;AAAA,IAC1C,IAAA,EAAM,KAAK,IAAA,IAAQ,SAAA;AAAA,IACnB,WAAW,IAAA,CAAK,SAAA,KAAc,OAAA,CAAQ,CAAC,GAAG,IAAA,IAAQ,CAAA,CAAA;AAAA,IAClD,OAAA,EAAS,KAAK,OAAA,KAAY,OAAA,CAAQ,QAAQ,MAAA,GAAS,CAAC,GAAG,IAAA,IAAQ,IAAA,CAAA;AAAA,IAC/D,OAAO,OAAA,CAAQ,MAAA;AAAA,IACf,SAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAA;AAAA,IACA,OAAO,SAAA,CAAU,OAAO,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,GAAA,EAAK,CAAA,CAAE,KAAK,KAAA,EAAO,CAAA,CAAE,OAAO,KAAA,EAAO,CAAA,CAAE,OAAM,CAAE;AAAA,GACvF;AACF;AAEO,SAAS,gBAAA,CAAiB,QAAwB,KAAA,EAAmC;AAC1F,EAAA,MAAM,wBAAQ,IAAI,GAAA,CAAI,CAAC,GAAG,OAAO,IAAA,CAAK,MAAA,CAAO,WAAW,CAAA,EAAG,GAAG,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,WAAW,CAAC,CAAC,CAAA;AAC7F,EAAA,MAAM,OAAqB,EAAC;AAC5B,EAAA,KAAA,MAAW,aAAa,KAAA,EAAO;AAC7B,IAAA,MAAM,CAAA,GAAI,MAAA,CAAO,WAAA,CAAY,SAAS,GAAG,SAAA,IAAa,CAAA;AACtD,IAAA,MAAM,CAAA,GAAI,KAAA,CAAM,WAAA,CAAY,SAAS,GAAG,SAAA,IAAa,CAAA;AACrD,IAAA,IAAI,CAAA,IAAK,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,EAAE,SAAA,EAAW,MAAA,EAAQ,CAAA,EAAG,KAAA,EAAO,CAAA,EAAG,KAAA,EAAO,CAAA,GAAI,GAAG,CAAA;AAAA,EACxE;AACA,EAAA,IAAA,CAAK,KAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,QAAQ,CAAA,CAAE,KAAA,IAAS,CAAA,CAAE,MAAA,GAAS,EAAE,MAAA,IAAU,CAAA,CAAE,UAAU,aAAA,CAAc,CAAA,CAAE,SAAS,CAAC,CAAA;AACtG,EAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,KAAA,CAAM,KAAA,CAAM,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAC,CAAA;AACvD,EAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,MAAA,CAAO,KAAA,CAAM,IAAI,CAAC,CAAA,KAAM,CAAA,CAAE,GAAG,CAAC,CAAA;AACzD,EAAA,OAAO;AAAA,IACL,MAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAA,CAAO,KAAA,EAAO,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,KAAA,EAAO,KAAA,CAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,EAAM;AAAA,IACrF,SAAA,EAAW,EAAE,MAAA,EAAQ,MAAA,CAAO,SAAA,EAAW,KAAA,EAAO,KAAA,CAAM,SAAA,EAAW,KAAA,EAAO,KAAA,CAAM,SAAA,GAAY,MAAA,CAAO,SAAA,EAAU;AAAA,IACzG,MAAA,EAAQ,EAAE,MAAA,EAAQ,MAAA,CAAO,MAAA,EAAQ,KAAA,EAAO,KAAA,CAAM,MAAA,EAAQ,KAAA,EAAO,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,MAAA,EAAO;AAAA,IAC1F,aAAA,EAAe,MAAA,CAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,SAAA,CAAU,GAAA,CAAI,CAAA,CAAE,GAAG,CAAC,CAAA;AAAA,IAC/D,QAAA,EAAU,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,UAAA,CAAW,GAAA,CAAI,CAAA,CAAE,GAAG,CAAC,CAAA;AAAA,IAC1D,aAAa,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,QAAQ,CAAC;AAAA,GAC7C;AACF;AAGO,SAAS,iBAAiB,CAAA,EAAuB;AACtD,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,GAAG,CAAA,CAAE,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,SAAA,CAAU,MAAA,GAAS,CAAC,CAAC,CAAA;AACnE,EAAA,MAAM,IAAA,GAAO,CAAC,KAAA,EAAe,CAAA,EAAoB,CAAA,EAAoB,CAAA,KAAsB,CAAA,EAAG,KAAA,CAAM,MAAA,CAAO,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,CAAC,CAAC,CAAA,CAAA,EAAI,MAAA,CAAO,CAAC,CAAA,CAAE,QAAA,CAAS,CAAC,CAAC,CAAA,CAAA,EAAI,CAAA,CAAE,QAAA,CAAS,CAAC,CAAC,CAAA,CAAA;AAChL,EAAA,MAAM,KAAA,GAAQ,CAAC,CAAA,EAAW,MAAA,GAAS,EAAA,KAAgB,MAAM,CAAA,GAAI,OAAA,GAAO,CAAA,EAAG,CAAA,GAAI,CAAA,GAAI,GAAA,GAAM,EAAE,CAAA,EAAG,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,GAAI,CAAA,GAAI,EAAE,OAAA,CAAQ,CAAC,CAAC,CAAA,EAAG,MAAM,CAAA,CAAA;AACzI,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,CAAK,WAAA,EAAa,EAAE,MAAA,CAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAG,CAAA,CAAE,KAAA,CAAM,KAAK,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAG,QAAG,CAAA,EAAG,IAAA,CAAK,gBAAA,EAAkB,EAAE,SAAA,CAAU,MAAA,EAAQ,CAAA,CAAE,SAAA,CAAU,OAAO,KAAA,CAAM,CAAA,CAAE,SAAA,CAAU,KAAK,CAAC,CAAC,CAAA;AACjL,EAAA,IAAI,CAAA,CAAE,MAAA,CAAO,MAAA,IAAU,CAAA,CAAE,OAAO,KAAA,EAAO,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,WAAA,EAAa,CAAA,CAAE,MAAA,CAAO,MAAA,CAAO,QAAQ,CAAC,CAAA,EAAG,CAAA,CAAE,MAAA,CAAO,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAA,EAAG,MAAM,MAAA,CAAO,CAAA,CAAE,MAAA,CAAO,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAClK,EAAA,KAAA,MAAW,KAAK,CAAA,CAAE,IAAA,MAAU,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,CAAA,CAAA,CAAA,EAAK,CAAA,CAAE,QAAQ,CAAA,CAAE,KAAA,EAAO,MAAM,CAAA,CAAE,KAAK,CAAC,CAAC,CAAA;AAC5F,EAAA,IAAI,EAAE,aAAA,CAAc,MAAA,EAAQ,GAAA,CAAI,IAAA,CAAK,IAAI,mBAAA,EAAqB,GAAG,CAAA,CAAE,aAAA,CAAc,IAAI,CAAC,CAAA,KAAM,OAAO,CAAA,CAAE,KAAK,EAAE,CAAC,CAAA;AAC7G,EAAA,IAAI,EAAE,QAAA,CAAS,MAAA,EAAQ,GAAA,CAAI,IAAA,CAAK,IAAI,MAAA,EAAQ,GAAG,CAAA,CAAE,QAAA,CAAS,IAAI,CAAC,CAAA,KAAM,OAAO,CAAA,CAAE,KAAK,EAAE,CAAC,CAAA;AACtF,EAAA,OAAO,GAAA,CAAI,KAAK,IAAI,CAAA;AACtB;AAGO,SAAS,YAAY,IAAA,EAAwG;AAClI,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG,OAAO,EAAE,OAAA,EAAS,IAAA,EAAwB,QAAA,EAAU,EAAC,EAAG,OAAA,EAAS,IAAA,EAAK;AAC/F,EAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,KAAS,QAAA,EAAU;AACpC,IAAA,MAAM,CAAA,GAAI,IAAA;AACV,IAAA,IAAI,KAAA,CAAM,QAAQ,CAAA,CAAE,OAAO,GAAG,OAAO,EAAE,SAAS,CAAA,CAAE,OAAA,EAA2B,UAAU,KAAA,CAAM,OAAA,CAAQ,EAAE,QAAQ,CAAA,GAAK,EAAE,QAAA,GAAgC,EAAC,EAAG,OAAA,EAAS,IAAA,EAAK;AACxK,IAAA,IAAI,CAAA,CAAE,WAAA,IAAe,OAAO,CAAA,CAAE,gBAAgB,QAAA,EAAU,OAAO,EAAE,OAAA,EAAS,EAAC,EAAG,QAAA,EAAU,EAAC,EAAG,SAAS,IAAA,EAAuB;AAAA,EAC9H;AACA,EAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AACjE;;;ACzFO,SAAS,gBAAgB,OAAA,EAAiD;AAC/E,EAAA,MAAM,SAAiC,EAAC;AACxC,EAAA,KAAA,MAAW,CAAA,IAAK,OAAA,EAAS,IAAI,CAAA,CAAE,SAAA,EAAW,MAAA,CAAO,CAAA,CAAE,SAAS,CAAA,GAAA,CAAK,MAAA,CAAO,CAAA,CAAE,SAAS,KAAK,CAAA,IAAK,CAAA;AAC7F,EAAA,OAAO,MAAA;AACT;AAEO,SAAS,WAAA,CAAY,SAAyB,MAAA,EAAuC;AAC1F,EAAA,MAAM,IAAY,OAAO,MAAA,KAAW,WAAW,EAAE,GAAA,EAAK,QAAO,GAAI,MAAA;AACjE,EAAA,MAAM,QAAA,GAAW,OAAO,CAAA,CAAE,GAAG,MAAM,QAAA,GAAW,CAAA,CAAE,GAAG,CAAA,GAAI,CAAA;AACvD,EAAA,MAAM,MAAA,GAAS,gBAAgB,OAAO,CAAA;AACtC,EAAA,MAAM,aAAgC,EAAC;AACvC,EAAA,MAAM,QAA+B,EAAC;AACtC,EAAA,MAAM,KAAA,uBAAY,GAAA,CAAI,CAAC,GAAG,MAAA,CAAO,IAAA,CAAK,MAAM,CAAA,EAAG,GAAG,OAAO,IAAA,CAAK,CAAC,EAAE,MAAA,CAAO,CAAC,MAAM,CAAA,KAAM,GAAG,CAAC,CAAC,CAAA;AAC1F,EAAA,KAAA,MAAW,aAAa,KAAA,EAAO;AAC7B,IAAA,MAAM,OAAA,GAAU,OAAO,CAAA,CAAE,SAAS,MAAM,QAAA,GAAW,CAAA,CAAE,SAAS,CAAA,GAAK,QAAA;AACnE,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,SAAS,CAAA,IAAK,CAAA;AACvC,IAAA,IAAI,SAAA,GAAY,SAAS,UAAA,CAAW,IAAA,CAAK,EAAE,SAAA,EAAW,SAAA,EAAW,SAAS,CAAA;AAAA,SAAA,IACjE,SAAA,GAAY,SAAS,KAAA,CAAM,IAAA,CAAK,EAAE,SAAA,EAAW,SAAA,EAAW,SAAS,CAAA;AAAA,EAC5E;AACA,EAAA,UAAA,CAAW,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,SAAA,GAAY,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,SAAA,GAAY,CAAA,CAAE,OAAA,CAAQ,CAAA;AAC7E,EAAA,OAAO,EAAE,EAAA,EAAI,UAAA,CAAW,WAAW,CAAA,EAAG,UAAA,EAAY,OAAO,MAAA,EAAO;AAClE;AAGO,SAAS,SAAS,OAAA,EAAiC;AACxD,EAAA,OAAO,EAAE,GAAA,EAAK,CAAA,EAAG,GAAG,eAAA,CAAgB,OAAO,CAAA,EAAE;AAC/C;ACbA,IAAM,OAA+B,EAAE,OAAA,EAAS,4BAA4B,KAAA,EAAO,gCAAA,EAAkC,QAAQ,yBAAA,EAA0B;AAEhJ,SAAS,YAAA,GAA8B;AAC5C,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,aAAA,CAAc,MAAA,CAAA,IAAA,CAAY,GAAG,CAAC,CAAA;AACnD,EAAA,KAAA,MAAW,OAAO,CAAC,IAAA,CAAK,MAAM,IAAA,EAAM,OAAO,GAAG,IAAA,CAAK,IAAA,EAAM,IAAA,EAAM,WAAW,GAAG,IAAA,CAAK,IAAA,EAAM,MAAM,IAAA,EAAM,WAAW,CAAC,CAAA,EAAG;AACjH,IAAA,IAAI,UAAA,CAAW,IAAA,CAAK,GAAA,EAAK,YAAY,CAAC,CAAA,IAAK,UAAA,CAAW,IAAA,CAAK,GAAA,EAAK,UAAU,CAAC,CAAA,EAAG,OAAO,GAAA;AAAA,EACvF;AACA,EAAA,OAAO,IAAA;AACT;AAEA,IAAM,IAAA,GAAO,CAAC,GAAA,KAA8B;AAC1C,EAAA,GAAA,CAAI,SAAA,CAAU,+BAA+B,GAAG,CAAA;AAChD,EAAA,GAAA,CAAI,SAAA,CAAU,gCAAgC,oBAAoB,CAAA;AAClE,EAAA,GAAA,CAAI,SAAA,CAAU,gCAAgC,cAAc,CAAA;AAC9D,CAAA;AAEA,SAAS,QAAA,CAAS,GAAA,EAAsB,KAAA,GAAQ,CAAA,GAAI,OAAO,IAAA,EAAuB;AAChF,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,IAAI,IAAA,GAAO,CAAA;AACX,IAAA,GAAA,CAAI,EAAA,CAAG,MAAA,EAAQ,CAAC,CAAA,KAAc;AAC5B,MAAA,IAAA,IAAQ,CAAA,CAAE,MAAA;AACV,MAAA,IAAI,OAAO,KAAA,EAAO;AAChB,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,gBAAgB,CAAC,CAAA;AAClC,QAAA,GAAA,CAAI,OAAA,EAAQ;AACZ,QAAA;AAAA,MACF;AACA,MAAA,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IACf,CAAC,CAAA;AACD,IAAA,GAAA,CAAI,EAAA,CAAG,KAAA,EAAO,MAAM,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,MAAM,CAAA,CAAE,QAAA,CAAS,MAAM,CAAC,CAAC,CAAA;AACnE,IAAA,GAAA,CAAI,EAAA,CAAG,SAAS,MAAM,CAAA;AAAA,EACxB,CAAC,CAAA;AACH;AAEO,SAAS,iBAAA,CAAkB,OAAA,GAAwB,EAAC,EAAyB;AAClF,EAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,WAAA;AAC7B,EAAA,MAAM,WAAW,OAAA,CAAQ,QAAA,KAAa,MAAA,GAAY,YAAA,KAAiB,OAAA,CAAQ,QAAA;AAC3E,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,IAAA;AAC3C,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAoB;AACrC,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAoB;AACvC,EAAA,IAAI,GAAA,GAAM,EAAA;AAEV,EAAA,MAAM,IAAA,GAAO,CAAC,OAAA,EAA8B,OAAA,KAA2B;AACrE,IAAA,MAAM,IAAA,GAAO,CAAA,MAAA,EAAS,IAAA,CAAK,SAAA,CAAU,OAAO,CAAC;;AAAA,CAAA;AAC7C,IAAA,KAAA,MAAW,OAAO,OAAA,EAAS;AACzB,MAAA,IAAI;AACF,QAAA,GAAA,CAAI,MAAM,IAAI,CAAA;AAAA,MAChB,CAAA,CAAA,MAAQ;AACN,QAAA,OAAA,CAAQ,OAAO,GAAG,CAAA;AAAA,MACpB;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,CAAC,OAAA,KAA2B;AACxC,IAAA,IAAI,CAAC,OAAA,IAAW,OAAO,OAAA,KAAY,QAAA,EAAU;AAC7C,IAAA,MAAM,CAAA,GAAI,OAAA;AACV,IAAA,IAAI,CAAA,CAAE,iBAAA,KAAsB,IAAA,EAAM,IAAA,CAAK,MAAM,CAAC,CAAA;AAAA,SAAA,IACrC,CAAA,CAAE,mBAAmB,IAAA,IAAQ,CAAA,CAAE,wBAAwB,IAAA,EAAM,IAAA,CAAK,QAAQ,CAAC,CAAA;AAAA,EACtF,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAO,GAAA,EAAK,GAAA,KAAQ;AAC9C,IAAA,IAAA,CAAK,GAAG,CAAA;AACR,IAAA,MAAM,IAAI,IAAI,GAAA,CAAI,GAAA,CAAI,GAAA,IAAO,KAAK,cAAc,CAAA;AAChD,IAAA,IAAI,GAAA,CAAI,WAAW,SAAA,EAAW;AAC5B,MAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,MAAA,GAAA,CAAI,GAAA,EAAI;AACR,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAA,CAAE,QAAA,KAAa,GAAA,IAAO,GAAA,CAAI,WAAW,KAAA,EAAO;AAC9C,MAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,MAAA,GAAA,CAAI,UAAU,UAAA,EAAY,CAAA,kBAAA,EAAqB,kBAAA,CAAmB,GAAG,CAAC,CAAA,CAAE,CAAA;AACxE,MAAA,GAAA,CAAI,GAAA,EAAI;AACR,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAA,CAAE,QAAA,KAAa,SAAA,IAAa,GAAA,CAAI,WAAW,KAAA,EAAO;AACpD,MAAA,MAAM,OAAO,CAAA,CAAE,YAAA,CAAa,IAAI,MAAM,CAAA,KAAM,QAAQ,IAAA,GAAO,MAAA;AAC3D,MAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,MAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,mBAAmB,CAAA;AACjD,MAAA,GAAA,CAAI,SAAA,CAAU,iBAAiB,UAAU,CAAA;AACzC,MAAA,GAAA,CAAI,SAAA,CAAU,cAAc,YAAY,CAAA;AACxC,MAAA,GAAA,CAAI,MAAM,iBAAiB,CAAA;AAC3B,MAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,MAAA,MAAM,IAAA,GAAO,YAAY,MAAM;AAC7B,QAAA,IAAI;AACF,UAAA,GAAA,CAAI,MAAM,YAAY,CAAA;AAAA,QACxB,CAAA,CAAA,MAAQ;AAAA,QAER;AAAA,MACF,GAAG,WAAW,CAAA;AACd,MAAA,GAAA,CAAI,EAAA,CAAG,SAAS,MAAM;AACpB,QAAA,aAAA,CAAc,IAAI,CAAA;AAClB,QAAA,IAAA,CAAK,OAAO,GAAG,CAAA;AAAA,MACjB,CAAC,CAAA;AAED,MAAA,IAAI,SAAS,IAAA,EAAM;AACjB,QAAA,IAAA,CAAK,MAAA,EAAQ,EAAE,cAAA,EAAgB,IAAA,EAAM,SAAS,CAAA,EAAG,IAAA,EAAM,OAAA,EAAS,OAAA,EAAS,EAAE,IAAA,EAAM,IAAA,CAAK,IAAA,IAAQ,CAAA;AAC9F,QAAA,GAAA,CAAI,GAAG,OAAA,EAAS,MAAM,KAAK,MAAA,EAAQ,EAAE,gBAAgB,IAAA,EAAM,OAAA,EAAS,GAAG,IAAA,EAAM,OAAA,EAAS,SAAS,EAAE,IAAA,EAAM,KAAK,IAAA,EAAK,EAAG,CAAC,CAAA;AAAA,MACvH;AACA,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAA,CAAE,QAAA,KAAa,UAAA,IAAc,GAAA,CAAI,WAAW,MAAA,EAAQ;AACtD,MAAA,IAAI;AACF,QAAA,MAAM,SAAS,IAAA,CAAK,KAAA,CAAO,MAAM,QAAA,CAAS,GAAG,KAAM,MAAM,CAAA;AACzD,QAAA,KAAA,MAAW,CAAA,IAAK,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,SAAS,CAAC,MAAM,CAAA,EAAG,KAAA,CAAM,CAAC,CAAA;AAClE,QAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,QAAA,GAAA,CAAI,GAAA,EAAI;AAAA,MACV,SAAS,CAAA,EAAG;AACV,QAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,QAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,YAAY,CAAA;AAC1C,QAAA,GAAA,CAAI,GAAA,CAAI,MAAA,CAAQ,CAAA,CAAY,OAAA,IAAW,CAAC,CAAC,CAAA;AAAA,MAC3C;AACA,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAA,CAAE,QAAA,KAAa,SAAA,IAAa,GAAA,CAAI,WAAW,KAAA,EAAO;AACpD,MAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,MAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,kBAAkB,CAAA;AAChD,MAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,EAAE,EAAA,EAAI,IAAA,EAAM,IAAA,EAAM,IAAA,CAAK,IAAA,EAAM,MAAA,EAAQ,MAAA,CAAO,IAAA,EAAM,CAAC,CAAA;AAC1E,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAA,GAAO,CAAA,CAAE,QAAA,CAAS,OAAA,CAAQ,OAAO,EAAE,CAAA;AACzC,IAAA,IAAI,yBAAyB,IAAA,CAAK,IAAI,CAAA,IAAK,GAAA,CAAI,WAAW,KAAA,EAAO;AAC/D,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,QAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,YAAY,CAAA;AAC1C,QAAA,GAAA,CAAI,IAAI,8DAA8D,CAAA;AACtE,QAAA;AAAA,MACF;AACA,MAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,MAAA,GAAA,CAAI,SAAA,CAAU,cAAA,EAAgB,IAAA,CAAK,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,WAAA,CAAY,GAAG,CAAC,CAAC,CAAA,IAAK,0BAA0B,CAAA;AACnG,MAAA,GAAA,CAAI,SAAA,CAAU,iBAAiB,UAAU,CAAA;AACzC,MAAA,GAAA,CAAI,IAAI,YAAA,CAAa,IAAA,CAAK,QAAA,EAAU,IAAI,CAAC,CAAC,CAAA;AAC1C,MAAA;AAAA,IACF;AACA,IAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,IAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,YAAY,CAAA;AAC1C,IAAA,GAAA,CAAI,IAAI,WAAW,CAAA;AAAA,EACrB,CAAC,CAAA;AAED,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAA,CAAO,IAAA,CAAK,SAAS,MAAM,CAAA;AAC3B,IAAA,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,IAAA,IAAQ,IAAA,EAAM,MAAM,MAAM;AAC9C,MAAA,MAAM,IAAA,GAAO,OAAO,OAAA,EAAQ;AAC5B,MAAA,MAAM,IAAA,GAAO,OAAO,IAAA,KAAS,QAAA,IAAY,OAAO,IAAA,CAAK,IAAA,GAAO,QAAQ,IAAA,IAAQ,IAAA;AAC5E,MAAA,GAAA,GAAM,CAAA,OAAA,EAAU,SAAS,SAAA,IAAa,IAAA,KAAS,OAAO,WAAA,GAAc,IAAI,IAAI,IAAI,CAAA,CAAA;AAChF,MAAA,OAAA,CAAQ;AAAA,QACN,MAAA;AAAA,QACA,GAAA;AAAA,QACA,IAAA;AAAA,QACA,MAAA,EAAQ,OAAO,EAAE,IAAA,EAAM,KAAK,IAAA,EAAM,MAAA,EAAQ,OAAO,IAAA,EAAK,CAAA;AAAA,QACtD,KAAA,EAAO,MACL,IAAI,OAAA,CAAc,CAAC,IAAA,KAAS;AAC1B,UAAA,KAAA,MAAW,GAAA,IAAO,CAAC,GAAG,IAAA,EAAM,GAAG,MAAM,CAAA,MAAO,GAAA,EAAI;AAChD,UAAA,IAAA,CAAK,KAAA,EAAM;AACX,UAAA,MAAA,CAAO,KAAA,EAAM;AACb,UAAA,MAAA,CAAO,KAAA,CAAM,MAAM,IAAA,EAAM,CAAA;AAAA,QAC3B,CAAC;AAAA,OACJ,CAAA;AAAA,IACH,CAAC,CAAA;AAAA,EACH,CAAC,CAAA;AACH;;;ACpLA,IAAM,IAAA,GAAO,CAAC,IAAA,KAA0B,IAAA,CAAK,MAAMA,YAAAA,CAAa,IAAA,EAAM,MAAM,CAAC,CAAA;AAE7E,SAAS,UAAU,IAAA,EAA8B;AAC/C,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA;AACrC,EAAA,IAAI,MAAA,CAAO,OAAA,EAAS,OAAO,MAAA,CAAO,OAAA;AAClC,EAAA,IAAI,MAAA,CAAO,QAAA,CAAS,MAAA,IAAU,CAAC,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,OAAO,MAAA,CAAO,QAAA,CAAS,MAAA,CAAO,QAAA,CAAS,SAAS,CAAC,CAAA;AACvG,EAAA,OAAO,gBAAA,CAAiB,MAAA,CAAO,OAAA,EAAS,EAAE,MAAM,IAAA,CAAK,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA,CAAE,OAAA,CAAQ,SAAA,EAAW,EAAE,GAAG,CAAA;AACvG;AAGA,eAAe,aAAa,IAAA,EAAiC;AAC3D,EAAA,MAAM,IAAA,GAAO,CAAC,IAAA,KAAgC;AAC5C,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAC3B,IAAA,OAAO,KAAK,CAAA,GAAK,IAAA,CAAK,CAAA,GAAI,CAAC,KAAK,EAAA,GAAM,IAAA;AAAA,EACxC,CAAA;AACA,EAAA,MAAM,QAAQ,MAAM,iBAAA,CAAkB,EAAE,IAAA,EAAM,OAAO,IAAA,CAAK,QAAQ,CAAA,IAAK,IAAI,GAAG,IAAA,EAAM,IAAA,CAAK,QAAQ,CAAA,IAAK,aAAa,CAAA;AACnH,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,KAAA,CAAM,GAAG,CAAA,CAAA,CAAG,CAAA;AAChD,EAAA,OAAA,CAAQ,IAAI,CAAA,6CAAA,EAAgD,KAAA,CAAM,GAAG,CAAA,4CAAA,EAA+C,KAAA,CAAM,GAAG,CAAA,CAAA,CAAG,CAAA;AAChI,EAAA,OAAA,CAAQ,IAAI,iBAAiB,CAAA;AAC7B,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC9B,IAAA,MAAM,IAAA,GAAO,MAAY,KAAK,KAAA,CAAM,KAAA,GAAQ,IAAA,CAAK,MAAM,OAAA,CAAQ,CAAC,CAAC,CAAA;AACjE,IAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,IAAI,CAAA;AAC3B,IAAA,OAAA,CAAQ,IAAA,CAAK,WAAW,IAAI,CAAA;AAC5B,IAAA,KAAA,CAAM,OAAO,IAAA,CAAK,OAAA,EAAS,MAAM,OAAA,CAAQ,CAAC,CAAC,CAAA;AAAA,EAC7C,CAAC,CAAA;AACH;AAEO,SAAS,KAAK,IAAA,EAA0C;AAC7D,EAAA,MAAM,CAAC,GAAA,EAAK,GAAG,IAAI,CAAA,GAAI,IAAA;AACvB,EAAA,IAAI,GAAA,KAAQ,OAAA,EAAS,OAAO,YAAA,CAAa,IAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,MAAc,OAAA,CAAQ,KAAA,CAAM,CAAA,CAAE,OAAO,GAAG,CAAA,CAAE,CAAA;AAChG,EAAA,MAAM,IAAA,GAAO,CAAC,IAAA,KAAgC;AAC5C,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAA;AAC3B,IAAA,OAAO,KAAK,CAAA,GAAK,IAAA,CAAK,CAAA,GAAI,CAAC,KAAK,EAAA,GAAM,IAAA;AAAA,EACxC,CAAA;AACA,EAAA,MAAM,UAAA,GAAa,IAAA,CAAK,MAAA,CAAO,CAAC,CAAA,EAAG,MAAM,CAAC,CAAA,CAAE,UAAA,CAAW,IAAI,CAAA,IAAK,EAAE,IAAI,CAAA,IAAK,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA,CAAG,UAAA,CAAW,IAAI,CAAA,IAAK,IAAA,CAAK,CAAA,GAAI,CAAC,CAAA,KAAM,QAAA,CAAS,CAAA;AACrI,EAAA,IAAI;AACF,IAAA,QAAQ,GAAA;AAAK,MACX,KAAK,OAAA,EAAS;AACZ,QAAA,MAAM,IAAA,GAAO,WAAW,CAAC,CAAA;AACzB,QAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,0CAA0C,CAAA;AACrE,QAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA;AAC1C,QAAA,OAAA,CAAQ,GAAA,CAAI,YAAY,OAAA,EAAS,MAAA,CAAO,KAAK,SAAS,CAAA,IAAK,EAAE,CAAC,CAAC,CAAA;AAC/D,QAAA,OAAO,CAAA;AAAA,MACT;AAAA,MACA,KAAK,SAAA,EAAW;AACd,QAAA,MAAM,IAAA,GAAO,WAAW,CAAC,CAAA;AACzB,QAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,iEAAiE,CAAA;AAC5F,QAAA,MAAM,OAAA,GAAU,UAAU,IAAI,CAAA;AAC9B,QAAA,MAAM,GAAA,GAAM,KAAK,OAAO,CAAA;AACxB,QAAA,IAAI,GAAA,EAAK;AACP,UAAA,aAAA,CAAc,KAAK,IAAA,CAAK,SAAA,CAAU,SAAS,IAAA,EAAM,CAAC,IAAI,IAAI,CAAA;AAC1D,UAAA,OAAA,CAAQ,GAAA,CAAI,GAAG,GAAG,CAAA,EAAA,EAAK,QAAQ,KAAK,CAAA,UAAA,EAAa,OAAA,CAAQ,SAAS,CAAA,UAAA,CAAY,CAAA;AAAA,QAChF,CAAA,cAAe,GAAA,CAAI,IAAA,CAAK,UAAU,OAAA,EAAS,IAAA,EAAM,CAAC,CAAC,CAAA;AACnD,QAAA,OAAO,CAAA;AAAA,MACT;AAAA,MACA,KAAK,SAAA,EAAW;AACd,QAAA,MAAM,CAAC,CAAA,EAAG,CAAC,CAAA,GAAI,UAAA;AACf,QAAA,IAAI,CAAC,CAAA,IAAK,CAAC,GAAG,MAAM,IAAI,MAAM,yDAAyD,CAAA;AACvF,QAAA,MAAM,MAAM,gBAAA,CAAiB,SAAA,CAAU,CAAC,CAAA,EAAG,SAAA,CAAU,CAAC,CAAC,CAAA;AACvD,QAAA,OAAA,CAAQ,GAAA,CAAI,gBAAA,CAAiB,GAAG,CAAC,CAAA;AACjC,QAAA,IAAI,GAAA,CAAI,YAAY,MAAA,EAAQ;AAC1B,UAAA,OAAA,CAAQ,KAAA,CAAM;AAAA,EAAK,GAAA,CAAI,WAAA,CAAY,MAAM,CAAA,UAAA,EAAa,GAAA,CAAI,YAAY,MAAA,KAAW,CAAA,GAAI,EAAA,GAAK,GAAG,CAAA,WAAA,CAAa,CAAA;AAC1G,UAAA,OAAO,CAAA;AAAA,QACT;AACA,QAAA,OAAO,CAAA;AAAA,MACT;AAAA,MACA,KAAK,QAAA,EAAU;AACb,QAAA,MAAM,IAAA,GAAO,WAAW,CAAC,CAAA;AACzB,QAAA,IAAI,CAAC,IAAA,EAAM,MAAM,IAAI,MAAM,kEAAkE,CAAA;AAC7F,QAAA,MAAM,EAAE,OAAA,EAAQ,GAAI,WAAA,CAAY,IAAA,CAAK,IAAI,CAAC,CAAA;AAC1C,QAAA,IAAI,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,EAAG;AAC3B,UAAA,OAAA,CAAQ,GAAA,CAAI,KAAK,SAAA,CAAU,QAAA,CAAS,OAAO,CAAA,EAAG,IAAA,EAAM,CAAC,CAAC,CAAA;AACtD,UAAA,OAAO,CAAA;AAAA,QACT;AACA,QAAA,MAAM,UAAA,GAAa,WAAW,CAAC,CAAA;AAC/B,QAAA,IAAI,CAAC,UAAA,EAAY,MAAM,IAAI,MAAM,yDAAyD,CAAA;AAC1F,QAAA,MAAM,MAAA,GAAS,WAAA,CAAY,OAAA,EAAS,IAAA,CAAK,UAAU,CAAW,CAAA;AAC9D,QAAA,IAAI,OAAO,EAAA,EAAI;AACb,UAAA,OAAA,CAAQ,GAAA,CAAI,kBAAkB,MAAA,CAAO,IAAA,CAAK,OAAO,MAAM,CAAA,CAAE,MAAM,CAAA,uCAAA,CAAyC,CAAA;AACxG,UAAA,OAAO,CAAA;AAAA,QACT;AACA,QAAA,KAAA,MAAW,CAAA,IAAK,MAAA,CAAO,UAAA,EAAY,OAAA,CAAQ,MAAM,CAAA,CAAA,EAAI,CAAA,CAAE,SAAS,CAAA,GAAA,EAAM,CAAA,CAAE,SAAS,CAAA,mBAAA,EAAsB,CAAA,CAAE,OAAO,CAAA,CAAA,CAAG,CAAA;AACnH,QAAA,OAAA,CAAQ,KAAA,CAAM,IAAA,GAAO,WAAA,CAAY,OAAO,CAAC,CAAA;AACzC,QAAA,OAAO,CAAA;AAAA,MACT;AAAA,MACA;AACE,QAAA,OAAA,CAAQ,MAAM,+DAA+D,CAAA;AAC7E,QAAA,OAAO,MAAM,CAAA,GAAI,CAAA;AAAA;AACrB,EACF,SAAS,CAAA,EAAG;AACV,IAAA,OAAA,CAAQ,KAAA,CAAO,EAAY,OAAO,CAAA;AAClC,IAAA,OAAO,CAAA;AAAA,EACT;AACF;AAEA,IAAM,MAAA,GAAS,OAAO,OAAA,KAAY,WAAA,IAAe,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,CAAA,IAAK,qCAAqC,IAAA,CAAK,OAAA,CAAQ,IAAA,CAAK,CAAC,KAAK,EAAE,CAAA;AAC/I,IAAI,QAAQ,KAAK,OAAA,CAAQ,QAAQ,IAAA,CAAK,OAAA,CAAQ,KAAK,KAAA,CAAM,CAAC,CAAC,CAAC,EAAE,IAAA,CAAK,CAAC,SAAS,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAC,CAAA","file":"cli.js","sourcesContent":["import type { Change, CommitCause, CommitPriority, HookChange, HookSnapshot, Options, ParentInfo, RenderReport, RenderTrigger, SourceLocation } from './types';\n\nconst now = (): number =>\n typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now();\n\nexport interface BuildInput {\n component: string;\n instanceId?: number;\n renderCount: number;\n prevProps: Record<string, unknown>;\n nextProps: Record<string, unknown>;\n propChanges: Change[];\n stateChanges?: Change[];\n hookChanges?: HookChange[];\n hookState?: HookSnapshot[];\n contexts?: { name: string; value: unknown }[];\n state?: Record<string, unknown>;\n parent?: ParentInfo | null;\n owner?: string | null;\n path?: string[];\n /** Default true: `useWhyRerender` and the console output assume the component decides on props alone. */\n memoized?: boolean;\n selfDuration?: number;\n treeDuration?: number;\n commitId?: number;\n commitPriority?: CommitPriority;\n source?: SourceLocation;\n updaters?: string[];\n commitCause?: CommitCause;\n afterCommit?: number;\n key?: string | null;\n}\n\n/** `useCart › useState#0` when custom hook names are known, else `useState #0`. */\nexport const hookLabel = (c: { hook: string; index: number; custom?: string[] }): string =>\n c.custom && c.custom.length ? `${c.custom.join(' › ')} › ${c.hook}#${c.index}` : `${c.hook} #${c.index}`;\n\n/** Store-specific advice for a `useSyncExternalStore` snapshot that is a new reference with equal contents. */\nexport function storeAdvice(custom: string[] | undefined): string {\n const chain = custom ?? [];\n if (chain.some((n) => /^useSelector$/.test(n)))\n return 'the Redux selector returns a new object on every call: return a stored slice, pass shallowEqual as the equality function, or memoize it with createSelector';\n if (chain.some((n) => /^useAppSelector$/.test(n)))\n return 'the selector returns a new object on every call: return a stored slice, pass shallowEqual, or memoize it with createSelector';\n if (chain.some((n) => /^use[A-Z]\\w*Store$/.test(n) || n === 'useStore' || n === 'useBoundStore'))\n return 'the store selector returns a new object on every call: select a primitive, or wrap the selector with useShallow (Zustand) / an equality function';\n return 'getSnapshot returns a new reference with the same contents: cache the snapshot in the store and return the same object while the data is unchanged';\n}\n\nconst isGenuine = (c: Change): boolean => c.kind === 'different' || c.kind === 'added' || c.kind === 'removed';\n\n/** `children` re-created in the parent's render: the classic memo-defeater, with its own advice. */\nconst isChildren = (change: Change): boolean => change.path === 'children';\n\nexport function fixFor(change: Change): string {\n if (isChildren(change) && (change.kind === 'element' || change.kind === 'deep-equal')) {\n return `lift the children out of the parent's render: memoize them with useMemo, hoist static elements to module scope, or render them from a component that does not re-render`;\n }\n switch (change.kind) {\n case 'function':\n return `wrap it in useCallback (or hoist it out of the parent's render)`;\n case 'element':\n return `memoize the element with useMemo, or pass it as children from a stable parent`;\n case 'deep-equal':\n return Array.isArray(change.next)\n ? `memoize the array with useMemo (same items, new reference)`\n : `memoize the object with useMemo, or hoist it to module scope if it is constant`;\n default:\n return '';\n }\n}\n\nfunction describe(change: Change): string {\n if (isChildren(change) && (change.kind === 'element' || change.kind === 'deep-equal')) {\n return `children are new React elements with the same types and props on every render of the parent`;\n }\n switch (change.kind) {\n case 'deep-equal':\n return `prop \"${change.path}\" is a new reference but deep-equal to the previous value`;\n case 'function':\n return `prop \"${change.path}\" is a new function instance on every render`;\n case 'element':\n return `prop \"${change.path}\" is a new React element with the same type and props`;\n case 'different':\n return `prop \"${change.path}\" changed`;\n case 'added':\n return `prop \"${change.path}\" was added`;\n case 'removed':\n return `prop \"${change.path}\" was removed`;\n }\n}\n\nexport function buildReport(input: BuildInput): RenderReport {\n const stateChanges = input.stateChanges ?? [];\n const hookChanges = input.hookChanges ?? [];\n const isStateHook = (h: HookChange): boolean => h.hook === 'useState' || h.hook === 'useReducer';\n const genuineProps = input.propChanges.some(isGenuine);\n const genuineState = stateChanges.some(isGenuine) || hookChanges.some((h) => isStateHook(h) && isGenuine(h));\n const genuineHooks = hookChanges.some((h) => !isStateHook(h) && isGenuine(h));\n const causes = [genuineProps && 'props', genuineState && 'state', genuineHooks && 'hooks'].filter(Boolean) as RenderTrigger[];\n\n let trigger: RenderTrigger;\n if (causes.length === 0) trigger = 'parent';\n else if (causes.length === 1) trigger = causes[0]!;\n else trigger = 'mixed';\n\n const avoidable = trigger === 'parent';\n const reasons: string[] = [];\n\n if (avoidable && input.propChanges.length === 0 && stateChanges.length === 0 && hookChanges.length === 0) {\n const who = input.parent ? `<${input.parent.name}> re-rendered (${describeTrigger(input.parent.trigger)})` : 'its parent re-rendered';\n reasons.push(`re-rendered with identical props because ${who}. Wrap \"${input.component}\" in React.memo (or extend PureComponent).`);\n } else if (avoidable && input.parent) {\n reasons.push(`caused by <${input.parent.name}> re-rendering (${describeTrigger(input.parent.trigger)}).`);\n }\n for (const c of input.propChanges) {\n const fix = fixFor(c);\n reasons.push(fix ? `${describe(c)}: ${fix}.` : `${describe(c)}.`);\n }\n const memoized = input.memoized !== false;\n if (avoidable && !memoized && input.propChanges.length > 0) {\n reasons.push(\n `\"${input.component}\" is not memoized, so fixing the props alone will not stop this re-render: also wrap it in React.memo (or extend PureComponent).`,\n );\n }\n for (const c of stateChanges) {\n if (isGenuine(c)) reasons.push(`state \"${c.path}\" changed.`);\n else reasons.push(`setState was called with a value deep-equal to the current \"${c.path}\" (new reference, same contents).`);\n }\n for (const c of hookChanges) {\n if (c.hook === 'useContext' && isGenuine(c)) {\n const where = c.provider && c.provider.component ? ` (provided by <${c.provider.component}>)` : '';\n const keys =\n c.changedKeys && typeof c.totalKeys === 'number' && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys\n ? `: only ${c.changedKeys.map((k) => `\"${k}\"`).join(', ')} of ${c.totalKeys} keys changed, yet every consumer re-renders. Split the context or memoize the slices consumers read`\n : '';\n reasons.push(`${c.path} changed${where}${keys}.`);\n } else if (isGenuine(c)) reasons.push(`${hookLabel(c)} changed.`);\n else if (isStateHook(c))\n reasons.push(`${hookLabel(c)} was set to a value deep-equal to the current one (new reference, same contents): reuse the existing object or bail out before calling the setter.`);\n else if (c.hook === 'useSyncExternalStore') reasons.push(`${hookLabel(c)} returned a new reference that is deep-equal to the previous value: ${storeAdvice(c.custom)}.`);\n else reasons.push(`${hookLabel(c)} returned a new reference that is deep-equal to the previous value: memoize the context/store value where it is produced.`);\n }\n if (input.commitCause === 'effect-after-commit') {\n const who = input.updaters && input.updaters.length ? input.updaters.map((u) => `<${u}>`).join(', ') : 'a component that rendered in it';\n reasons.push(`this commit was scheduled right after commit #${input.afterCommit ?? '?'} by ${who}: an effect there set state (effect → setState loop). Derive the value during render or compute it before setting state.`);\n } else if (input.commitCause === 'suspense-resolved') {\n reasons.push('this commit shows content that was suspended (a Suspense boundary resolved).');\n }\n\n const report: RenderReport = {\n component: input.component,\n instanceId: input.instanceId ?? 0,\n commitId: input.commitId ?? 0,\n renderCount: input.renderCount,\n trigger,\n avoidable,\n memoized,\n props: { prev: input.prevProps, next: input.nextProps },\n propChanges: input.propChanges,\n stateChanges,\n hookChanges,\n parent: input.parent ?? null,\n owner: input.owner ?? null,\n path: input.path ?? [],\n reasons,\n time: now(),\n };\n if (input.selfDuration !== undefined) report.selfDuration = input.selfDuration;\n if (input.treeDuration !== undefined) report.treeDuration = input.treeDuration;\n if (input.commitPriority) report.commitPriority = input.commitPriority;\n if (input.source) report.source = input.source;\n if (input.hookState) report.hookState = input.hookState;\n if (input.contexts) report.contexts = input.contexts;\n if (input.state) report.state = input.state;\n if (input.updaters && input.updaters.length) report.updaters = input.updaters;\n if (input.commitCause) report.commitCause = input.commitCause;\n if (input.afterCommit !== undefined) report.afterCommit = input.afterCommit;\n if (input.key !== undefined) report.key = input.key;\n return report;\n}\n\nfunction describeTrigger(t: RenderTrigger): string {\n switch (t) {\n case 'props':\n return 'its props changed';\n case 'state':\n return 'its state changed';\n case 'hooks':\n return 'a context or store it reads changed';\n case 'mixed':\n return 'its props and state changed';\n default:\n return 'its own parent re-rendered';\n }\n}\n\nconst KIND_LABEL: Record<Change['kind'], string> = {\n 'deep-equal': 'equal by value',\n function: 'new function',\n element: 'equal element',\n different: 'changed',\n added: 'added',\n removed: 'removed',\n};\n\nexport function summarize(report: RenderReport): string {\n const counts = new Map<string, number>();\n for (const c of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {\n counts.set(c.kind, (counts.get(c.kind) ?? 0) + 1);\n }\n const parts = [...counts].map(([kind, n]) => `${n} ${KIND_LABEL[kind as Change['kind']]}`);\n if (parts.length === 0) parts.push('no changes');\n return parts.join(', ');\n}\n\n/** Default notifier: prints a console group per report. */\nexport function printReport(report: RenderReport, options: Options): void {\n const c = options.console ?? console;\n const open = options.collapse === false ? c.group : c.groupCollapsed;\n const verdict = report.avoidable ? 'avoidable re-render' : `re-render (${report.trigger})`;\n open.call(c, `[rerender-lens] <${report.component}> ${verdict}: ${summarize(report)}`);\n for (const r of report.reasons) c.log(`- ${r}`);\n if (report.path.length) c.log(`at ${[...report.path, report.component].join(' > ')}`);\n for (const ch of [...report.propChanges, ...report.stateChanges, ...report.hookChanges]) {\n c.log(`${ch.path} (${KIND_LABEL[ch.kind]})`, { prev: ch.prev, next: ch.next });\n }\n c.log('props', report.props);\n if (report.hookState && report.hookState.length) c.log('hooks', Object.fromEntries(report.hookState.map((h) => [h.path, h.value])));\n if (report.contexts && report.contexts.length) c.log('contexts', Object.fromEntries(report.contexts.map((x) => [x.name, x.value])));\n if (report.state) c.log('state', report.state);\n c.groupEnd();\n}\n","/**\n * Concrete fixes derived from reports, ranked by how many avoidable re-renders each one removes.\n * The same logic drives the extension's Fixes view; here it is typed on `RenderReport` for tests,\n * CI budgets and the CLI. Pure.\n */\nimport type { Change, HookChange, RenderReport } from './types';\nimport { storeAdvice } from './report';\n\nexport type FixKind = 'memo' | 'useCallback' | 'useMemo' | 'useMemoElement' | 'children' | 'contextValue' | 'splitContext' | 'storeSnapshot' | 'bailout';\n\nexport interface Fix {\n kind: FixKind;\n /** Where the change goes (the component that must be edited, or `<Ctx>.Provider`). */\n owner: string;\n /** The component whose re-render this removes. */\n target: string;\n prop: string | null;\n label: string;\n detail: string;\n}\n\nexport interface RankedFix extends Fix {\n key: string;\n /** Avoidable re-renders this fix removes. */\n count: number;\n components: Record<string, number>;\n}\n\nconst AVOIDABLE = new Set<string>(['deep-equal', 'function', 'element']);\nconst rootOf = (path: string): string => path.split(/[.[]/)[0] || path;\n\nexport function fixesFor(r: RenderReport): Fix[] {\n const out: Fix[] = [];\n const owner = r.owner || (r.parent && r.parent.name) || '?';\n const changes: Change[] = [...r.propChanges, ...r.stateChanges, ...r.hookChanges];\n if (r.avoidable && (changes.length === 0 || r.memoized === false)) {\n out.push({\n kind: 'memo',\n owner: r.component,\n target: r.component,\n prop: null,\n label: `Wrap <${r.component}> in React.memo`,\n detail: changes.length === 0 ? `<${r.component}> re-rendered with identical props because <${(r.parent && r.parent.name) || 'its parent'}> re-rendered.` : `<${r.component}> is not memoized: fixing its props alone will not stop the re-render.`,\n });\n }\n for (const c of r.propChanges) {\n if (!AVOIDABLE.has(c.kind)) continue;\n const root = rootOf(c.path);\n if (root === 'children' && (c.kind === 'element' || c.kind === 'deep-equal')) {\n out.push({ kind: 'children', owner, target: r.component, prop: 'children', label: `memoize children of <${r.component}> in <${owner}>`, detail: `<${owner}> re-creates the children of <${r.component}> on every render.` });\n } else if (c.kind === 'function') {\n out.push({ kind: 'useCallback', owner, target: r.component, prop: root, label: `useCallback(${root}) in <${owner}>`, detail: `prop \"${c.path}\" of <${r.component}> is a new function on every render of <${owner}>.` });\n } else if (c.kind === 'element') {\n out.push({ kind: 'useMemoElement', owner, target: r.component, prop: root, label: `memoize element prop ${root} in <${owner}>`, detail: `prop \"${c.path}\" of <${r.component}> is a new element with the same type and props on every render of <${owner}>.` });\n } else {\n out.push({ kind: 'useMemo', owner, target: r.component, prop: root, label: `useMemo(${root}) in <${owner}>`, detail: `prop \"${c.path}\" of <${r.component}> is a new ${Array.isArray(c.next) ? 'array' : 'object'} with the same contents on every render of <${owner}>.` });\n }\n }\n for (const c of [...r.stateChanges, ...r.hookChanges] as (Change & Partial<HookChange>)[]) {\n const isContext = c.hook === 'useContext';\n const ctxName = isContext ? (/useContext\\((.*)\\)/.exec(c.path) || [])[1] || 'Context' : '';\n const providerOwner = isContext && c.provider && c.provider.component ? c.provider.component : null;\n if (isContext && c.kind === 'different' && c.changedKeys && typeof c.totalKeys === 'number' && c.changedKeys.length > 0 && c.changedKeys.length < c.totalKeys) {\n out.push({\n kind: 'splitContext',\n owner: providerOwner || `${ctxName}.Provider`,\n target: r.component,\n prop: ctxName,\n label: `split ${ctxName}${providerOwner ? ` in <${providerOwner}>` : ''}: only ${c.changedKeys.join(', ')} changed`,\n detail: `${c.changedKeys.length} of ${c.totalKeys} keys changed in ${ctxName}, yet every consumer re-rendered.`,\n });\n continue;\n }\n if (!AVOIDABLE.has(c.kind)) continue;\n if (isContext) {\n out.push({ kind: 'contextValue', owner: providerOwner || `${ctxName}.Provider`, target: r.component, prop: ctxName, label: `memoize the ${ctxName} provider value${providerOwner ? ` in <${providerOwner}>` : ''}`, detail: `${ctxName} produced a new value that is deep-equal to the previous one.` });\n } else if (c.hook === 'useSyncExternalStore') {\n const chain = c.custom || [];\n const redux = chain.some((n) => /^use(App)?Selector$/.test(n));\n const zustand = !redux && chain.some((n) => /^use[A-Z]\\w*Store$/.test(n) || n === 'useStore' || n === 'useBoundStore');\n out.push({ kind: 'storeSnapshot', owner: r.component, target: r.component, prop: c.path, label: redux ? `memoize the selector in <${r.component}>` : zustand ? `useShallow in <${r.component}>` : `stable getSnapshot in <${r.component}>`, detail: storeAdvice(c.custom) });\n } else {\n out.push({ kind: 'bailout', owner: r.component, target: r.component, prop: c.path, label: `bail out before setting ${c.path} in <${r.component}>`, detail: `${c.path} was set to a value deep-equal to the current one (new reference).` });\n }\n }\n return out;\n}\n\nexport const fixKey = (f: Fix): string => `${f.kind}|${f.owner}|${f.prop || f.target}`;\n\n/** Every fix across `reports`, most re-renders removed first. */\nexport function rankFixes(reports: RenderReport[]): RankedFix[] {\n const byKey = new Map<string, RankedFix>();\n for (const r of reports) {\n if (!r.avoidable) continue;\n for (const f of fixesFor(r)) {\n const key = fixKey(f);\n let agg = byKey.get(key);\n if (!agg) {\n agg = { ...f, key, count: 0, components: {} };\n byKey.set(key, agg);\n }\n agg.count++;\n agg.components[r.component] = (agg.components[r.component] || 0) + 1;\n }\n }\n return [...byKey.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label));\n}\n\n/** Multi-line text for a test failure or a CI log. */\nexport function formatFixes(reports: RenderReport[], limit = 10): string {\n const ranked = rankFixes(reports);\n if (!ranked.length) return 'No avoidable re-renders.';\n const lines = ranked.slice(0, limit).map((f, i) => `${String(i + 1).padStart(2)}. ${f.label} (removes ${f.count}: ${Object.entries(f.components).map(([c, n]) => `<${c}>${n > 1 ? ' x' + n : ''}`).join(', ')})`);\n if (ranked.length > limit) lines.push(` … ${ranked.length - limit} more`);\n return lines.join('\\n');\n}\n","/**\n * Session summaries and before/after comparison, the same shape the extension records and exports,\n * so a session file committed next to the code can be compared in CI or with the CLI. Pure.\n */\nimport type { RenderReport } from './types';\nimport { rankFixes } from './fixes';\n\nexport interface SessionSummary {\n id: string;\n name: string;\n startedAt: number;\n endedAt: number | null;\n total: number;\n avoidable: number;\n wasted: number;\n byComponent: Record<string, { total: number; avoidable: number; wasted: number }>;\n fixes: { key: string; label: string; count: number }[];\n}\n\nexport interface CompareRow {\n component: string;\n before: number;\n after: number;\n delta: number;\n}\n\nexport interface Comparison {\n before: SessionSummary;\n after: SessionSummary;\n rows: CompareRow[];\n total: { before: number; after: number; delta: number };\n avoidable: { before: number; after: number; delta: number };\n wasted: { before: number; after: number; delta: number };\n resolvedFixes: SessionSummary['fixes'];\n newFixes: SessionSummary['fixes'];\n /** Components whose avoidable re-renders went up. */\n regressions: CompareRow[];\n}\n\nexport function summarizeReports(reports: RenderReport[], meta: Partial<Pick<SessionSummary, 'id' | 'name' | 'startedAt' | 'endedAt'>> = {}): SessionSummary {\n const byComponent: SessionSummary['byComponent'] = {};\n let avoidable = 0;\n let wasted = 0;\n for (const r of reports) {\n const c = (byComponent[r.component] ||= { total: 0, avoidable: 0, wasted: 0 });\n c.total++;\n if (r.avoidable) {\n c.avoidable++;\n avoidable++;\n if (typeof r.selfDuration === 'number') {\n c.wasted += r.selfDuration;\n wasted += r.selfDuration;\n }\n }\n }\n return {\n id: meta.id ?? `s${Date.now().toString(36)}`,\n name: meta.name ?? 'session',\n startedAt: meta.startedAt ?? (reports[0]?.time ?? 0),\n endedAt: meta.endedAt ?? (reports[reports.length - 1]?.time ?? null),\n total: reports.length,\n avoidable,\n wasted,\n byComponent,\n fixes: rankFixes(reports).map((f) => ({ key: f.key, label: f.label, count: f.count })),\n };\n}\n\nexport function compareSummaries(before: SessionSummary, after: SessionSummary): Comparison {\n const names = new Set([...Object.keys(before.byComponent), ...Object.keys(after.byComponent)]);\n const rows: CompareRow[] = [];\n for (const component of names) {\n const b = before.byComponent[component]?.avoidable || 0;\n const a = after.byComponent[component]?.avoidable || 0;\n if (b || a) rows.push({ component, before: b, after: a, delta: a - b });\n }\n rows.sort((x, y) => x.delta - y.delta || y.before - x.before || x.component.localeCompare(y.component));\n const afterKeys = new Set(after.fixes.map((f) => f.key));\n const beforeKeys = new Set(before.fixes.map((f) => f.key));\n return {\n before,\n after,\n rows,\n total: { before: before.total, after: after.total, delta: after.total - before.total },\n avoidable: { before: before.avoidable, after: after.avoidable, delta: after.avoidable - before.avoidable },\n wasted: { before: before.wasted, after: after.wasted, delta: after.wasted - before.wasted },\n resolvedFixes: before.fixes.filter((f) => !afterKeys.has(f.key)),\n newFixes: after.fixes.filter((f) => !beforeKeys.has(f.key)),\n regressions: rows.filter((r) => r.delta > 0),\n };\n}\n\n/** Plain-text table of a comparison (CLI, CI logs). */\nexport function formatComparison(c: Comparison): string {\n const w = Math.max(14, ...c.rows.map((r) => r.component.length + 2));\n const line = (label: string, b: string | number, a: string | number, d: string): string => `${label.padEnd(w)} ${String(b).padStart(8)} ${String(a).padStart(8)} ${d.padStart(8)}`;\n const delta = (n: number, suffix = ''): string => (n === 0 ? '±0' : `${n > 0 ? '+' : ''}${Number.isInteger(n) ? n : n.toFixed(1)}${suffix}`);\n const out = [line('avoidable', c.before.name.slice(0, 8), c.after.name.slice(0, 8), 'Δ'), line('all components', c.avoidable.before, c.avoidable.after, delta(c.avoidable.delta))];\n if (c.wasted.before || c.wasted.after) out.push(line('wasted ms', c.wasted.before.toFixed(1), c.wasted.after.toFixed(1), delta(Number(c.wasted.delta.toFixed(1)))));\n for (const r of c.rows) out.push(line(`<${r.component}>`, r.before, r.after, delta(r.delta)));\n if (c.resolvedFixes.length) out.push('', 'no longer needed:', ...c.resolvedFixes.map((f) => ` - ${f.label}`));\n if (c.newFixes.length) out.push('', 'new:', ...c.newFixes.map((f) => ` - ${f.label}`));\n return out.join('\\n');\n}\n\n/** Reports from a panel export (`{ rerenderLens: true, reports }`), a bare array, or a session summary file. */\nexport function parseExport(data: unknown): { reports: RenderReport[]; sessions: SessionSummary[]; summary: SessionSummary | null } {\n if (Array.isArray(data)) return { reports: data as RenderReport[], sessions: [], summary: null };\n if (data && typeof data === 'object') {\n const d = data as { reports?: unknown; sessions?: unknown; byComponent?: unknown };\n if (Array.isArray(d.reports)) return { reports: d.reports as RenderReport[], sessions: Array.isArray(d.sessions) ? (d.sessions as SessionSummary[]) : [], summary: null };\n if (d.byComponent && typeof d.byComponent === 'object') return { reports: [], sessions: [], summary: data as SessionSummary };\n }\n throw new Error('not a rerender-lens export or session summary');\n}\n","/**\n * Re-render budgets for CI: a JSON baseline of allowed avoidable re-renders per component, checked\n * against a collector's reports (like a bundle-size budget). Pure; reading and writing the file is\n * up to the test runner (`fs`), so this works in Node and in the browser.\n */\nimport type { RenderReport } from './types';\nimport { formatFixes } from './fixes';\n\n/** `{ \"*\": 0, \"ProductRow\": 3 }`: max avoidable re-renders per component; `*` is the default. */\nexport type Budget = Record<string, number>;\n\nexport interface BudgetViolation {\n component: string;\n avoidable: number;\n allowed: number;\n}\n\nexport interface BudgetResult {\n ok: boolean;\n violations: BudgetViolation[];\n /** Components with headroom left, so budgets can be tightened. */\n slack: { component: string; avoidable: number; allowed: number }[];\n counts: Record<string, number>;\n}\n\nexport function avoidableCounts(reports: RenderReport[]): Record<string, number> {\n const counts: Record<string, number> = {};\n for (const r of reports) if (r.avoidable) counts[r.component] = (counts[r.component] || 0) + 1;\n return counts;\n}\n\nexport function checkBudget(reports: RenderReport[], budget: Budget | number): BudgetResult {\n const b: Budget = typeof budget === 'number' ? { '*': budget } : budget;\n const fallback = typeof b['*'] === 'number' ? b['*'] : 0;\n const counts = avoidableCounts(reports);\n const violations: BudgetViolation[] = [];\n const slack: BudgetResult['slack'] = [];\n const names = new Set([...Object.keys(counts), ...Object.keys(b).filter((k) => k !== '*')]);\n for (const component of names) {\n const allowed = typeof b[component] === 'number' ? b[component]! : fallback;\n const avoidable = counts[component] || 0;\n if (avoidable > allowed) violations.push({ component, avoidable, allowed });\n else if (avoidable < allowed) slack.push({ component, avoidable, allowed });\n }\n violations.sort((x, y) => y.avoidable - y.allowed - (x.avoidable - x.allowed));\n return { ok: violations.length === 0, violations, slack, counts };\n}\n\n/** A budget that exactly matches the current counts (the baseline to commit). */\nexport function toBudget(reports: RenderReport[]): Budget {\n return { '*': 0, ...avoidableCounts(reports) };\n}\n\n/** Throws with the violations and the ranked fixes; use it from a test. */\nexport function assertWithinBudget(reports: RenderReport[], budget: Budget | number): BudgetResult {\n const result = checkBudget(reports, budget);\n if (result.ok) return result;\n const lines = result.violations.map((v) => ` <${v.component}>: ${v.avoidable} avoidable re-render${v.avoidable === 1 ? '' : 's'} (budget ${v.allowed})`);\n throw new Error(`Re-render budget exceeded:\\n${lines.join('\\n')}\\n\\nFixes, most impact first:\\n${formatFixes(reports)}`);\n}\n","/**\n * A tiny local relay (`npx rerender-lens panel`): serves the panel and forwards messages between\n * app pages and panel pages over Server-Sent Events + POST, so any app (Next.js, Webpack, a\n * production-like build) gets the panel without the extension and without a same-origin channel.\n *\n * GET / redirect to /panel.html?relay=<this server>\n * GET /panel.(html|js|css) the panel page\n * GET /events?role=app SSE: commands from panels\n * GET /events?role=panel SSE: hello / report / clear from apps, replies to commands\n * POST /message one message or an array; `{ __rerenderLens }` and replies go to\n * panels, `{ __rerenderLensCmd }` goes to apps\n *\n * Dependency-free (node:http). CORS is open: the relay is a local dev tool, bind it to 127.0.0.1.\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface RelayOptions {\n port?: number;\n host?: string;\n /** Directory with panel.html/js/css; found automatically in the package. */\n panelDir?: string | null;\n /** Milliseconds between SSE keep-alive comments. Default 15000. */\n keepAliveMs?: number;\n}\n\nexport interface RelayServer {\n server: Server;\n /** `http://127.0.0.1:4141` once listening. */\n url: string;\n port: number;\n close(): Promise<void>;\n /** Connected app and panel streams. */\n counts(): { apps: number; panels: number };\n}\n\nconst MIME: Record<string, string> = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8' };\n\nexport function findPanelDir(): string | null {\n const here = dirname(fileURLToPath(import.meta.url));\n for (const dir of [join(here, '..', 'panel'), join(here, '..', 'extension'), join(here, '..', '..', 'extension')]) {\n if (existsSync(join(dir, 'panel.html')) && existsSync(join(dir, 'panel.js'))) return dir;\n }\n return null;\n}\n\nconst cors = (res: ServerResponse): void => {\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n};\n\nfunction readBody(req: IncomingMessage, limit = 8 * 1024 * 1024): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size > limit) {\n reject(new Error('body too large'));\n req.destroy();\n return;\n }\n chunks.push(c);\n });\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n req.on('error', reject);\n });\n}\n\nexport function createRelayServer(options: RelayOptions = {}): Promise<RelayServer> {\n const host = options.host ?? '127.0.0.1';\n const panelDir = options.panelDir === undefined ? findPanelDir() : options.panelDir;\n const keepAliveMs = options.keepAliveMs ?? 15_000;\n const apps = new Set<ServerResponse>();\n const panels = new Set<ServerResponse>();\n let url = '';\n\n const send = (targets: Set<ServerResponse>, message: unknown): void => {\n const line = `data: ${JSON.stringify(message)}\\n\\n`;\n for (const res of targets) {\n try {\n res.write(line);\n } catch {\n targets.delete(res);\n }\n }\n };\n\n const route = (message: unknown): void => {\n if (!message || typeof message !== 'object') return;\n const m = message as Record<string, unknown>;\n if (m.__rerenderLensCmd === true) send(apps, m);\n else if (m.__rerenderLens === true || m.__rerenderLensReply === true) send(panels, m);\n };\n\n const server = createServer(async (req, res) => {\n cors(res);\n const u = new URL(req.url || '/', 'http://relay');\n if (req.method === 'OPTIONS') {\n res.statusCode = 204;\n res.end();\n return;\n }\n if (u.pathname === '/' && req.method === 'GET') {\n res.statusCode = 302;\n res.setHeader('Location', `/panel.html?relay=${encodeURIComponent(url)}`);\n res.end();\n return;\n }\n if (u.pathname === '/events' && req.method === 'GET') {\n const role = u.searchParams.get('role') === 'app' ? apps : panels;\n res.statusCode = 200;\n res.setHeader('Content-Type', 'text/event-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.setHeader('Connection', 'keep-alive');\n res.write(': connected\\n\\n');\n role.add(res);\n const ping = setInterval(() => {\n try {\n res.write(': ping\\n\\n');\n } catch {\n /* closed */\n }\n }, keepAliveMs);\n req.on('close', () => {\n clearInterval(ping);\n role.delete(res);\n });\n // Tell panels an app went away so they show \"disconnected\" instead of waiting.\n if (role === apps) {\n send(panels, { __rerenderLens: true, version: 2, type: 'relay', payload: { apps: apps.size } });\n req.on('close', () => send(panels, { __rerenderLens: true, version: 2, type: 'relay', payload: { apps: apps.size } }));\n }\n return;\n }\n if (u.pathname === '/message' && req.method === 'POST') {\n try {\n const parsed = JSON.parse((await readBody(req)) || 'null') as unknown;\n for (const m of Array.isArray(parsed) ? parsed : [parsed]) route(m);\n res.statusCode = 204;\n res.end();\n } catch (e) {\n res.statusCode = 400;\n res.setHeader('Content-Type', 'text/plain');\n res.end(String((e as Error).message || e));\n }\n return;\n }\n if (u.pathname === '/status' && req.method === 'GET') {\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ ok: true, apps: apps.size, panels: panels.size }));\n return;\n }\n const file = u.pathname.replace(/^\\//, '');\n if (/^panel\\.(html|js|css)$/.test(file) && req.method === 'GET') {\n if (!panelDir) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'text/plain');\n res.end('rerender-lens: panel files not found (is the package built?)');\n return;\n }\n res.statusCode = 200;\n res.setHeader('Content-Type', MIME[file.slice(file.lastIndexOf('.'))] || 'application/octet-stream');\n res.setHeader('Cache-Control', 'no-store');\n res.end(readFileSync(join(panelDir, file)));\n return;\n }\n res.statusCode = 404;\n res.setHeader('Content-Type', 'text/plain');\n res.end('not found');\n });\n\n return new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(options.port ?? 4141, host, () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : options.port ?? 4141;\n url = `http://${host === '0.0.0.0' || host === '::' ? 'localhost' : host}:${port}`;\n resolve({\n server,\n url,\n port,\n counts: () => ({ apps: apps.size, panels: panels.size }),\n close: () =>\n new Promise<void>((done) => {\n for (const res of [...apps, ...panels]) res.end();\n apps.clear();\n panels.clear();\n server.close(() => done());\n }),\n });\n });\n });\n}\n","#!/usr/bin/env node\n/**\n * rerender-lens CLI: work with panel exports and session files from a terminal or CI.\n *\n * rerender-lens fixes <export.json> ranked fixes for the reports in an export\n * rerender-lens summary <export.json> [--out s.json] session summary of an export (commit it as a baseline)\n * rerender-lens compare <before.json> <after.json> before/after table; exit 1 on regressions\n * rerender-lens budget <export.json> <budget.json> check avoidable re-renders per component; exit 1 on violations\n * rerender-lens budget <export.json> --init print a budget matching the export\n * rerender-lens panel [--port 4141] [--host 127.0.0.1] serve the panel and relay reports from any app\n */\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { formatFixes } from './fixes';\nimport { compareSummaries, formatComparison, parseExport, summarizeReports, type SessionSummary } from './sessions';\nimport { checkBudget, toBudget, type Budget } from './budget';\nimport { createRelayServer } from './relay';\n\nconst read = (file: string): unknown => JSON.parse(readFileSync(file, 'utf8'));\n\nfunction summaryOf(file: string): SessionSummary {\n const parsed = parseExport(read(file));\n if (parsed.summary) return parsed.summary;\n if (parsed.sessions.length && !parsed.reports.length) return parsed.sessions[parsed.sessions.length - 1]!;\n return summarizeReports(parsed.reports, { name: file.replace(/^.*[\\\\/]/, '').replace(/\\.json$/, '') });\n}\n\n/** `rerender-lens panel`: start the relay and keep running until it closes. */\nasync function panelCommand(args: string[]): Promise<number> {\n const flag = (name: string): string | null => {\n const i = args.indexOf(name);\n return i >= 0 ? (args[i + 1] ?? '') : null;\n };\n const relay = await createRelayServer({ port: Number(flag('--port') || 4141), host: flag('--host') || '127.0.0.1' });\n console.log(`rerender-lens panel: ${relay.url}/`);\n console.log(`in the app: createDevtoolsNotifier({ relay: '${relay.url}' }) or window.__RERENDER_LENS_RELAY__ = '${relay.url}'`);\n console.log('Ctrl+C to stop.');\n return new Promise((resolve) => {\n const stop = (): void => void relay.close().then(() => resolve(0));\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n relay.server.once('close', () => resolve(0));\n });\n}\n\nexport function main(argv: string[]): number | Promise<number> {\n const [cmd, ...args] = argv;\n if (cmd === 'panel') return panelCommand(args).catch((e: Error) => (console.error(e.message), 1));\n const flag = (name: string): string | null => {\n const i = args.indexOf(name);\n return i >= 0 ? (args[i + 1] ?? '') : null;\n };\n const positional = args.filter((a, i) => !a.startsWith('--') && !(i > 0 && args[i - 1]!.startsWith('--') && args[i - 1] !== '--init'));\n try {\n switch (cmd) {\n case 'fixes': {\n const file = positional[0];\n if (!file) throw new Error('usage: rerender-lens fixes <export.json>');\n const { reports } = parseExport(read(file));\n console.log(formatFixes(reports, Number(flag('--limit') || 20)));\n return 0;\n }\n case 'summary': {\n const file = positional[0];\n if (!file) throw new Error('usage: rerender-lens summary <export.json> [--out summary.json]');\n const summary = summaryOf(file);\n const out = flag('--out');\n if (out) {\n writeFileSync(out, JSON.stringify(summary, null, 2) + '\\n');\n console.log(`${out}: ${summary.total} renders, ${summary.avoidable} avoidable`);\n } else console.log(JSON.stringify(summary, null, 2));\n return 0;\n }\n case 'compare': {\n const [a, b] = positional;\n if (!a || !b) throw new Error('usage: rerender-lens compare <before.json> <after.json>');\n const cmp = compareSummaries(summaryOf(a), summaryOf(b));\n console.log(formatComparison(cmp));\n if (cmp.regressions.length) {\n console.error(`\\n${cmp.regressions.length} component${cmp.regressions.length === 1 ? '' : 's'} regressed.`);\n return 1;\n }\n return 0;\n }\n case 'budget': {\n const file = positional[0];\n if (!file) throw new Error('usage: rerender-lens budget <export.json> <budget.json> | --init');\n const { reports } = parseExport(read(file));\n if (args.includes('--init')) {\n console.log(JSON.stringify(toBudget(reports), null, 2));\n return 0;\n }\n const budgetFile = positional[1];\n if (!budgetFile) throw new Error('usage: rerender-lens budget <export.json> <budget.json>');\n const result = checkBudget(reports, read(budgetFile) as Budget);\n if (result.ok) {\n console.log(`Within budget (${Object.keys(result.counts).length} components with avoidable re-renders).`);\n return 0;\n }\n for (const v of result.violations) console.error(`<${v.component}>: ${v.avoidable} avoidable (budget ${v.allowed})`);\n console.error('\\n' + formatFixes(reports));\n return 1;\n }\n default:\n console.error('usage: rerender-lens <panel|fixes|summary|compare|budget> ...');\n return cmd ? 1 : 0;\n }\n } catch (e) {\n console.error((e as Error).message);\n return 1;\n }\n}\n\nconst isMain = typeof process !== 'undefined' && Array.isArray(process.argv) && /rerender-lens(\\.c?js)?$|cli\\.c?js$/.test(process.argv[1] || '');\nif (isMain) void Promise.resolve(main(process.argv.slice(2))).then((code) => process.exit(code));\n"]}