@recursica/adapter-tester 5.3.0 → 5.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 (35) hide show
  1. package/README.md +7 -1
  2. package/dist/cli.cjs +12 -11
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.js +354 -212
  5. package/dist/cli.js.map +1 -1
  6. package/dist/devServer.d.ts +5 -2
  7. package/dist/devServer.d.ts.map +1 -1
  8. package/dist/fileConfig.d.ts +1 -1
  9. package/dist/harness/mantineSourceOfTruth.d.ts +1 -1
  10. package/dist/harness/mantineSourceOfTruth.d.ts.map +1 -1
  11. package/dist/index.cjs +1 -1
  12. package/dist/index.html +226 -3
  13. package/dist/index.js +1 -1
  14. package/dist/{mantineSourceOfTruth-6KZlsQ67.cjs → mantineSourceOfTruth-DiQas6OS.cjs} +29 -16
  15. package/dist/mantineSourceOfTruth-DiQas6OS.cjs.map +1 -0
  16. package/dist/{mantineSourceOfTruth-CAKhcVCT.js → mantineSourceOfTruth-xUEi1k8U.js} +42 -29
  17. package/dist/mantineSourceOfTruth-xUEi1k8U.js.map +1 -0
  18. package/dist/runVisualRegression-CQk97Qkv.js +5051 -0
  19. package/dist/runVisualRegression-CQk97Qkv.js.map +1 -0
  20. package/dist/runVisualRegression-DNafR8Lb.cjs +13 -0
  21. package/dist/runVisualRegression-DNafR8Lb.cjs.map +1 -0
  22. package/dist/testing/runVisualRegression.d.ts +16 -2
  23. package/dist/testing/runVisualRegression.d.ts.map +1 -1
  24. package/dist/testing.cjs +1 -5
  25. package/dist/testing.cjs.map +1 -1
  26. package/dist/testing.js +2 -380
  27. package/dist/testing.js.map +1 -1
  28. package/package.json +3 -2
  29. package/public/index.html +226 -3
  30. package/dist/index-C6uYPRmx.cjs +0 -9
  31. package/dist/index-C6uYPRmx.cjs.map +0 -1
  32. package/dist/index-DqQzFSAH.js +0 -4667
  33. package/dist/index-DqQzFSAH.js.map +0 -1
  34. package/dist/mantineSourceOfTruth-6KZlsQ67.cjs.map +0 -1
  35. package/dist/mantineSourceOfTruth-CAKhcVCT.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { spawn as I, spawnSync as W } from "node:child_process";
3
- import { readFileSync as T, mkdirSync as M, writeFileSync as E, existsSync as U } from "node:fs";
4
- import { dirname as N, join as a, resolve as $ } from "node:path";
5
- import { fileURLToPath as G, pathToFileURL as B } from "node:url";
2
+ import { spawn as W, spawnSync as re } from "node:child_process";
3
+ import { readFileSync as O, mkdirSync as B, writeFileSync as E, existsSync as A } from "node:fs";
4
+ import { dirname as M, join as l, resolve as C } from "node:path";
5
+ import { fileURLToPath as z, pathToFileURL as oe } from "node:url";
6
+ import { chromium as se } from "@playwright/test";
6
7
  import K from "express";
