@vizejs/vite-plugin-musea 0.33.0 → 0.34.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 +7 -7
- package/dist/a11y/index.d.mts +2 -0
- package/dist/a11y/index.mjs +2 -0
- package/dist/{a11y-7maCHrYD.js → a11y-DNCg2qCB.mjs} +16 -24
- package/dist/a11y-DNCg2qCB.mjs.map +1 -0
- package/dist/autogen/index.d.mts +66 -0
- package/dist/autogen/index.d.mts.map +1 -0
- package/dist/autogen/index.mjs +2 -0
- package/dist/{autogen-dfLosbY_.js → autogen-3-y1d0ou.mjs} +29 -21
- package/dist/autogen-3-y1d0ou.mjs.map +1 -0
- package/dist/cli/{index.d.ts → index.d.mts} +3 -24
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/{index.js → index.mjs} +62 -62
- package/dist/cli/index.mjs.map +1 -0
- package/dist/gallery/assets/{cssMode-B7TGecRV.js → cssMode-cT8muvO_.js} +1 -1
- package/dist/gallery/assets/{editor.api-BC1PBCLb.js → editor.api-aFfQJDkw.js} +1 -1
- package/dist/gallery/assets/{editor.main-DLQsz_NY.js → editor.main-DMqE48tW.js} +2 -2
- package/dist/gallery/assets/{freemarker2-D2ZPJ6qb.js → freemarker2-B6IBKB9r.js} +1 -1
- package/dist/gallery/assets/{handlebars-CcyadhwH.js → handlebars-zNX2LtzG.js} +1 -1
- package/dist/gallery/assets/{html-q3gp27Kd.js → html-BKYZbazO.js} +1 -1
- package/dist/gallery/assets/{htmlMode-T3iVYbeW.js → htmlMode-Bl1BwXnQ.js} +1 -1
- package/dist/gallery/assets/index-9OXG8DGI.css +1 -0
- package/dist/gallery/assets/{index-Cr99oWYP.js → index-Dltd3znx.js} +5 -5
- package/dist/gallery/assets/{javascript-S0QyTjKY.js → javascript-DwBI_Z0J.js} +1 -1
- package/dist/gallery/assets/{jsonMode-BIxPM6pZ.js → jsonMode-CBK55pb7.js} +1 -1
- package/dist/gallery/assets/{liquid-CNck0h86.js → liquid-D87ggETD.js} +1 -1
- package/dist/gallery/assets/{mdx-D6_ZVroO.js → mdx-CBs_aoHd.js} +1 -1
- package/dist/gallery/assets/{monaco.contribution-rExTCYiy.js → monaco.contribution-BHYQJQ-0.js} +2 -2
- package/dist/gallery/assets/{python-DSEg-0AY.js → python-CWI5d6bd.js} +1 -1
- package/dist/gallery/assets/{razor-kebzKtsv.js → razor-CPcSAg2x.js} +1 -1
- package/dist/gallery/assets/{tsMode-BZSsu7di.js → tsMode-1ZmI8w72.js} +1 -1
- package/dist/gallery/assets/{typescript-MSiFowAO.js → typescript-CVZCJg8D.js} +1 -1
- package/dist/gallery/assets/{xml-D6T21GmG.js → xml-B_rQCZJa.js} +1 -1
- package/dist/gallery/assets/{yaml-CVRjrT5r.js → yaml-gB0iHXGe.js} +1 -1
- package/dist/gallery/index.html +17 -14
- package/dist/index-BWuuTDDw.d.mts +151 -0
- package/dist/index-BWuuTDDw.d.mts.map +1 -0
- package/dist/{index.d.ts → index.d.mts} +6 -224
- package/dist/index.d.mts.map +1 -0
- package/dist/{index.js → index.mjs} +168 -178
- package/dist/index.mjs.map +1 -0
- package/dist/{vrt-D6OumJUH.d.ts → vrt-B4uxOrnN.d.mts} +6 -18
- package/dist/vrt-B4uxOrnN.d.mts.map +1 -0
- package/dist/{vrt-5_c9P1YY.js → vrt-CjFf5GR0.mjs} +39 -47
- package/dist/vrt-CjFf5GR0.mjs.map +1 -0
- package/dist/vrt.d.mts +2 -0
- package/dist/vrt.mjs +2 -0
- package/package.json +49 -49
- package/dist/a11y-7maCHrYD.js.map +0 -1
- package/dist/a11y-CjpWs0s0.js +0 -3
- package/dist/autogen-Dx-SIBf_.js +0 -3
- package/dist/autogen-dfLosbY_.js.map +0 -1
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/gallery/assets/index-Cp7AWs0x.css +0 -1
- package/dist/index.css +0 -496
- package/dist/index.css.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/vrt-5_c9P1YY.js.map +0 -1
- package/dist/vrt-D6OumJUH.d.ts.map +0 -1
- package/dist/vrt.d.ts +0 -2
- package/dist/vrt.js +0 -3
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { PNG } from "pngjs";
|
|
4
|
-
|
|
5
4
|
//#region src/vrt/comparison.ts
|
|
6
5
|
/**
|
|
6
|
+
* Pixel comparison utilities for VRT.
|
|
7
|
+
*
|
|
8
|
+
* Provides PNG reading/writing, color delta calculation using YIQ color space,
|
|
9
|
+
* and anti-aliasing detection for pixel-level image comparison.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
7
12
|
* Read PNG file and return PNG object.
|
|
8
13
|
*/
|
|
9
14
|
async function readPng(filepath) {
|
|
@@ -98,16 +103,13 @@ function matchGlob(filepath, pattern) {
|
|
|
98
103
|
function escapeHtml(str) {
|
|
99
104
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
100
105
|
}
|
|
101
|
-
|
|
102
106
|
//#endregion
|
|
103
107
|
//#region src/vrt/utils.ts
|
|
104
108
|
/**
|
|
105
109
|
* Build URL for variant preview.
|
|
106
110
|
*/
|
|
107
111
|
function buildVariantUrl(baseUrl, artPath, variantName) {
|
|
108
|
-
|
|
109
|
-
const encodedVariant = encodeURIComponent(variantName);
|
|
110
|
-
return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;
|
|
112
|
+
return `${baseUrl}/__musea__/preview?art=${encodeURIComponent(artPath)}&variant=${encodeURIComponent(variantName)}`;
|
|
111
113
|
}
|
|
112
114
|
/**
|
|
113
115
|
* Compute VRT summary statistics from a list of results.
|
|
@@ -122,7 +124,6 @@ function computeSummary(results, startTime) {
|
|
|
122
124
|
duration: Date.now() - startTime
|
|
123
125
|
};
|
|
124
126
|
}
|
|
125
|
-
|
|
126
127
|
//#endregion
|
|
127
128
|
//#region src/vrt/runner-comparison.ts
|
|
128
129
|
/**
|
|
@@ -137,9 +138,7 @@ async function captureAndCompare(runner, art, variantName, viewport, baseUrl) {
|
|
|
137
138
|
const capture = runner.getCapture();
|
|
138
139
|
const comparison = runner.getComparison();
|
|
139
140
|
const snapshotDir = options.snapshotDir;
|
|
140
|
-
const
|
|
141
|
-
const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;
|
|
142
|
-
const snapshotName = `${artBaseName}--${variantName}--${viewportName}.png`;
|
|
141
|
+
const snapshotName = `${path.basename(art.path, ".art.vue")}--${variantName}--${viewport.name || `${viewport.width}x${viewport.height}`}.png`;
|
|
143
142
|
const snapshotPath = path.join(snapshotDir, snapshotName);
|
|
144
143
|
const currentPath = path.join(snapshotDir, "current", snapshotName);
|
|
145
144
|
const diffPath = path.join(snapshotDir, "diff", snapshotName);
|
|
@@ -179,8 +178,7 @@ async function captureAndCompare(runner, art, variantName, viewport, baseUrl) {
|
|
|
179
178
|
path: currentPath,
|
|
180
179
|
fullPage: capture.fullPage
|
|
181
180
|
});
|
|
182
|
-
|
|
183
|
-
if (!hasBaseline) {
|
|
181
|
+
if (!await fileExists(snapshotPath)) {
|
|
184
182
|
await fs.promises.copyFile(currentPath, snapshotPath);
|
|
185
183
|
return {
|
|
186
184
|
artPath: art.path,
|
|
@@ -228,22 +226,22 @@ async function compareImages(baselinePath, currentPath, diffPath, comparison) {
|
|
|
228
226
|
const baseline = await readPng(baselinePath);
|
|
229
227
|
const current = await readPng(currentPath);
|
|
230
228
|
if (baseline.width !== current.width || baseline.height !== current.height) {
|
|
231
|
-
const width
|
|
232
|
-
const height
|
|
233
|
-
const diff
|
|
234
|
-
width
|
|
235
|
-
height
|
|
229
|
+
const width = Math.max(baseline.width, current.width);
|
|
230
|
+
const height = Math.max(baseline.height, current.height);
|
|
231
|
+
const diff = new PNG({
|
|
232
|
+
width,
|
|
233
|
+
height
|
|
236
234
|
});
|
|
237
|
-
for (let i = 0; i < diff
|
|
238
|
-
diff
|
|
239
|
-
diff
|
|
240
|
-
diff
|
|
241
|
-
diff
|
|
235
|
+
for (let i = 0; i < diff.data.length; i += 4) {
|
|
236
|
+
diff.data[i] = 255;
|
|
237
|
+
diff.data[i + 1] = 0;
|
|
238
|
+
diff.data[i + 2] = 0;
|
|
239
|
+
diff.data[i + 3] = 255;
|
|
242
240
|
}
|
|
243
|
-
await writePng(diff
|
|
241
|
+
await writePng(diff, diffPath);
|
|
244
242
|
return {
|
|
245
|
-
diffPixels: width
|
|
246
|
-
totalPixels: width
|
|
243
|
+
diffPixels: width * height,
|
|
244
|
+
totalPixels: width * height,
|
|
247
245
|
diffPercentage: 100
|
|
248
246
|
};
|
|
249
247
|
}
|
|
@@ -272,9 +270,7 @@ async function compareImages(baselinePath, currentPath, diffPath, comparison) {
|
|
|
272
270
|
const r2 = current.data[idx];
|
|
273
271
|
const g2 = current.data[idx + 1];
|
|
274
272
|
const b2 = current.data[idx + 2];
|
|
275
|
-
|
|
276
|
-
const delta = colorDelta(r1, g1, b1, a1, r2, g2, b2, a2);
|
|
277
|
-
if (delta > threshold * 255 * 255) if (useAntiAliasing && isAntiAliased(baseline, current, x, y, width, height)) {
|
|
273
|
+
if (colorDelta(r1, g1, b1, a1, r2, g2, b2, useAlpha ? current.data[idx + 3] : 255) > threshold * 255 * 255) if (useAntiAliasing && isAntiAliased(baseline, current, x, y, width, height)) {
|
|
278
274
|
diff.data[idx] = 255;
|
|
279
275
|
diff.data[idx + 1] = 200;
|
|
280
276
|
diff.data[idx + 2] = 0;
|
|
@@ -302,7 +298,6 @@ async function compareImages(baselinePath, currentPath, diffPath, comparison) {
|
|
|
302
298
|
diffPercentage
|
|
303
299
|
};
|
|
304
300
|
}
|
|
305
|
-
|
|
306
301
|
//#endregion
|
|
307
302
|
//#region src/vrt/runner.ts
|
|
308
303
|
/**
|
|
@@ -435,9 +430,8 @@ var MuseaVrtRunner = class {
|
|
|
435
430
|
},
|
|
436
431
|
deviceScaleFactor: viewport.deviceScaleFactor ?? 1
|
|
437
432
|
});
|
|
438
|
-
const page = await context.newPage();
|
|
439
433
|
return {
|
|
440
|
-
page,
|
|
434
|
+
page: await context.newPage(),
|
|
441
435
|
context
|
|
442
436
|
};
|
|
443
437
|
}
|
|
@@ -476,7 +470,7 @@ var MuseaVrtRunner = class {
|
|
|
476
470
|
let cleaned = 0;
|
|
477
471
|
try {
|
|
478
472
|
const files = await fs.promises.readdir(snapshotDir);
|
|
479
|
-
const validNames = new Set();
|
|
473
|
+
const validNames = /* @__PURE__ */ new Set();
|
|
480
474
|
for (const art of artFiles) {
|
|
481
475
|
const artBaseName = path.basename(art.path, ".art.vue");
|
|
482
476
|
for (const variant of art.variants) {
|
|
@@ -502,10 +496,15 @@ var MuseaVrtRunner = class {
|
|
|
502
496
|
return computeSummary(results, this.startTime);
|
|
503
497
|
}
|
|
504
498
|
};
|
|
505
|
-
|
|
506
499
|
//#endregion
|
|
507
500
|
//#region src/vrt/report.ts
|
|
508
501
|
/**
|
|
502
|
+
* VRT report generation for Musea.
|
|
503
|
+
*
|
|
504
|
+
* Generates HTML and JSON reports from VRT results for visual review
|
|
505
|
+
* and CI integration.
|
|
506
|
+
*/
|
|
507
|
+
/**
|
|
509
508
|
* Generate VRT report in HTML format.
|
|
510
509
|
* Supports side-by-side, overlay, and slider comparison modes.
|
|
511
510
|
*/
|
|
@@ -517,14 +516,14 @@ function generateVrtReport(results, summary) {
|
|
|
517
516
|
if (minutes === 0) return `${seconds}s`;
|
|
518
517
|
return `${minutes}m ${seconds % 60}s`;
|
|
519
518
|
};
|
|
520
|
-
const timestamp = new Date().toLocaleString("ja-JP", {
|
|
519
|
+
const timestamp = (/* @__PURE__ */ new Date()).toLocaleString("ja-JP", {
|
|
521
520
|
year: "numeric",
|
|
522
521
|
month: "2-digit",
|
|
523
522
|
day: "2-digit",
|
|
524
523
|
hour: "2-digit",
|
|
525
524
|
minute: "2-digit"
|
|
526
525
|
});
|
|
527
|
-
|
|
526
|
+
return `<!DOCTYPE html>
|
|
528
527
|
<html lang="en">
|
|
529
528
|
<head>
|
|
530
529
|
<meta charset="UTF-8">
|
|
@@ -695,12 +694,7 @@ function generateVrtReport(results, summary) {
|
|
|
695
694
|
const viewportName = r.viewport.name || `${r.viewport.width}×${r.viewport.height}`;
|
|
696
695
|
let details = "";
|
|
697
696
|
if (r.error) details = `<div class="result-details error">${escapeHtml(r.error)}</div>`;
|
|
698
|
-
else if (r.diffPercentage !== void 0) {
|
|
699
|
-
const diffFormatted = r.diffPercentage.toFixed(3);
|
|
700
|
-
const pixelsFormatted = r.diffPixels?.toLocaleString() ?? "0";
|
|
701
|
-
const totalFormatted = r.totalPixels?.toLocaleString() ?? "0";
|
|
702
|
-
details = `<div class="result-details">diff: ${diffFormatted}% (${pixelsFormatted} / ${totalFormatted} pixels)</div>`;
|
|
703
|
-
}
|
|
697
|
+
else if (r.diffPercentage !== void 0) details = `<div class="result-details">diff: ${r.diffPercentage.toFixed(3)}% (${r.diffPixels?.toLocaleString() ?? "0"} / ${r.totalPixels?.toLocaleString() ?? "0"} pixels)</div>`;
|
|
704
698
|
let images = "";
|
|
705
699
|
if (!r.error && !r.passed && r.diffPath) images = `<div class="result-images" data-baseline="file://${r.snapshotPath}" data-current="file://${r.currentPath}" data-diff="file://${r.diffPath}">
|
|
706
700
|
${r.snapshotPath ? `<div class="image-container"><div class="image-label">Baseline</div><div class="image-wrapper"><img src="file://${r.snapshotPath}" alt="Baseline" loading="lazy" /></div></div>` : ""}
|
|
@@ -743,17 +737,16 @@ function generateVrtReport(results, summary) {
|
|
|
743
737
|
// Mode switching would update result-images display; this is a static report for now
|
|
744
738
|
});
|
|
745
739
|
});
|
|
746
|
-
|
|
740
|
+
<\/script>
|
|
747
741
|
</body>
|
|
748
742
|
</html>`;
|
|
749
|
-
return html;
|
|
750
743
|
}
|
|
751
744
|
/**
|
|
752
745
|
* Generate VRT JSON report for CI integration.
|
|
753
746
|
*/
|
|
754
747
|
function generateVrtJsonReport(results, summary) {
|
|
755
748
|
return JSON.stringify({
|
|
756
|
-
timestamp: new Date().toISOString(),
|
|
749
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
757
750
|
summary,
|
|
758
751
|
results: results.map((r) => ({
|
|
759
752
|
art: path.basename(r.artPath, ".art.vue"),
|
|
@@ -765,11 +758,10 @@ function generateVrtJsonReport(results, summary) {
|
|
|
765
758
|
}))
|
|
766
759
|
}, null, 2);
|
|
767
760
|
}
|
|
768
|
-
|
|
769
761
|
//#endregion
|
|
770
762
|
//#region src/vrt.ts
|
|
771
763
|
var vrt_default = MuseaVrtRunner;
|
|
772
|
-
|
|
773
764
|
//#endregion
|
|
774
|
-
export { MuseaVrtRunner, generateVrtJsonReport, generateVrtReport, vrt_default };
|
|
775
|
-
|
|
765
|
+
export { MuseaVrtRunner as i, generateVrtJsonReport as n, generateVrtReport as r, vrt_default as t };
|
|
766
|
+
|
|
767
|
+
//# sourceMappingURL=vrt-CjFf5GR0.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vrt-CjFf5GR0.mjs","names":[],"sources":["../src/vrt/comparison.ts","../src/vrt/utils.ts","../src/vrt/runner-comparison.ts","../src/vrt/runner.ts","../src/vrt/report.ts","../src/vrt.ts"],"sourcesContent":["/**\n * Pixel comparison utilities for VRT.\n *\n * Provides PNG reading/writing, color delta calculation using YIQ color space,\n * and anti-aliasing detection for pixel-level image comparison.\n */\n\nimport fs from \"node:fs\";\nimport { PNG } from \"pngjs\";\n\n/**\n * Read PNG file and return PNG object.\n */\nexport async function readPng(filepath: string): Promise<PNG> {\n return new Promise((resolve, reject) => {\n fs.createReadStream(filepath)\n .pipe(new PNG())\n .on(\"parsed\", function (this: PNG) {\n resolve(this);\n })\n .on(\"error\", reject);\n });\n}\n\n/**\n * Write PNG object to file.\n */\nexport async function writePng(png: PNG, filepath: string): Promise<void> {\n return new Promise((resolve, reject) => {\n png.pack().pipe(fs.createWriteStream(filepath)).on(\"finish\", resolve).on(\"error\", reject);\n });\n}\n\n/**\n * Calculate color delta using YIQ color space.\n */\nexport function colorDelta(\n r1: number,\n g1: number,\n b1: number,\n a1: number,\n r2: number,\n g2: number,\n b2: number,\n a2: number,\n): number {\n if (a1 !== 255) {\n r1 = blend(r1, 255, a1 / 255);\n g1 = blend(g1, 255, a1 / 255);\n b1 = blend(b1, 255, a1 / 255);\n }\n if (a2 !== 255) {\n r2 = blend(r2, 255, a2 / 255);\n g2 = blend(g2, 255, a2 / 255);\n b2 = blend(b2, 255, a2 / 255);\n }\n\n const y1 = r1 * 0.29889531 + g1 * 0.58662247 + b1 * 0.11448223;\n const i1 = r1 * 0.59597799 - g1 * 0.2741761 - b1 * 0.32180189;\n const q1 = r1 * 0.21147017 - g1 * 0.52261711 + b1 * 0.31114694;\n\n const y2 = r2 * 0.29889531 + g2 * 0.58662247 + b2 * 0.11448223;\n const i2 = r2 * 0.59597799 - g2 * 0.2741761 - b2 * 0.32180189;\n const q2 = r2 * 0.21147017 - g2 * 0.52261711 + b2 * 0.31114694;\n\n const dy = y1 - y2;\n const di = i1 - i2;\n const dq = q1 - q2;\n\n return dy * dy * 0.5053 + di * di * 0.299 + dq * dq * 0.1957;\n}\n\n/**\n * Blend foreground with background using alpha.\n */\nfunction blend(fg: number, bg: number, alpha: number): number {\n return bg + (fg - bg) * alpha;\n}\n\n/**\n * Check if file exists.\n */\nexport async function fileExists(filepath: string): Promise<boolean> {\n try {\n await fs.promises.access(filepath);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Simple anti-aliasing detection.\n * A pixel is likely anti-aliased if its neighbors have high contrast in opposite directions.\n */\nexport function isAntiAliased(\n img1: PNG,\n img2: PNG,\n x: number,\n y: number,\n width: number,\n height: number,\n): boolean {\n const minX = Math.max(0, x - 1);\n const maxX = Math.min(width - 1, x + 1);\n const minY = Math.max(0, y - 1);\n const maxY = Math.min(height - 1, y + 1);\n\n let zeroes = 0;\n let positives = 0;\n let negatives = 0;\n\n for (let ny = minY; ny <= maxY; ny++) {\n for (let nx = minX; nx <= maxX; nx++) {\n if (nx === x && ny === y) continue;\n const idx = (ny * width + nx) * 4;\n\n const delta = colorDelta(\n img1.data[idx],\n img1.data[idx + 1],\n img1.data[idx + 2],\n img1.data[idx + 3],\n img2.data[idx],\n img2.data[idx + 1],\n img2.data[idx + 2],\n img2.data[idx + 3],\n );\n\n if (delta === 0) {\n zeroes++;\n } else if (delta > 0) {\n positives++;\n } else {\n negatives++;\n }\n }\n }\n\n // If neighbors are mixed (some match, some differ), it's likely AA\n return zeroes > 0 && (positives > 0 || negatives > 0) && positives + negatives < 4;\n}\n\n/**\n * Simple glob matching for pattern-based filtering.\n */\nexport function matchGlob(filepath: string, pattern: string): boolean {\n const regex = pattern\n .replace(/\\./g, \"\\\\.\")\n .replace(/\\*\\*/g, \".*\")\n .replace(/\\*(?!\\*)/g, \"[^/]*\");\n return new RegExp(`^${regex}$`).test(filepath);\n}\n\n/**\n * Escape HTML special characters.\n */\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","/**\n * Utility functions for VRT runner.\n *\n * Includes summary generation, URL builders, and helpers\n * used across the runner modules.\n */\n\nimport type { VrtResult, VrtSummary } from \"./types.js\";\n\n/**\n * Build URL for variant preview.\n */\nexport function buildVariantUrl(baseUrl: string, artPath: string, variantName: string): string {\n const encodedPath = encodeURIComponent(artPath);\n const encodedVariant = encodeURIComponent(variantName);\n return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;\n}\n\n/**\n * Compute VRT summary statistics from a list of results.\n */\nexport function computeSummary(results: VrtResult[], startTime: number): VrtSummary {\n return {\n total: results.length,\n passed: results.filter((r) => r.passed && !r.isNew).length,\n failed: results.filter((r) => !r.passed && !r.error).length,\n new: results.filter((r) => r.isNew).length,\n skipped: results.filter((r) => r.error).length,\n duration: Date.now() - startTime,\n };\n}\n","/**\n * Screenshot capture and pixel comparison logic for VRT.\n *\n * Contains the captureAndCompare flow (browser context, navigation,\n * element hiding/masking, screenshot, baseline check) and the\n * pixel-level compareImages implementation.\n */\n\nimport type { BrowserContext, Page } from \"playwright\";\nimport type { ArtFileInfo, ViewportConfig } from \"../types/index.js\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { PNG } from \"pngjs\";\n\nimport { readPng, writePng, colorDelta, isAntiAliased, fileExists } from \"./comparison.js\";\nimport type { VrtResult } from \"./types.js\";\nimport type { MuseaVrtRunner } from \"./runner.js\";\nimport { buildVariantUrl } from \"./utils.js\";\n\n/**\n * Capture screenshot and compare with baseline.\n *\n * Standalone function that operates on a MuseaVrtRunner instance.\n */\nexport async function captureAndCompare(\n runner: MuseaVrtRunner,\n art: ArtFileInfo,\n variantName: string,\n viewport: ViewportConfig,\n baseUrl: string,\n): Promise<VrtResult> {\n const browser = runner.getBrowser();\n if (!browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n\n const options = runner.getOptions();\n const capture = runner.getCapture();\n const comparison = runner.getComparison();\n\n const snapshotDir = options.snapshotDir;\n const artBaseName = path.basename(art.path, \".art.vue\");\n const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;\n const snapshotName = `${artBaseName}--${variantName}--${viewportName}.png`;\n const snapshotPath = path.join(snapshotDir, snapshotName);\n const currentPath = path.join(snapshotDir, \"current\", snapshotName);\n const diffPath = path.join(snapshotDir, \"diff\", snapshotName);\n\n // Ensure directories exist\n await fs.promises.mkdir(path.dirname(snapshotPath), { recursive: true });\n await fs.promises.mkdir(path.join(snapshotDir, \"current\"), { recursive: true });\n await fs.promises.mkdir(path.join(snapshotDir, \"diff\"), { recursive: true });\n\n let context: BrowserContext | null = null;\n let page: Page | null = null;\n\n try {\n context = await browser.newContext({\n viewport: {\n width: viewport.width,\n height: viewport.height,\n },\n deviceScaleFactor: viewport.deviceScaleFactor ?? 1,\n });\n page = await context.newPage();\n\n // Navigate to variant preview URL\n const variantUrl = buildVariantUrl(baseUrl, art.path, variantName);\n const waitUntil = capture.waitForNetwork ? (\"networkidle\" as const) : (\"load\" as const);\n await page.goto(variantUrl, { waitUntil });\n\n // Wait for content to render\n await page.waitForSelector(capture.waitSelector, { timeout: 10000 });\n\n // Additional wait for animations to settle\n await page.waitForTimeout(capture.settleTime);\n\n // Hide elements before capture\n if (capture.hideElements.length > 0) {\n for (const selector of capture.hideElements) {\n await page.evaluate((sel) => {\n document.querySelectorAll(sel).forEach((el) => {\n (el as HTMLElement).style.visibility = \"hidden\";\n });\n }, selector);\n }\n }\n\n // Mask elements before capture (replace with colored box)\n if (capture.maskElements.length > 0) {\n for (const selector of capture.maskElements) {\n await page.evaluate((sel) => {\n document.querySelectorAll(sel).forEach((el) => {\n const htmlEl = el as HTMLElement;\n htmlEl.style.background = \"#ff00ff\";\n htmlEl.style.color = \"transparent\";\n htmlEl.innerHTML = \"\";\n });\n }, selector);\n }\n }\n\n // Take screenshot\n await page.screenshot({\n path: currentPath,\n fullPage: capture.fullPage,\n });\n\n // Check if baseline exists\n const hasBaseline = await fileExists(snapshotPath);\n\n if (!hasBaseline) {\n // First run - save as baseline\n await fs.promises.copyFile(currentPath, snapshotPath);\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed: true,\n snapshotPath,\n currentPath,\n isNew: true,\n };\n }\n\n // Compare images using pixel comparison\n const comparisonResult = await compareImages(snapshotPath, currentPath, diffPath, comparison);\n\n const passed = comparisonResult.diffPercentage <= options.threshold;\n\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed,\n snapshotPath,\n currentPath,\n diffPath: passed ? undefined : diffPath,\n diffPercentage: comparisonResult.diffPercentage,\n diffPixels: comparisonResult.diffPixels,\n totalPixels: comparisonResult.totalPixels,\n };\n } catch (error) {\n return {\n artPath: art.path,\n variantName,\n viewport,\n passed: false,\n snapshotPath,\n error: error instanceof Error ? error.message : String(error),\n };\n } finally {\n if (page) await page.close();\n if (context) await context.close();\n }\n}\n\n/**\n * Compare two PNG images and generate a diff image.\n * Returns pixel difference statistics.\n */\nexport async function compareImages(\n baselinePath: string,\n currentPath: string,\n diffPath: string,\n comparison: {\n antiAliasing?: boolean;\n alpha?: boolean;\n diffColor?: { r: number; g: number; b: number };\n },\n): Promise<{ diffPixels: number; totalPixels: number; diffPercentage: number }> {\n const baseline = await readPng(baselinePath);\n const current = await readPng(currentPath);\n\n // Handle size mismatch\n if (baseline.width !== current.width || baseline.height !== current.height) {\n const width = Math.max(baseline.width, current.width);\n const height = Math.max(baseline.height, current.height);\n const diff = new PNG({ width, height });\n\n // Fill with red to indicate size mismatch\n for (let i = 0; i < diff.data.length; i += 4) {\n diff.data[i] = 255; // R\n diff.data[i + 1] = 0; // G\n diff.data[i + 2] = 0; // B\n diff.data[i + 3] = 255; // A\n }\n\n await writePng(diff, diffPath);\n\n return {\n diffPixels: width * height,\n totalPixels: width * height,\n diffPercentage: 100,\n };\n }\n\n const width = baseline.width;\n const height = baseline.height;\n const totalPixels = width * height;\n const diff = new PNG({ width, height });\n\n const useAntiAliasing = comparison.antiAliasing ?? true;\n const useAlpha = comparison.alpha ?? true;\n const diffColor = comparison.diffColor ?? { r: 255, g: 0, b: 0 };\n\n // Pixel comparison\n let diffPixels = 0;\n const threshold = 0.1; // Color difference threshold\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const idx = (y * width + x) * 4;\n\n const r1 = baseline.data[idx];\n const g1 = baseline.data[idx + 1];\n const b1 = baseline.data[idx + 2];\n const a1 = useAlpha ? baseline.data[idx + 3] : 255;\n\n const r2 = current.data[idx];\n const g2 = current.data[idx + 1];\n const b2 = current.data[idx + 2];\n const a2 = useAlpha ? current.data[idx + 3] : 255;\n\n // Calculate color difference using YIQ color space\n const delta = colorDelta(r1, g1, b1, a1, r2, g2, b2, a2);\n\n if (delta > threshold * 255 * 255) {\n // Anti-aliasing detection: check if pixel is likely AA\n if (useAntiAliasing && isAntiAliased(baseline, current, x, y, width, height)) {\n // Mark as AA (yellow)\n diff.data[idx] = 255;\n diff.data[idx + 1] = 200;\n diff.data[idx + 2] = 0;\n diff.data[idx + 3] = 128;\n } else {\n // Mark as different\n diffPixels++;\n diff.data[idx] = diffColor.r;\n diff.data[idx + 1] = diffColor.g;\n diff.data[idx + 2] = diffColor.b;\n diff.data[idx + 3] = 255;\n }\n } else {\n // Grayscale for matching pixels\n const gray = Math.round((r2 + g2 + b2) / 3);\n diff.data[idx] = gray;\n diff.data[idx + 1] = gray;\n diff.data[idx + 2] = gray;\n diff.data[idx + 3] = 128; // Semi-transparent\n }\n }\n }\n\n // Only write diff if there are differences\n if (diffPixels > 0) {\n await writePng(diff, diffPath);\n }\n\n const diffPercentage = (diffPixels / totalPixels) * 100;\n\n return {\n diffPixels,\n totalPixels,\n diffPercentage,\n };\n}\n","/**\n * VRT runner using Playwright for browser automation.\n *\n * Manages browser lifecycle, screenshot capture, and baseline comparison\n * for visual regression testing of Musea art file variants.\n */\n\nimport type { Browser, BrowserContext, Page } from \"playwright\";\nimport type {\n ArtFileInfo,\n VrtOptions,\n ViewportConfig,\n CaptureConfig,\n ComparisonConfig,\n CiConfig,\n} from \"../types/index.js\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { fileExists, matchGlob } from \"./comparison.js\";\nimport { captureAndCompare } from \"./runner-comparison.js\";\nimport { computeSummary } from \"./utils.js\";\n\nexport type { VrtResult, VrtSummary, ExtendedVrtOptions, PixelCompareOptions } from \"./types.js\";\n\nimport type { VrtResult, VrtSummary, ExtendedVrtOptions } from \"./types.js\";\n\n/**\n * VRT runner using Playwright.\n */\nexport class MuseaVrtRunner {\n private options: Required<VrtOptions>;\n private capture: Required<CaptureConfig>;\n private comparison: ComparisonConfig;\n private ci: CiConfig;\n private browser: Browser | null = null;\n private startTime: number = 0;\n\n constructor(options: ExtendedVrtOptions = {}) {\n this.options = {\n snapshotDir: options.snapshotDir ?? \".vize/snapshots\",\n threshold: options.threshold ?? 0.1,\n viewports: options.viewports ?? [\n { width: 1280, height: 720, name: \"desktop\" },\n { width: 375, height: 667, name: \"mobile\" },\n ],\n };\n this.capture = {\n fullPage: options.capture?.fullPage ?? false,\n waitForNetwork: options.capture?.waitForNetwork ?? true,\n settleTime: options.capture?.settleTime ?? 100,\n waitSelector: options.capture?.waitSelector ?? \".musea-variant\",\n hideElements: options.capture?.hideElements ?? [],\n maskElements: options.capture?.maskElements ?? [],\n };\n this.comparison = options.comparison ?? {};\n this.ci = options.ci ?? {};\n }\n\n // --- Internal accessors used by runner-comparison ---\n\n /** @internal */\n getBrowser(): Browser | null {\n return this.browser;\n }\n\n /** @internal */\n getOptions(): Required<VrtOptions> {\n return this.options;\n }\n\n /** @internal */\n getCapture(): Required<CaptureConfig> {\n return this.capture;\n }\n\n /** @internal */\n getComparison(): ComparisonConfig {\n return this.comparison;\n }\n\n /**\n * Initialize Playwright browser.\n */\n async init(): Promise<void> {\n const { chromium } = await import(\"playwright\");\n this.browser = await chromium.launch({ headless: true });\n this.startTime = Date.now();\n }\n\n /**\n * Close browser and cleanup.\n */\n async close(): Promise<void> {\n if (this.browser) {\n await this.browser.close();\n this.browser = null;\n }\n }\n\n /**\n * Alias for init() - used by the plugin API.\n */\n async start(): Promise<void> {\n return this.init();\n }\n\n /**\n * Alias for close() - used by the plugin API.\n */\n async stop(): Promise<void> {\n return this.close();\n }\n\n /**\n * Run VRT tests for all Art files.\n */\n async runAllTests(artFiles: ArtFileInfo[], baseUrl: string): Promise<VrtResult[]> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n\n const results: VrtResult[] = [];\n const retries = this.ci.retries ?? 0;\n\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) {\n continue;\n }\n\n // Determine viewports: use per-variant viewport if defined, else global viewports\n const viewports = variant.args?.viewport\n ? [variant.args.viewport as ViewportConfig]\n : this.options.viewports;\n\n for (const viewport of viewports) {\n let result: VrtResult | null = null;\n let attempts = 0;\n\n while (attempts <= retries) {\n result = await this.captureAndCompare(art, variant.name, viewport, baseUrl);\n if (result.passed || result.isNew || !result.error) {\n break;\n }\n attempts++;\n if (attempts <= retries) {\n console.log(\n `[vrt] Retry ${attempts}/${retries}: ${path.basename(art.path)}/${variant.name}`,\n );\n }\n }\n\n if (result) {\n results.push(result);\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Run VRT tests - alias used by the plugin API that accepts options.\n */\n async runTests(\n artFiles: ArtFileInfo[],\n baseUrl: string,\n _options?: { updateSnapshots?: boolean },\n ): Promise<VrtResult[]> {\n const results = await this.runAllTests(artFiles, baseUrl);\n if (_options?.updateSnapshots) {\n await this.updateBaselines(results);\n }\n return results;\n }\n\n /**\n * Capture screenshot and compare with baseline.\n */\n async captureAndCompare(\n art: ArtFileInfo,\n variantName: string,\n viewport: ViewportConfig,\n baseUrl: string,\n ): Promise<VrtResult> {\n return captureAndCompare(this, art, variantName, viewport, baseUrl);\n }\n\n /**\n * Get the Playwright Page for external use (e.g., a11y auditing).\n */\n async createPage(viewport: ViewportConfig): Promise<{ page: Page; context: BrowserContext }> {\n if (!this.browser) {\n throw new Error(\"VRT runner not initialized. Call init() first.\");\n }\n const context = await this.browser.newContext({\n viewport: { width: viewport.width, height: viewport.height },\n deviceScaleFactor: viewport.deviceScaleFactor ?? 1,\n });\n const page = await context.newPage();\n return { page, context };\n }\n\n /**\n * Update baseline snapshots with current screenshots.\n */\n async updateBaselines(results: VrtResult[]): Promise<number> {\n let updated = 0;\n const snapshotDir = this.options.snapshotDir;\n const currentDir = path.join(snapshotDir, \"current\");\n\n for (const result of results) {\n const currentPath = path.join(currentDir, path.basename(result.snapshotPath));\n\n if (await fileExists(currentPath)) {\n await fs.promises.copyFile(currentPath, result.snapshotPath);\n updated++;\n console.log(`[vrt] Updated: ${path.basename(result.snapshotPath)}`);\n }\n }\n\n return updated;\n }\n\n /**\n * Approve specific failed results (update their baselines).\n */\n async approveResults(results: VrtResult[], pattern?: string): Promise<number> {\n const toApprove = pattern\n ? results.filter((r) => {\n const name = `${path.basename(r.artPath, \".art.vue\")}/${r.variantName}`;\n return name.includes(pattern) || matchGlob(name, pattern);\n })\n : results.filter((r) => !r.passed && !r.error);\n\n return this.updateBaselines(toApprove);\n }\n\n /**\n * Clean orphaned snapshots (no corresponding art/variant).\n */\n async cleanOrphans(artFiles: ArtFileInfo[]): Promise<number> {\n const snapshotDir = this.options.snapshotDir;\n let cleaned = 0;\n\n try {\n const files = await fs.promises.readdir(snapshotDir);\n const validNames = new Set<string>();\n\n for (const art of artFiles) {\n const artBaseName = path.basename(art.path, \".art.vue\");\n for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n for (const viewport of this.options.viewports) {\n const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;\n validNames.add(`${artBaseName}--${variant.name}--${viewportName}.png`);\n }\n }\n }\n\n for (const file of files) {\n if (file.endsWith(\".png\") && !validNames.has(file)) {\n await fs.promises.unlink(path.join(snapshotDir, file));\n cleaned++;\n console.log(`[vrt] Cleaned: ${file}`);\n }\n }\n } catch {\n // Directory may not exist yet\n }\n\n return cleaned;\n }\n\n /**\n * Get VRT summary statistics.\n */\n getSummary(results: VrtResult[]): VrtSummary {\n return computeSummary(results, this.startTime);\n }\n}\n","/**\n * VRT report generation for Musea.\n *\n * Generates HTML and JSON reports from VRT results for visual review\n * and CI integration.\n */\n\nimport path from \"node:path\";\n\nimport type { VrtResult, VrtSummary } from \"./types.js\";\nimport { escapeHtml } from \"./comparison.js\";\n\n/**\n * Generate VRT report in HTML format.\n * Supports side-by-side, overlay, and slider comparison modes.\n */\nexport function generateVrtReport(results: VrtResult[], summary: VrtSummary): string {\n const formatDuration = (ms: number): string => {\n if (ms < 1000) return `${ms}ms`;\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n if (minutes === 0) return `${seconds}s`;\n return `${minutes}m ${seconds % 60}s`;\n };\n\n const timestamp = new Date().toLocaleString(\"ja-JP\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>VRT Report - Musea</title>\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-accent: #a34828;\n --musea-accent-hover: #c45a32;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n --musea-success: #4ade80;\n --musea-error: #f87171;\n --musea-info: #60a5fa;\n --musea-warning: #fbbf24;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n }\n\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 1rem 2rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n position: sticky;\n top: 0;\n z-index: 100;\n }\n .header-left { display: flex; align-items: center; gap: 1rem; }\n .logo { font-size: 1.25rem; font-weight: 700; color: var(--musea-accent); }\n .header-title { color: var(--musea-text-muted); font-size: 0.875rem; }\n .header-meta { display: flex; align-items: center; gap: 1.5rem; font-size: 0.8125rem; color: var(--musea-text-muted); }\n .header-meta span { display: flex; align-items: center; gap: 0.375rem; }\n\n .main { max-width: 1400px; margin: 0 auto; padding: 2rem; }\n\n .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; padding: 1.25rem; position: relative; overflow: hidden; }\n .stat::before { content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; }\n .stat.passed::before { background: var(--musea-success); }\n .stat.failed::before { background: var(--musea-error); }\n .stat.new::before { background: var(--musea-info); }\n .stat.skipped::before { background: var(--musea-warning); }\n .stat-value { font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; margin-bottom: 0.25rem; }\n .stat.passed .stat-value { color: var(--musea-success); }\n .stat.failed .stat-value { color: var(--musea-error); }\n .stat.new .stat-value { color: var(--musea-info); }\n .stat.skipped .stat-value { color: var(--musea-warning); }\n .stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; }\n\n .filters { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; }\n .filter-btn { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); color: var(--musea-text); padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; font-size: 0.8125rem; font-weight: 500; transition: all 0.15s ease; }\n .filter-btn:hover { background: var(--musea-bg-tertiary); border-color: var(--musea-text-muted); }\n .filter-btn.active { background: var(--musea-accent); border-color: var(--musea-accent); color: #fff; }\n .filter-btn .count { opacity: 0.7; margin-left: 0.25rem; }\n\n /* Comparison mode toggle */\n .compare-modes { display: flex; gap: 0.25rem; margin-bottom: 1.5rem; background: var(--musea-bg-secondary); border-radius: 6px; padding: 0.25rem; width: fit-content; }\n .compare-mode-btn { background: none; border: none; color: var(--musea-text-muted); padding: 0.375rem 0.75rem; border-radius: 4px; cursor: pointer; font-size: 0.75rem; font-weight: 500; transition: all 0.15s ease; }\n .compare-mode-btn.active { background: var(--musea-bg-tertiary); color: var(--musea-text); }\n\n .results { display: flex; flex-direction: column; gap: 0.75rem; }\n .result { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; overflow: hidden; transition: border-color 0.15s ease; }\n .result:hover { border-color: var(--musea-text-muted); }\n .result-header { padding: 1rem 1.25rem; display: flex; justify-content: space-between; align-items: center; cursor: pointer; border-left: 3px solid transparent; background: var(--musea-bg-tertiary); }\n .result.passed .result-header { border-left-color: var(--musea-success); }\n .result.failed .result-header { border-left-color: var(--musea-error); }\n .result.new .result-header { border-left-color: var(--musea-info); }\n .result.error .result-header { border-left-color: var(--musea-warning); }\n\n .result-info { display: flex; align-items: center; gap: 1rem; }\n .result-name { font-weight: 600; font-size: 0.9375rem; }\n .result-meta { color: var(--musea-text-muted); font-size: 0.8125rem; padding: 0.125rem 0.5rem; background: var(--musea-bg-secondary); border-radius: 4px; }\n .result-badge { padding: 0.25rem 0.625rem; border-radius: 4px; font-size: 0.6875rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }\n .result.passed .result-badge { background: rgba(74, 222, 128, 0.15); color: var(--musea-success); }\n .result.failed .result-badge { background: rgba(248, 113, 113, 0.15); color: var(--musea-error); }\n .result.new .result-badge { background: rgba(96, 165, 250, 0.15); color: var(--musea-info); }\n .result.error .result-badge { background: rgba(251, 191, 36, 0.15); color: var(--musea-warning); }\n\n .result-body { border-top: 1px solid var(--musea-border); }\n .result-details { padding: 0.875rem 1.25rem; font-size: 0.8125rem; color: var(--musea-text-muted); font-family: 'SF Mono', 'Fira Code', monospace; background: var(--musea-bg-primary); }\n .result-details.error { color: var(--musea-error); }\n\n .result-images { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; padding: 1.25rem; background: var(--musea-bg-primary); }\n .result-images.overlay { grid-template-columns: 1fr; }\n .image-container { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 6px; overflow: hidden; }\n .image-label { padding: 0.625rem 0.875rem; font-size: 0.6875rem; font-weight: 600; color: var(--musea-text-muted); text-transform: uppercase; letter-spacing: 0.08em; background: var(--musea-bg-tertiary); border-bottom: 1px solid var(--musea-border); }\n .image-wrapper { padding: 0.5rem; background: repeating-conic-gradient(var(--musea-bg-tertiary) 0% 25%, var(--musea-bg-secondary) 0% 50%) 50% / 16px 16px; }\n .image-container img { width: 100%; height: auto; display: block; border-radius: 2px; }\n\n /* Slider comparison */\n .slider-compare { position: relative; overflow: hidden; }\n .slider-compare img { display: block; width: 100%; }\n .slider-overlay { position: absolute; top: 0; left: 0; bottom: 0; overflow: hidden; border-right: 2px solid var(--musea-accent); }\n .slider-overlay img { display: block; min-width: 100%; height: 100%; object-fit: cover; }\n\n .empty-state { text-align: center; padding: 4rem 2rem; color: var(--musea-text-muted); }\n .all-passed { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 1.5rem; text-align: center; margin-bottom: 1.5rem; }\n .all-passed-icon { font-size: 2.5rem; margin-bottom: 0.5rem; }\n .all-passed-text { color: var(--musea-success); font-weight: 600; }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div class=\"header-left\">\n <div class=\"logo\">Musea</div>\n <span class=\"header-title\">Visual Regression Report</span>\n </div>\n <div class=\"header-meta\">\n <span>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><polyline points=\"12 6 12 12 16 14\"/>\n </svg>\n ${formatDuration(summary.duration)}\n </span>\n <span>\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"18\" rx=\"2\" ry=\"2\"/><line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"/><line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"/><line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"/>\n </svg>\n ${timestamp}\n </span>\n </div>\n </header>\n\n <main class=\"main\">\n <div class=\"summary\">\n <div class=\"stat passed\"><div class=\"stat-value\">${summary.passed}</div><div class=\"stat-label\">Passed</div></div>\n <div class=\"stat failed\"><div class=\"stat-value\">${summary.failed}</div><div class=\"stat-label\">Failed</div></div>\n <div class=\"stat new\"><div class=\"stat-value\">${summary.new}</div><div class=\"stat-label\">New</div></div>\n <div class=\"stat skipped\"><div class=\"stat-value\">${summary.skipped}</div><div class=\"stat-label\">Skipped</div></div>\n </div>\n\n ${\n summary.failed === 0 && summary.skipped === 0 && summary.total > 0\n ? `<div class=\"all-passed\">\n <div class=\"all-passed-icon\">✓</div>\n <div class=\"all-passed-text\">All ${summary.total} visual tests passed</div>\n </div>`\n : \"\"\n }\n\n <div class=\"filters\">\n <button class=\"filter-btn active\" data-filter=\"all\">All<span class=\"count\">(${summary.total})</span></button>\n <button class=\"filter-btn\" data-filter=\"failed\">Failed<span class=\"count\">(${summary.failed})</span></button>\n <button class=\"filter-btn\" data-filter=\"passed\">Passed<span class=\"count\">(${summary.passed})</span></button>\n <button class=\"filter-btn\" data-filter=\"new\">New<span class=\"count\">(${summary.new})</span></button>\n </div>\n\n <div class=\"compare-modes\">\n <button class=\"compare-mode-btn active\" data-mode=\"side-by-side\">Side by Side</button>\n <button class=\"compare-mode-btn\" data-mode=\"overlay\">Overlay</button>\n <button class=\"compare-mode-btn\" data-mode=\"slider\">Slider</button>\n </div>\n\n <div class=\"results\">\n ${\n results.length === 0\n ? `<div class=\"empty-state\"><p>No visual tests found</p></div>`\n : results\n .map((r) => {\n const status = r.error ? \"error\" : r.isNew ? \"new\" : r.passed ? \"passed\" : \"failed\";\n const badge = r.error ? \"Error\" : r.isNew ? \"New\" : r.passed ? \"Passed\" : \"Failed\";\n const artName = path.basename(r.artPath, \".art.vue\");\n const viewportName = r.viewport.name || `${r.viewport.width}×${r.viewport.height}`;\n\n let details = \"\";\n if (r.error) {\n details = `<div class=\"result-details error\">${escapeHtml(r.error)}</div>`;\n } else if (r.diffPercentage !== undefined) {\n const diffFormatted = r.diffPercentage.toFixed(3);\n const pixelsFormatted = r.diffPixels?.toLocaleString() ?? \"0\";\n const totalFormatted = r.totalPixels?.toLocaleString() ?? \"0\";\n details = `<div class=\"result-details\">diff: ${diffFormatted}% (${pixelsFormatted} / ${totalFormatted} pixels)</div>`;\n }\n\n let images = \"\";\n if (!r.error && !r.passed && r.diffPath) {\n images = `<div class=\"result-images\" data-baseline=\"file://${r.snapshotPath}\" data-current=\"file://${r.currentPath}\" data-diff=\"file://${r.diffPath}\">\n ${r.snapshotPath ? `<div class=\"image-container\"><div class=\"image-label\">Baseline</div><div class=\"image-wrapper\"><img src=\"file://${r.snapshotPath}\" alt=\"Baseline\" loading=\"lazy\" /></div></div>` : \"\"}\n ${r.currentPath ? `<div class=\"image-container\"><div class=\"image-label\">Current</div><div class=\"image-wrapper\"><img src=\"file://${r.currentPath}\" alt=\"Current\" loading=\"lazy\" /></div></div>` : \"\"}\n ${r.diffPath ? `<div class=\"image-container\"><div class=\"image-label\">Diff</div><div class=\"image-wrapper\"><img src=\"file://${r.diffPath}\" alt=\"Diff\" loading=\"lazy\" /></div></div>` : \"\"}\n </div>`;\n }\n\n const hasBody = details || images;\n\n return `<div class=\"result ${status}\" data-status=\"${status}\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <div class=\"result-name\">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</div>\n <div class=\"result-meta\">${escapeHtml(viewportName)}</div>\n </div>\n <span class=\"result-badge\">${badge}</span>\n </div>\n ${hasBody ? `<div class=\"result-body\">${details}${images}</div>` : \"\"}\n </div>`;\n })\n .join(\"\")\n }\n </div>\n </main>\n\n <script>\n // Filter buttons\n document.querySelectorAll('.filter-btn').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n const filter = btn.dataset.filter;\n document.querySelectorAll('.result').forEach(result => {\n result.style.display = (filter === 'all' || result.dataset.status === filter) ? 'block' : 'none';\n });\n });\n });\n\n // Compare mode buttons\n document.querySelectorAll('.compare-mode-btn').forEach(btn => {\n btn.addEventListener('click', () => {\n document.querySelectorAll('.compare-mode-btn').forEach(b => b.classList.remove('active'));\n btn.classList.add('active');\n // Mode switching would update result-images display; this is a static report for now\n });\n });\n </script>\n</body>\n</html>`;\n\n return html;\n}\n\n/**\n * Generate VRT JSON report for CI integration.\n */\nexport function generateVrtJsonReport(results: VrtResult[], summary: VrtSummary): string {\n return JSON.stringify(\n {\n timestamp: new Date().toISOString(),\n summary,\n results: results.map((r) => ({\n art: path.basename(r.artPath, \".art.vue\"),\n variant: r.variantName,\n viewport: r.viewport.name || `${r.viewport.width}x${r.viewport.height}`,\n status: r.error ? \"error\" : r.isNew ? \"new\" : r.passed ? \"passed\" : \"failed\",\n diffPercentage: r.diffPercentage,\n error: r.error,\n })),\n },\n null,\n 2,\n );\n}\n","/**\n * Visual Regression Testing (VRT) module for Musea.\n * Uses Playwright for browser automation and pixel comparison.\n *\n * This file re-exports from the split VRT submodules for backward compatibility.\n */\n\nexport {\n MuseaVrtRunner,\n type VrtResult,\n type VrtSummary,\n type ExtendedVrtOptions,\n type PixelCompareOptions,\n} from \"./vrt/runner.js\";\n\nexport { generateVrtReport, generateVrtJsonReport } from \"./vrt/report.js\";\n\nimport { MuseaVrtRunner } from \"./vrt/runner.js\";\nexport default MuseaVrtRunner;\n"],"mappings":"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"}
|
package/dist/vrt.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as PixelCompareOptions, i as ExtendedVrtOptions, n as generateVrtReport, o as VrtResult, r as MuseaVrtRunner, s as VrtSummary, t as generateVrtJsonReport } from "./vrt-B4uxOrnN.mjs";
|
|
2
|
+
export { ExtendedVrtOptions, MuseaVrtRunner, MuseaVrtRunner as default, PixelCompareOptions, VrtResult, VrtSummary, generateVrtJsonReport, generateVrtReport };
|
package/dist/vrt.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,61 +1,65 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizejs/vite-plugin-musea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "Vite plugin for Musea - Component gallery for Vue components",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
"keywords": [
|
|
6
|
+
"component-gallery",
|
|
7
|
+
"musea",
|
|
8
|
+
"storybook",
|
|
9
|
+
"vite",
|
|
10
|
+
"vite-plugin",
|
|
11
|
+
"vue"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "ubugeeei",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/ubugeeei/vize.git",
|
|
18
|
+
"directory": "npm/vite-plugin-musea"
|
|
19
|
+
},
|
|
8
20
|
"bin": {
|
|
9
|
-
"musea-vrt": "./dist/cli/index.
|
|
21
|
+
"musea-vrt": "./dist/cli/index.mjs"
|
|
10
22
|
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"dist/gallery",
|
|
26
|
+
"bin"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "./dist/index.mjs",
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
11
31
|
"exports": {
|
|
12
32
|
".": {
|
|
13
|
-
"import": "./dist/index.
|
|
14
|
-
"types": "./dist/index.d.
|
|
33
|
+
"import": "./dist/index.mjs",
|
|
34
|
+
"types": "./dist/index.d.mts"
|
|
15
35
|
},
|
|
16
36
|
"./vrt": {
|
|
17
|
-
"import": "./dist/vrt.
|
|
18
|
-
"types": "./dist/vrt.d.
|
|
37
|
+
"import": "./dist/vrt.mjs",
|
|
38
|
+
"types": "./dist/vrt.d.mts"
|
|
19
39
|
},
|
|
20
40
|
"./cli": {
|
|
21
|
-
"import": "./dist/cli/index.
|
|
22
|
-
"types": "./dist/cli/index.d.
|
|
41
|
+
"import": "./dist/cli/index.mjs",
|
|
42
|
+
"types": "./dist/cli/index.d.mts"
|
|
23
43
|
},
|
|
24
44
|
"./a11y": {
|
|
25
|
-
"import": "./dist/a11y.
|
|
26
|
-
"types": "./dist/a11y.d.
|
|
45
|
+
"import": "./dist/a11y/index.mjs",
|
|
46
|
+
"types": "./dist/a11y/index.d.mts"
|
|
27
47
|
},
|
|
28
48
|
"./autogen": {
|
|
29
|
-
"import": "./dist/autogen.
|
|
30
|
-
"types": "./dist/autogen.d.
|
|
49
|
+
"import": "./dist/autogen/index.mjs",
|
|
50
|
+
"types": "./dist/autogen/index.d.mts"
|
|
31
51
|
}
|
|
32
52
|
},
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"dist/gallery",
|
|
36
|
-
"bin"
|
|
37
|
-
],
|
|
38
|
-
"keywords": [
|
|
39
|
-
"vite",
|
|
40
|
-
"vite-plugin",
|
|
41
|
-
"vue",
|
|
42
|
-
"storybook",
|
|
43
|
-
"component-gallery",
|
|
44
|
-
"musea"
|
|
45
|
-
],
|
|
46
|
-
"author": "ubugeeei",
|
|
47
|
-
"license": "MIT",
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "https://github.com/ubugeeei/vize.git",
|
|
51
|
-
"directory": "npm/vite-plugin-musea"
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
52
55
|
},
|
|
53
56
|
"dependencies": {
|
|
54
57
|
"pngjs": "^7.0.0",
|
|
55
|
-
"@vizejs/native": "0.
|
|
58
|
+
"@vizejs/native": "0.34.0"
|
|
56
59
|
},
|
|
57
60
|
"devDependencies": {
|
|
58
61
|
"@mdi/js": "^7.4.47",
|
|
62
|
+
"@tsdown/css": "^0.21.2",
|
|
59
63
|
"@types/node": "^22.14.0",
|
|
60
64
|
"@types/pngjs": "^6.0.5",
|
|
61
65
|
"@vitejs/plugin-vue": "^6.0.4",
|
|
@@ -63,18 +67,18 @@
|
|
|
63
67
|
"marked": "^17.0.1",
|
|
64
68
|
"marked-highlight": "^2.2.3",
|
|
65
69
|
"monaco-editor": "^0.52.0",
|
|
66
|
-
"tsdown": "^0.9.0",
|
|
67
70
|
"typescript": "^5.7.0",
|
|
68
|
-
"vite": "
|
|
71
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@latest",
|
|
72
|
+
"vite-plus": "latest",
|
|
69
73
|
"vue": "^3.5.0",
|
|
70
74
|
"vue-router": "^4.5.0",
|
|
71
|
-
"@vizejs/vite-plugin": "0.
|
|
75
|
+
"@vizejs/vite-plugin": "0.34.0"
|
|
72
76
|
},
|
|
73
77
|
"peerDependencies": {
|
|
74
78
|
"axe-core": "^4.7.0",
|
|
75
79
|
"playwright": "^1.40.0",
|
|
76
80
|
"vite": "^8.0.0",
|
|
77
|
-
"@vizejs/vite-plugin": "0.
|
|
81
|
+
"@vizejs/vite-plugin": "0.34.0"
|
|
78
82
|
},
|
|
79
83
|
"peerDependenciesMeta": {
|
|
80
84
|
"playwright": {
|
|
@@ -84,16 +88,12 @@
|
|
|
84
88
|
"optional": true
|
|
85
89
|
}
|
|
86
90
|
},
|
|
87
|
-
"publishConfig": {
|
|
88
|
-
"access": "public"
|
|
89
|
-
},
|
|
90
91
|
"scripts": {
|
|
91
|
-
"build": "
|
|
92
|
-
"build:gallery": "
|
|
93
|
-
"dev": "
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"fmt": "
|
|
97
|
-
"fmt:check": "oxfmt src tsdown.config.ts"
|
|
92
|
+
"build": "vp pack",
|
|
93
|
+
"build:gallery": "vp build --config gallery-vite.config.ts",
|
|
94
|
+
"dev": "vp pack --watch",
|
|
95
|
+
"check": "vp check src vite.config.ts",
|
|
96
|
+
"check:fix": "vp check --fix src vite.config.ts",
|
|
97
|
+
"fmt": "vp fmt --write src vite.config.ts"
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"a11y-7maCHrYD.js","names":["results: A11yResult[]","summary: A11ySummary","impact: string","str: string","options: A11yOptions","artFiles: ArtFileInfo[]","baseUrl: string","vrtRunner?: MuseaVrtRunner","results: A11yResult[]","defaultViewport: ViewportConfig","page: Page | null","context: { page: Page; context: { close(): Promise<void> } } | null","page: Page","artPath: string","variantName: string","violations: A11yViolation[]","runOnly: Record<string, unknown>","tags: string[]","rules: Record<string, { enabled: boolean }>"],"sources":["../src/a11y/report.ts","../src/a11y/index.ts"],"sourcesContent":["/**\n * A11y report generation for Musea.\n *\n * Generates HTML and JSON reports from accessibility audit results,\n * plus summary statistics computation.\n */\n\nimport type { A11yResult } from \"../types/index.js\";\nimport type { A11ySummary } from \"./index.js\";\nimport path from \"node:path\";\n\n/**\n * Compute a11y summary statistics from results.\n */\nexport function computeA11ySummary(results: A11yResult[]): A11ySummary {\n const components = new Set(results.map((r) => r.artPath));\n const allViolations = results.flatMap((r) => r.violations);\n\n return {\n totalComponents: components.size,\n totalVariants: results.length,\n totalViolations: allViolations.length,\n criticalCount: allViolations.filter((v) => v.impact === \"critical\").length,\n seriousCount: allViolations.filter((v) => v.impact === \"serious\").length,\n moderateCount: allViolations.filter((v) => v.impact === \"moderate\").length,\n minorCount: allViolations.filter((v) => v.impact === \"minor\").length,\n };\n}\n\n/**\n * Generate HTML report from a11y results.\n */\nexport function generateA11yHtmlReport(results: A11yResult[], summary: A11ySummary): string {\n const timestamp = new Date().toLocaleString(\"ja-JP\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n\n const impactColor = (impact: string): string => {\n switch (impact) {\n case \"critical\":\n return \"#f87171\";\n case \"serious\":\n return \"#fb923c\";\n case \"moderate\":\n return \"#fbbf24\";\n case \"minor\":\n return \"#60a5fa\";\n default:\n return \"#7b8494\";\n }\n };\n\n const resultItems = results\n .filter((r) => r.violations.length > 0)\n .map((r) => {\n const artName = path.basename(r.artPath, \".art.vue\");\n const violationRows = r.violations\n .map(\n (v) => `\n <tr>\n <td><span style=\"color:${impactColor(v.impact)};font-weight:600;text-transform:uppercase;font-size:0.6875rem\">${escapeHtml(v.impact)}</span></td>\n <td><code>${escapeHtml(v.id)}</code></td>\n <td>${escapeHtml(v.description)}</td>\n <td>${v.nodes}</td>\n <td>${v.helpUrl ? `<a href=\"${escapeHtml(v.helpUrl)}\" target=\"_blank\" style=\"color:#60a5fa\">docs</a>` : \"\"}</td>\n </tr>`,\n )\n .join(\"\");\n\n return `\n <div class=\"result\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <span class=\"result-name\">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</span>\n <span class=\"result-count\">${r.violations.length} violation(s)</span>\n </div>\n </div>\n <table class=\"violations-table\">\n <thead><tr><th>Impact</th><th>Rule</th><th>Description</th><th>Nodes</th><th>Help</th></tr></thead>\n <tbody>${violationRows}</tbody>\n </table>\n </div>`;\n })\n .join(\"\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>A11y Report - Musea</title>\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-accent: #a34828;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n }\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 1rem 2rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .logo { font-size: 1.25rem; font-weight: 700; color: var(--musea-accent); }\n .header-meta { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .main { max-width: 1200px; margin: 0 auto; padding: 2rem; }\n .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; padding: 1rem; text-align: center; }\n .stat-value { font-size: 1.75rem; font-weight: 700; font-variant-numeric: tabular-nums; }\n .stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; }\n .stat.critical .stat-value { color: #f87171; }\n .stat.serious .stat-value { color: #fb923c; }\n .stat.moderate .stat-value { color: #fbbf24; }\n .stat.minor .stat-value { color: #60a5fa; }\n .stat.total .stat-value { color: var(--musea-text); }\n .results { display: flex; flex-direction: column; gap: 1rem; }\n .result { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; overflow: hidden; }\n .result-header { padding: 1rem; background: var(--musea-bg-tertiary); display: flex; justify-content: space-between; align-items: center; }\n .result-name { font-weight: 600; }\n .result-count { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .violations-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }\n .violations-table th { padding: 0.75rem 1rem; text-align: left; color: var(--musea-text-muted); font-weight: 500; font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.08em; border-bottom: 1px solid var(--musea-border); }\n .violations-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--musea-border); }\n .violations-table code { background: var(--musea-bg-tertiary); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.75rem; }\n .all-clear { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 2rem; text-align: center; }\n .all-clear-text { color: #4ade80; font-weight: 600; }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div><span class=\"logo\">Musea</span> <span style=\"color:var(--musea-text-muted);font-size:0.875rem;margin-left:1rem\">Accessibility Report</span></div>\n <div class=\"header-meta\">${timestamp}</div>\n </header>\n <main class=\"main\">\n <div class=\"summary\">\n <div class=\"stat total\"><div class=\"stat-value\">${summary.totalViolations}</div><div class=\"stat-label\">Violations</div></div>\n <div class=\"stat critical\"><div class=\"stat-value\">${summary.criticalCount}</div><div class=\"stat-label\">Critical</div></div>\n <div class=\"stat serious\"><div class=\"stat-value\">${summary.seriousCount}</div><div class=\"stat-label\">Serious</div></div>\n <div class=\"stat moderate\"><div class=\"stat-value\">${summary.moderateCount}</div><div class=\"stat-label\">Moderate</div></div>\n <div class=\"stat minor\"><div class=\"stat-value\">${summary.minorCount}</div><div class=\"stat-label\">Minor</div></div>\n </div>\n ${\n summary.totalViolations === 0\n ? `<div class=\"all-clear\"><div class=\"all-clear-text\">No accessibility violations found across ${summary.totalVariants} variant(s)</div></div>`\n : `<div class=\"results\">${resultItems}</div>`\n }\n </main>\n</body>\n</html>`;\n}\n\n/**\n * Generate JSON report for CI integration.\n */\nexport function generateA11yJsonReport(results: A11yResult[]): string {\n const summary = computeA11ySummary(results);\n return JSON.stringify(\n {\n timestamp: new Date().toISOString(),\n summary,\n results: results.map((r) => ({\n art: path.basename(r.artPath, \".art.vue\"),\n variant: r.variantName,\n violations: r.violations,\n passes: r.passes,\n incomplete: r.incomplete,\n })),\n },\n null,\n 2,\n );\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","/**\n * Accessibility (a11y) testing module for Musea.\n * Uses axe-core for automated accessibility auditing via Playwright.\n */\n\nimport type { Page } from \"playwright\";\nimport type {\n ArtFileInfo,\n A11yResult,\n A11yViolation,\n A11yOptions,\n ViewportConfig,\n} from \"../types/index.js\";\nimport type { MuseaVrtRunner } from \"../vrt.js\";\n\nimport { computeA11ySummary, generateA11yHtmlReport, generateA11yJsonReport } from \"./report.js\";\n\n// Re-export report functions so consumers importing from a11y.js still work\nexport { computeA11ySummary, generateA11yHtmlReport, generateA11yJsonReport } from \"./report.js\";\n\n/**\n * A11y audit summary.\n */\nexport interface A11ySummary {\n totalComponents: number;\n totalVariants: number;\n totalViolations: number;\n criticalCount: number;\n seriousCount: number;\n moderateCount: number;\n minorCount: number;\n}\n\n/**\n * axe-core result shape (subset).\n */\ninterface AxeResult {\n violations: Array<{\n id: string;\n impact: string;\n description: string;\n helpUrl: string;\n nodes: Array<unknown>;\n }>;\n passes: Array<unknown>;\n incomplete: Array<unknown>;\n}\n\n/**\n * A11y runner using axe-core via Playwright.\n */\nexport class MuseaA11yRunner {\n private options: Required<A11yOptions>;\n\n constructor(options: A11yOptions = {}) {\n this.options = {\n enabled: options.enabled ?? true,\n includeRules: options.includeRules ?? [],\n excludeRules: options.excludeRules ?? [],\n level: options.level ?? \"AA\",\n };\n }\n\n /**\n * Run a11y audits on all art file variants.\n * Reuses VRT runner's browser if available.\n */\n async runAudits(\n artFiles: ArtFileInfo[],\n baseUrl: string,\n vrtRunner?: MuseaVrtRunner,\n ): Promise<A11yResult[]> {\n const results: A11yResult[] = [];\n const defaultViewport: ViewportConfig = { width: 1280, height: 720, name: \"desktop\" };\n\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n\n let page: Page | null = null;\n let context: { page: Page; context: { close(): Promise<void> } } | null = null;\n\n try {\n if (vrtRunner) {\n context = await vrtRunner.createPage(defaultViewport);\n page = context.page;\n } else {\n // Standalone mode: launch own browser\n const { chromium } = await import(\"playwright\");\n const browser = await chromium.launch({ headless: true });\n const ctx = await browser.newContext({\n viewport: { width: defaultViewport.width, height: defaultViewport.height },\n });\n page = await ctx.newPage();\n context = { page, context: ctx };\n }\n\n const variantUrl = this.buildVariantUrl(baseUrl, art.path, variant.name);\n await page.goto(variantUrl, { waitUntil: \"networkidle\" });\n await page.waitForSelector(\".musea-variant\", { timeout: 10000 });\n await page.waitForTimeout(200);\n\n const result = await this.auditPage(page, art.path, variant.name);\n results.push(result);\n } catch (error) {\n results.push({\n artPath: art.path,\n variantName: variant.name,\n violations: [\n {\n id: \"audit-error\",\n impact: \"critical\",\n description: `Audit failed: ${error instanceof Error ? error.message : String(error)}`,\n helpUrl: \"\",\n nodes: 0,\n },\n ],\n passes: 0,\n incomplete: 0,\n });\n } finally {\n if (context) {\n await context.context.close();\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Audit a single page using axe-core.\n */\n async auditPage(page: Page, artPath: string, variantName: string): Promise<A11yResult> {\n // Inject axe-core into the page\n const axeSource = await this.getAxeSource();\n await page.evaluate(axeSource);\n\n // Build axe-core run options\n const runOptions = this.buildAxeOptions();\n\n // Run axe-core\n const axeResult = (await page.evaluate((opts) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (window as any).axe.run(document, opts);\n }, runOptions)) as AxeResult;\n\n // Map to our result format\n const violations: A11yViolation[] = axeResult.violations.map((v) => ({\n id: v.id,\n impact: v.impact as A11yViolation[\"impact\"],\n description: v.description,\n helpUrl: v.helpUrl,\n nodes: v.nodes.length,\n }));\n\n return {\n artPath,\n variantName,\n violations,\n passes: axeResult.passes.length,\n incomplete: axeResult.incomplete.length,\n };\n }\n\n /**\n * Get summary statistics from results.\n */\n getSummary(results: A11yResult[]): A11ySummary {\n return computeA11ySummary(results);\n }\n\n /**\n * Generate HTML report.\n */\n generateHtmlReport(results: A11yResult[]): string {\n const summary = this.getSummary(results);\n return generateA11yHtmlReport(results, summary);\n }\n\n /**\n * Generate JSON report for CI integration.\n */\n generateJsonReport(results: A11yResult[]): string {\n return generateA11yJsonReport(results);\n }\n\n /**\n * Get axe-core source code for injection.\n */\n private async getAxeSource(): Promise<string> {\n try {\n const axeCore = await import(\"axe-core\");\n return axeCore.source;\n } catch {\n throw new Error(\n \"axe-core is not installed. Install it as a peer dependency: npm install axe-core\",\n );\n }\n }\n\n /**\n * Build axe-core run options from configuration.\n */\n private buildAxeOptions(): Record<string, unknown> {\n const runOnly: Record<string, unknown> = {};\n\n // Set WCAG level\n const tags: string[] = [];\n switch (this.options.level) {\n case \"A\":\n tags.push(\"wcag2a\", \"wcag21a\");\n break;\n case \"AA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n case \"AAA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag2aaa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n }\n\n if (tags.length > 0) {\n runOnly.type = \"tag\";\n runOnly.values = tags;\n }\n\n const rules: Record<string, { enabled: boolean }> = {};\n\n for (const ruleId of this.options.includeRules) {\n rules[ruleId] = { enabled: true };\n }\n for (const ruleId of this.options.excludeRules) {\n rules[ruleId] = { enabled: false };\n }\n\n return {\n ...(Object.keys(runOnly).length > 0 ? { runOnly } : {}),\n ...(Object.keys(rules).length > 0 ? { rules } : {}),\n };\n }\n\n private buildVariantUrl(baseUrl: string, artPath: string, variantName: string): string {\n const encodedPath = encodeURIComponent(artPath);\n const encodedVariant = encodeURIComponent(variantName);\n return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;\n }\n}\n"],"mappings":"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"}
|
package/dist/a11y-CjpWs0s0.js
DELETED
package/dist/autogen-Dx-SIBf_.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"autogen-dfLosbY_.js","names":["source: string","props: PropDefinition[]","componentName: string","componentPath: string","options: AutogenOptions","variants: GeneratedVariant[]","defaultProps: Record<string, unknown>","typeStr: string","str: string","native: NativeAutogen | null","componentPath: string","options: AutogenOptions","props: PropDefinition[]","outputPath?: string"],"sources":["../src/autogen/fallback.ts","../src/autogen/index.ts"],"sourcesContent":["/**\n * JS-based fallback for variant auto-generation.\n *\n * Used when the native Rust binding is not available. Provides simple\n * regex-based prop extraction, minimal art file generation, and a pure\n * JS variant generator.\n */\n\nimport path from \"node:path\";\n\nimport type { AutogenOptions, AutogenOutput, GeneratedVariant, PropDefinition } from \"./index.js\";\n\n/**\n * Simple prop extraction fallback (when native binding not available).\n */\nexport function extractPropsSimple(source: string): PropDefinition[] {\n const props: PropDefinition[] = [];\n\n // Match defineProps<{ ... }>() or defineProps({ ... })\n const propsMatch = source.match(/defineProps\\s*<\\s*\\{([^}]*)\\}\\s*>/s);\n\n if (propsMatch) {\n const propsBlock = propsMatch[1];\n const propLines = propsBlock.split(\"\\n\");\n\n for (const line of propLines) {\n const propMatch = line.trim().match(/^(\\w+)(\\?)?:\\s*(.+?)\\s*;?\\s*$/);\n if (propMatch) {\n props.push({\n name: propMatch[1],\n propType: propMatch[3].replace(/,\\s*$/, \"\"),\n required: !propMatch[2],\n });\n }\n }\n }\n\n return props;\n}\n\n/**\n * Minimal art file for components with no props.\n */\nexport function generateMinimalArt(componentName: string, componentPath: string): string {\n return `<art title=\"${componentName}\" component=\"${componentPath}\">\n <variant name=\"Default\" default>\n <${componentName} />\n </variant>\n</art>\n\n<script setup lang=\"ts\">\nimport ${componentName} from '${componentPath}'\n</script>\n`;\n}\n\n/**\n * JS-based variant generation fallback.\n */\nexport function generateArtFileJs(\n componentPath: string,\n props: PropDefinition[],\n options: AutogenOptions,\n): AutogenOutput {\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n const maxVariants = options.maxVariants ?? 20;\n const variants: GeneratedVariant[] = [];\n\n // Default variant\n if (options.includeDefault !== false) {\n const defaultProps: Record<string, unknown> = {};\n for (const prop of props) {\n if (prop.defaultValue !== undefined) {\n defaultProps[prop.name] = prop.defaultValue;\n }\n }\n variants.push({\n name: \"Default\",\n isDefault: true,\n props: defaultProps,\n description: `${componentName} with default props`,\n });\n }\n\n // Enum variants\n if (options.includeEnumVariants !== false) {\n for (const prop of props) {\n const unionValues = parseUnionType(prop.propType);\n for (const val of unionValues) {\n if (variants.length >= maxVariants) break;\n const name =\n typeof val === \"string\" ? toPascalCase(val) : `${toPascalCase(prop.name)}_${String(val)}`;\n variants.push({\n name,\n isDefault: false,\n props: { [prop.name]: val },\n description: `${prop.name} = ${JSON.stringify(val)}`,\n });\n }\n }\n }\n\n // Boolean toggle variants\n if (options.includeBooleanToggles !== false) {\n for (const prop of props) {\n if (variants.length >= maxVariants) break;\n if (prop.propType.toLowerCase() === \"boolean\") {\n const nonDefault = prop.defaultValue === true ? false : true;\n variants.push({\n name: nonDefault ? toPascalCase(prop.name) : `No${toPascalCase(prop.name)}`,\n isDefault: false,\n props: { [prop.name]: nonDefault },\n description: `${prop.name} = ${nonDefault}`,\n });\n }\n }\n }\n\n // Generate art file content\n let content = `<art title=\"${componentName}\" component=\"${relPath}\">\\n`;\n for (const variant of variants) {\n const attrs = variant.isDefault ? `name=\"${variant.name}\" default` : `name=\"${variant.name}\"`;\n content += ` <variant ${attrs}>\\n`;\n\n const propsStr = Object.entries(variant.props)\n .map(([k, v]) => {\n if (typeof v === \"string\") return `${k}=\"${v}\"`;\n if (typeof v === \"boolean\" && v) return k;\n if (typeof v === \"boolean\" && !v) return `:${k}=\"false\"`;\n return `:${k}=\"${JSON.stringify(v)}\"`;\n })\n .join(\" \");\n\n content += ` <${componentName}${propsStr ? \" \" + propsStr : \"\"} />\\n`;\n content += ` </variant>\\n\\n`;\n }\n content += `</art>\\n\\n<script setup lang=\"ts\">\\nimport ${componentName} from '${relPath}'\\n</script>\\n`;\n\n return {\n variants,\n artFileContent: content,\n componentName,\n };\n}\n\nexport function parseUnionType(typeStr: string): unknown[] {\n const trimmed = typeStr.trim();\n if (!trimmed.includes(\"|\")) return [];\n\n if (trimmed.includes(\"'\") || trimmed.includes('\"')) {\n return trimmed\n .split(\"|\")\n .map((s) => s.trim().replace(/^['\"]|['\"]$/g, \"\"))\n .filter((s) => s.length > 0);\n }\n\n const parts = trimmed.split(\"|\").map((s) => s.trim());\n if (parts.every((p) => !isNaN(Number(p)))) {\n return parts.map(Number);\n }\n\n return [];\n}\n\nexport function toPascalCase(str: string): string {\n return str\n .split(/[\\s\\-_]+/)\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\"\");\n}\n","/**\n * Variant auto-generation module.\n * Generates .art.vue files from component prop analysis.\n *\n * JS-based fallback logic (extractPropsSimple, generateMinimalArt,\n * generateArtFileJs, and helpers) is extracted into `fallback.ts`.\n */\n\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { extractPropsSimple, generateMinimalArt, generateArtFileJs } from \"./fallback.js\";\n\n/**\n * Autogen configuration options.\n */\nexport interface AutogenOptions {\n /** Maximum number of variants to generate (default: 20) */\n maxVariants?: number;\n /** Include a \"Default\" variant with all default values (default: true) */\n includeDefault?: boolean;\n /** Include boolean toggle variants (default: true) */\n includeBooleanToggles?: boolean;\n /** Include enum/union variants (default: true) */\n includeEnumVariants?: boolean;\n /** Include boundary value variants for numbers (default: false) */\n includeBoundaryValues?: boolean;\n /** Include empty string variants for optional strings (default: false) */\n includeEmptyStrings?: boolean;\n}\n\n/**\n * Prop definition for variant generation.\n */\nexport interface PropDefinition {\n name: string;\n propType: string;\n required: boolean;\n defaultValue?: unknown;\n}\n\n/**\n * Generated variant.\n */\nexport interface GeneratedVariant {\n name: string;\n isDefault: boolean;\n props: Record<string, unknown>;\n description?: string;\n}\n\n/**\n * Autogen output.\n */\nexport interface AutogenOutput {\n variants: GeneratedVariant[];\n artFileContent: string;\n componentName: string;\n}\n\n// Native binding types\ninterface NativeAutogen {\n generateVariants?: (\n componentPath: string,\n props: Array<{\n name: string;\n prop_type: string;\n required: boolean;\n default_value?: unknown;\n }>,\n config?: {\n max_variants?: number;\n include_default?: boolean;\n include_boolean_toggles?: boolean;\n include_enum_variants?: boolean;\n include_boundary_values?: boolean;\n include_empty_strings?: boolean;\n },\n ) => {\n variants: Array<{\n name: string;\n is_default: boolean;\n props: Record<string, unknown>;\n description?: string;\n }>;\n art_file_content: string;\n component_name: string;\n };\n analyzeSfc?: (\n source: string,\n options?: { filename?: string },\n ) => {\n props: Array<{ name: string; type: string; required: boolean; default_value?: unknown }>;\n emits: string[];\n };\n}\n\nlet native: NativeAutogen | null = null;\n\nfunction loadNative(): NativeAutogen {\n if (native) return native;\n const require = createRequire(import.meta.url);\n try {\n native = require(\"@vizejs/native\") as NativeAutogen;\n return native;\n } catch (e) {\n throw new Error(\n `Failed to load @vizejs/native. Make sure it's installed and built:\\n${String(e)}`,\n );\n }\n}\n\n/**\n * Generate .art.vue file for a component.\n *\n * @param componentPath - Path to the Vue component file\n * @param options - Auto-generation options\n * @returns Generated .art.vue content and metadata\n */\nexport async function generateArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n): Promise<AutogenOutput> {\n const absolutePath = path.resolve(componentPath);\n const source = await fs.promises.readFile(absolutePath, \"utf-8\");\n\n const binding = loadNative();\n\n // Analyze component to extract props\n let props: PropDefinition[];\n if (binding.analyzeSfc) {\n const analysis = binding.analyzeSfc(source, { filename: absolutePath });\n props = analysis.props.map((p) => ({\n name: p.name,\n propType: p.type,\n required: p.required,\n defaultValue: p.default_value,\n }));\n } else {\n // Fallback: simple regex-based prop extraction\n props = extractPropsSimple(source);\n }\n\n if (props.length === 0) {\n // No props found: generate minimal art file\n const componentName = path.basename(componentPath, \".vue\");\n const relPath = `./${path.basename(componentPath)}`;\n return {\n variants: [{ name: \"Default\", isDefault: true, props: {} }],\n artFileContent: generateMinimalArt(componentName, relPath),\n componentName,\n };\n }\n\n // Use native variant generation if available\n if (binding.generateVariants) {\n const nativeProps = props.map((p) => ({\n name: p.name,\n prop_type: p.propType,\n required: p.required,\n default_value: p.defaultValue,\n }));\n\n const relPath = `./${path.basename(componentPath)}`;\n const result = binding.generateVariants(relPath, nativeProps, {\n max_variants: options.maxVariants,\n include_default: options.includeDefault,\n include_boolean_toggles: options.includeBooleanToggles,\n include_enum_variants: options.includeEnumVariants,\n include_boundary_values: options.includeBoundaryValues,\n include_empty_strings: options.includeEmptyStrings,\n });\n\n return {\n variants: result.variants.map((v) => ({\n name: v.name,\n isDefault: v.is_default,\n props: v.props,\n description: v.description,\n })),\n artFileContent: result.art_file_content,\n componentName: result.component_name,\n };\n }\n\n // Fallback: JS-based generation\n return generateArtFileJs(componentPath, props, options);\n}\n\n/**\n * Write generated .art.vue file to disk.\n */\nexport async function writeArtFile(\n componentPath: string,\n options: AutogenOptions = {},\n outputPath?: string,\n): Promise<string> {\n const output = await generateArtFile(componentPath, options);\n\n const targetPath = outputPath ?? componentPath.replace(/\\.vue$/, \".art.vue\");\n\n await fs.promises.mkdir(path.dirname(targetPath), { recursive: true });\n await fs.promises.writeFile(targetPath, output.artFileContent, \"utf-8\");\n\n return targetPath;\n}\n"],"mappings":"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"}
|
package/dist/cli/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cli/index.ts"],"sourcesContent":null,"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2BK,OAAA;AAAA,UAEY,UAAA,CAFL;EAEK,OAAA,EACN,OADgB"}
|