renderdiff 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,297 @@
1
+ import sharp from "sharp";
2
+ import { MASK_COLOR, masksFor } from "./config.js";
3
+ /**
4
+ * Chromium's screenshot backing store caps at this in any dimension; a single
5
+ * `fullPage` shot past it is silently truncated. Rather than refuse, a taller page is
6
+ * captured in vertical tiles of at most this height and stitched back together
7
+ * (`captureTiled`), so long pages still diff in full.
8
+ */
9
+ const MAX_CAPTURE_HEIGHT = 16_384;
10
+ /** Tile height for the stitched path. Under the cap, with margin. */
11
+ const TILE_HEIGHT = 16_000;
12
+ /**
13
+ * Sanity ceiling. Past this a page is far likelier a broken/runaway layout than a real
14
+ * one, and we would rather error than stitch a >100MB image. Named in the throw.
15
+ */
16
+ const MAX_PAGE_HEIGHT = 40_000;
17
+ /**
18
+ * Install network blocking, the fake clock and seeded randomness. Once per page,
19
+ * before any navigation.
20
+ */
21
+ export async function prepare(page, cfg, now) {
22
+ for (const pattern of cfg.blockUrlPatterns) {
23
+ await page.route(pattern, (route) => route.abort());
24
+ }
25
+ // Time flows, so Date-based debounce and busy-waits work; `capture` rewinds it per load.
26
+ await page.clock.install({ time: now });
27
+ // mulberry32 with a fixed seed, re-seeded on every navigation.
28
+ await page.addInitScript(() => {
29
+ let s = 0x9e3779b9;
30
+ Math.random = () => {
31
+ s = (s + 0x6d2b79f5) | 0;
32
+ let t = Math.imul(s ^ (s >>> 15), 1 | s);
33
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
34
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
35
+ };
36
+ });
37
+ }
38
+ export async function capture(page, url, route, side, width, cfg, now) {
39
+ await page.setViewportSize({ width, height: 900 });
40
+ await page.clock.setSystemTime(now);
41
+ const res = await page.goto(url, {
42
+ waitUntil: "domcontentloaded",
43
+ timeout: 45_000,
44
+ });
45
+ if (res && res.status() >= 400) {
46
+ throw new Error(`HTTP ${res.status()} at ${url}`);
47
+ }
48
+ await page.addStyleTag({ content: cfg.neutralizeCss });
49
+ // Native lazy images go eager; JS lazy loaders see every viewport-height slice, one
50
+ // frame each, since a scroll inside one synchronous loop never paints.
51
+ await page.evaluate(async (maxHeight) => {
52
+ // The fake clock drives rAF too, so add wall time for a real paint per slice.
53
+ const frame = () => new Promise((r) => requestAnimationFrame(() => setTimeout(r, 32)));
54
+ Array.from(document.images).forEach((img) => (img.loading = "eager"));
55
+ document.querySelectorAll("video").forEach((v) => {
56
+ v.pause();
57
+ v.currentTime = 0;
58
+ });
59
+ const h = Math.min(document.documentElement.scrollHeight, maxHeight);
60
+ for (let y = 0; y < h; y += window.innerHeight) {
61
+ window.scrollTo(0, y);
62
+ await frame();
63
+ }
64
+ window.scrollTo(0, 0);
65
+ await frame();
66
+ await new Promise((r) => setTimeout(r, 0));
67
+ const decoded = Promise.all(Array.from(document.images).map((img) => img.decode().catch(() => { })));
68
+ await Promise.race([decoded, new Promise((r) => setTimeout(r, 5_000))]);
69
+ }, MAX_PAGE_HEIGHT);
70
+ await page.evaluate(() => document.fonts.ready);
71
+ await page
72
+ .waitForLoadState("networkidle", { timeout: 3_000 })
73
+ .catch(() => { });
74
+ await page.addStyleTag({ content: cfg.neutralizeCss }); // anything injected late
75
+ const selector = route.selector?.[side];
76
+ const where = `on ${side} (${route[side]})`;
77
+ // count() doesn't wait, so a wrong selector fails now rather than after a 30s timeout.
78
+ const el = selector ? page.locator(selector) : null;
79
+ const n = await el?.count();
80
+ if (n !== undefined && n !== 1) {
81
+ throw new Error(`selector "${selector}" matched ${n} elements ${where}, expected 1`);
82
+ }
83
+ // Masks only paint the PNG, so hide them for the text read too; visibility keeps layout.
84
+ const masks = masksFor(cfg, page, route);
85
+ const hidden = [];
86
+ for (const l of masks) {
87
+ hidden.push(await l.evaluateAll((els) => els.map((e) => {
88
+ const prev = e.style.visibility;
89
+ e.style.visibility = "hidden";
90
+ return prev;
91
+ })));
92
+ }
93
+ const { scrollHeight, innerText, pageText, media } = await (el ?? page.locator(":root")).evaluate((root) => {
94
+ const body = document.body ?? document.documentElement;
95
+ return {
96
+ scrollHeight: document.documentElement.scrollHeight,
97
+ innerText: (root === document.documentElement ? body : root).innerText || "",
98
+ pageText: body.innerText?.length ?? 0,
99
+ media: Array.from(document.querySelectorAll("img, svg, canvas, video, iframe, input, textarea, select")).some((e) => {
100
+ const r = e.getBoundingClientRect();
101
+ return r.width * r.height >= 1024;
102
+ }),
103
+ };
104
+ });
105
+ // Reverse order, so an element two masks share gets its original value back.
106
+ for (let i = masks.length - 1; i >= 0; i--) {
107
+ await masks[i].evaluateAll((els, prev) => els.forEach((e, j) => (e.style.visibility = prev[j] ?? "")), hidden[i]);
108
+ }
109
+ const text = innerText
110
+ .split("\n")
111
+ .map((line) => line.replace(/\s+/g, " ").trim())
112
+ .filter(Boolean);
113
+ const blank = pageText < 200 && !media ? `height=${scrollHeight}, text=${pageText} chars` : null;
114
+ if (scrollHeight > MAX_PAGE_HEIGHT) {
115
+ throw new Error(`page is ${scrollHeight}px tall, past the ${MAX_PAGE_HEIGHT}px sanity ceiling ` +
116
+ `at ${url} — likely a broken layout, not a real page`);
117
+ }
118
+ // A clip of any height comes out whole, so only a page shot needs tiles.
119
+ const tiled = !el && scrollHeight > MAX_CAPTURE_HEIGHT;
120
+ const measure = async (l) => {
121
+ const b = await l.boundingBox();
122
+ if (!b?.width || !b.height)
123
+ throw new Error(`selector "${selector}" has no box ${where}`);
124
+ return b;
125
+ };
126
+ let clip;
127
+ // Brings in what lazy-loads near it; then scroll 0, so sticky chrome is as in a page shot.
128
+ if (el) {
129
+ await measure(el);
130
+ await el.scrollIntoViewIfNeeded();
131
+ }
132
+ // At scroll 0 viewport coordinates are page coordinates, which every path shoots in.
133
+ await page.evaluate(() => window.scrollTo(0, 0));
134
+ if (el) {
135
+ const b = await measure(el);
136
+ const x = Math.max(0, Math.floor(b.x));
137
+ const y = Math.max(0, Math.floor(b.y));
138
+ clip = { x, y, width: Math.ceil(b.x + b.width) - x, height: Math.ceil(b.y + b.height) - y };
139
+ if (clip.width <= 0 || clip.height <= 0) {
140
+ throw new Error(`selector "${selector}" is off the page ${where}`);
141
+ }
142
+ }
143
+ const boxes = (await Promise.all((await Promise.all(masks.map((l) => l.all())))
144
+ .flat()
145
+ .map((m) => m.boundingBox())))
146
+ .filter((b) => b !== null)
147
+ .map((b) => (clip ? { ...b, x: b.x - clip.x, y: b.y - clip.y } : b));
148
+ const shoot = async () => paintBoxes(tiled
149
+ ? await captureTiled(page, width, scrollHeight)
150
+ : await page.screenshot({ fullPage: true, clip, animations: "disabled" }), boxes);
151
+ // A tiled shot is expensive, so it gets one comparison.
152
+ const tries = tiled ? 2 : 3;
153
+ let prev = await shoot();
154
+ for (let n = 2;; n++) {
155
+ await page.waitForTimeout(100);
156
+ const png = await shoot();
157
+ if (png.equals(prev))
158
+ return { png, unstable: null, blank, text };
159
+ if (n === tries)
160
+ return { png, unstable: await differing(prev, png), blank, text };
161
+ prev = png;
162
+ }
163
+ }
164
+ const decode = (png) => sharp(png).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
165
+ /** Pixels that differ between two shots of the same load. Null if none, or if the size changed. */
166
+ async function differing(a, b) {
167
+ const [ra, rb] = await Promise.all([decode(a), decode(b)]);
168
+ // A page whose size changed between shots isn't masked; that diff still counts.
169
+ if (ra.info.width !== rb.info.width || ra.info.height !== rb.info.height) {
170
+ return null;
171
+ }
172
+ const mask = new Uint8Array(ra.info.width * ra.info.height);
173
+ const pa = new Uint32Array(ra.data.buffer, ra.data.byteOffset, mask.length);
174
+ const pb = new Uint32Array(rb.data.buffer, rb.data.byteOffset, mask.length);
175
+ let any = false;
176
+ for (let i = 0; i < mask.length; i++) {
177
+ if (pa[i] !== pb[i]) {
178
+ mask[i] = 1;
179
+ any = true;
180
+ }
181
+ }
182
+ return any ? mask : null;
183
+ }
184
+ /**
185
+ * Paint every pixel either side found unstable onto both sides, so that noise is left
186
+ * out of the measurement. Rewrites `left.png` / `right.png`; returns the painted pixel count.
187
+ */
188
+ export async function cancelUnstable(left, right) {
189
+ if (!left.unstable && !right.unstable)
190
+ return 0;
191
+ const raws = await Promise.all([decode(left.png), decode(right.png)]);
192
+ const w = Math.max(raws[0].info.width, raws[1].info.width);
193
+ const h = Math.max(raws[0].info.height, raws[1].info.height);
194
+ const union = new Uint8Array(w * h);
195
+ let count = 0;
196
+ [left, right].forEach((shot, s) => {
197
+ const u = shot.unstable;
198
+ if (!u)
199
+ return;
200
+ const sw = raws[s].info.width;
201
+ for (let i = 0; i < u.length; i++) {
202
+ if (!u[i])
203
+ continue;
204
+ const j = Math.floor(i / sw) * w + (i % sw);
205
+ if (!union[j]) {
206
+ union[j] = 1;
207
+ count++;
208
+ }
209
+ }
210
+ });
211
+ await Promise.all([left, right].map(async (shot, s) => {
212
+ const { data, info } = raws[s];
213
+ for (let y = 0; y < info.height; y++) {
214
+ for (let x = 0; x < info.width; x++) {
215
+ if (!union[y * w + x])
216
+ continue;
217
+ const o = (y * info.width + x) * 4;
218
+ data[o] = 0xff;
219
+ data[o + 1] = 0x00;
220
+ data[o + 2] = 0xff;
221
+ data[o + 3] = 0xff;
222
+ }
223
+ }
224
+ shot.png = await sharp(data, {
225
+ raw: { width: info.width, height: info.height, channels: 4 },
226
+ })
227
+ .png()
228
+ .toBuffer();
229
+ }));
230
+ return count;
231
+ }
232
+ async function paintBoxes(png, boxes) {
233
+ if (!boxes.length)
234
+ return png;
235
+ const { width: W = 0, height: H = 0 } = await sharp(png).metadata();
236
+ const rects = boxes.flatMap((b) => {
237
+ const left = Math.max(0, Math.floor(b.x));
238
+ const top = Math.max(0, Math.floor(b.y));
239
+ const width = Math.min(W, Math.ceil(b.x + b.width)) - left;
240
+ const height = Math.min(H, Math.ceil(b.y + b.height)) - top;
241
+ if (width <= 0 || height <= 0)
242
+ return [];
243
+ return [
244
+ {
245
+ input: {
246
+ create: { width, height, channels: 4, background: MASK_COLOR },
247
+ },
248
+ left,
249
+ top,
250
+ },
251
+ ];
252
+ });
253
+ return rects.length ? sharp(png).composite(rects).png().toBuffer() : png;
254
+ }
255
+ /**
256
+ * Capture a page taller than Chromium's one-shot limit as vertical tiles, then stitch
257
+ * them into a single full-height PNG.
258
+ *
259
+ * Playwright's `page.screenshot({ clip })` clips against the viewport, so it cannot
260
+ * reach a band below the fold. We drop to a raw CDP `Page.captureScreenshot` with
261
+ * `captureBeyondViewport: true`, which renders the whole page offscreen and clips in
262
+ * page coordinates — no per-tile scrolling. That matters here: the page renders at
263
+ * scroll-0 for every tile, exactly as a `fullPage` shot would, so a pinned/sticky
264
+ * element and its spacer stay put instead of shifting between tiles.
265
+ *
266
+ * `deviceScaleFactor` is 1 (`CONTEXT_OPTIONS` in config.ts), so clip coords are CSS px.
267
+ */
268
+ async function captureTiled(page, width, scrollHeight) {
269
+ const client = await page.context().newCDPSession(page);
270
+ try {
271
+ const tiles = [];
272
+ for (let y = 0; y < scrollHeight; y += TILE_HEIGHT) {
273
+ const height = Math.min(TILE_HEIGHT, scrollHeight - y);
274
+ const { data } = await client.send("Page.captureScreenshot", {
275
+ format: "png",
276
+ captureBeyondViewport: true,
277
+ clip: { x: 0, y, width, height, scale: 1 },
278
+ });
279
+ tiles.push(Buffer.from(data, "base64"));
280
+ }
281
+ return sharp({
282
+ create: {
283
+ width,
284
+ height: scrollHeight,
285
+ channels: 4,
286
+ background: MASK_COLOR,
287
+ },
288
+ })
289
+ .composite(tiles.map((input, i) => ({ input, left: 0, top: i * TILE_HEIGHT })))
290
+ .png()
291
+ .toBuffer();
292
+ }
293
+ finally {
294
+ await client.detach();
295
+ }
296
+ }
297
+ //# sourceMappingURL=capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture.js","sourceRoot":"","sources":["../src/capture.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Screenshot diff between two origins.
4
+ *
5
+ * renderdiff <left-origin> <right-origin> <route…>
6
+ *
7
+ * Screenshots the given routes on both origins at several viewport widths and
8
+ * pixel-diffs them. Routes are required and literal — the tool does no route discovery.
9
+ *
10
+ * Per-site knobs (masks for content that drifts, extra selectors to hide, defaults for
11
+ * the flags below) live in a `renderdiff.config.ts` / `.mjs` in the working directory.
12
+ */
13
+ import { label, loadConfig, parseRoutes, resolveConfig, selectorText, version, } from "./config.js";
14
+ /** `Number(raw)`, but reject anything that isn't a finite number with a flag-named error. */
15
+ const num = (flag, raw) => {
16
+ const n = Number(raw);
17
+ if (!Number.isFinite(n)) {
18
+ throw new Error(`${flag} expects a number, got "${raw}"`);
19
+ }
20
+ return n;
21
+ };
22
+ function parseArgs(argv) {
23
+ const a = {
24
+ dryRun: false,
25
+ open: false,
26
+ help: false,
27
+ version: false,
28
+ routes: [],
29
+ };
30
+ const origins = [];
31
+ const sel = {};
32
+ for (let i = 0; i < argv.length; i++) {
33
+ const raw = argv[i];
34
+ // Accept both `--flag value` and `--flag=value`.
35
+ const eq = raw.startsWith("--") ? raw.indexOf("=") : -1;
36
+ const v = eq === -1 ? raw : raw.slice(0, eq);
37
+ const inline = eq === -1 ? undefined : raw.slice(eq + 1);
38
+ // Require a value: the inline `--flag=value`, or the next argv token — but not
39
+ // another flag, which means the value was simply omitted. Only advance `i` when
40
+ // a positional token is actually consumed.
41
+ const next = () => {
42
+ if (inline !== undefined)
43
+ return inline;
44
+ const peek = argv[i + 1];
45
+ if (peek === undefined || peek.startsWith("-")) {
46
+ throw new Error(`${v} needs a value`);
47
+ }
48
+ return argv[++i];
49
+ };
50
+ if (v === "--help" || v === "-h")
51
+ a.help = true;
52
+ else if (v === "--version" || v === "-v")
53
+ a.version = true;
54
+ else if (v === "--dry-run")
55
+ a.dryRun = true;
56
+ else if (v === "--open")
57
+ a.open = true;
58
+ else if (v === "--progress")
59
+ a.progress = true;
60
+ else if (v === "--no-progress")
61
+ a.progress = false;
62
+ else if (v === "--left")
63
+ a.left = next();
64
+ else if (v === "--right")
65
+ a.right = next();
66
+ else if (v === "--out")
67
+ a.outDir = next();
68
+ else if (v === "--config")
69
+ a.config = next();
70
+ else if (v === "--selector" || v === "--left-selector" || v === "--right-selector") {
71
+ const s = next();
72
+ if (!s)
73
+ throw new Error(`${v} needs a value`);
74
+ sel[v === "--selector" ? "both" : v === "--left-selector" ? "left" : "right"] = s;
75
+ }
76
+ else if (v === "--width") {
77
+ const w = num("--width", next());
78
+ if (w <= 0)
79
+ throw new Error(`--width expects a positive number, got ${w}`);
80
+ (a.widths ??= []).push(w);
81
+ }
82
+ else if (v === "--concurrency") {
83
+ const n = num("--concurrency", next());
84
+ if (!Number.isInteger(n) || n < 1) {
85
+ throw new Error(`--concurrency expects a positive integer, got ${n}`);
86
+ }
87
+ a.concurrency = n;
88
+ }
89
+ else if (v === "--threshold") {
90
+ const t = num("--threshold", next());
91
+ if (t < 0 || t > 1)
92
+ throw new Error(`--threshold expects 0..1, got ${t}`);
93
+ a.threshold = t;
94
+ }
95
+ else if (v.startsWith("-"))
96
+ throw new Error(`unknown flag: ${v}`);
97
+ else if (/^https?:\/\//i.test(v))
98
+ origins.push(v);
99
+ else
100
+ a.routes.push(v);
101
+ }
102
+ if (origins.length > 2) {
103
+ throw new Error(`at most two origins (left, right), got ${origins.length}: ${origins.join(" ")}`);
104
+ }
105
+ for (const [i, side] of ["left", "right"].entries()) {
106
+ if (origins[i] === undefined)
107
+ continue;
108
+ if (a[side] !== undefined) {
109
+ throw new Error(`${side} origin given twice: ${origins[i]} and --${side} ${a[side]}`);
110
+ }
111
+ a[side] = origins[i];
112
+ }
113
+ const left = sel.left ?? sel.both;
114
+ const right = sel.right ?? sel.both;
115
+ if (!left !== !right) {
116
+ const [has, lacks] = left ? ["left", "right"] : ["right", "left"];
117
+ throw new Error(`--${has}-selector needs --${lacks}-selector or --selector`);
118
+ }
119
+ if (left && right)
120
+ a.selector = { left, right };
121
+ return a;
122
+ }
123
+ const HELP = `
124
+ renderdiff — screenshot diff between two origins
125
+
126
+ Usage
127
+ renderdiff <left-origin> <right-origin> <route…> [options]
128
+
129
+ renderdiff http://localhost:3000 https://example.com / /about
130
+ renderdiff http://localhost:3000 https://example.com /pricing::/plans
131
+
132
+ An argument starting with http:// or https:// is an origin: the first is left, the
133
+ second right (or pass --left / --right). A route is a literal path, loaded on both sides; /a::/b loads
134
+ /a on the left and /b on the right. No globs, no discovery.
135
+
136
+ With --selector, each side compares the one element it matches instead of the page;
137
+ it must match exactly one. It applies to every route, over the config's own selectors.
138
+
139
+ Writes report.html, results.json and crops of each change to --out. Exits 1 if a
140
+ route errors or falls below --threshold, 2 on bad arguments or an origin that is down.
141
+
142
+ Options
143
+ --left <url> Left origin, instead of the first positional one.
144
+ --right <url> Right origin, instead of the second positional one.
145
+ --width <n> Viewport width; repeatable. Replaces the defaults.
146
+ --threshold <0..1> Fail if any comparison scores below this.
147
+ --out <dir> Output directory (default: ./renderdiff-out)
148
+ --concurrency <n> Parallel workers (default: 2)
149
+ --config <path> Config file. Default: renderdiff.config.{ts,mts,js,mjs} in cwd.
150
+ --selector <sel> Compare one element (a Playwright selector) on both sides.
151
+ --left-selector <sel>, --right-selector <sel>
152
+ That side's selector, over --selector.
153
+ --dry-run Print the URL pairs and exit. No browser.
154
+ --open Open the report when the run finishes.
155
+ --progress A line per shot as it finishes. The default on a terminal;
156
+ otherwise one summary of the differing routes at the end.
157
+ --no-progress The end-of-run summary, even on a terminal.
158
+ -h, --help Show this.
159
+ -v, --version Print the version.
160
+
161
+ Default widths: 1440, 1152, 896, 704, 375
162
+
163
+ Masks, hidden selectors, blocked URLs and route lists go in a config file; CLI flags
164
+ win over it. See the README, or skills/renderdiff/reference.md.
165
+ `;
166
+ const usage = () => {
167
+ console.error(" Usage: renderdiff <left-origin> <right-origin> <route…>");
168
+ console.error(" Try: renderdiff --help");
169
+ };
170
+ // A function declaration, not a const arrow: TypeScript only lets a `never`-returning
171
+ // call narrow control flow when the callee is declared this way.
172
+ function die(message) {
173
+ console.error(`✗ ${message}\n`);
174
+ usage();
175
+ process.exit(2);
176
+ }
177
+ const alive = async (origin) => {
178
+ try {
179
+ return ((await fetch(origin, {
180
+ method: "HEAD",
181
+ signal: AbortSignal.timeout(3000),
182
+ })).status < 500);
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ };
188
+ /**
189
+ * Refuse to start unless both origins answer — two error pages diffed against each
190
+ * other score well, which is the likeliest way this tool lies.
191
+ */
192
+ async function preflight(left, right) {
193
+ const [leftUp, rightUp] = await Promise.all([alive(left), alive(right)]);
194
+ if (leftUp && rightUp)
195
+ return;
196
+ const down = [!leftUp && `left (${left})`, !rightUp && `right (${right})`]
197
+ .filter(Boolean)
198
+ .join(" and ");
199
+ console.error(`✗ ${down} not responding.\n`);
200
+ console.error(" If that is a local dev server, start it first — and make sure it serves the");
201
+ console.error(" same assets as the other origin (a bare preview server that 404s images will");
202
+ console.error(" collapse every page and diff as garbage).\n");
203
+ process.exit(2);
204
+ }
205
+ let args;
206
+ try {
207
+ args = parseArgs(process.argv.slice(2));
208
+ }
209
+ catch (e) {
210
+ die(e instanceof Error ? e.message : String(e));
211
+ }
212
+ if (args.help) {
213
+ console.log(HELP.trimEnd());
214
+ process.exit(0);
215
+ }
216
+ if (args.version) {
217
+ console.log(version());
218
+ process.exit(0);
219
+ }
220
+ const { config: fileConfig, path: configPath } = await loadConfig(args.config).catch((e) => {
221
+ console.error(`✗ ${e instanceof Error ? e.message : String(e)}`);
222
+ process.exit(2);
223
+ });
224
+ let cfg;
225
+ try {
226
+ cfg = resolveConfig(fileConfig, args);
227
+ }
228
+ catch (e) {
229
+ die(e instanceof Error ? e.message : String(e));
230
+ }
231
+ const rawRoutes = args.routes.length ? args.routes : (fileConfig.routes ?? []);
232
+ if (!rawRoutes.length) {
233
+ die("no routes given.");
234
+ }
235
+ let routes;
236
+ try {
237
+ routes = parseRoutes(rawRoutes, args.selector);
238
+ }
239
+ catch (e) {
240
+ die(e instanceof Error ? e.message : String(e));
241
+ }
242
+ if (configPath)
243
+ console.log(`Config: ${configPath}`);
244
+ console.log(`Routes: ${routes.length}`);
245
+ if (args.dryRun) {
246
+ for (const r of routes) {
247
+ console.log(` ${label(r).padEnd(52)} ${cfg.left}${r.left} | ${cfg.right}${r.right}` +
248
+ (r.selector ? ` [${selectorText(r.selector)}]` : ""));
249
+ }
250
+ process.exit(0);
251
+ }
252
+ await preflight(cfg.left, cfg.right);
253
+ const { run } = await import("./run.js");
254
+ try {
255
+ process.exit(await run({
256
+ routes,
257
+ cfg,
258
+ open: args.open,
259
+ progress: args.progress ?? !!process.stdout.isTTY,
260
+ }));
261
+ }
262
+ catch (e) {
263
+ console.error(`✗ ${e instanceof Error ? e.message : String(e)}`);
264
+ process.exit(1);
265
+ }
266
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}