@recursica/adapter-tester 3.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -27
- package/dist/adapter-tester.schema.json.d.ts +36 -15
- package/dist/cli.cjs +41 -20
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +344 -218
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +38 -14
- package/dist/config.d.ts.map +1 -1
- package/dist/devServer.d.ts +1 -1
- package/dist/devServer.d.ts.map +1 -1
- package/dist/fileConfig.d.ts +25 -10
- package/dist/fileConfig.d.ts.map +1 -1
- package/dist/golden/diffPng.d.ts +12 -7
- package/dist/golden/diffPng.d.ts.map +1 -1
- package/dist/golden/manifestStore.d.ts +19 -1
- package/dist/golden/manifestStore.d.ts.map +1 -1
- package/dist/harness/mantineSourceOfTruth.d.ts +10 -1
- package/dist/harness/mantineSourceOfTruth.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{mantineSourceOfTruth-BUkqMNEo.js → mantineSourceOfTruth-DE7XOgea.js} +36 -34
- package/dist/mantineSourceOfTruth-DE7XOgea.js.map +1 -0
- package/dist/{mantineSourceOfTruth-BUb5MZy0.cjs → mantineSourceOfTruth-Dpe4mlmF.cjs} +6 -6
- package/dist/mantineSourceOfTruth-Dpe4mlmF.cjs.map +1 -0
- package/dist/portDiscovery.d.ts +39 -0
- package/dist/portDiscovery.d.ts.map +1 -0
- package/dist/testing/runVisualRegression.d.ts +69 -20
- package/dist/testing/runVisualRegression.d.ts.map +1 -1
- package/dist/testing.cjs +4 -3
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +286 -174
- package/dist/testing.js.map +1 -1
- package/package.json +2 -6
- package/src/adapter-tester.schema.json +36 -15
- package/dist/mantineSourceOfTruth-BUb5MZy0.cjs.map +0 -1
- package/dist/mantineSourceOfTruth-BUkqMNEo.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,323 +1,449 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn as
|
|
3
|
-
import { readFileSync as S,
|
|
4
|
-
import { dirname as
|
|
5
|
-
import { fileURLToPath as
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import { m as
|
|
10
|
-
import { a as
|
|
11
|
-
const
|
|
12
|
-
function
|
|
13
|
-
return new Promise((
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}),
|
|
18
|
-
|
|
19
|
-
}),
|
|
20
|
-
|
|
21
|
-
}),
|
|
2
|
+
import { spawn as _, spawnSync as q } from "node:child_process";
|
|
3
|
+
import { readFileSync as S, mkdirSync as F, writeFileSync as O, existsSync as I } from "node:fs";
|
|
4
|
+
import { dirname as D, join as a, resolve as j } from "node:path";
|
|
5
|
+
import { fileURLToPath as M, pathToFileURL as H } from "node:url";
|
|
6
|
+
import W from "express";
|
|
7
|
+
import { createProxyMiddleware as B } from "http-proxy-middleware";
|
|
8
|
+
import K from "node:net";
|
|
9
|
+
import { m as X } from "./mantineSourceOfTruth-DE7XOgea.js";
|
|
10
|
+
import { a as Y, i as z } from "./index-DqQzFSAH.js";
|
|
11
|
+
const Q = /\x1b\[[0-9;]*m/g, Z = /Local:\s*(https?:\/\/localhost:\d+)/;
|
|
12
|
+
function ee(e) {
|
|
13
|
+
return new Promise((t) => {
|
|
14
|
+
const r = new K.Socket();
|
|
15
|
+
r.setTimeout(200), r.once("connect", () => {
|
|
16
|
+
r.destroy(), t(!0);
|
|
17
|
+
}), r.once("timeout", () => {
|
|
18
|
+
r.destroy(), t(!1);
|
|
19
|
+
}), r.once("error", () => {
|
|
20
|
+
r.destroy(), t(!1);
|
|
21
|
+
}), r.connect(e, "127.0.0.1");
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
function te(e) {
|
|
25
|
+
try {
|
|
26
|
+
const t = JSON.parse(S(e, "utf8")).port;
|
|
27
|
+
return typeof t == "number" ? t : void 0;
|
|
28
|
+
} catch {
|
|
29
|
+
return;
|
|
29
30
|
}
|
|
30
|
-
return !1;
|
|
31
31
|
}
|
|
32
|
-
function
|
|
33
|
-
|
|
32
|
+
function re(e, t) {
|
|
33
|
+
F(D(e), { recursive: !0 }), O(e, JSON.stringify({ port: t }));
|
|
34
34
|
}
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
function oe(e, t) {
|
|
36
|
+
return new Promise((r, o) => {
|
|
37
|
+
var y, w;
|
|
38
|
+
const s = _(e.command, {
|
|
39
|
+
cwd: e.cwd,
|
|
40
|
+
stdio: "pipe",
|
|
41
|
+
shell: !0
|
|
42
|
+
});
|
|
43
|
+
let n = !1;
|
|
44
|
+
const d = setTimeout(() => {
|
|
45
|
+
n || (n = !0, s.kill(), o(
|
|
46
|
+
new Error(
|
|
47
|
+
`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
|
+
));
|
|
50
|
+
}, t), h = (i, u) => {
|
|
51
|
+
const l = i.toString();
|
|
52
|
+
for (const c of l.split(`
|
|
47
53
|
`))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if (c.trim()) {
|
|
55
|
+
const p = u ? `${e.name} SB ERROR` : `${e.name} SB`;
|
|
56
|
+
(u ? console.error : console.log)(`[${p}] ${c.trim()}`);
|
|
57
|
+
}
|
|
58
|
+
if (n) return;
|
|
59
|
+
const v = l.replace(Q, "").match(Z);
|
|
60
|
+
if (v) {
|
|
61
|
+
n = !0, clearTimeout(d);
|
|
62
|
+
const c = v[1], p = Number(new URL(c).port);
|
|
63
|
+
re(e.cacheFile, p), r({ url: c, port: p, process: s });
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
(y = s.stdout) == null || y.on("data", (i) => h(i, !1)), (w = s.stderr) == null || w.on("data", (i) => h(i, !0)), s.on("error", (i) => {
|
|
67
|
+
n || (n = !0, clearTimeout(d), o(i));
|
|
68
|
+
}), s.on("exit", (i) => {
|
|
69
|
+
n || (n = !0, clearTimeout(d), o(
|
|
70
|
+
new Error(
|
|
71
|
+
`${e.name}'s Storybook exited (code ${i}) before reporting its URL.`
|
|
72
|
+
)
|
|
73
|
+
));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function x(e, t) {
|
|
78
|
+
return {
|
|
79
|
+
name: e,
|
|
80
|
+
command: t.command,
|
|
81
|
+
cwd: t.cwd,
|
|
82
|
+
reuseExistingServer: t.reuseExistingServer,
|
|
83
|
+
cacheFile: t.cacheFile,
|
|
84
|
+
timeoutMs: t.timeout
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function A(e) {
|
|
88
|
+
if (e.reuseExistingServer) {
|
|
89
|
+
const t = te(e.cacheFile);
|
|
90
|
+
if (t !== void 0 && await ee(t))
|
|
91
|
+
return console.log(
|
|
92
|
+
`[Dev Launcher] ${e.name} is already running on port ${t} (reused).`
|
|
93
|
+
), {
|
|
94
|
+
url: `http://localhost:${t}`,
|
|
95
|
+
port: t,
|
|
96
|
+
process: null
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return console.log(`[Dev Launcher] Launching Storybook for ${e.name}...`), oe(e, e.timeoutMs ?? 120 * 1e3);
|
|
56
100
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
101
|
+
const N = D(M(import.meta.url));
|
|
102
|
+
function se(e) {
|
|
103
|
+
const t = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
104
|
+
console.log(`[Dev Launcher] Auto-launching browser: ${e}`), _(t, [e], { shell: process.platform === "win32" }).on(
|
|
60
105
|
"error",
|
|
61
|
-
(
|
|
62
|
-
console.error("[Dev Launcher] Failed to auto-launch browser:",
|
|
106
|
+
(r) => {
|
|
107
|
+
console.error("[Dev Launcher] Failed to auto-launch browser:", r);
|
|
63
108
|
}
|
|
64
109
|
);
|
|
65
110
|
}
|
|
66
|
-
function
|
|
67
|
-
const [
|
|
68
|
-
if (!(
|
|
111
|
+
async function ne(e, t, r = {}) {
|
|
112
|
+
const [o, s] = e.targets, [n, d] = t;
|
|
113
|
+
if (!(o != null && o.sourceOfTruth) || !s || s.sourceOfTruth || !n || !d)
|
|
69
114
|
throw new Error(
|
|
70
115
|
"adapter-tester dev mode requires exactly two targets: [sourceOfTruth, target] — check adapter-tester.config.json."
|
|
71
116
|
);
|
|
72
|
-
const h =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
117
|
+
const h = r.port ?? 6010;
|
|
118
|
+
console.log(
|
|
119
|
+
`[Dev Launcher] Resolving ${o.name} and ${s.name} Storybooks...`
|
|
120
|
+
);
|
|
121
|
+
const [y, w] = await Promise.all([
|
|
122
|
+
A(
|
|
123
|
+
x(o.name, n)
|
|
124
|
+
),
|
|
125
|
+
A(x(s.name, d))
|
|
126
|
+
]);
|
|
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 = W(), l = a(N, "../public"), v = a(N, "../report-header.txt");
|
|
131
|
+
u.get("/", (f, T, b) => {
|
|
132
|
+
if (f.query.path) {
|
|
133
|
+
b();
|
|
76
134
|
return;
|
|
77
135
|
}
|
|
78
|
-
const
|
|
136
|
+
const J = S(a(l, "index.html"), "utf8").replace(
|
|
79
137
|
"<head>",
|
|
80
138
|
`<head>
|
|
81
139
|
<script>window.__ADAPTER_TESTER__ = ${JSON.stringify({
|
|
82
140
|
ownName: s.name,
|
|
83
|
-
sourceOfTruthName:
|
|
84
|
-
sourceOfTruthPort:
|
|
141
|
+
sourceOfTruthName: o.name,
|
|
142
|
+
sourceOfTruthPort: y.port
|
|
85
143
|
})};<\/script>`
|
|
86
144
|
);
|
|
87
|
-
|
|
88
|
-
}),
|
|
145
|
+
T.send(J);
|
|
146
|
+
}), u.get("/report-header.txt", (f, T) => {
|
|
89
147
|
try {
|
|
90
|
-
|
|
148
|
+
T.type("text/plain").send(S(v, "utf8"));
|
|
91
149
|
} catch {
|
|
92
|
-
|
|
150
|
+
T.status(500).send("Error loading report-header.txt");
|
|
93
151
|
}
|
|
94
|
-
}),
|
|
152
|
+
}), u.use(
|
|
95
153
|
"/",
|
|
96
|
-
|
|
97
|
-
target:
|
|
154
|
+
B({
|
|
155
|
+
target: w.url,
|
|
98
156
|
changeOrigin: !0,
|
|
99
157
|
ws: !0
|
|
100
158
|
})
|
|
101
159
|
);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
v = !0, console.log(
|
|
160
|
+
let c = !1;
|
|
161
|
+
const p = () => {
|
|
162
|
+
if (!c) {
|
|
163
|
+
c = !0, console.log(
|
|
107
164
|
`
|
|
108
165
|
[Dev Launcher] Shutting down Dev Mode server and spawned Storybooks...`
|
|
109
166
|
);
|
|
110
|
-
for (const
|
|
167
|
+
for (const f of i) f.kill("SIGINT");
|
|
111
168
|
process.exit(0);
|
|
112
169
|
}
|
|
113
170
|
};
|
|
114
|
-
process.on("SIGINT",
|
|
171
|
+
process.on("SIGINT", p), process.on("SIGTERM", p), u.listen(h, () => {
|
|
115
172
|
console.log(`
|
|
116
173
|
====================================================
|
|
117
174
|
🚀 Adapter Dev Mode proxy running at:
|
|
118
175
|
http://localhost:${h}
|
|
119
176
|
====================================================
|
|
120
|
-
`);
|
|
121
|
-
const [p, d] = await Promise.all([
|
|
122
|
-
T(u),
|
|
123
|
-
T(n)
|
|
124
|
-
]);
|
|
125
|
-
p ? console.log(
|
|
126
|
-
`[Dev Launcher] ${r.name} is already running on port ${u}.`
|
|
127
|
-
) : m.push($(r.name, a)), d ? console.log(
|
|
128
|
-
`[Dev Launcher] ${s.name} is already running on port ${n}.`
|
|
129
|
-
) : m.push($(s.name, c));
|
|
130
|
-
const g = [];
|
|
131
|
-
p || g.push(j(u)), d || g.push(j(n)), g.length > 0 ? (console.log("[Dev Launcher] Waiting for Storybooks to be responsive..."), (await Promise.all(g)).every(Boolean) ? console.log("[Dev Launcher] All Storybooks are active and responsive!") : console.warn(
|
|
132
|
-
"[Dev Launcher] Warning: some Storybooks timed out during startup, but proceeding..."
|
|
133
|
-
)) : console.log("[Dev Launcher] Both Storybooks already active."), X(`http://localhost:${h}`);
|
|
177
|
+
`), se(`http://localhost:${h}`);
|
|
134
178
|
});
|
|
135
179
|
}
|
|
136
|
-
const
|
|
137
|
-
$schema:
|
|
138
|
-
$id:
|
|
139
|
-
title:
|
|
140
|
-
description:
|
|
141
|
-
type:
|
|
142
|
-
additionalProperties:
|
|
143
|
-
properties:
|
|
144
|
-
},
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
function
|
|
148
|
-
if (
|
|
149
|
-
const
|
|
150
|
-
var
|
|
151
|
-
const s = (
|
|
152
|
-
return ` - ${
|
|
180
|
+
const ie = "http://json-schema.org/draft-07/schema#", ae = "https://github.com/borderux/recursica/tree/main/packages/adapter-tester/src/adapter-tester.schema.json", ce = "adapter-tester.config.json", ue = "Configuration for @recursica/adapter-tester. All fields are optional — an adapter repo with a normal `storybook` npm script needs no config file at all.", de = "object", le = !1, pe = { $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/mantine-adapter 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/mantine-adapter 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`." } } }, { 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 (mantine-adapter). 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`." }, 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`)." } }, he = {
|
|
181
|
+
$schema: ie,
|
|
182
|
+
$id: ae,
|
|
183
|
+
title: ce,
|
|
184
|
+
description: ue,
|
|
185
|
+
type: de,
|
|
186
|
+
additionalProperties: le,
|
|
187
|
+
properties: pe
|
|
188
|
+
}, fe = z, U = new Y.Ajv({ allErrors: !0, strict: !0 });
|
|
189
|
+
fe(U);
|
|
190
|
+
const $ = U.compile(he);
|
|
191
|
+
function me(e, t) {
|
|
192
|
+
if ($(e)) return;
|
|
193
|
+
const r = ($.errors ?? []).map((o) => {
|
|
194
|
+
var n;
|
|
195
|
+
const s = (n = o.params) != null && n.additionalProperty ? ` '${o.params.additionalProperty}'` : "";
|
|
196
|
+
return ` - ${o.instancePath || "root"} ${o.message}${s}`;
|
|
153
197
|
}).join(`
|
|
154
198
|
`);
|
|
155
|
-
throw new Error(`Invalid ${
|
|
156
|
-
${
|
|
199
|
+
throw new Error(`Invalid ${t}:
|
|
200
|
+
${r}`);
|
|
157
201
|
}
|
|
158
|
-
const
|
|
159
|
-
function
|
|
160
|
-
const
|
|
161
|
-
if (!
|
|
202
|
+
const ge = "@recursica/mantine-adapter", ye = "adapter-tester.config.json", L = "npm run storybook", we = 6006, C = 6011, R = "Mantine", ve = 10, Te = 3500;
|
|
203
|
+
function G(e) {
|
|
204
|
+
const t = a(e, "package.json");
|
|
205
|
+
if (!I(t))
|
|
162
206
|
throw new Error(
|
|
163
|
-
`No package.json found in ${
|
|
207
|
+
`No package.json found in ${e} — run adapter-tester from the root of the project being tested.`
|
|
164
208
|
);
|
|
165
|
-
return JSON.parse(S(
|
|
209
|
+
return JSON.parse(S(t, "utf8"));
|
|
166
210
|
}
|
|
167
|
-
function
|
|
211
|
+
function be(e) {
|
|
168
212
|
var s;
|
|
169
|
-
const
|
|
170
|
-
return
|
|
213
|
+
const r = (s = G(e).scripts) == null ? void 0 : s.storybook, o = r == null ? void 0 : r.match(/(?:-p|--port)[ =](\d+)/);
|
|
214
|
+
return o ? Number(o[1]) : we;
|
|
171
215
|
}
|
|
172
|
-
function
|
|
173
|
-
const
|
|
174
|
-
return
|
|
216
|
+
function Se(e) {
|
|
217
|
+
const r = G(e).name;
|
|
218
|
+
return r ? r.split("/").pop() : "Adapter";
|
|
175
219
|
}
|
|
176
|
-
function
|
|
177
|
-
const
|
|
178
|
-
if (!
|
|
220
|
+
function ke(e) {
|
|
221
|
+
const t = a(e, ye);
|
|
222
|
+
if (!I(t))
|
|
179
223
|
return {};
|
|
180
|
-
const
|
|
181
|
-
return
|
|
224
|
+
const r = JSON.parse(S(t, "utf8"));
|
|
225
|
+
return me(r, t), r;
|
|
182
226
|
}
|
|
183
|
-
function
|
|
184
|
-
var
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
227
|
+
function Oe(e, t = {}) {
|
|
228
|
+
var p, f, T;
|
|
229
|
+
const r = ke(e), o = r.isSourceOfTruthAdapter ?? !1;
|
|
230
|
+
if (t.mantineAdapterVersion && o)
|
|
231
|
+
throw new Error(
|
|
232
|
+
"--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."
|
|
233
|
+
);
|
|
234
|
+
const s = r.name ?? Se(e), n = ((p = r.storybook) == null ? void 0 : p.port) ?? be(e), d = ((f = r.storybook) == null ? void 0 : f.command) ?? L, h = j(e, ((T = r.storybook) == null ? void 0 : T.cwd) ?? "."), y = {
|
|
235
|
+
command: d,
|
|
236
|
+
port: n,
|
|
237
|
+
cwd: h,
|
|
189
238
|
reuseExistingServer: !process.env.CI,
|
|
239
|
+
cacheFile: a(e, ".adapter-tester", "last-port-own.json"),
|
|
190
240
|
timeout: 120 * 1e3
|
|
191
|
-
},
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
241
|
+
}, w = {
|
|
242
|
+
goldenThresholdPixels: r.goldenThresholdPixels ?? ve,
|
|
243
|
+
sourceOfTruthThresholdPixels: r.sourceOfTruthThresholdPixels ?? Te,
|
|
244
|
+
stories: r.stories,
|
|
245
|
+
excludeTitlePrefixes: r.excludeTitlePrefixes,
|
|
196
246
|
// Keyed off `ownCwd`, not `cwd` — the project actually being tested, not
|
|
197
|
-
// wherever the config file happens to live. Matters
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
goldenDir:
|
|
203
|
-
isSourceOfTruthAdapter:
|
|
247
|
+
// wherever the config file happens to live. Matters whenever
|
|
248
|
+
// `storybook.cwd` points at a sibling package: its goldens must resolve
|
|
249
|
+
// to that package's own `test/golden/` (the same directory used when
|
|
250
|
+
// that package runs adapter-tester directly), not a directory under
|
|
251
|
+
// wherever this config file lives.
|
|
252
|
+
goldenDir: a(h, "test", "golden"),
|
|
253
|
+
isSourceOfTruthAdapter: o,
|
|
204
254
|
// Overwritten by the CLI from --update-golden/--approve-divergence.
|
|
205
|
-
goldenMode: "check"
|
|
255
|
+
goldenMode: "check",
|
|
256
|
+
// Overwritten by the CLI from --divergence-only.
|
|
257
|
+
checkMode: "own"
|
|
206
258
|
};
|
|
207
|
-
if (
|
|
259
|
+
if (o)
|
|
208
260
|
return {
|
|
209
261
|
engineConfig: {
|
|
210
|
-
...
|
|
211
|
-
targets: [{ name:
|
|
262
|
+
...w,
|
|
263
|
+
targets: [{ name: s, url: `http://localhost:${n}` }]
|
|
212
264
|
},
|
|
213
|
-
webServers: [
|
|
265
|
+
webServers: [y]
|
|
214
266
|
};
|
|
215
|
-
const
|
|
267
|
+
const i = r.sourceOfTruth ?? {
|
|
216
268
|
type: "mantine-harness"
|
|
217
269
|
};
|
|
218
|
-
let
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
270
|
+
let u, l, v, c;
|
|
271
|
+
if (i.type === "url") {
|
|
272
|
+
if (t.mantineAdapterVersion)
|
|
273
|
+
throw new Error(
|
|
274
|
+
`--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.`
|
|
275
|
+
);
|
|
276
|
+
u = i.name ?? R, l = i.port ?? C;
|
|
277
|
+
const b = j(e, i.cwd ?? ".");
|
|
278
|
+
v = {
|
|
279
|
+
command: i.command ?? L,
|
|
280
|
+
port: l,
|
|
281
|
+
cwd: b,
|
|
226
282
|
reuseExistingServer: !process.env.CI,
|
|
283
|
+
cacheFile: a(e, ".adapter-tester", "last-port-source-of-truth.json"),
|
|
227
284
|
timeout: 120 * 1e3
|
|
228
|
-
},
|
|
285
|
+
}, c = {
|
|
229
286
|
type: "local",
|
|
230
|
-
dir:
|
|
287
|
+
dir: a(b, "test", "golden")
|
|
231
288
|
};
|
|
232
|
-
} else
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
289
|
+
} else {
|
|
290
|
+
u = R, l = i.port ?? C;
|
|
291
|
+
const b = t.mantineAdapterVersion ?? i.mantineAdapterVersion;
|
|
292
|
+
v = X({
|
|
293
|
+
dir: a(e, ".adapter-tester/mantine-harness"),
|
|
294
|
+
port: l,
|
|
295
|
+
mantineAdapterVersion: b,
|
|
296
|
+
storybookTemplateVersion: i.storybookTemplateVersion
|
|
297
|
+
}), c = {
|
|
239
298
|
type: "npm",
|
|
240
|
-
packageName:
|
|
241
|
-
versionSpec:
|
|
242
|
-
cacheDir:
|
|
299
|
+
packageName: ge,
|
|
300
|
+
versionSpec: b ?? "latest",
|
|
301
|
+
cacheDir: a(e, ".adapter-tester/mantine-golden-cache")
|
|
243
302
|
};
|
|
303
|
+
}
|
|
244
304
|
return {
|
|
245
305
|
engineConfig: {
|
|
246
|
-
...
|
|
306
|
+
...w,
|
|
247
307
|
targets: [
|
|
248
308
|
{
|
|
249
|
-
name:
|
|
250
|
-
url: `http://localhost:${
|
|
309
|
+
name: u,
|
|
310
|
+
url: `http://localhost:${l}`,
|
|
251
311
|
sourceOfTruth: !0
|
|
252
312
|
},
|
|
253
|
-
{ name:
|
|
313
|
+
{ name: s, url: `http://localhost:${n}` }
|
|
254
314
|
],
|
|
255
|
-
sourceOfTruthGolden:
|
|
315
|
+
sourceOfTruthGolden: c
|
|
256
316
|
},
|
|
257
317
|
// [sourceOfTruth, own] — Dev Mode needs both; the automated golden run
|
|
258
318
|
// (cli.ts) only ever boots the last entry, since its divergence check
|
|
259
319
|
// reads stored golden files, not a live source-of-truth page.
|
|
260
|
-
webServers: [
|
|
320
|
+
webServers: [v, y]
|
|
261
321
|
};
|
|
262
322
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
323
|
+
const xe = D(M(import.meta.url)), Ae = H(a(xe, "testing.js")).href, g = process.argv.slice(2);
|
|
324
|
+
let V;
|
|
325
|
+
const k = g.indexOf("--source-of-truth-version");
|
|
326
|
+
if (k !== -1) {
|
|
327
|
+
const e = g[k + 1];
|
|
328
|
+
if (e === void 0 || e.startsWith("--"))
|
|
329
|
+
throw new Error(
|
|
330
|
+
"--source-of-truth-version requires a value, e.g. --source-of-truth-version 0.53.0"
|
|
331
|
+
);
|
|
332
|
+
V = e, g.splice(k, 2);
|
|
333
|
+
}
|
|
334
|
+
const E = process.cwd(), { engineConfig: m, webServers: P } = Oe(E, {
|
|
335
|
+
mantineAdapterVersion: V
|
|
336
|
+
});
|
|
337
|
+
g.includes("--update-golden") ? m.goldenMode = "update-golden" : g.includes("--approve-divergence") && (m.goldenMode = "approve-divergence");
|
|
338
|
+
if (g.includes("--divergence-only")) {
|
|
339
|
+
if (m.isSourceOfTruthAdapter)
|
|
340
|
+
throw new Error(
|
|
341
|
+
"--divergence-only has nothing to diverge from — isSourceOfTruthAdapter is true in this project's adapter-tester.config.json."
|
|
342
|
+
);
|
|
343
|
+
m.checkMode = "divergence";
|
|
344
|
+
} else if (m.goldenMode === "approve-divergence")
|
|
345
|
+
throw new Error(
|
|
346
|
+
"--approve-divergence only makes sense with --divergence-only — there's nothing to approve without the divergence check running."
|
|
347
|
+
);
|
|
348
|
+
g.includes("--dry-run") && (console.log(JSON.stringify({ engineConfig: m, webServers: P }, null, 2)), process.exit(0));
|
|
349
|
+
const Ee = /* @__PURE__ */ new Set([
|
|
267
350
|
"--dry-run",
|
|
268
351
|
"--serve",
|
|
269
352
|
"--update-golden",
|
|
270
|
-
"--approve-divergence"
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
353
|
+
"--approve-divergence",
|
|
354
|
+
"--divergence-only"
|
|
355
|
+
]), Pe = g.filter((e) => !Ee.has(e));
|
|
356
|
+
De().catch((e) => {
|
|
357
|
+
console.error(e), process.exitCode = 1;
|
|
358
|
+
});
|
|
359
|
+
async function De() {
|
|
360
|
+
if (g.includes("--serve")) {
|
|
361
|
+
if (m.isSourceOfTruthAdapter)
|
|
362
|
+
throw new Error(
|
|
363
|
+
"Dev Mode (--serve) has nothing to sync this project's Storybook against — isSourceOfTruthAdapter is true in this project's adapter-tester.config.json."
|
|
364
|
+
);
|
|
365
|
+
await ne(m, P);
|
|
366
|
+
} else {
|
|
367
|
+
const e = P.at(-1);
|
|
368
|
+
if (!e)
|
|
369
|
+
throw new Error("resolveConfig() returned no webServers to boot.");
|
|
370
|
+
await je(e, m);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function je(e, t) {
|
|
374
|
+
const r = a(E, ".adapter-tester/run");
|
|
375
|
+
F(r, { recursive: !0 });
|
|
376
|
+
const o = t.targets.at(-1);
|
|
377
|
+
if (!o)
|
|
378
|
+
throw new Error("resolveConfig() returned no targets to check.");
|
|
379
|
+
const { url: s, process: n } = await A(
|
|
380
|
+
x(o.name, e)
|
|
381
|
+
);
|
|
382
|
+
o.url = s, O(
|
|
383
|
+
a(r, "playwright.config.js"),
|
|
284
384
|
`// Generated by \`adapter-tester\` — do not edit, regenerated on every run.
|
|
285
385
|
import { defineConfig, devices } from "@playwright/test";
|
|
286
386
|
|
|
287
387
|
export default defineConfig({
|
|
288
|
-
testDir: ${JSON.stringify(
|
|
289
|
-
//
|
|
290
|
-
// story's
|
|
291
|
-
//
|
|
292
|
-
|
|
388
|
+
testDir: ${JSON.stringify(r)},
|
|
389
|
+
// Stories run across Playwright's default worker pool — each golden check
|
|
390
|
+
// only reads/writes its own story's manifest.json entry, under a lock, so
|
|
391
|
+
// concurrent workers never race each other (see \`updateManifestEntry\`).
|
|
392
|
+
// Override with \`--workers <n>\` (forwarded straight through to Playwright).
|
|
393
|
+
fullyParallel: true,
|
|
293
394
|
forbidOnly: !!process.env.CI,
|
|
294
395
|
retries: process.env.CI ? 2 : 0,
|
|
295
|
-
workers: 1,
|
|
296
396
|
reporter: [["html", { open: "never" }], ["list"]],
|
|
297
397
|
use: { trace: "on-first-retry" },
|
|
298
398
|
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
|
|
299
|
-
webServer
|
|
399
|
+
// No webServer entry — this project's own Storybook is already running by
|
|
400
|
+
// the time this config is used (see runAutomated() in cli.ts).
|
|
300
401
|
});
|
|
301
402
|
`
|
|
302
|
-
),
|
|
303
|
-
|
|
403
|
+
), O(
|
|
404
|
+
a(r, "visual-regression.spec.js"),
|
|
304
405
|
`// Generated by \`adapter-tester\` — do not edit, regenerated on every run.
|
|
305
|
-
import {
|
|
406
|
+
import { test, expect } from "@playwright/test";
|
|
407
|
+
import { resolveVisualRegressionPlan } from ${JSON.stringify(Ae)};
|
|
408
|
+
|
|
409
|
+
// \`test.describe\`/\`test\` are called here, not inside resolveVisualRegressionPlan,
|
|
410
|
+
// so Playwright's HTML report groups the suite under this file instead of a
|
|
411
|
+
// sourcemapped path into adapter-tester's own library code.
|
|
412
|
+
const { ownTargetName, suiteLabel, stories, missingFromSourceOfTruth, checkStory } =
|
|
413
|
+
await resolveVisualRegressionPlan(${JSON.stringify(t, null, 2)});
|
|
306
414
|
|
|
307
|
-
|
|
415
|
+
test.describe(\`\${ownTargetName} — \${suiteLabel}\`, () => {
|
|
416
|
+
if (missingFromSourceOfTruth.length > 0) {
|
|
417
|
+
test("story parity with source of truth", () => {
|
|
418
|
+
expect(
|
|
419
|
+
missingFromSourceOfTruth,
|
|
420
|
+
\`\${missingFromSourceOfTruth.length} stor(y/ies) exist in the source of truth but are missing here. Add the missing story, or mark it \\\`exclude: true\\\` under \\\`stories\\\` in adapter-tester.config.json if intentional.\`,
|
|
421
|
+
).toEqual([]);
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
for (const story of stories) {
|
|
425
|
+
test(story.id, async ({ browser }, testInfo) => {
|
|
426
|
+
await checkStory(story, browser, testInfo);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
});
|
|
308
430
|
`
|
|
309
431
|
);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
432
|
+
try {
|
|
433
|
+
const d = q(
|
|
434
|
+
"npx",
|
|
435
|
+
[
|
|
436
|
+
"playwright",
|
|
437
|
+
"test",
|
|
438
|
+
"--config",
|
|
439
|
+
a(r, "playwright.config.js"),
|
|
440
|
+
...Pe
|
|
441
|
+
],
|
|
442
|
+
{ stdio: "inherit", cwd: E, shell: process.platform === "win32" }
|
|
443
|
+
);
|
|
444
|
+
process.exitCode = d.status ?? 1;
|
|
445
|
+
} finally {
|
|
446
|
+
n == null || n.kill("SIGTERM");
|
|
447
|
+
}
|
|
322
448
|
}
|
|
323
449
|
//# sourceMappingURL=cli.js.map
|