7
- import { createProxyMiddleware as X } from "http-proxy-middleware";
8
- import Y from "node:net";
9
- import { m as z } from "./mantineSourceOfTruth-CAKhcVCT.js";
10
- import { a as Q, i as Z } from "./index-DqQzFSAH.js";
11
- const ee = /\x1b\[[0-9;]*m/g, te = /Local:\s*(https?:\/\/localhost:\d+)/;
12
- function re(e) {
8
+ import { createProxyMiddleware as ne } from "http-proxy-middleware";
9
+ import { a as ie, b as ae, c as ce, d as de, e as ue, f as le, i as pe } from "./runVisualRegression-CQk97Qkv.js";
10
+ import he from "node:net";
11
+ import { m as fe } from "./mantineSourceOfTruth-xUEi1k8U.js";
12
+ const me = /\x1b\[[0-9;]*m/g, ge = /Local:\s*(https?:\/\/localhost:\d+)/;
13
+ function ye(e) {
13
14
  return new Promise((t) => {
14
- const r = new Y.Socket();
15
+ const r = new he.Socket();
15
16
  r.setTimeout(200), r.once("connect", () => {
16
17
  r.destroy(), t(!0);
17
18
  }), r.once("timeout", () => {
@@ -21,60 +22,60 @@ function re(e) {
21
22
  }), r.connect(e, "127.0.0.1");
22
23
  });
23
24
  }
24
- function oe(e) {
25
+ function we(e) {
25
26
  try {
26
- const t = JSON.parse(T(e, "utf8")).port;
27
+ const t = JSON.parse(O(e, "utf8")).port;
27
28
  return typeof t == "number" ? t : void 0;
28
29
  } catch {
29
30
  return;
30
31
  }
31
32
  }
32
- function se(e, t) {
33
- M(N(e), { recursive: !0 }), E(e, JSON.stringify({ port: t }));
33
+ function ve(e, t) {
34
+ B(M(e), { recursive: !0 }), E(e, JSON.stringify({ port: t }));
34
35
  }
35
- function ne(e, t) {
36
- return new Promise((r, o) => {
37
- var y, w;
38
- const s = I(e.command, {
36
+ function be(e, t) {
37
+ return new Promise((r, s) => {
38
+ var w, u;
39
+ const a = W(e.command, {
39
40
  cwd: e.cwd,
40
41
  stdio: "pipe",
41
42
  shell: !0
42
43
  });
43
- let n = !1;
44
- const p = setTimeout(() => {
45
- n || (n = !0, s.kill(), o(
44
+ let i = !1;
45
+ const g = setTimeout(() => {
46
+ i || (i = !0, a.kill(), s(
46
47
  new Error(
47
48
  `Timed out after ${t}ms waiting for ${e.name}'s Storybook to report its URL (no "Local: http://localhost:<port>" line seen in its output).`
48
49
  )
49
50
  ));
50
- }, t), l = (i, u) => {
51
- const h = i.toString();
52
- for (const c of h.split(`
51
+ }, t), y = (o, n) => {
52
+ const c = o.toString();
53
+ for (const p of c.split(`
53
54
  `))
54
- if (c.trim()) {
55
- const m = u ? `${e.name} SB ERROR` : `${e.name} SB`;
56
- (u ? console.error : console.log)(`[${m}] ${c.trim()}`);
55
+ if (p.trim()) {
56
+ const f = n ? `${e.name} SB ERROR` : `${e.name} SB`;
57
+ (n ? console.error : console.log)(`[${f}] ${p.trim()}`);
57
58
  }
58
- if (n) return;
59
- const v = h.replace(ee, "").match(te);
60
- if (v) {
61
- n = !0, clearTimeout(p);
62
- const c = v[1], m = Number(new URL(c).port);
63
- se(e.cacheFile, m), r({ url: c, port: m, process: s });
59
+ if (i) return;
60
+ const d = c.replace(me, "").match(ge);
61
+ if (d) {
62
+ i = !0, clearTimeout(g);
63
+ const p = d[1], f = Number(new URL(p).port);
64
+ ve(e.cacheFile, f), r({ url: p, port: f, process: a });
64
65
  }
65
66
  };
66
- (y = s.stdout) == null || y.on("data", (i) => l(i, !1)), (w = s.stderr) == null || w.on("data", (i) => l(i, !0)), s.on("error", (i) => {
67
- n || (n = !0, clearTimeout(p), o(i));
68
- }), s.on("exit", (i) => {
69
- n || (n = !0, clearTimeout(p), o(
67
+ (w = a.stdout) == null || w.on("data", (o) => y(o, !1)), (u = a.stderr) == null || u.on("data", (o) => y(o, !0)), a.on("error", (o) => {
68
+ i || (i = !0, clearTimeout(g), s(o));
69
+ }), a.on("exit", (o) => {
70
+ i || (i = !0, clearTimeout(g), s(
70
71
  new Error(
71
- `${e.name}'s Storybook exited (code ${i}) before reporting its URL.`
72
+ `${e.name}'s Storybook exited (code ${o}) before reporting its URL.`
72
73
  )
73
74
  ));
74
75
  });
75
76
  });
76
77
  }
77
- function A(e, t) {
78
+ function L(e, t) {
78
79
  return {
79
80
  name: e,
80
81
  command: t.command,
@@ -84,10 +85,10 @@ function A(e, t) {
84
85
  timeoutMs: t.timeout
85
86
  };
86
87
  }
87
- async function D(e) {
88
+ async function R(e) {
88
89
  if (e.reuseExistingServer) {
89
- const t = oe(e.cacheFile);
90
- if (t !== void 0 && await re(t))
90
+ const t = we(e.cacheFile);
91
+ if (t !== void 0 && await ye(t))
91
92
  return console.log(
92
93
  `[Dev Launcher] ${e.name} is already running on port ${t} (reused).`
93
94
  ), {
@@ -96,155 +97,285 @@ async function D(e) {
96
97
  process: null
97
98
  };
98
99
  }
99
- return console.log(`[Dev Launcher] Launching Storybook for ${e.name}...`), ne(e, e.timeoutMs ?? 120 * 1e3);
100
+ return console.log(`[Dev Launcher] Launching Storybook for ${e.name}...`), be(e, e.timeoutMs ?? 120 * 1e3);
100
101
  }
101
- const _ = N(G(import.meta.url));
102
- function ie(e) {
102
+ const G = M(z(import.meta.url));
103
+ function Te(e) {
103
104
  const t = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
104
- console.log(`[Dev Launcher] Auto-launching browser: ${e}`), I(t, [e], { shell: process.platform === "win32" }).on(
105
+ console.log(`[Dev Launcher] Auto-launching browser: ${e}`), W(t, [e], { shell: process.platform === "win32" }).on(
105
106
  "error",
106
107
  (r) => {
107
108
  console.error("[Dev Launcher] Failed to auto-launch browser:", r);
108
109
  }
109
110
  );
110
111
  }
111
- async function ae(e, t, r = {}) {
112
- const [o, s] = e.targets, [n, p] = t;
113
- if (!(o != null && o.sourceOfTruth) || !s || s.sourceOfTruth || !n || !p)
112
+ function ke(e) {
113
+ const t = l(
114
+ e,
115
+ "node_modules/@recursica/adapter-mantine-v8/package.json"
116
+ ), r = A(t) ? t : l(e, "package.json");
117
+ return JSON.parse(O(r, "utf8")).version;
118
+ }
119
+ const Se = 6010, U = (e) => `data:image/png;base64,${e.toString("base64")}`;
120
+ function Oe(e, t, r, s, a) {
121
+ let i, g = Promise.resolve();
122
+ const y = (u) => ue(
123
+ u,
124
+ Object.fromEntries(
125
+ Object.entries(t.stories ?? {}).filter(([, o]) => o.sourceOfTruthThreshold !== void 0).map(([o, n]) => [o, n.sourceOfTruthThreshold])
126
+ ),
127
+ t.sourceOfTruthThresholdPixels
128
+ ), w = async (u) => {
129
+ i ?? (i = se.launch());
130
+ const o = await (await i).newPage(), n = ae(u, t.stories ?? {});
131
+ try {
132
+ await o.setViewportSize(n), await o.goto(`${r}/iframe.html?id=${u}&viewMode=story`, {
133
+ waitUntil: "networkidle"
134
+ });
135
+ const c = await ce(o), d = await s.readImage(u), p = y(u);
136
+ if (!d)
137
+ return {
138
+ status: "no-golden",
139
+ diffPixels: null,
140
+ threshold: p,
141
+ viewport: n,
142
+ golden: null,
143
+ diff: null
144
+ };
145
+ const { diffPixels: f, diffImage: v } = de(c, d);
146
+ return {
147
+ status: v ? "ok" : "size-mismatch",
148
+ diffPixels: v ? f : null,
149
+ threshold: p,
150
+ viewport: n,
151
+ golden: U(d),
152
+ diff: v ? U(v) : null
153
+ };
154
+ } finally {
155
+ await o.close();
156
+ }
157
+ };
158
+ return e.get("/__golden/diff", (u, o) => {
159
+ if (s instanceof Error) {
160
+ o.status(503).json({ error: s.message });
161
+ return;
162
+ }
163
+ const n = String(u.query.id ?? "");
164
+ if (!n) {
165
+ o.status(400).json({ error: "id is required" });
166
+ return;
167
+ }
168
+ const c = g.then(() => w(n));
169
+ g = c.catch(() => {
170
+ }), c.then(
171
+ (d) => o.json(d),
172
+ (d) => o.status(500).json({ error: d.message })
173
+ );
174
+ }), e.get("/__golden/image", async (u, o) => {
175
+ const n = s instanceof Error ? null : await s.readImage(String(u.query.id ?? ""));
176
+ if (!n) {
177
+ o.status(404).end();
178
+ return;
179
+ }
180
+ o.type("image/png").send(n);
181
+ }), e.post("/__golden/threshold", K.json(), (u, o) => {
182
+ var p, f;
183
+ const { id: n, value: c } = u.body ?? {};
184
+ if (typeof n != "string" || !n || !Number.isInteger(c) || c < 0) {
185
+ o.status(400).json({ error: "id (string) and value (integer >= 0) are required" });
186
+ return;
187
+ }
188
+ const d = A(a) ? JSON.parse(O(a, "utf8")) : {};
189
+ d.stories = {
190
+ ...d.stories,
191
+ [n]: { ...(p = d.stories) == null ? void 0 : p[n], sourceOfTruthThreshold: c }
192
+ }, E(a, JSON.stringify(d, null, 2) + `
193
+ `), t.stories = {
194
+ ...t.stories,
195
+ [n]: { ...(f = t.stories) == null ? void 0 : f[n], sourceOfTruthThreshold: c }
196
+ }, o.json({ id: n, value: c });
197
+ }), {
198
+ close: async () => {
199
+ i && await (await i).close();
200
+ }
201
+ };
202
+ }
203
+ async function xe(e, t, r) {
204
+ const [s, a] = e.targets, [i, g] = t;
205
+ if (!(s != null && s.sourceOfTruth) || !a || a.sourceOfTruth || !g || !i)
114
206
  throw new Error(
115
207
  "adapter-tester dev mode requires exactly two targets: [sourceOfTruth, target] — check adapter-tester.config.json."
116
208
  );
117
- const l = r.port ?? 6010;
209
+ if (!e.sourceOfTruthGolden)
210
+ throw new Error(
211
+ "adapter-tester dev mode needs a sourceOfTruthGolden location to diff against — check adapter-tester.config.json."
212
+ );
118
213
  console.log(
119
- `[Dev Launcher] Resolving ${o.name} and ${s.name} Storybooks...`
214
+ `[Dev Launcher] Resolving ${s.name} and ${a.name} Storybooks...`
120
215
  );
121
- const [y, w] = await Promise.all([
122
- D(
123
- A(o.name, n)
216
+ const [y, w, u] = await Promise.all([
217
+ R(
218
+ L(s.name, i)
124
219
  ),
125
- D(A(s.name, p))
220
+ R(L(a.name, g)),
221
+ // The live Storybooks still work without the golden (e.g. offline), so
222
+ // a failure here only disables Golden/Compute Diff, loudly.
223
+ ie(e.sourceOfTruthGolden).catch(
224
+ (m) => (console.warn(
225
+ `[Dev Launcher] WARNING: source-of-truth golden unavailable — Golden and Compute Diff are disabled: ${m.message}`
226
+ ), m)
227
+ )
126
228
  ]);
127
- console.log("[Dev Launcher] Both Storybooks are active and responsive!");
128
- const i = [y.process, w.process].filter(
129
- (f) => f !== null
130
- ), u = K(), h = a(_, "../public"), v = a(_, "../report-header.txt");
131
- u.get("/", (f, b, k) => {
132
- if (f.query.path) {
133
- k();
229
+ console.log("[Dev Launcher] Dev Mode is ready!");
230
+ const o = [y.process, w.process].filter(
231
+ (m) => !!m
232
+ ), n = K(), c = l(G, "../public"), d = l(G, "../report-header.txt"), p = ke(
233
+ i.cwd
234
+ ), f = Oe(
235
+ n,
236
+ e,
237
+ w.url,
238
+ u,
239
+ r.configPath
240
+ );
241
+ n.get("/", (m, S, x) => {
242
+ if (m.query.path) {
243
+ x();
134
244
  return;
135
245
  }
136
- const S = T(a(h, "index.html"), "utf8").replace(
246
+ const te = O(l(c, "index.html"), "utf8").replace(
137
247
  "<head>",
138
248
  `<head>
139
249
  <script>window.__ADAPTER_TESTER__ = ${JSON.stringify({
140
- ownName: s.name,
141
- sourceOfTruthName: o.name,
250
+ ownName: a.name,
251
+ sourceOfTruthName: s.name,
252
+ sourceOfTruthVersion: p,
142
253
  sourceOfTruthPort: y.port
143
254
  })};<\/script>`
144
255
  );
145
- b.send(S);
146
- }), u.get("/report-header.txt", (f, b) => {
256
+ S.send(te);
257
+ }), n.get("/report-header.txt", (m, S) => {
147
258
  if (e.reportHeader !== void 0) {
148
- b.type("text/plain").send(e.reportHeader);
259
+ S.type("text/plain").send(e.reportHeader);
149
260
  return;
150
261
  }
151
262
  try {
152
- b.type("text/plain").send(T(v, "utf8"));
263
+ S.type("text/plain").send(O(d, "utf8"));
153
264
  } catch {
154
- b.status(500).send("Error loading report-header.txt");
265
+ S.status(500).send("Error loading report-header.txt");
155
266
  }
156
- }), u.use(
267
+ }), n.use(
157
268
  "/",
158
- X({
269
+ ne({
159
270
  target: w.url,
160
271
  changeOrigin: !0,
161
272
  ws: !0
162
273
  })
163
274
  );
164
- let c = !1;
165
- const m = () => {
166
- if (!c) {
167
- c = !0, console.log(
275
+ let v = !1;
276
+ const P = () => {
277
+ if (!v) {
278
+ v = !0, console.log(
168
279
  `
169
280
  [Dev Launcher] Shutting down Dev Mode server and spawned Storybooks...`
170
281
  );
171
- for (const f of i) f.kill("SIGINT");
172
- process.exit(0);
282
+ for (const m of o) m.kill("SIGINT");
283
+ f.close().finally(() => process.exit(0));
173
284
  }
174
285
  };
175
- process.on("SIGINT", m), process.on("SIGTERM", m), u.listen(l, () => {
176
- console.log(`
286
+ process.on("SIGINT", P), process.on("SIGTERM", P);
287
+ const b = r.port ?? Se, N = r.port ? b : b + 19;
288
+ let k = b;
289
+ for (; ; )
290
+ try {
291
+ await new Promise((m, S) => {
292
+ const x = n.listen(k);
293
+ x.once("listening", () => m()), x.once("error", S);
294
+ });
295
+ break;
296
+ } catch (m) {
297
+ if (m.code !== "EADDRINUSE" || k >= N) {
298
+ console.error(
299
+ `[Dev Launcher] Dev Mode server failed to listen on port ${k}: ${m.message}. Pass --port <n> to use another port.`
300
+ );
301
+ for (const x of o) x.kill("SIGINT");
302
+ process.exit(1);
303
+ }
304
+ console.log(
305
+ `[Dev Launcher] Port ${k} is in use, trying ${k + 1}...`
306
+ ), k++;
307
+ }
308
+ console.log(`
177
309
  ====================================================
178
310
  🚀 Adapter Dev Mode proxy running at:
179
- http://localhost:${l}
311
+ http://localhost:${k}
180
312
  ====================================================
181
- `), ie(`http://localhost:${l}`);
182
- });
313
+ `), Te(`http://localhost:${k}`);
183
314
  }
184
- const ce = "http://json-schema.org/draft-07/schema#", de = "https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/adapter-tester.schema.json", ue = "adapter-tester.config.json", pe = "Configuration for @recursica/adapter-tester. All fields are optional — an adapter repo with a normal `storybook` npm script needs no config file at all.", le = "object", he = !1, me = { $schema: { type: "string", description: 'Optional pointer to this schema for editor autocomplete, e.g. "https://raw.githubusercontent.com/borderux/recursica/main/packages/adapter-tester/src/adapter-tester.schema.json".' }, name: { type: "string", description: `Label for this project's own target. Defaults to the unscoped name in this project's package.json (e.g. "@recursica/some-adapter" -> "some-adapter").` }, storybook: { type: "object", additionalProperties: !1, description: "This project's own Storybook target.", properties: { port: { type: "integer", minimum: 1, maximum: 65535, description: "First-guess port only, not authoritative — the real port is auto-detected from this Storybook's own startup output, since Storybook silently falls back to an OS-assigned port whenever this one is taken. Auto-detected from this project's own `scripts.storybook` (a `-p <port>`/`--port <port>` flag) when omitted, falling back to 6006." }, command: { type: "string", description: "Command that boots the target's Storybook. Defaults to `npm run storybook`." }, cwd: { type: "string", description: 'Directory the command runs in, relative to the config file. Defaults to ".".' } } }, sourceOfTruth: { description: 'The comparison target. Defaults to `{ "type": "mantine-harness" }`.', oneOf: [{ type: "object", additionalProperties: !1, description: "Default mode: boots a throwaway harness that installs the published @recursica/adapter-mantine-v8 from npm — no monorepo checkout required.", properties: { type: { const: "mantine-harness" }, port: { type: "integer", minimum: 1, maximum: 65535, description: "First-guess port only, not authoritative — the real port is auto-detected from the throwaway harness's Storybook's own startup output." }, mantineAdapterVersion: { type: "string", description: "npm version/tag of @recursica/adapter-mantine-v8 to install in the harness. Defaults to `latest`." }, storybookTemplateVersion: { type: "string", description: "npm version/tag of @recursica/storybook-template to install in the harness. Defaults to `latest`." }, tokensSource: { type: "string", enum: ["target", "mantine-package"], description: `Which Recursica token snapshot the harness's Mantine components render with. "target" (default): this project's own committed recursica_variables_scoped.css/recursica_tokens.json/recursica_brand.json/recursica_ui-kit.json — makes the divergence check mean exactly "given the same tokens this project is using, does it render the same as Mantine's own components," isolating adapter-mapping correctness from any token-snapshot drift between repos. "mantine-package": @recursica/adapter-mantine-v8's own bundled token files instead (whatever it was last published with) — also flags, as a side effect, a project whose own committed tokens have gone stale relative to Mantine's. Never the deprecated @recursica/official-release package.` } } }, { type: "object", additionalProperties: !1, description: "Non-standard mode: points at an already-addressable Storybook — e.g. a sibling workspace package's own Storybook inside this monorepo.", required: ["type"], properties: { type: { const: "url" }, name: { type: "string", description: 'Label for the source-of-truth target. Defaults to "Mantine".' }, port: { type: "integer", minimum: 1, maximum: 65535, description: "First-guess port only, not authoritative — the real port is auto-detected from the source-of-truth Storybook's own startup output. Defaults to 6011." }, command: { type: "string", description: "Command that boots the source-of-truth Storybook. Defaults to `npm run storybook`." }, cwd: { type: "string", description: 'Directory the command runs in, relative to the config file. Defaults to ".".' } } }] }, isSourceOfTruthAdapter: { type: "boolean", description: "True only for the source-of-truth adapter's own config (@recursica/adapter-mantine-v8). Skips `sourceOfTruth` entirely — there's nothing above it to diverge from — and runs the own-drift golden check standalone, against just this project's own Storybook. Defaults to false." }, goldenThresholdPixels: { type: "number", minimum: 0, description: "Global visual diff threshold for the own-drift check (this project's live render vs. its own committed `test/golden/` image), in mismatched pixels, before a story is considered a failure. Same library/component on both sides, so this should stay tight. Defaults to 10. AI agents must not modify this field — see AGENT.md.", default: 10 }, sourceOfTruthThresholdPixels: { type: "number", minimum: 0, description: "Global visual diff threshold for the source-of-truth divergence check (this project's golden vs. the source-of-truth adapter's golden), in mismatched pixels, before a difference is flagged. Comparing across two different component libraries has legitimate structural variation (native control widgets, font rendering, etc.), so this is expected to be set much higher than `goldenThresholdPixels`. Ignored on the source-of-truth adapter's own config (`isSourceOfTruthAdapter: true`), which has no divergence check. Defaults to 3500. AI agents must not modify this field — see AGENT.md.", default: 3500 }, stories: { type: "object", additionalProperties: { type: "object", additionalProperties: !1, properties: { goldenThreshold: { type: "number", minimum: 0, description: "Diff threshold override for matching stories' own-drift check, in mismatched pixels. Overrides `goldenThresholdPixels`." }, sourceOfTruthThreshold: { type: "number", minimum: 0, description: "Diff threshold override for matching stories' source-of-truth divergence check, in mismatched pixels. Overrides `sourceOfTruthThresholdPixels`." }, viewport: { type: "object", additionalProperties: !1, required: ["width", "height"], description: "Browser viewport (CSS pixels) matching stories are captured at, instead of the default 800x600. For stories whose rendering depends on the page width itself, such as `@media` breakpoints, which a story can't change from inside.", properties: { width: { type: "integer", minimum: 1 }, height: { type: "integer", minimum: 1 } } }, exclude: { type: "boolean", description: "Skip matching stories entirely — no own-drift check, no divergence check, no golden captured. For stories that don't have a cross-adapter counterpart; use `excludeTitlePrefixes` to drop a whole title instead." } } }, description: "Per-story overrides, keyed by story id prefix (a story matches if its id equals the key or starts with it). When more than one key matches a story, the longest (most specific) key wins." }, excludeTitlePrefixes: { type: "array", items: { type: "string" }, description: "Storybook title prefixes to exclude from comparison, in addition to the built-in denylist (`Theme/*`, `Tokens/*`, `Introduction`)." }, reportHeader: { type: "string", description: 'Overrides the AI report header text shown in Dev Mode\'s "Full Report" export. Defaults to the contents of `report-header.txt`.' } }, fe = {
185
- $schema: ce,
186
- $id: de,
187
- title: ue,
188
- description: pe,
189
- type: le,
190
- additionalProperties: he,
191
- properties: me
192
- }, ge = Z, V = new Q.Ajv({ allErrors: !0, strict: !0 });
193
- ge(V);
194
- const L = V.compile(fe);
195
- function ye(e, t) {
196
- if (L(e)) return;
197
- const r = (L.errors ?? []).map((o) => {
198
- var n;
199
- const s = (n = o.params) != null && n.additionalProperty ? ` '${o.params.additionalProperty}'` : "";
200
- return ` - ${o.instancePath || "root"} ${o.message}${s}`;
315
+ const Pe = "http://json-schema.org/draft-07/schema#", De = "https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/adapter-tester.schema.json", Ee = "adapter-tester.config.json", je = "Configuration for @recursica/adapter-tester. All fields are optional — an adapter repo with a normal `storybook` npm script needs no config file at all.", Ae = "object", Ne = !1, _e = { $schema: { type: "string", description: 'Optional pointer to this schema for editor autocomplete, e.g. "https://raw.githubusercontent.com/borderux/recursica/main/packages/adapter-tester/src/adapter-tester.schema.json".' }, name: { type: "string", description: `Label for this project's own target. Defaults to the unscoped name in this project's package.json (e.g. "@recursica/some-adapter" -> "some-adapter").` }, storybook: { type: "object", additionalProperties: !1, description: "This project's own Storybook target.", properties: { port: { type: "integer", minimum: 1, maximum: 65535, description: "First-guess port only, not authoritative — the real port is auto-detected from this Storybook's own startup output, since Storybook silently falls back to an OS-assigned port whenever this one is taken. Auto-detected from this project's own `scripts.storybook` (a `-p <port>`/`--port <port>` flag) when omitted, falling back to 6006." }, command: { type: "string", description: "Command that boots the target's Storybook. Defaults to `npm run storybook`." }, cwd: { type: "string", description: 'Directory the command runs in, relative to the config file. Defaults to ".".' } } }, sourceOfTruth: { description: 'The comparison target. Defaults to `{ "type": "mantine-harness" }`.', oneOf: [{ type: "object", additionalProperties: !1, description: "Default mode: boots a throwaway harness that installs the published @recursica/adapter-mantine-v8 from npm — no monorepo checkout required.", properties: { type: { const: "mantine-harness" }, port: { type: "integer", minimum: 1, maximum: 65535, description: "First-guess port only, not authoritative — the real port is auto-detected from the throwaway harness's Storybook's own startup output." }, mantineAdapterVersion: { type: "string", description: "npm version/tag of @recursica/adapter-mantine-v8 to install in the harness. Defaults to `latest`." }, storybookTemplateVersion: { type: "string", description: "npm version/tag of @recursica/storybook-template to install in the harness. Defaults to `latest`." }, tokensSource: { type: "string", enum: ["target", "mantine-package"], description: `Which Recursica token snapshot the harness's Mantine components render with. "target" (default): this project's own committed recursica_variables_scoped.css/recursica_tokens.json/recursica_brand.json/recursica_ui-kit.json — makes the divergence check mean exactly "given the same tokens this project is using, does it render the same as Mantine's own components," isolating adapter-mapping correctness from any token-snapshot drift between repos. "mantine-package": @recursica/adapter-mantine-v8's own bundled token files instead (whatever it was last published with) — also flags, as a side effect, a project whose own committed tokens have gone stale relative to Mantine's. Never the deprecated @recursica/official-release package.` } } }, { type: "object", additionalProperties: !1, description: "Non-standard mode: points at an already-addressable Storybook — e.g. a sibling workspace package's own Storybook inside this monorepo.", required: ["type"], properties: { type: { const: "url" }, name: { type: "string", description: 'Label for the source-of-truth target. Defaults to "Mantine".' }, port: { type: "integer", minimum: 1, maximum: 65535, description: "First-guess port only, not authoritative — the real port is auto-detected from the source-of-truth Storybook's own startup output. Defaults to 6011." }, command: { type: "string", description: "Command that boots the source-of-truth Storybook. Defaults to `npm run storybook`." }, cwd: { type: "string", description: 'Directory the command runs in, relative to the config file. Defaults to ".".' } } }] }, isSourceOfTruthAdapter: { type: "boolean", description: "True only for the source-of-truth adapter's own config (@recursica/adapter-mantine-v8). Skips `sourceOfTruth` entirely — there's nothing above it to diverge from — and runs the own-drift golden check standalone, against just this project's own Storybook. Defaults to false." }, goldenThresholdPixels: { type: "number", minimum: 0, description: "Global visual diff threshold for the own-drift check (this project's live render vs. its own committed `test/golden/` image), in mismatched pixels, before a story is considered a failure. Same library/component on both sides, so this should stay tight. Defaults to 10. AI agents must not modify this field — see AGENT.md.", default: 10 }, sourceOfTruthThresholdPixels: { type: "number", minimum: 0, description: "Global visual diff threshold for the source-of-truth divergence check (this project's golden vs. the source-of-truth adapter's golden), in mismatched pixels, before a difference is flagged. Comparing across two different component libraries has legitimate structural variation (native control widgets, font rendering, etc.), so this is expected to be set much higher than `goldenThresholdPixels`. Ignored on the source-of-truth adapter's own config (`isSourceOfTruthAdapter: true`), which has no divergence check. Defaults to 3500. AI agents must not modify this field — see AGENT.md.", default: 3500 }, stories: { type: "object", additionalProperties: { type: "object", additionalProperties: !1, properties: { goldenThreshold: { type: "number", minimum: 0, description: "Diff threshold override for matching stories' own-drift check, in mismatched pixels. Overrides `goldenThresholdPixels`." }, sourceOfTruthThreshold: { type: "number", minimum: 0, description: "Diff threshold override for matching stories' source-of-truth divergence check, in mismatched pixels. Overrides `sourceOfTruthThresholdPixels`." }, viewport: { type: "object", additionalProperties: !1, required: ["width", "height"], description: "Browser viewport (CSS pixels) matching stories are captured at, instead of the default 800x600. For stories whose rendering depends on the page width itself, such as `@media` breakpoints, which a story can't change from inside.", properties: { width: { type: "integer", minimum: 1 }, height: { type: "integer", minimum: 1 } } }, exclude: { type: "boolean", description: "Skip matching stories entirely — no own-drift check, no divergence check, no golden captured. For stories that don't have a cross-adapter counterpart; use `excludeTitlePrefixes` to drop a whole title instead." } } }, description: "Per-story overrides, keyed by story id prefix (a story matches if its id equals the key or starts with it). When more than one key matches a story, the longest (most specific) key wins." }, excludeTitlePrefixes: { type: "array", items: { type: "string" }, description: "Storybook title prefixes to exclude from comparison, in addition to the built-in denylist (`Theme/*`, `Tokens/*`, `Introduction`)." }, reportHeader: { type: "string", description: 'Overrides the AI report header text shown in Dev Mode\'s "Full Report" export. Defaults to the contents of `report-header.txt`.' } }, $e = {
316
+ $schema: Pe,
317
+ $id: De,
318
+ title: Ee,
319
+ description: je,
320
+ type: Ae,
321
+ additionalProperties: Ne,
322
+ properties: _e
323
+ }, Ie = pe, X = new le.Ajv({ allErrors: !0, strict: !0 });
324
+ Ie(X);
325
+ const V = X.compile($e);
326
+ function Le(e, t) {
327
+ if (V(e)) return;
328
+ const r = (V.errors ?? []).map((s) => {
329
+ var i;
330
+ const a = (i = s.params) != null && i.additionalProperty ? ` '${s.params.additionalProperty}'` : "";
331
+ return ` - ${s.instancePath || "root"} ${s.message}${a}`;
201
332
  }).join(`
202
333
  `);
203
334
  throw new Error(`Invalid ${t}:
204
335
  ${r}`);
205
336
  }
206
- const we = "@recursica/adapter-mantine-v8", ve = "adapter-tester.config.json", F = "npm run storybook", be = 6006, R = 6011, C = "Mantine", ke = 10, Te = 3500;
207
- function H(e) {
208
- const t = a(e, "package.json");
209
- if (!U(t))
337
+ const Re = "@recursica/adapter-mantine-v8", Y = "adapter-tester.config.json", q = "npm run storybook", Fe = 6006, J = 6011, H = "Mantine", Me = 10, Ce = 3500;
338
+ function Q(e) {
339
+ const t = l(e, "package.json");
340
+ if (!A(t))
210
341
  throw new Error(
211
342
  `No package.json found in ${e} — run adapter-tester from the root of the project being tested.`
212
343
  );
213
- return JSON.parse(T(t, "utf8"));
344
+ return JSON.parse(O(t, "utf8"));
214
345
  }
215
- function Se(e) {
216
- var s;
217
- const r = (s = H(e).scripts) == null ? void 0 : s.storybook, o = r == null ? void 0 : r.match(/(?:-p|--port)[ =](\d+)/);
218
- return o ? Number(o[1]) : be;
346
+ function Ge(e) {
347
+ var a;
348
+ const r = (a = Q(e).scripts) == null ? void 0 : a.storybook, s = r == null ? void 0 : r.match(/(?:-p|--port)[ =](\d+)/);
349
+ return s ? Number(s[1]) : Fe;
219
350
  }
220
- function Oe(e) {
221
- const r = H(e).name;
351
+ function Ue(e) {
352
+ const r = Q(e).name;
222
353
  return r ? r.split("/").pop() : "Adapter";
223
354
  }
224
- function xe(e) {
225
- const t = a(e, ve);
226
- if (!U(t))
355
+ function Ve(e) {
356
+ const t = l(e, Y);
357
+ if (!A(t))
227
358
  return {};
228
- const r = JSON.parse(T(t, "utf8"));
229
- return ye(r, t), r;
359
+ const r = JSON.parse(O(t, "utf8"));
360
+ return Le(r, t), r;
230
361
  }
231
- function Ee(e, t = {}) {
232
- var m, f, b;
233
- const r = xe(e), o = r.isSourceOfTruthAdapter ?? !1;
234
- if (t.mantineAdapterVersion && o)
362
+ function qe(e, t = {}) {
363
+ var f, v, P;
364
+ const r = Ve(e), s = r.isSourceOfTruthAdapter ?? !1;
365
+ if (t.mantineAdapterVersion && s)
235
366
  throw new Error(
236
367
  "--source-of-truth-version has no effect on the source-of-truth adapter's own config (isSourceOfTruthAdapter: true) — there's nothing to pin a version for."
237
368
  );
238
- const s = r.name ?? Oe(e), n = ((m = r.storybook) == null ? void 0 : m.port) ?? Se(e), p = ((f = r.storybook) == null ? void 0 : f.command) ?? F, l = $(e, ((b = r.storybook) == null ? void 0 : b.cwd) ?? "."), y = {
239
- command: p,
240
- port: n,
241
- cwd: l,
369
+ const a = r.name ?? Ue(e), i = ((f = r.storybook) == null ? void 0 : f.port) ?? Ge(e), g = ((v = r.storybook) == null ? void 0 : v.command) ?? q, y = C(e, ((P = r.storybook) == null ? void 0 : P.cwd) ?? "."), w = {
370
+ command: g,
371
+ port: i,
372
+ cwd: y,
242
373
  reuseExistingServer: !process.env.CI,
243
- cacheFile: a(e, ".adapter-tester", "last-port-own.json"),
374
+ cacheFile: l(e, ".adapter-tester", "last-port-own.json"),
244
375
  timeout: 120 * 1e3
245
- }, w = {
246
- goldenThresholdPixels: r.goldenThresholdPixels ?? ke,
247
- sourceOfTruthThresholdPixels: r.sourceOfTruthThresholdPixels ?? Te,
376
+ }, u = {
377
+ goldenThresholdPixels: r.goldenThresholdPixels ?? Me,
378
+ sourceOfTruthThresholdPixels: r.sourceOfTruthThresholdPixels ?? Ce,
248
379
  stories: r.stories,
249
380
  excludeTitlePrefixes: r.excludeTitlePrefixes,
250
381
  reportHeader: r.reportHeader,
@@ -254,153 +385,164 @@ function Ee(e, t = {}) {
254
385
  // to that package's own `test/golden/` (the same directory used when
255
386
  // that package runs adapter-tester directly), not a directory under
256
387
  // wherever this config file lives.
257
- goldenDir: a(l, "test", "golden"),
258
- isSourceOfTruthAdapter: o,
388
+ goldenDir: l(y, "test", "golden"),
389
+ isSourceOfTruthAdapter: s,
259
390
  // Overwritten by the CLI from --update-golden/--approve-divergence.
260
391
  goldenMode: "check",
261
392
  // Overwritten by the CLI from --divergence-only.
262
393
  checkMode: "own"
263
394
  };
264
- if (o)
395
+ if (s)
265
396
  return {
266
397
  engineConfig: {
267
- ...w,
268
- targets: [{ name: s, url: `http://localhost:${n}` }]
398
+ ...u,
399
+ targets: [{ name: a, url: `http://localhost:${i}` }]
269
400
  },
270
- webServers: [y]
401
+ webServers: [w]
271
402
  };
272
- const i = r.sourceOfTruth ?? {
403
+ const o = r.sourceOfTruth ?? {
273
404
  type: "mantine-harness"
274
405
  };
275
- let u, h, v, c;
276
- if (i.type === "url") {
406
+ let n, c, d, p;
407
+ if (o.type === "url") {
277
408
  if (t.mantineAdapterVersion)
278
409
  throw new Error(
279
410
  `--source-of-truth-version has no effect with sourceOfTruth.type: "url" — that mode reads a sibling package's local checkout directly, not a published version.`
280
411
  );
281
- u = i.name ?? C, h = i.port ?? R;
282
- const k = $(e, i.cwd ?? ".");
283
- v = {
284
- command: i.command ?? F,
285
- port: h,
286
- cwd: k,
412
+ n = o.name ?? H, c = o.port ?? J;
413
+ const b = C(e, o.cwd ?? ".");
414
+ d = {
415
+ command: o.command ?? q,
416
+ port: c,
417
+ cwd: b,
287
418
  reuseExistingServer: !process.env.CI,
288
- cacheFile: a(e, ".adapter-tester", "last-port-source-of-truth.json"),
419
+ cacheFile: l(e, ".adapter-tester", "last-port-source-of-truth.json"),
289
420
  timeout: 120 * 1e3
290
- }, c = {
421
+ }, p = {
291
422
  type: "local",
292
- dir: a(k, "test", "golden")
423
+ dir: l(b, "test", "golden")
293
424
  };
294
425
  } else {
295
- u = C, h = i.port ?? R;
296
- const k = t.mantineAdapterVersion ?? i.mantineAdapterVersion, S = i.tokensSource ?? "target";
297
- v = z({
298
- dir: a(e, ".adapter-tester/mantine-harness"),
299
- port: h,
300
- mantineAdapterVersion: k,
301
- storybookTemplateVersion: i.storybookTemplateVersion,
302
- tokensSource: S,
426
+ n = H, c = o.port ?? J;
427
+ const b = t.mantineAdapterVersion ?? o.mantineAdapterVersion, N = o.tokensSource ?? "target";
428
+ d = fe({
429
+ dir: l(e, ".adapter-tester/mantine-harness"),
430
+ port: c,
431
+ mantineAdapterVersion: b,
432
+ storybookTemplateVersion: o.storybookTemplateVersion,
433
+ tokensSource: N,
303
434
  // `ownCwd`, not `cwd` — the project actually being tested (same
304
435
  // reasoning `goldenDir` above uses), source of the 4 token files
305
436
  // copied into the harness when `tokensSource` is "target".
306
- targetDir: l
307
- }), c = {
437
+ targetDir: y
438
+ }), p = {
308
439
  type: "npm",
309
- packageName: we,
310
- versionSpec: k ?? "latest",
311
- cacheDir: a(e, ".adapter-tester/mantine-golden-cache")
440
+ packageName: Re,
441
+ versionSpec: b ?? "latest",
442
+ cacheDir: l(e, ".adapter-tester/mantine-golden-cache")
312
443
  };
313
444
  }
314
445
  return {
315
446
  engineConfig: {
316
- ...w,
447
+ ...u,
317
448
  targets: [
318
449
  {
319
- name: u,
320
- url: `http://localhost:${h}`,
450
+ name: n,
451
+ url: `http://localhost:${c}`,
321
452
  sourceOfTruth: !0
322
453
  },
323
- { name: s, url: `http://localhost:${n}` }
454
+ { name: a, url: `http://localhost:${i}` }
324
455
  ],
325
- sourceOfTruthGolden: c
456
+ sourceOfTruthGolden: p
326
457
  },
327
458
  // [sourceOfTruth, own] — Dev Mode needs both; the automated golden run
328
459
  // (cli.ts) only ever boots the last entry, since its divergence check
329
460
  // reads stored golden files, not a live source-of-truth page.
330
- webServers: [v, y]
461
+ webServers: [d, w]
331
462
  };
332
463
  }
333
- const Ae = N(G(import.meta.url)), De = B(a(Ae, "testing.js")).href, d = process.argv.slice(2);
334
- let J;
335
- const O = d.indexOf("--source-of-truth-version");
336
- if (O !== -1) {
337
- const e = d[O + 1];
464
+ const Je = M(z(import.meta.url)), He = oe(l(Je, "testing.js")).href, h = process.argv.slice(2);
465
+ let Z;
466
+ const _ = h.indexOf("--source-of-truth-version");
467
+ if (_ !== -1) {
468
+ const e = h[_ + 1];
338
469
  if (e === void 0 || e.startsWith("--"))
339
470
  throw new Error(
340
471
  "--source-of-truth-version requires a value, e.g. --source-of-truth-version 0.53.0"
341
472
  );
342
- J = e, d.splice(O, 2);
473
+ Z = e, h.splice(_, 2);
474
+ }
475
+ let D;
476
+ const $ = h.indexOf("--port");
477
+ if ($ !== -1) {
478
+ const e = h[$ + 1];
479
+ if (D = Number(e), e === void 0 || !Number.isInteger(D) || D <= 0)
480
+ throw new Error("--port requires a port number, e.g. --port 6020");
481
+ h.splice($, 2);
343
482
  }
344
- let q;
345
- const x = d.indexOf("--story");
346
- if (x !== -1) {
347
- const e = d[x + 1];
483
+ let ee;
484
+ const I = h.indexOf("--story");
485
+ if (I !== -1) {
486
+ const e = h[I + 1];
348
487
  if (e === void 0 || e.startsWith("--"))
349
488
  throw new Error(
350
489
  "--story requires a story id, e.g. --story ui-kit-button--loading"
351
490
  );
352
- q = e, d.splice(x, 2);
491
+ ee = e, h.splice(I, 2);
353
492
  }
354
- const P = process.cwd(), { engineConfig: g, webServers: j } = Ee(P, {
355
- mantineAdapterVersion: J
493
+ const j = process.cwd(), { engineConfig: T, webServers: F } = qe(j, {
494
+ mantineAdapterVersion: Z
356
495
  });
357
- d.includes("--update-golden") ? g.goldenMode = "update-golden" : d.includes("--approve-divergence") && (g.goldenMode = "approve-divergence");
358
- if (d.includes("--divergence-only")) {
359
- if (g.isSourceOfTruthAdapter)
496
+ h.includes("--update-golden") ? T.goldenMode = "update-golden" : h.includes("--approve-divergence") && (T.goldenMode = "approve-divergence");
497
+ if (h.includes("--divergence-only")) {
498
+ if (T.isSourceOfTruthAdapter)
360
499
  throw new Error(
361
500
  "--divergence-only has nothing to diverge from — isSourceOfTruthAdapter is true in this project's adapter-tester.config.json."
362
501
  );
363
- g.checkMode = "divergence";
364
- } else if (g.goldenMode === "approve-divergence")
502
+ T.checkMode = "divergence";
503
+ } else if (T.goldenMode === "approve-divergence")
365
504
  throw new Error(
366
505
  "--approve-divergence only makes sense with --divergence-only — there's nothing to approve without the divergence check running."
367
506
  );
368
- d.includes("--dry-run") && (console.log(JSON.stringify({ engineConfig: g, webServers: j }, null, 2)), process.exit(0));
369
- const Pe = /* @__PURE__ */ new Set([
507
+ h.includes("--dry-run") && (console.log(JSON.stringify({ engineConfig: T, webServers: F }, null, 2)), process.exit(0));
508
+ const We = /* @__PURE__ */ new Set([
370
509
  "--dry-run",
371
510
  "--serve",
372
511
  "--update-golden",
373
512
  "--approve-divergence",
374
513
  "--divergence-only"
375
- ]), je = d.filter((e) => !Pe.has(e));
376
- Ne().catch((e) => {
514
+ ]), Be = h.filter((e) => !We.has(e));
515
+ ze().catch((e) => {
377
516
  console.error(e), process.exitCode = 1;
378
517
  });
379
- async function Ne() {
380
- if (d.includes("--serve")) {
381
- if (g.isSourceOfTruthAdapter)
518
+ async function ze() {
519
+ if (h.includes("--serve")) {
520
+ if (T.isSourceOfTruthAdapter)
382
521
  throw new Error(
383
522
  "Dev Mode (--serve) has nothing to sync this project's Storybook against — isSourceOfTruthAdapter is true in this project's adapter-tester.config.json."
384
523
  );
385
- await ae(g, j);
524
+ await xe(T, F, {
525
+ port: D,
526
+ configPath: l(j, Y)
527
+ });
386
528
  } else {
387
- const e = j.at(-1);
529
+ const e = F.at(-1);
388
530
  if (!e)
389
531
  throw new Error("resolveConfig() returned no webServers to boot.");
390
- await $e(e, g);
532
+ await Ke(e, T);
391
533
  }
392
534
  }
393
- async function $e(e, t) {
394
- const r = a(P, ".adapter-tester/run");
395
- M(r, { recursive: !0 });
396
- const o = t.targets.at(-1);
397
- if (!o)
535
+ async function Ke(e, t) {
536
+ const r = l(j, ".adapter-tester/run");
537
+ B(r, { recursive: !0 });
538
+ const s = t.targets.at(-1);
539
+ if (!s)
398
540
  throw new Error("resolveConfig() returned no targets to check.");
399
- const { url: s, process: n } = await D(
400
- A(o.name, e)
541
+ const { url: a, process: i } = await R(
542
+ L(s.name, e)
401
543
  );
402
- o.url = s, E(
403
- a(r, "playwright.config.js"),
544
+ s.url = a, E(
545
+ l(r, "playwright.config.js"),
404
546
  `// Generated by \`adapter-tester\` — do not edit, regenerated on every run.
405
547
  import { defineConfig, devices } from "@playwright/test";
406
548
 
@@ -423,10 +565,10 @@ export default defineConfig({
423
565
  });
424
566
  `
425
567
  ), E(
426
- a(r, "visual-regression.spec.js"),
568
+ l(r, "visual-regression.spec.js"),
427
569
  `// Generated by \`adapter-tester\` — do not edit, regenerated on every run.
428
570
  import { test, expect } from "@playwright/test";
429
- import { resolveVisualRegressionPlan } from ${JSON.stringify(De)};
571
+ import { resolveVisualRegressionPlan } from ${JSON.stringify(He)};
430
572
 
431
573
  // \`test.describe\`/\`test\` are called here, not inside resolveVisualRegressionPlan,
432
574
  // so Playwright's HTML report groups the suite under this file instead of a
@@ -436,7 +578,7 @@ const { ownTargetName, suiteLabel, stories, missingFromSourceOfTruth, checkStory
436
578
 
437
579
  // Set from \`--story <story-id>\` — scopes this run to exactly one story and
438
580
  // skips the (suite-wide) parity check, which has nothing to do with it.
439
- const storyId = ${JSON.stringify(q ?? null)};
581
+ const storyId = ${JSON.stringify(ee ?? null)};
440
582
  const scopedStories = storyId
441
583
  ? stories.filter((story) => story.id === storyId)
442
584
  : stories;
@@ -462,20 +604,20 @@ test.describe(\`\${ownTargetName} — \${suiteLabel}\`, () => {
462
604
  `
463
605
  );
464
606
  try {
465
- const p = W(
607
+ const g = re(
466
608
  "npx",
467
609
  [
468
610
  "playwright",
469
611
  "test",
470
612
  "--config",
471
- a(r, "playwright.config.js"),
472
- ...je
613
+ l(r, "playwright.config.js"),
614
+ ...Be
473
615
  ],
474
- { stdio: "inherit", cwd: P, shell: process.platform === "win32" }
616
+ { stdio: "inherit", cwd: j, shell: process.platform === "win32" }
475
617
  );
476
- process.exitCode = p.status ?? 1;
618
+ process.exitCode = g.status ?? 1;
477
619
  } finally {
478
- n == null || n.kill("SIGTERM");
620
+ i == null || i.kill("SIGTERM");
479
621
  }
480
622
  }
481
623
  //# sourceMappingURL=cli.js.map