@vizejs/vite-plugin-musea 0.424.9 → 0.425.1
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/dist/a11y/index.d.mts +1 -1
- package/dist/cli/index.mjs +4 -3
- package/dist/cli/index.mjs.map +1 -1
- package/dist/{index-DzE3Pi5c.d.mts → index-HVtO9FK5.d.mts} +2 -2
- package/dist/{index-DzE3Pi5c.d.mts.map → index-HVtO9FK5.d.mts.map} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{vrt-6lGTi1kM.d.mts → vrt-BPwRc95_.d.mts} +2 -1
- package/dist/vrt-BPwRc95_.d.mts.map +1 -0
- package/dist/{vrt-Dr5Gs71k.mjs → vrt-UxzWFygI.mjs} +30 -16
- package/dist/vrt-UxzWFygI.mjs.map +1 -0
- package/dist/vrt.d.mts +1 -1
- package/dist/vrt.mjs +1 -1
- package/package.json +10 -4
- package/dist/vrt-6lGTi1kM.d.mts.map +0 -1
- package/dist/vrt-Dr5Gs71k.mjs.map +0 -1
package/dist/a11y/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as generateA11yHtmlReport, i as computeA11ySummary, n as MuseaA11yRunner, o as generateA11yJsonReport, r as resolveAxeSource, t as A11ySummary } from "../index-
|
|
1
|
+
import { a as generateA11yHtmlReport, i as computeA11ySummary, n as MuseaA11yRunner, o as generateA11yJsonReport, r as resolveAxeSource, t as A11ySummary } from "../index-HVtO9FK5.mjs";
|
|
2
2
|
export { A11ySummary, MuseaA11yRunner, computeA11ySummary, generateA11yHtmlReport, generateA11yJsonReport, resolveAxeSource };
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as readMuseaOptions, r as processMuseaArtFile } from "../options-CGA4BetG.mjs";
|
|
3
|
-
import { a as normalizeVrtWorkerCount, i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport } from "../vrt-
|
|
3
|
+
import { a as normalizeVrtWorkerCount, i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport } from "../vrt-UxzWFygI.mjs";
|
|
4
4
|
import fs, { realpathSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -48,7 +48,7 @@ async function parseArtFile(filePath) {
|
|
|
48
48
|
* Contains the runVrt, runApprove, runClean, and runGenerate command implementations.
|
|
49
49
|
*/
|
|
50
50
|
function hasCiBlockingVrtResult(summary) {
|
|
51
|
-
return summary.failed > 0;
|
|
51
|
+
return summary.failed > 0 || (summary.errors ?? 0) > 0;
|
|
52
52
|
}
|
|
53
53
|
function isArtFileInput(filePath) {
|
|
54
54
|
return filePath.endsWith(".art.vue");
|
|
@@ -85,6 +85,7 @@ async function runVrt(options, artFiles) {
|
|
|
85
85
|
console.log(` Passed: ${summary.passed}`);
|
|
86
86
|
console.log(` Failed: ${summary.failed}`);
|
|
87
87
|
console.log(` New: ${summary.new}`);
|
|
88
|
+
console.log(` Errors: ${summary.errors ?? 0}`);
|
|
88
89
|
console.log(` Skipped: ${summary.skipped}`);
|
|
89
90
|
console.log(` Total: ${summary.total}`);
|
|
90
91
|
console.log(` Duration: ${(summary.duration / 1e3).toFixed(2)}s\n`);
|
|
@@ -148,7 +149,7 @@ async function runVrt(options, artFiles) {
|
|
|
148
149
|
console.log(` HTML report: ${htmlPath}\n`);
|
|
149
150
|
}
|
|
150
151
|
if (options.ci && hasCiBlockingVrtResult(summary)) {
|
|
151
|
-
console.log(" CI mode: Exiting with error due to failures\n");
|
|
152
|
+
console.log(" CI mode: Exiting with error due to failures or errors\n");
|
|
152
153
|
process.exit(1);
|
|
153
154
|
}
|
|
154
155
|
} finally {
|
package/dist/cli/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/utils.ts","../../src/cli/commands.ts","../../src/cli/config.ts","../../src/cli/index.ts"],"sourcesContent":["/**\n * CLI utility functions for art file scanning and parsing.\n *\n * Extracted from cli.ts to keep file sizes manageable.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { processMuseaArtFile } from \"../plugin/art-processing.js\";\nimport type { ArtFileInfo } from \"../types/index.js\";\n\n/** Recursively scan a directory for .art.vue files. */\nexport async function scanArtFiles(root: string): Promise<string[]> {\n const files: string[] = [];\n\n async function scan(dir: string): Promise<void> {\n let entries: fs.Dirent[];\n try {\n entries = await fs.promises.readdir(dir, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EACCES\") {\n return;\n }\n throw error;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n // Skip node_modules and dist\n if (entry.name === \"node_modules\" || entry.name === \"dist\") {\n continue;\n }\n\n if (entry.isDirectory()) {\n await scan(fullPath);\n } else if (entry.isFile() && entry.name.endsWith(\".art.vue\")) {\n files.push(fullPath);\n }\n }\n }\n\n await scan(root);\n return files;\n}\n\n/** Parse a single .art.vue file into an ArtFileInfo structure. */\nexport async function parseArtFile(filePath: string): Promise<ArtFileInfo | null> {\n return processMuseaArtFile(filePath, {\n root: path.dirname(filePath),\n command: \"serve\",\n });\n}\n","/**\n * CLI command handlers for the Musea VRT tool.\n *\n * Extracted from cli.ts to keep file sizes manageable.\n * Contains the runVrt, runApprove, runClean, and runGenerate command implementations.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { MuseaVrtRunner, generateVrtReport, generateVrtJsonReport } from \"../vrt.js\";\nimport type { ExtendedVrtOptions, VrtSummary } from \"../vrt.js\";\nimport { normalizeVrtWorkerCount } from \"../vrt.js\";\nimport type { ArtFileInfo } from \"../types/index.js\";\n\nimport type { CliOptions } from \"./index.js\";\n\nexport function hasCiBlockingVrtResult(summary: VrtSummary): boolean {\n return summary.failed > 0;\n}\n\nexport function isArtFileInput(filePath: string): boolean {\n return filePath.endsWith(\".art.vue\");\n}\n\nexport function createVrtOptions(options: CliOptions): ExtendedVrtOptions {\n const configured = options.vrt ?? {};\n const vrtOptions: ExtendedVrtOptions = {\n ...configured,\n snapshotDir: path.join(options.output, \"snapshots\"),\n threshold: options.thresholdProvided\n ? options.threshold\n : (configured.threshold ?? options.threshold),\n };\n const workers = options.workersProvided ? options.workers : configured.workers;\n if (workers !== undefined) {\n vrtOptions.workers = normalizeVrtWorkerCount(workers);\n }\n return vrtOptions;\n}\n\nexport async function runVrt(options: CliOptions, artFiles: ArtFileInfo[]): Promise<void> {\n const totalVariants = artFiles.reduce(\n (sum, art) => sum + art.variants.filter((v) => !v.skipVrt).length,\n 0,\n );\n\n console.log(` Testing ${totalVariants} variant(s) across ${artFiles.length} art file(s)\\n`);\n\n const runner = new MuseaVrtRunner({\n ...createVrtOptions(options),\n ci: options.ci ? { failOnDiff: true, jsonReport: options.json } : undefined,\n });\n\n try {\n console.log(\" Launching browser...\");\n await runner.init();\n\n console.log(\" Running visual regression tests...\\n\");\n\n // Run tests\n const results = await runner.runAllTests(artFiles, options.baseUrl);\n const summary = runner.getSummary(results);\n\n // Print results\n console.log(\" Results:\");\n console.log(\" ---------\");\n console.log(` Passed: ${summary.passed}`);\n console.log(` Failed: ${summary.failed}`);\n console.log(` New: ${summary.new}`);\n console.log(` Skipped: ${summary.skipped}`);\n console.log(` Total: ${summary.total}`);\n console.log(` Duration: ${(summary.duration / 1000).toFixed(2)}s\\n`);\n\n // Run a11y audits if requested\n if (options.a11y) {\n console.log(\" Running accessibility audits...\\n\");\n try {\n const { MuseaA11yRunner } = await import(\"../a11y/index.js\");\n const a11yRunner = new MuseaA11yRunner();\n const a11yResults = await a11yRunner.runAudits(artFiles, options.baseUrl, runner);\n const a11ySummary = a11yRunner.getSummary(a11yResults);\n\n console.log(\" A11y Results:\");\n console.log(\" -------------\");\n console.log(` Components: ${a11ySummary.totalComponents}`);\n console.log(` Variants: ${a11ySummary.totalVariants}`);\n console.log(` Violations: ${a11ySummary.totalViolations}`);\n console.log(` Critical: ${a11ySummary.criticalCount}`);\n console.log(` Serious: ${a11ySummary.seriousCount}`);\n console.log(` Not audited: ${a11ySummary.erroredVariants}\\n`);\n\n // Generate a11y report\n const reportDir = options.output;\n await fs.promises.mkdir(reportDir, { recursive: true });\n\n if (options.json) {\n const a11yJson = a11yRunner.generateJsonReport(a11yResults);\n const a11yPath = path.join(reportDir, \"a11y-report.json\");\n await fs.promises.writeFile(a11yPath, a11yJson);\n console.log(` A11y JSON report: ${a11yPath}\\n`);\n } else {\n const a11yHtml = a11yRunner.generateHtmlReport(a11yResults);\n const a11yPath = path.join(reportDir, \"a11y-report.html\");\n await fs.promises.writeFile(a11yPath, a11yHtml);\n console.log(` A11y HTML report: ${a11yPath}\\n`);\n }\n\n // CI mode - exit with error on critical/serious violations. A variant\n // whose audit never ran fails too, but under its own message: it is\n // not an accessibility finding, and reporting it as one sends people\n // looking for a component defect that does not exist.\n if (options.ci && a11ySummary.erroredVariants > 0) {\n console.log(\n ` CI mode: ${a11ySummary.erroredVariants} variant(s) could not be audited\\n`,\n );\n process.exit(1);\n }\n if (options.ci && (a11ySummary.criticalCount > 0 || a11ySummary.seriousCount > 0)) {\n console.log(\" CI mode: Accessibility violations found\\n\");\n process.exit(1);\n }\n } catch (e) {\n console.warn(\" A11y audits skipped:\", e instanceof Error ? e.message : String(e));\n console.warn(\" Make sure axe-core is installed: npm install axe-core\\n\");\n }\n }\n\n // Update baselines if requested\n if (options.update) {\n console.log(\" Updating baselines...\");\n const updated = await runner.updateBaselines(results);\n console.log(` Updated ${updated} baseline(s)\\n`);\n }\n\n // Generate VRT report\n const reportDir = options.output;\n await fs.promises.mkdir(reportDir, { recursive: true });\n\n if (options.json) {\n const jsonReport = generateVrtJsonReport(results, summary);\n const jsonPath = path.join(reportDir, \"vrt-report.json\");\n await fs.promises.writeFile(jsonPath, jsonReport);\n console.log(` JSON report: ${jsonPath}\\n`);\n } else {\n const htmlReport = generateVrtReport(results, summary);\n const htmlPath = path.join(reportDir, \"vrt-report.html\");\n await fs.promises.writeFile(htmlPath, htmlReport);\n console.log(` HTML report: ${htmlPath}\\n`);\n }\n\n // CI mode - exit with error if visual diffs occurred.\n if (options.ci && hasCiBlockingVrtResult(summary)) {\n console.log(\" CI mode: Exiting with error due to failures\\n\");\n process.exit(1);\n }\n } finally {\n await runner.close();\n }\n}\n\nexport async function runApprove(options: CliOptions, artFiles: ArtFileInfo[]): Promise<void> {\n const runner = new MuseaVrtRunner(createVrtOptions(options));\n\n try {\n console.log(\" Launching browser...\");\n await runner.init();\n\n console.log(\" Running tests to find diffs...\\n\");\n\n const results = await runner.runAllTests(artFiles, options.baseUrl);\n const failed = results.filter((r) => !r.passed && !r.error);\n\n if (failed.length === 0) {\n console.log(\" No failed tests to approve.\\n\");\n return;\n }\n\n const pattern = options.pattern;\n if (pattern) {\n console.log(` Approving snapshots matching: ${pattern}\\n`);\n } else {\n console.log(` Approving all ${failed.length} failed snapshot(s)...\\n`);\n }\n\n const approved = await runner.approveResults(results, pattern);\n console.log(` Approved ${approved} snapshot(s)\\n`);\n } finally {\n await runner.close();\n }\n}\n\nexport async function runClean(options: CliOptions, artFiles: ArtFileInfo[]): Promise<void> {\n const runner = new MuseaVrtRunner(createVrtOptions(options));\n\n console.log(\" Scanning for orphaned snapshots...\\n\");\n\n const cleaned = await runner.cleanOrphans(artFiles);\n\n if (cleaned === 0) {\n console.log(\" No orphaned snapshots found.\\n\");\n } else {\n console.log(`\\n Cleaned ${cleaned} orphaned snapshot(s)\\n`);\n }\n}\n\nexport async function runGenerate(options: CliOptions): Promise<void> {\n if (!options.componentPath) {\n console.error(\" Error: Missing component path.\");\n console.error(\" Usage: musea-vrt generate <component.vue>\\n\");\n process.exit(1);\n }\n\n const componentPath = path.resolve(options.componentPath);\n if (isArtFileInput(componentPath)) {\n console.error(\n \" Error: musea-vrt generate expects a source .vue component, not a .art.vue file.\",\n );\n console.error(\" Pass the component file that the art file should wrap.\\n\");\n process.exit(1);\n }\n\n // Check file exists\n try {\n await fs.promises.access(componentPath);\n } catch {\n console.error(` Error: File not found: ${componentPath}\\n`);\n process.exit(1);\n }\n\n console.log(` Generating art file for: ${path.relative(process.cwd(), componentPath)}\\n`);\n\n try {\n const { writeArtFile } = await import(\"../autogen/index.js\");\n const outputPath = await writeArtFile(componentPath);\n const relOutput = path.relative(process.cwd(), outputPath);\n\n console.log(` Generated: ${relOutput}\\n`);\n } catch (e) {\n console.error(\" Generation failed:\", e instanceof Error ? e.message : String(e));\n process.exit(1);\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { loadConfigFromFile, type PluginOption } from \"vite\";\n\nimport type { MuseaVrtOptions } from \"../types/index.js\";\nimport { readMuseaOptions } from \"../plugin/options.js\";\n\nexport async function loadMuseaVrtOptions(\n configPath: string,\n cwd = process.cwd(),\n): Promise<MuseaVrtOptions | undefined> {\n const resolvedConfigPath = path.isAbsolute(configPath)\n ? configPath\n : path.resolve(cwd, configPath);\n if (!(await fileExists(resolvedConfigPath))) return undefined;\n\n const loaded = await loadConfigFromFile(\n {\n command: \"serve\",\n mode: \"development\",\n isPreview: false,\n isSsrBuild: false,\n },\n resolvedConfigPath,\n );\n if (!loaded) return undefined;\n\n const plugins: unknown[] = [];\n await collectPlugins(loaded.config.plugins, plugins);\n\n for (const plugin of plugins) {\n const options = readMuseaOptions(plugin);\n if (options?.vrt) return options.vrt;\n }\n return undefined;\n}\n\nasync function collectPlugins(\n input: PluginOption | Promise<PluginOption> | undefined,\n plugins: unknown[],\n) {\n const resolved = await input;\n if (!resolved) return;\n\n if (Array.isArray(resolved)) {\n for (const item of resolved) {\n await collectPlugins(item, plugins);\n }\n return;\n }\n\n plugins.push(resolved);\n}\n\nasync 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","#!/usr/bin/env node\n/**\n * Musea CLI\n *\n * Usage:\n * musea-vrt [command] [options]\n *\n * Commands:\n * (default) Run VRT tests\n * approve [pat] Approve failed snapshots (optionally filtered by pattern)\n * clean Remove orphaned snapshots\n *\n * Options:\n * -u, --update Update baseline snapshots\n * -c, --config Path to vite config (default: vite.config.ts)\n * -o, --output Output directory for reports (default: .vize)\n * -t, --threshold Diff threshold percentage (default: 0.1)\n * -w, --workers Number of concurrent captures (default: 1)\n * --json Output JSON report instead of HTML\n * --ci CI mode - exit with non-zero code on failures\n * --a11y Run accessibility audits alongside VRT\n * -h, --help Show help\n */\n\nimport path from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ArtFileInfo } from \"../types/index.js\";\nimport { scanArtFiles, parseArtFile } from \"./utils.js\";\nimport { runVrt, runApprove, runClean, runGenerate } from \"./commands.js\";\nimport { loadMuseaVrtOptions } from \"./config.js\";\nimport type { MuseaVrtOptions } from \"../types/index.js\";\n\ntype Command = \"run\" | \"approve\" | \"clean\" | \"generate\";\n\nexport interface CliOptions {\n command: Command;\n update: boolean;\n config: string;\n output: string;\n threshold: number;\n thresholdProvided: boolean;\n workers: number;\n workersProvided: boolean;\n json: boolean;\n ci: boolean;\n a11y: boolean;\n help: boolean;\n baseUrl: string;\n pattern?: string;\n componentPath?: string;\n vrt?: MuseaVrtOptions;\n}\n\nexport function parseArgs(args: string[]): CliOptions {\n const options: CliOptions = {\n command: \"run\",\n update: false,\n config: \"vite.config.ts\",\n output: \".vize\",\n threshold: 0.1,\n thresholdProvided: false,\n workers: 1,\n workersProvided: false,\n json: false,\n ci: false,\n a11y: false,\n help: false,\n baseUrl: \"http://localhost:5173\",\n };\n\n let i = 0;\n\n // Check for subcommand as first arg\n if (args.length > 0 && !args[0].startsWith(\"-\")) {\n const sub = args[0];\n if (sub === \"approve\") {\n options.command = \"approve\";\n i = 1;\n // Optional pattern argument after approve\n if (args.length > 1 && !args[1].startsWith(\"-\")) {\n options.pattern = args[1];\n i = 2;\n }\n } else if (sub === \"clean\") {\n options.command = \"clean\";\n i = 1;\n } else if (sub === \"generate\") {\n options.command = \"generate\";\n i = 1;\n // Required component path argument\n if (args.length > 1 && !args[1].startsWith(\"-\")) {\n options.componentPath = args[1];\n i = 2;\n }\n }\n }\n\n for (; i < args.length; i++) {\n const arg = args[i];\n switch (arg) {\n case \"-u\":\n case \"--update\":\n options.update = true;\n break;\n case \"-c\":\n case \"--config\":\n options.config = args[++i] || \"vite.config.ts\";\n break;\n case \"-o\":\n case \"--output\":\n options.output = args[++i] || \".vize\";\n break;\n case \"-t\":\n case \"--threshold\":\n options.threshold = parseThreshold(args[++i]);\n options.thresholdProvided = true;\n break;\n case \"-w\":\n case \"--workers\":\n options.workers = parseWorkers(args[++i]);\n options.workersProvided = true;\n break;\n case \"--json\":\n options.json = true;\n break;\n case \"--ci\":\n options.ci = true;\n break;\n case \"--a11y\":\n options.a11y = true;\n break;\n case \"-b\":\n case \"--base-url\":\n options.baseUrl = args[++i] || \"http://localhost:5173\";\n break;\n case \"-h\":\n case \"--help\":\n options.help = true;\n break;\n }\n }\n\n return options;\n}\n\nfunction parseThreshold(value: string | undefined): number {\n const threshold = Number.parseFloat(value ?? \"\");\n return Number.isFinite(threshold) ? threshold : 0.1;\n}\n\nfunction parseWorkers(value: string | undefined): number {\n const workers = Number.parseInt(value ?? \"\", 10);\n return Number.isFinite(workers) ? Math.max(1, workers) : 1;\n}\n\nfunction printHelp(): void {\n console.log(`\nMusea VRT - Visual Regression Testing for Component Gallery\n\nUsage:\n musea-vrt [command] [options]\n\nCommands:\n (default) Run VRT tests\n approve [pattern] Approve failed snapshots and update baselines\n Optional pattern filters which snapshots to approve\n clean Remove orphaned snapshots (no matching art/variant)\n generate <component> Auto-generate .art.vue from a Vue component\n\nOptions:\n -u, --update Update baseline snapshots with current screenshots\n -c, --config <path> Path to vite config file (default: vite.config.ts)\n -o, --output <dir> Output directory for reports (default: .vize)\n -t, --threshold <n> Diff threshold percentage (default: 0.1)\n -b, --base-url <url> Base URL for dev server (default: http://localhost:5173)\n -w, --workers <n> Number of concurrent captures (default: 1)\n --json Output JSON report instead of HTML\n --ci CI mode - exit with non-zero code on failures\n --a11y Run accessibility audits alongside VRT\n -h, --help Show this help message\n\nExamples:\n # Run VRT tests\n musea-vrt\n\n # Update baseline snapshots\n musea-vrt -u\n\n # Run with custom threshold\n musea-vrt -t 0.5\n\n # CI mode with JSON output\n musea-vrt --ci --json\n\n # Run with accessibility audits\n musea-vrt --a11y\n\n # Capture variants concurrently\n musea-vrt --workers 8\n\n # Approve all failed snapshots\n musea-vrt approve\n\n # Approve specific snapshots by pattern\n musea-vrt approve \"Button/*\"\n\n # Clean orphaned snapshots\n musea-vrt clean\n\n # Auto-generate .art.vue from component\n musea-vrt generate src/components/Button.vue\n\n # Custom base URL\n musea-vrt -b http://localhost:3000\n`);\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const options = parseArgs(args);\n\n if (options.help) {\n printHelp();\n process.exit(0);\n }\n\n const cwd = process.cwd();\n\n console.log(\"\\n Musea VRT\");\n console.log(\" =========\\n\");\n\n // Handle generate command early (doesn't need art file scanning)\n if (options.command === \"generate\") {\n try {\n await runGenerate(options);\n } catch (error) {\n console.error(\"\\n Error:\", error);\n process.exit(1);\n }\n return;\n }\n\n options.vrt = await loadMuseaVrtOptions(options.config, cwd);\n\n // Scan for art files\n console.log(\" Scanning for art files...\");\n const artFilePaths = await scanArtFiles(cwd);\n\n if (artFilePaths.length === 0) {\n console.log(\" No art files found.\\n\");\n process.exit(0);\n }\n\n console.log(` Found ${artFilePaths.length} art file(s)\\n`);\n\n // Parse art files\n const artFiles: ArtFileInfo[] = [];\n for (const filePath of artFilePaths) {\n const art = await parseArtFile(filePath);\n if (art) {\n artFiles.push(art);\n }\n }\n\n try {\n switch (options.command) {\n case \"run\":\n await runVrt(options, artFiles);\n break;\n case \"approve\":\n await runApprove(options, artFiles);\n break;\n case \"clean\":\n await runClean(options, artFiles);\n break;\n case \"generate\":\n // Handled above before art file scanning\n break;\n }\n } catch (error) {\n console.error(\"\\n Error:\", error);\n process.exit(1);\n }\n}\n\nif (isDirectRun()) {\n main().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n\nfunction isDirectRun(): boolean {\n const entrypoint = process.argv[1];\n if (!entrypoint) {\n return false;\n }\n\n try {\n return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(entrypoint));\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/utils.ts","../../src/cli/commands.ts","../../src/cli/config.ts","../../src/cli/index.ts"],"sourcesContent":["/**\n * CLI utility functions for art file scanning and parsing.\n *\n * Extracted from cli.ts to keep file sizes manageable.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { processMuseaArtFile } from \"../plugin/art-processing.js\";\nimport type { ArtFileInfo } from \"../types/index.js\";\n\n/** Recursively scan a directory for .art.vue files. */\nexport async function scanArtFiles(root: string): Promise<string[]> {\n const files: string[] = [];\n\n async function scan(dir: string): Promise<void> {\n let entries: fs.Dirent[];\n try {\n entries = await fs.promises.readdir(dir, { withFileTypes: true });\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"EACCES\") {\n return;\n }\n throw error;\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n\n // Skip node_modules and dist\n if (entry.name === \"node_modules\" || entry.name === \"dist\") {\n continue;\n }\n\n if (entry.isDirectory()) {\n await scan(fullPath);\n } else if (entry.isFile() && entry.name.endsWith(\".art.vue\")) {\n files.push(fullPath);\n }\n }\n }\n\n await scan(root);\n return files;\n}\n\n/** Parse a single .art.vue file into an ArtFileInfo structure. */\nexport async function parseArtFile(filePath: string): Promise<ArtFileInfo | null> {\n return processMuseaArtFile(filePath, {\n root: path.dirname(filePath),\n command: \"serve\",\n });\n}\n","/**\n * CLI command handlers for the Musea VRT tool.\n *\n * Extracted from cli.ts to keep file sizes manageable.\n * Contains the runVrt, runApprove, runClean, and runGenerate command implementations.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { MuseaVrtRunner, generateVrtReport, generateVrtJsonReport } from \"../vrt.js\";\nimport type { ExtendedVrtOptions, VrtSummary } from \"../vrt.js\";\nimport { normalizeVrtWorkerCount } from \"../vrt.js\";\nimport type { ArtFileInfo } from \"../types/index.js\";\n\nimport type { CliOptions } from \"./index.js\";\n\nexport function hasCiBlockingVrtResult(summary: VrtSummary): boolean {\n return summary.failed > 0 || (summary.errors ?? 0) > 0;\n}\n\nexport function isArtFileInput(filePath: string): boolean {\n return filePath.endsWith(\".art.vue\");\n}\n\nexport function createVrtOptions(options: CliOptions): ExtendedVrtOptions {\n const configured = options.vrt ?? {};\n const vrtOptions: ExtendedVrtOptions = {\n ...configured,\n snapshotDir: path.join(options.output, \"snapshots\"),\n threshold: options.thresholdProvided\n ? options.threshold\n : (configured.threshold ?? options.threshold),\n };\n const workers = options.workersProvided ? options.workers : configured.workers;\n if (workers !== undefined) {\n vrtOptions.workers = normalizeVrtWorkerCount(workers);\n }\n return vrtOptions;\n}\n\nexport async function runVrt(options: CliOptions, artFiles: ArtFileInfo[]): Promise<void> {\n const totalVariants = artFiles.reduce(\n (sum, art) => sum + art.variants.filter((v) => !v.skipVrt).length,\n 0,\n );\n\n console.log(` Testing ${totalVariants} variant(s) across ${artFiles.length} art file(s)\\n`);\n\n const runner = new MuseaVrtRunner({\n ...createVrtOptions(options),\n ci: options.ci ? { failOnDiff: true, jsonReport: options.json } : undefined,\n });\n\n try {\n console.log(\" Launching browser...\");\n await runner.init();\n\n console.log(\" Running visual regression tests...\\n\");\n\n // Run tests\n const results = await runner.runAllTests(artFiles, options.baseUrl);\n const summary = runner.getSummary(results);\n\n // Print results\n console.log(\" Results:\");\n console.log(\" ---------\");\n console.log(` Passed: ${summary.passed}`);\n console.log(` Failed: ${summary.failed}`);\n console.log(` New: ${summary.new}`);\n console.log(` Errors: ${summary.errors ?? 0}`);\n console.log(` Skipped: ${summary.skipped}`);\n console.log(` Total: ${summary.total}`);\n console.log(` Duration: ${(summary.duration / 1000).toFixed(2)}s\\n`);\n\n // Run a11y audits if requested\n if (options.a11y) {\n console.log(\" Running accessibility audits...\\n\");\n try {\n const { MuseaA11yRunner } = await import(\"../a11y/index.js\");\n const a11yRunner = new MuseaA11yRunner();\n const a11yResults = await a11yRunner.runAudits(artFiles, options.baseUrl, runner);\n const a11ySummary = a11yRunner.getSummary(a11yResults);\n\n console.log(\" A11y Results:\");\n console.log(\" -------------\");\n console.log(` Components: ${a11ySummary.totalComponents}`);\n console.log(` Variants: ${a11ySummary.totalVariants}`);\n console.log(` Violations: ${a11ySummary.totalViolations}`);\n console.log(` Critical: ${a11ySummary.criticalCount}`);\n console.log(` Serious: ${a11ySummary.seriousCount}`);\n console.log(` Not audited: ${a11ySummary.erroredVariants}\\n`);\n\n // Generate a11y report\n const reportDir = options.output;\n await fs.promises.mkdir(reportDir, { recursive: true });\n\n if (options.json) {\n const a11yJson = a11yRunner.generateJsonReport(a11yResults);\n const a11yPath = path.join(reportDir, \"a11y-report.json\");\n await fs.promises.writeFile(a11yPath, a11yJson);\n console.log(` A11y JSON report: ${a11yPath}\\n`);\n } else {\n const a11yHtml = a11yRunner.generateHtmlReport(a11yResults);\n const a11yPath = path.join(reportDir, \"a11y-report.html\");\n await fs.promises.writeFile(a11yPath, a11yHtml);\n console.log(` A11y HTML report: ${a11yPath}\\n`);\n }\n\n // CI mode - exit with error on critical/serious violations. A variant\n // whose audit never ran fails too, but under its own message: it is\n // not an accessibility finding, and reporting it as one sends people\n // looking for a component defect that does not exist.\n if (options.ci && a11ySummary.erroredVariants > 0) {\n console.log(\n ` CI mode: ${a11ySummary.erroredVariants} variant(s) could not be audited\\n`,\n );\n process.exit(1);\n }\n if (options.ci && (a11ySummary.criticalCount > 0 || a11ySummary.seriousCount > 0)) {\n console.log(\" CI mode: Accessibility violations found\\n\");\n process.exit(1);\n }\n } catch (e) {\n console.warn(\" A11y audits skipped:\", e instanceof Error ? e.message : String(e));\n console.warn(\" Make sure axe-core is installed: npm install axe-core\\n\");\n }\n }\n\n // Update baselines if requested\n if (options.update) {\n console.log(\" Updating baselines...\");\n const updated = await runner.updateBaselines(results);\n console.log(` Updated ${updated} baseline(s)\\n`);\n }\n\n // Generate VRT report\n const reportDir = options.output;\n await fs.promises.mkdir(reportDir, { recursive: true });\n\n if (options.json) {\n const jsonReport = generateVrtJsonReport(results, summary);\n const jsonPath = path.join(reportDir, \"vrt-report.json\");\n await fs.promises.writeFile(jsonPath, jsonReport);\n console.log(` JSON report: ${jsonPath}\\n`);\n } else {\n const htmlReport = generateVrtReport(results, summary);\n const htmlPath = path.join(reportDir, \"vrt-report.html\");\n await fs.promises.writeFile(htmlPath, htmlReport);\n console.log(` HTML report: ${htmlPath}\\n`);\n }\n\n // CI mode - exit with error if visual diffs or runner errors occurred.\n if (options.ci && hasCiBlockingVrtResult(summary)) {\n console.log(\" CI mode: Exiting with error due to failures or errors\\n\");\n process.exit(1);\n }\n } finally {\n await runner.close();\n }\n}\n\nexport async function runApprove(options: CliOptions, artFiles: ArtFileInfo[]): Promise<void> {\n const runner = new MuseaVrtRunner(createVrtOptions(options));\n\n try {\n console.log(\" Launching browser...\");\n await runner.init();\n\n console.log(\" Running tests to find diffs...\\n\");\n\n const results = await runner.runAllTests(artFiles, options.baseUrl);\n const failed = results.filter((r) => !r.passed && !r.error);\n\n if (failed.length === 0) {\n console.log(\" No failed tests to approve.\\n\");\n return;\n }\n\n const pattern = options.pattern;\n if (pattern) {\n console.log(` Approving snapshots matching: ${pattern}\\n`);\n } else {\n console.log(` Approving all ${failed.length} failed snapshot(s)...\\n`);\n }\n\n const approved = await runner.approveResults(results, pattern);\n console.log(` Approved ${approved} snapshot(s)\\n`);\n } finally {\n await runner.close();\n }\n}\n\nexport async function runClean(options: CliOptions, artFiles: ArtFileInfo[]): Promise<void> {\n const runner = new MuseaVrtRunner(createVrtOptions(options));\n\n console.log(\" Scanning for orphaned snapshots...\\n\");\n\n const cleaned = await runner.cleanOrphans(artFiles);\n\n if (cleaned === 0) {\n console.log(\" No orphaned snapshots found.\\n\");\n } else {\n console.log(`\\n Cleaned ${cleaned} orphaned snapshot(s)\\n`);\n }\n}\n\nexport async function runGenerate(options: CliOptions): Promise<void> {\n if (!options.componentPath) {\n console.error(\" Error: Missing component path.\");\n console.error(\" Usage: musea-vrt generate <component.vue>\\n\");\n process.exit(1);\n }\n\n const componentPath = path.resolve(options.componentPath);\n if (isArtFileInput(componentPath)) {\n console.error(\n \" Error: musea-vrt generate expects a source .vue component, not a .art.vue file.\",\n );\n console.error(\" Pass the component file that the art file should wrap.\\n\");\n process.exit(1);\n }\n\n // Check file exists\n try {\n await fs.promises.access(componentPath);\n } catch {\n console.error(` Error: File not found: ${componentPath}\\n`);\n process.exit(1);\n }\n\n console.log(` Generating art file for: ${path.relative(process.cwd(), componentPath)}\\n`);\n\n try {\n const { writeArtFile } = await import(\"../autogen/index.js\");\n const outputPath = await writeArtFile(componentPath);\n const relOutput = path.relative(process.cwd(), outputPath);\n\n console.log(` Generated: ${relOutput}\\n`);\n } catch (e) {\n console.error(\" Generation failed:\", e instanceof Error ? e.message : String(e));\n process.exit(1);\n }\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { loadConfigFromFile, type PluginOption } from \"vite\";\n\nimport type { MuseaVrtOptions } from \"../types/index.js\";\nimport { readMuseaOptions } from \"../plugin/options.js\";\n\nexport async function loadMuseaVrtOptions(\n configPath: string,\n cwd = process.cwd(),\n): Promise<MuseaVrtOptions | undefined> {\n const resolvedConfigPath = path.isAbsolute(configPath)\n ? configPath\n : path.resolve(cwd, configPath);\n if (!(await fileExists(resolvedConfigPath))) return undefined;\n\n const loaded = await loadConfigFromFile(\n {\n command: \"serve\",\n mode: \"development\",\n isPreview: false,\n isSsrBuild: false,\n },\n resolvedConfigPath,\n );\n if (!loaded) return undefined;\n\n const plugins: unknown[] = [];\n await collectPlugins(loaded.config.plugins, plugins);\n\n for (const plugin of plugins) {\n const options = readMuseaOptions(plugin);\n if (options?.vrt) return options.vrt;\n }\n return undefined;\n}\n\nasync function collectPlugins(\n input: PluginOption | Promise<PluginOption> | undefined,\n plugins: unknown[],\n) {\n const resolved = await input;\n if (!resolved) return;\n\n if (Array.isArray(resolved)) {\n for (const item of resolved) {\n await collectPlugins(item, plugins);\n }\n return;\n }\n\n plugins.push(resolved);\n}\n\nasync 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","#!/usr/bin/env node\n/**\n * Musea CLI\n *\n * Usage:\n * musea-vrt [command] [options]\n *\n * Commands:\n * (default) Run VRT tests\n * approve [pat] Approve failed snapshots (optionally filtered by pattern)\n * clean Remove orphaned snapshots\n *\n * Options:\n * -u, --update Update baseline snapshots\n * -c, --config Path to vite config (default: vite.config.ts)\n * -o, --output Output directory for reports (default: .vize)\n * -t, --threshold Diff threshold percentage (default: 0.1)\n * -w, --workers Number of concurrent captures (default: 1)\n * --json Output JSON report instead of HTML\n * --ci CI mode - exit with non-zero code on failures\n * --a11y Run accessibility audits alongside VRT\n * -h, --help Show help\n */\n\nimport path from \"node:path\";\nimport { realpathSync } from \"node:fs\";\nimport { fileURLToPath } from \"node:url\";\nimport type { ArtFileInfo } from \"../types/index.js\";\nimport { scanArtFiles, parseArtFile } from \"./utils.js\";\nimport { runVrt, runApprove, runClean, runGenerate } from \"./commands.js\";\nimport { loadMuseaVrtOptions } from \"./config.js\";\nimport type { MuseaVrtOptions } from \"../types/index.js\";\n\ntype Command = \"run\" | \"approve\" | \"clean\" | \"generate\";\n\nexport interface CliOptions {\n command: Command;\n update: boolean;\n config: string;\n output: string;\n threshold: number;\n thresholdProvided: boolean;\n workers: number;\n workersProvided: boolean;\n json: boolean;\n ci: boolean;\n a11y: boolean;\n help: boolean;\n baseUrl: string;\n pattern?: string;\n componentPath?: string;\n vrt?: MuseaVrtOptions;\n}\n\nexport function parseArgs(args: string[]): CliOptions {\n const options: CliOptions = {\n command: \"run\",\n update: false,\n config: \"vite.config.ts\",\n output: \".vize\",\n threshold: 0.1,\n thresholdProvided: false,\n workers: 1,\n workersProvided: false,\n json: false,\n ci: false,\n a11y: false,\n help: false,\n baseUrl: \"http://localhost:5173\",\n };\n\n let i = 0;\n\n // Check for subcommand as first arg\n if (args.length > 0 && !args[0].startsWith(\"-\")) {\n const sub = args[0];\n if (sub === \"approve\") {\n options.command = \"approve\";\n i = 1;\n // Optional pattern argument after approve\n if (args.length > 1 && !args[1].startsWith(\"-\")) {\n options.pattern = args[1];\n i = 2;\n }\n } else if (sub === \"clean\") {\n options.command = \"clean\";\n i = 1;\n } else if (sub === \"generate\") {\n options.command = \"generate\";\n i = 1;\n // Required component path argument\n if (args.length > 1 && !args[1].startsWith(\"-\")) {\n options.componentPath = args[1];\n i = 2;\n }\n }\n }\n\n for (; i < args.length; i++) {\n const arg = args[i];\n switch (arg) {\n case \"-u\":\n case \"--update\":\n options.update = true;\n break;\n case \"-c\":\n case \"--config\":\n options.config = args[++i] || \"vite.config.ts\";\n break;\n case \"-o\":\n case \"--output\":\n options.output = args[++i] || \".vize\";\n break;\n case \"-t\":\n case \"--threshold\":\n options.threshold = parseThreshold(args[++i]);\n options.thresholdProvided = true;\n break;\n case \"-w\":\n case \"--workers\":\n options.workers = parseWorkers(args[++i]);\n options.workersProvided = true;\n break;\n case \"--json\":\n options.json = true;\n break;\n case \"--ci\":\n options.ci = true;\n break;\n case \"--a11y\":\n options.a11y = true;\n break;\n case \"-b\":\n case \"--base-url\":\n options.baseUrl = args[++i] || \"http://localhost:5173\";\n break;\n case \"-h\":\n case \"--help\":\n options.help = true;\n break;\n }\n }\n\n return options;\n}\n\nfunction parseThreshold(value: string | undefined): number {\n const threshold = Number.parseFloat(value ?? \"\");\n return Number.isFinite(threshold) ? threshold : 0.1;\n}\n\nfunction parseWorkers(value: string | undefined): number {\n const workers = Number.parseInt(value ?? \"\", 10);\n return Number.isFinite(workers) ? Math.max(1, workers) : 1;\n}\n\nfunction printHelp(): void {\n console.log(`\nMusea VRT - Visual Regression Testing for Component Gallery\n\nUsage:\n musea-vrt [command] [options]\n\nCommands:\n (default) Run VRT tests\n approve [pattern] Approve failed snapshots and update baselines\n Optional pattern filters which snapshots to approve\n clean Remove orphaned snapshots (no matching art/variant)\n generate <component> Auto-generate .art.vue from a Vue component\n\nOptions:\n -u, --update Update baseline snapshots with current screenshots\n -c, --config <path> Path to vite config file (default: vite.config.ts)\n -o, --output <dir> Output directory for reports (default: .vize)\n -t, --threshold <n> Diff threshold percentage (default: 0.1)\n -b, --base-url <url> Base URL for dev server (default: http://localhost:5173)\n -w, --workers <n> Number of concurrent captures (default: 1)\n --json Output JSON report instead of HTML\n --ci CI mode - exit with non-zero code on failures\n --a11y Run accessibility audits alongside VRT\n -h, --help Show this help message\n\nExamples:\n # Run VRT tests\n musea-vrt\n\n # Update baseline snapshots\n musea-vrt -u\n\n # Run with custom threshold\n musea-vrt -t 0.5\n\n # CI mode with JSON output\n musea-vrt --ci --json\n\n # Run with accessibility audits\n musea-vrt --a11y\n\n # Capture variants concurrently\n musea-vrt --workers 8\n\n # Approve all failed snapshots\n musea-vrt approve\n\n # Approve specific snapshots by pattern\n musea-vrt approve \"Button/*\"\n\n # Clean orphaned snapshots\n musea-vrt clean\n\n # Auto-generate .art.vue from component\n musea-vrt generate src/components/Button.vue\n\n # Custom base URL\n musea-vrt -b http://localhost:3000\n`);\n}\n\nasync function main(): Promise<void> {\n const args = process.argv.slice(2);\n const options = parseArgs(args);\n\n if (options.help) {\n printHelp();\n process.exit(0);\n }\n\n const cwd = process.cwd();\n\n console.log(\"\\n Musea VRT\");\n console.log(\" =========\\n\");\n\n // Handle generate command early (doesn't need art file scanning)\n if (options.command === \"generate\") {\n try {\n await runGenerate(options);\n } catch (error) {\n console.error(\"\\n Error:\", error);\n process.exit(1);\n }\n return;\n }\n\n options.vrt = await loadMuseaVrtOptions(options.config, cwd);\n\n // Scan for art files\n console.log(\" Scanning for art files...\");\n const artFilePaths = await scanArtFiles(cwd);\n\n if (artFilePaths.length === 0) {\n console.log(\" No art files found.\\n\");\n process.exit(0);\n }\n\n console.log(` Found ${artFilePaths.length} art file(s)\\n`);\n\n // Parse art files\n const artFiles: ArtFileInfo[] = [];\n for (const filePath of artFilePaths) {\n const art = await parseArtFile(filePath);\n if (art) {\n artFiles.push(art);\n }\n }\n\n try {\n switch (options.command) {\n case \"run\":\n await runVrt(options, artFiles);\n break;\n case \"approve\":\n await runApprove(options, artFiles);\n break;\n case \"clean\":\n await runClean(options, artFiles);\n break;\n case \"generate\":\n // Handled above before art file scanning\n break;\n }\n } catch (error) {\n console.error(\"\\n Error:\", error);\n process.exit(1);\n }\n}\n\nif (isDirectRun()) {\n main().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n });\n}\n\nfunction isDirectRun(): boolean {\n const entrypoint = process.argv[1];\n if (!entrypoint) {\n return false;\n }\n\n try {\n return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(path.resolve(entrypoint));\n } catch {\n return false;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as ArtFileInfo, r as MuseaVrtRunner } from "./vrt-
|
|
1
|
+
import { l as ArtFileInfo, r as MuseaVrtRunner } from "./vrt-BPwRc95_.mjs";
|
|
2
2
|
import { Page } from "playwright";
|
|
3
3
|
|
|
4
4
|
//#region src/types/api.d.ts
|
|
@@ -167,4 +167,4 @@ declare class MuseaA11yRunner {
|
|
|
167
167
|
}
|
|
168
168
|
//#endregion
|
|
169
169
|
export { generateA11yHtmlReport as a, A11yResult as c, computeA11ySummary as i, AnalysisApiResponse as l, MuseaA11yRunner as n, generateA11yJsonReport as o, resolveAxeSource as r, A11yOptions as s, A11ySummary as t, PaletteApiResponse as u };
|
|
170
|
-
//# sourceMappingURL=index-
|
|
170
|
+
//# sourceMappingURL=index-HVtO9FK5.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-
|
|
1
|
+
{"version":3,"file":"index-HVtO9FK5.d.mts","names":[],"sources":["../src/types/api.ts","../src/a11y/report.ts","../src/a11y/index.ts"],"mappings":";;;;;;;UAGiB,kBAAA;EACf,KAAA;EACA,QAAA,EAAU,cAAc;EACxB,MAAA;EACA,IAAA;EACA,UAAA;AAAA;;;;UAMe,cAAA;EACf,IAAA;EACA,OAAA,EAAS,WAAA;EACT,aAAA;EACA,WAAA;EACA,QAAA;EACA,OAAA,EAAS,KAAK;IAAG,KAAA;IAAe,KAAA;EAAA;EAChC,KAAA;IAAU,GAAA;IAAa,GAAA;IAAa,IAAA;EAAA;EACpC,KAAA;AAAA;;;;KAMU,WAAA;;;AANL;UAuBU,mBAAA;EACf,KAAA,EAAO,KAAK;IACV,IAAA;IACA,IAAA;IACA,QAAA;IACA,aAAA;EAAA;EAEF,KAAA;AAAA;;;;UAUe,WAAA;EAbb;EAeF,OAAA;EAZA;EAcA,YAAA;EAdK;EAgBL,YAAA;EAN0B;EAQ1B,KAAA;AAAA;;;;UAMe,UAAA;EACf,OAAA;EACA,WAAA;EACA,UAAA,EAAY,aAAa;EACzB,MAAA;EACA,UAAA;EAFyB;;;;;;;;EAWzB,KAAA;AAAA;AAMF;;;AAAA,UAAiB,aAAA;EACf,EAAA;EACA,MAAA;EACA,WAAA;EACA,OAAA;EACA,KAAA;AAAA;;;;;;iBCvFc,kBAAA,CAAmB,OAAA,EAAS,UAAA,KAAe,WAAW;;;;iBAmBtD,sBAAA,CAAuB,OAAA,EAAS,UAAA,IAAc,OAAA,EAAS,WAAW;ADnBlF;;;AAAA,iBCgLgB,sBAAA,CAAuB,OAAqB,EAAZ,UAAU;;;;;;UCvKzC,WAAA;EACf,eAAA;EACA,aAAA;EFjBU;EEmBV,eAAA;EACA,eAAA;EACA,aAAA;EACA,YAAA;EACA,aAAA;EACA,UAAA;AAAA;;;;;;;;;;;;iBA6BoB,gBAAA,IAAoB,OAAO;;;AFvC1C;cEgEM,eAAA;EAAA,QACH,OAAA;cAEI,OAAA,GAAS,WAAA;EF7DA;AAAA;AAiBvB;;EEyDQ,SAAA,CACJ,QAAA,EAAU,WAAA,IACV,OAAA,UACA,SAAA,GAAY,cAAA,GACX,OAAA,CAAQ,UAAA;EF5DC;;;EEoHN,SAAA,CAAU,IAAA,EAAM,IAAA,EAAM,OAAA,UAAiB,WAAA,WAAsB,OAAA,CAAQ,UAAA;EFlHzE;;;EEqJF,UAAA,CAAW,OAAA,EAAS,UAAA,KAAe,WAAA;EFjJ9B;AAAA;AAUP;EE8IE,kBAAA,CAAmB,OAAA,EAAS,UAAA;;;;EAQ5B,kBAAA,CAAmB,OAAA,EAAS,UAAA;EFhJ5B;;;EAAA,QEuJQ,eAAA;EAAA,QAqCA,eAAA;AAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as VrtOptions, i as ViewportConfig, n as CiConfig, r as ComparisonConfig, t as CaptureConfig } from "./vrt-DVub2B6r.mjs";
|
|
2
2
|
import { a as MuseaTokenPreviewConfig, c as ResolvedTokenPreview, d as StyleDictionaryConfig, f as StyleDictionaryOutput, i as MuseaVrtOptions, l as resolveTokenPreview, m as parseTokens, n as MuseaTheme, o as MuseaTokenPreviewKind, p as TokenCategory, r as MuseaThemeColors, s as MuseaTokenPreviewRule, t as MuseaOptions, u as DesignToken } from "./plugin-CIrKPW8K.mjs";
|
|
3
|
-
import { c as VrtSummary, d as ArtVariant, f as CsfOutput, l as ArtFileInfo, n as generateVrtReport, r as MuseaVrtRunner, s as VrtResult, t as generateVrtJsonReport, u as ArtMetadata } from "./vrt-
|
|
4
|
-
import { c as A11yResult, l as AnalysisApiResponse, n as MuseaA11yRunner, s as A11yOptions, t as A11ySummary, u as PaletteApiResponse } from "./index-
|
|
3
|
+
import { c as VrtSummary, d as ArtVariant, f as CsfOutput, l as ArtFileInfo, n as generateVrtReport, r as MuseaVrtRunner, s as VrtResult, t as generateVrtJsonReport, u as ArtMetadata } from "./vrt-BPwRc95_.mjs";
|
|
4
|
+
import { c as A11yResult, l as AnalysisApiResponse, n as MuseaA11yRunner, s as A11yOptions, t as A11ySummary, u as PaletteApiResponse } from "./index-HVtO9FK5.mjs";
|
|
5
5
|
import { AutogenOptions, AutogenOutput, GeneratedVariant, PropDefinition, generateArtFile, writeArtFile } from "./autogen/index.mjs";
|
|
6
6
|
import { Plugin } from "vite";
|
|
7
7
|
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { A as resolveInsideAny, C as parseJsonBody, D as assertVueSourcePath, E as assertArtVueSourcePath, F as loadNative, M as resolveUrlPathInside, N as HttpError, O as decodeUrlComponent, P as analyzeSfcFallback, S as createDevSessionToken, T as validateDevApiRequest, _ as scanArtFiles, a as parseScriptSetupForArt, c as resolveArtComponent, d as resolveReadableArtPath, f as buildThemeConfig, g as resolveScanRoots, h as matchGlob, i as generateArtModule, j as resolveTrustedSourcePath, k as resolveInside, l as allowedSourceRoots, m as generateStorybookFiles, o as rewriteRelativeImportStatement, p as escapeHtml, r as processMuseaArtFile, s as expandSelfTag, t as attachMuseaOptions, u as resolveComponentSourcePath, v as shouldProcess, w as serializeScriptValue, x as collectRequestBody, y as toPascalCase } from "./options-CGA4BetG.mjs";
|
|
2
2
|
import { a as generateGalleryGlobalsScript, i as generateDevGlobalsScript, n as generateGalleryScript, r as generateGalleryModule, t as generateGalleryBody } from "./template-B_y7_OsF.mjs";
|
|
3
|
-
import { i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport } from "./vrt-
|
|
3
|
+
import { i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport } from "./vrt-UxzWFygI.mjs";
|
|
4
4
|
import { t as MuseaA11yRunner } from "./a11y-Ci9EEv6F.mjs";
|
|
5
5
|
import { n as writeArtFile, t as generateArtFile } from "./autogen-B-N0cJbZ.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
@@ -90,6 +90,7 @@ interface VrtSummary {
|
|
|
90
90
|
failed: number;
|
|
91
91
|
new: number;
|
|
92
92
|
skipped: number;
|
|
93
|
+
errors?: number;
|
|
93
94
|
duration: number;
|
|
94
95
|
}
|
|
95
96
|
/**
|
|
@@ -208,4 +209,4 @@ declare function generateVrtReport(results: VrtResult[], summary: VrtSummary): s
|
|
|
208
209
|
declare function generateVrtJsonReport(results: VrtResult[], summary: VrtSummary): string;
|
|
209
210
|
//#endregion
|
|
210
211
|
export { ExtendedVrtOptions as a, VrtSummary as c, ArtVariant as d, CsfOutput as f, normalizeVrtWorkerCount as i, ArtFileInfo as l, generateVrtReport as n, PixelCompareOptions as o, MuseaVrtRunner as r, VrtResult as s, generateVrtJsonReport as t, ArtMetadata as u };
|
|
211
|
-
//# sourceMappingURL=vrt-
|
|
212
|
+
//# sourceMappingURL=vrt-BPwRc95_.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vrt-BPwRc95_.d.mts","names":[],"sources":["../src/types/art.ts","../src/vrt/types.ts","../src/vrt/runner.ts","../src/vrt/report.ts"],"mappings":"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"}
|
|
@@ -22,6 +22,7 @@ async function readPng(filepath) {
|
|
|
22
22
|
* Write PNG object to file.
|
|
23
23
|
*/
|
|
24
24
|
async function writePng(png, filepath) {
|
|
25
|
+
await fs.promises.mkdir(path.dirname(filepath), { recursive: true });
|
|
25
26
|
return new Promise((resolve, reject) => {
|
|
26
27
|
png.pack().pipe(fs.createWriteStream(filepath)).on("finish", resolve).on("error", reject);
|
|
27
28
|
});
|
|
@@ -112,15 +113,29 @@ function buildVariantUrl(baseUrl, artPath, variantName) {
|
|
|
112
113
|
return `${baseUrl}/__musea__/preview?art=${encodeURIComponent(artPath)}&variant=${encodeURIComponent(variantName)}`;
|
|
113
114
|
}
|
|
114
115
|
/**
|
|
116
|
+
* Build a filesystem-safe snapshot file name for a VRT capture.
|
|
117
|
+
*/
|
|
118
|
+
function buildSnapshotName(artPath, variantName, viewport) {
|
|
119
|
+
const artBaseName = path.basename(artPath, ".art.vue");
|
|
120
|
+
const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;
|
|
121
|
+
return `${encodeSnapshotNamePart(artBaseName)}--${encodeSnapshotNamePart(variantName)}--${encodeSnapshotNamePart(viewportName)}.png`;
|
|
122
|
+
}
|
|
123
|
+
function encodeSnapshotNamePart(value) {
|
|
124
|
+
const encoded = encodeURIComponent(value);
|
|
125
|
+
return encoded.length === 0 ? "unnamed" : encoded;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
115
128
|
* Compute VRT summary statistics from a list of results.
|
|
116
129
|
*/
|
|
117
130
|
function computeSummary(results, startTime) {
|
|
131
|
+
const errors = results.filter((r) => r.error).length;
|
|
118
132
|
return {
|
|
119
133
|
total: results.length,
|
|
120
134
|
passed: results.filter((r) => r.passed && !r.isNew).length,
|
|
121
135
|
failed: results.filter((r) => !r.passed && !r.error).length,
|
|
122
136
|
new: results.filter((r) => r.isNew).length,
|
|
123
|
-
skipped:
|
|
137
|
+
skipped: 0,
|
|
138
|
+
errors,
|
|
124
139
|
duration: Date.now() - startTime
|
|
125
140
|
};
|
|
126
141
|
}
|
|
@@ -138,13 +153,13 @@ async function captureAndCompare(runner, art, variantName, viewport, baseUrl) {
|
|
|
138
153
|
const capture = runner.getCapture();
|
|
139
154
|
const comparison = runner.getComparison();
|
|
140
155
|
const snapshotDir = options.snapshotDir;
|
|
141
|
-
const snapshotName =
|
|
156
|
+
const snapshotName = buildSnapshotName(art.path, variantName, viewport);
|
|
142
157
|
const snapshotPath = path.join(snapshotDir, snapshotName);
|
|
143
158
|
const currentPath = path.join(snapshotDir, "current", snapshotName);
|
|
144
159
|
const diffPath = path.join(snapshotDir, "diff", snapshotName);
|
|
145
160
|
await fs.promises.mkdir(path.dirname(snapshotPath), { recursive: true });
|
|
146
|
-
await fs.promises.mkdir(path.
|
|
147
|
-
await fs.promises.mkdir(path.
|
|
161
|
+
await fs.promises.mkdir(path.dirname(currentPath), { recursive: true });
|
|
162
|
+
await fs.promises.mkdir(path.dirname(diffPath), { recursive: true });
|
|
148
163
|
let context = null;
|
|
149
164
|
let page = null;
|
|
150
165
|
try {
|
|
@@ -439,8 +454,9 @@ var MuseaVrtRunner = class {
|
|
|
439
454
|
const snapshotDir = this.options.snapshotDir;
|
|
440
455
|
const currentDir = path.join(snapshotDir, "current");
|
|
441
456
|
for (const result of results) {
|
|
442
|
-
const currentPath = path.join(currentDir, path.basename(result.snapshotPath));
|
|
457
|
+
const currentPath = result.currentPath ?? path.join(currentDir, path.basename(result.snapshotPath));
|
|
443
458
|
if (await fileExists(currentPath)) {
|
|
459
|
+
await fs.promises.mkdir(path.dirname(result.snapshotPath), { recursive: true });
|
|
444
460
|
await fs.promises.copyFile(currentPath, result.snapshotPath);
|
|
445
461
|
updated++;
|
|
446
462
|
console.log(`[vrt] Updated: ${path.basename(result.snapshotPath)}`);
|
|
@@ -467,15 +483,9 @@ var MuseaVrtRunner = class {
|
|
|
467
483
|
try {
|
|
468
484
|
const files = await fs.promises.readdir(snapshotDir);
|
|
469
485
|
const validNames = /* @__PURE__ */ new Set();
|
|
470
|
-
for (const art of artFiles) {
|
|
471
|
-
|
|
472
|
-
for (const
|
|
473
|
-
if (variant.skipVrt) continue;
|
|
474
|
-
for (const viewport of this.options.viewports) {
|
|
475
|
-
const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;
|
|
476
|
-
validNames.add(`${artBaseName}--${variant.name}--${viewportName}.png`);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
486
|
+
for (const art of artFiles) for (const variant of art.variants) {
|
|
487
|
+
if (variant.skipVrt) continue;
|
|
488
|
+
for (const viewport of this.options.viewports) validNames.add(buildSnapshotName(art.path, variant.name, viewport));
|
|
479
489
|
}
|
|
480
490
|
for (const file of files) if (file.endsWith(".png") && !validNames.has(file)) {
|
|
481
491
|
await fs.promises.unlink(path.join(snapshotDir, file));
|
|
@@ -606,11 +616,13 @@ function generateVrtReport(results, summary) {
|
|
|
606
616
|
.stat.passed::before { background: var(--musea-success); }
|
|
607
617
|
.stat.failed::before { background: var(--musea-error); }
|
|
608
618
|
.stat.new::before { background: var(--musea-info); }
|
|
619
|
+
.stat.error::before { background: var(--musea-warning); }
|
|
609
620
|
.stat.skipped::before { background: var(--musea-warning); }
|
|
610
621
|
.stat-value { font-size: 2rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; margin-bottom: 0.25rem; }
|
|
611
622
|
.stat.passed .stat-value { color: var(--musea-success); }
|
|
612
623
|
.stat.failed .stat-value { color: var(--musea-error); }
|
|
613
624
|
.stat.new .stat-value { color: var(--musea-info); }
|
|
625
|
+
.stat.error .stat-value { color: var(--musea-warning); }
|
|
614
626
|
.stat.skipped .stat-value { color: var(--musea-warning); }
|
|
615
627
|
.stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; font-weight: 500; }
|
|
616
628
|
|
|
@@ -693,10 +705,11 @@ function generateVrtReport(results, summary) {
|
|
|
693
705
|
<div class="stat passed"><div class="stat-value">${summary.passed}</div><div class="stat-label">Passed</div></div>
|
|
694
706
|
<div class="stat failed"><div class="stat-value">${summary.failed}</div><div class="stat-label">Failed</div></div>
|
|
695
707
|
<div class="stat new"><div class="stat-value">${summary.new}</div><div class="stat-label">New</div></div>
|
|
708
|
+
<div class="stat error"><div class="stat-value">${summary.errors ?? 0}</div><div class="stat-label">Errors</div></div>
|
|
696
709
|
<div class="stat skipped"><div class="stat-value">${summary.skipped}</div><div class="stat-label">Skipped</div></div>
|
|
697
710
|
</div>
|
|
698
711
|
|
|
699
|
-
${summary.failed === 0 && summary.skipped === 0 && summary.total > 0 ? `<div class="all-passed">
|
|
712
|
+
${summary.failed === 0 && (summary.errors ?? 0) === 0 && summary.skipped === 0 && summary.total > 0 ? `<div class="all-passed">
|
|
700
713
|
<div class="all-passed-icon">✓</div>
|
|
701
714
|
<div class="all-passed-text">All ${summary.total} visual tests passed</div>
|
|
702
715
|
</div>` : ""}
|
|
@@ -704,6 +717,7 @@ function generateVrtReport(results, summary) {
|
|
|
704
717
|
<div class="filters">
|
|
705
718
|
<button class="filter-btn active" data-filter="all">All<span class="count">(${summary.total})</span></button>
|
|
706
719
|
<button class="filter-btn" data-filter="failed">Failed<span class="count">(${summary.failed})</span></button>
|
|
720
|
+
<button class="filter-btn" data-filter="error">Errors<span class="count">(${summary.errors ?? 0})</span></button>
|
|
707
721
|
<button class="filter-btn" data-filter="passed">Passed<span class="count">(${summary.passed})</span></button>
|
|
708
722
|
<button class="filter-btn" data-filter="new">New<span class="count">(${summary.new})</span></button>
|
|
709
723
|
</div>
|
|
@@ -792,4 +806,4 @@ var vrt_default = MuseaVrtRunner;
|
|
|
792
806
|
//#endregion
|
|
793
807
|
export { normalizeVrtWorkerCount as a, MuseaVrtRunner as i, generateVrtJsonReport as n, generateVrtReport as r, vrt_default as t };
|
|
794
808
|
|
|
795
|
-
//# sourceMappingURL=vrt-
|
|
809
|
+
//# sourceMappingURL=vrt-UxzWFygI.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vrt-UxzWFygI.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 path from \"node:path\";\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 await fs.promises.mkdir(path.dirname(filepath), { recursive: true });\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\";\nimport type { ViewportConfig } from \"../types/index.js\";\nimport path from \"node:path\";\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 * Build a filesystem-safe snapshot file name for a VRT capture.\n */\nexport function buildSnapshotName(\n artPath: string,\n variantName: string,\n viewport: ViewportConfig,\n): string {\n const artBaseName = path.basename(artPath, \".art.vue\");\n const viewportName = viewport.name || `${viewport.width}x${viewport.height}`;\n return `${encodeSnapshotNamePart(artBaseName)}--${encodeSnapshotNamePart(\n variantName,\n )}--${encodeSnapshotNamePart(viewportName)}.png`;\n}\n\nfunction encodeSnapshotNamePart(value: string): string {\n const encoded = encodeURIComponent(value);\n return encoded.length === 0 ? \"unnamed\" : encoded;\n}\n\n/**\n * Compute VRT summary statistics from a list of results.\n */\nexport function computeSummary(results: VrtResult[], startTime: number): VrtSummary {\n const errors = results.filter((r) => r.error).length;\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: 0,\n errors,\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 { buildSnapshotName, 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 snapshotName = buildSnapshotName(art.path, variantName, viewport);\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.dirname(currentPath), { recursive: true });\n await fs.promises.mkdir(path.dirname(diffPath), { 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 diffPixels++;\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 { buildSnapshotName, computeSummary } from \"./utils.js\";\n\nexport type { VrtResult, VrtSummary, ExtendedVrtOptions, PixelCompareOptions } from \"./types.js\";\n\nimport type { VrtResult, VrtSummary, ExtendedVrtOptions } from \"./types.js\";\n\ntype VrtJob = {\n art: ArtFileInfo;\n variantName: string;\n viewport: ViewportConfig;\n};\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 workers: normalizeVrtWorkerCount(options.workers),\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 retries = this.ci.retries ?? 0;\n const jobs = createVrtJobs(artFiles, this.options.viewports);\n\n return runJobsWithWorkers(jobs, this.options.workers, async (job) => {\n let result: VrtResult | null = null;\n let attempts = 0;\n\n while (attempts <= retries) {\n result = await this.captureAndCompare(job.art, job.variantName, job.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(job.art.path)}/${job.variantName}`,\n );\n }\n }\n\n return result;\n });\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 =\n result.currentPath ?? path.join(currentDir, path.basename(result.snapshotPath));\n\n if (await fileExists(currentPath)) {\n await fs.promises.mkdir(path.dirname(result.snapshotPath), { recursive: true });\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 for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n for (const viewport of this.options.viewports) {\n validNames.add(buildSnapshotName(art.path, variant.name, viewport));\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\nexport function normalizeVrtWorkerCount(workers: number | undefined): number {\n if (workers === undefined || !Number.isFinite(workers)) {\n return 1;\n }\n return Math.max(1, Math.floor(workers));\n}\n\nfunction createVrtJobs(artFiles: ArtFileInfo[], defaultViewports: ViewportConfig[]): VrtJob[] {\n const jobs: VrtJob[] = [];\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) {\n continue;\n }\n\n const viewports = variant.args?.viewport\n ? [variant.args.viewport as ViewportConfig]\n : defaultViewports;\n\n for (const viewport of viewports) {\n jobs.push({ art, variantName: variant.name, viewport });\n }\n }\n }\n return jobs;\n}\n\nasync function runJobsWithWorkers<T>(\n jobs: readonly VrtJob[],\n workerCount: number,\n runJob: (job: VrtJob) => Promise<T | null>,\n): Promise<T[]> {\n const results = Array.from<T | null>({ length: jobs.length }, () => null);\n let nextJobIndex = 0;\n\n async function runWorker(): Promise<void> {\n while (true) {\n const jobIndex = nextJobIndex++;\n const job = jobs[jobIndex];\n if (!job) {\n return;\n }\n results[jobIndex] = await runJob(job);\n }\n }\n\n const activeWorkers = Math.min(normalizeVrtWorkerCount(workerCount), jobs.length);\n await Promise.all(Array.from({ length: activeWorkers }, () => runWorker()));\n return results.filter((result): result is T => result !== null);\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.error::before { background: var(--musea-warning); }\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.error .stat-value { color: var(--musea-warning); }\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 error\"><div class=\"stat-value\">${summary.errors ?? 0}</div><div class=\"stat-label\">Errors</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 &&\n (summary.errors ?? 0) === 0 &&\n summary.skipped === 0 &&\n 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=\"error\">Errors<span class=\"count\">(${summary.errors ?? 0})</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 normalizeVrtWorkerCount,\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
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as ExtendedVrtOptions, c as VrtSummary, i as normalizeVrtWorkerCount, n as generateVrtReport, o as PixelCompareOptions, r as MuseaVrtRunner, s as VrtResult, t as generateVrtJsonReport } from "./vrt-
|
|
1
|
+
import { a as ExtendedVrtOptions, c as VrtSummary, i as normalizeVrtWorkerCount, n as generateVrtReport, o as PixelCompareOptions, r as MuseaVrtRunner, s as VrtResult, t as generateVrtJsonReport } from "./vrt-BPwRc95_.mjs";
|
|
2
2
|
export { type ExtendedVrtOptions, MuseaVrtRunner, MuseaVrtRunner as default, type PixelCompareOptions, type VrtResult, type VrtSummary, generateVrtJsonReport, generateVrtReport, normalizeVrtWorkerCount };
|
package/dist/vrt.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as normalizeVrtWorkerCount, i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport, t as vrt_default } from "./vrt-
|
|
1
|
+
import { a as normalizeVrtWorkerCount, i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport, t as vrt_default } from "./vrt-UxzWFygI.mjs";
|
|
2
2
|
export { MuseaVrtRunner, vrt_default as default, generateVrtJsonReport, generateVrtReport, normalizeVrtWorkerCount };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizejs/vite-plugin-musea",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.425.1",
|
|
4
4
|
"description": "Vite plugin for Musea - Component gallery for Vue components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"component-gallery",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"access": "public"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@vizejs/native": "0.
|
|
65
|
+
"@vizejs/native": "0.425.1",
|
|
66
66
|
"pngjs": "7.0.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@types/node": "25.9.2",
|
|
72
72
|
"@types/pngjs": "6.0.5",
|
|
73
73
|
"@vitejs/plugin-vue": "6.0.7",
|
|
74
|
-
"@vizejs/vite-plugin": "0.
|
|
74
|
+
"@vizejs/vite-plugin": "0.425.1",
|
|
75
75
|
"highlight.js": "11.11.1",
|
|
76
76
|
"marked": "18.0.9",
|
|
77
77
|
"marked-highlight": "2.2.4",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"vue-router": "4.5.1"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
-
"@vizejs/vite-plugin": "0.
|
|
87
|
+
"@vizejs/vite-plugin": "0.425.1",
|
|
88
88
|
"axe-core": "^4.7.0",
|
|
89
89
|
"playwright": "^1.40.0",
|
|
90
90
|
"vite": "^7.3.0 || ^8.0.0"
|
|
@@ -100,6 +100,12 @@
|
|
|
100
100
|
"engines": {
|
|
101
101
|
"node": ">=22"
|
|
102
102
|
},
|
|
103
|
+
"metadata": {
|
|
104
|
+
"vize": {
|
|
105
|
+
"stability": "experimental",
|
|
106
|
+
"stabilityDocs": "https://github.com/ubugeeei-prod/vize/blob/main/docs/content/stability.md#package-support-tiers"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
103
109
|
"scripts": {
|
|
104
110
|
"build": "vp pack && vp build --config gallery-vite.config.ts",
|
|
105
111
|
"build:gallery": "vp build --config gallery-vite.config.ts",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vrt-6lGTi1kM.d.mts","names":[],"sources":["../src/types/art.ts","../src/vrt/types.ts","../src/vrt/runner.ts","../src/vrt/report.ts"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"vrt-Dr5Gs71k.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 diffPixels++;\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\ntype VrtJob = {\n art: ArtFileInfo;\n variantName: string;\n viewport: ViewportConfig;\n};\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 workers: normalizeVrtWorkerCount(options.workers),\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 retries = this.ci.retries ?? 0;\n const jobs = createVrtJobs(artFiles, this.options.viewports);\n\n return runJobsWithWorkers(jobs, this.options.workers, async (job) => {\n let result: VrtResult | null = null;\n let attempts = 0;\n\n while (attempts <= retries) {\n result = await this.captureAndCompare(job.art, job.variantName, job.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(job.art.path)}/${job.variantName}`,\n );\n }\n }\n\n return result;\n });\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\nexport function normalizeVrtWorkerCount(workers: number | undefined): number {\n if (workers === undefined || !Number.isFinite(workers)) {\n return 1;\n }\n return Math.max(1, Math.floor(workers));\n}\n\nfunction createVrtJobs(artFiles: ArtFileInfo[], defaultViewports: ViewportConfig[]): VrtJob[] {\n const jobs: VrtJob[] = [];\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) {\n continue;\n }\n\n const viewports = variant.args?.viewport\n ? [variant.args.viewport as ViewportConfig]\n : defaultViewports;\n\n for (const viewport of viewports) {\n jobs.push({ art, variantName: variant.name, viewport });\n }\n }\n }\n return jobs;\n}\n\nasync function runJobsWithWorkers<T>(\n jobs: readonly VrtJob[],\n workerCount: number,\n runJob: (job: VrtJob) => Promise<T | null>,\n): Promise<T[]> {\n const results = Array.from<T | null>({ length: jobs.length }, () => null);\n let nextJobIndex = 0;\n\n async function runWorker(): Promise<void> {\n while (true) {\n const jobIndex = nextJobIndex++;\n const job = jobs[jobIndex];\n if (!job) {\n return;\n }\n results[jobIndex] = await runJob(job);\n }\n }\n\n const activeWorkers = Math.min(normalizeVrtWorkerCount(workerCount), jobs.length);\n await Promise.all(Array.from({ length: activeWorkers }, () => runWorker()));\n return results.filter((result): result is T => result !== null);\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 normalizeVrtWorkerCount,\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":";;;;;;;;;;;;;AAaA,eAAsB,QAAQ,UAAgC;CAC5D,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,GAAG,iBAAiB,QAAQ,EACzB,KAAK,IAAI,IAAI,CAAC,EACd,GAAG,UAAU,WAAqB;GACjC,QAAQ,IAAI;EACd,CAAC,EACA,GAAG,SAAS,MAAM;CACvB,CAAC;AACH;;;;AAKA,eAAsB,SAAS,KAAU,UAAiC;CACxE,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,IAAI,KAAK,EAAE,KAAK,GAAG,kBAAkB,QAAQ,CAAC,EAAE,GAAG,UAAU,OAAO,EAAE,GAAG,SAAS,MAAM;CAC1F,CAAC;AACH;;;;AAKA,SAAgB,WACd,IACA,IACA,IACA,IACA,IACA,IACA,IACA,IACQ;CACR,IAAI,OAAO,KAAK;EACd,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG;EAC5B,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG;EAC5B,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG;CAC9B;CACA,IAAI,OAAO,KAAK;EACd,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG;EAC5B,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG;EAC5B,KAAK,MAAM,IAAI,KAAK,KAAK,GAAG;CAC9B;CAEA,MAAM,KAAK,KAAK,YAAa,KAAK,YAAa,KAAK;CACpD,MAAM,KAAK,KAAK,YAAa,KAAK,WAAY,KAAK;CACnD,MAAM,KAAK,KAAK,YAAa,KAAK,YAAa,KAAK;CAEpD,MAAM,KAAK,KAAK,YAAa,KAAK,YAAa,KAAK;CACpD,MAAM,KAAK,KAAK,YAAa,KAAK,WAAY,KAAK;CACnD,MAAM,KAAK,KAAK,YAAa,KAAK,YAAa,KAAK;CAEpD,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,KAAK;CAChB,MAAM,KAAK,KAAK;CAEhB,OAAO,KAAK,KAAK,QAAS,KAAK,KAAK,OAAQ,KAAK,KAAK;AACxD;;;;AAKA,SAAS,MAAM,IAAY,IAAY,OAAuB;CAC5D,OAAO,MAAM,KAAK,MAAM;AAC1B;;;;AAKA,eAAsB,WAAW,UAAoC;CACnE,IAAI;EACF,MAAM,GAAG,SAAS,OAAO,QAAQ;EACjC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAgB,cACd,MACA,MACA,GACA,GACA,OACA,QACS;CACT,MAAM,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;CAC9B,MAAM,OAAO,KAAK,IAAI,QAAQ,GAAG,IAAI,CAAC;CACtC,MAAM,OAAO,KAAK,IAAI,GAAG,IAAI,CAAC;CAC9B,MAAM,OAAO,KAAK,IAAI,SAAS,GAAG,IAAI,CAAC;CAEvC,IAAI,SAAS;CACb,IAAI,YAAY;CAChB,IAAI,YAAY;CAEhB,KAAK,IAAI,KAAK,MAAM,MAAM,MAAM,MAC9B,KAAK,IAAI,KAAK,MAAM,MAAM,MAAM,MAAM;EACpC,IAAI,OAAO,KAAK,OAAO,GAAG;EAC1B,MAAM,OAAO,KAAK,QAAQ,MAAM;EAEhC,MAAM,QAAQ,WACZ,KAAK,KAAK,MACV,KAAK,KAAK,MAAM,IAChB,KAAK,KAAK,MAAM,IAChB,KAAK,KAAK,MAAM,IAChB,KAAK,KAAK,MACV,KAAK,KAAK,MAAM,IAChB,KAAK,KAAK,MAAM,IAChB,KAAK,KAAK,MAAM,EAClB;EAEA,IAAI,UAAU,GACZ;OACK,IAAI,QAAQ,GACjB;OAEA;CAEJ;CAIF,OAAO,SAAS,MAAM,YAAY,KAAK,YAAY,MAAM,YAAY,YAAY;AACnF;;;;AAKA,SAAgB,UAAU,UAAkB,SAA0B;CACpE,MAAM,QAAQ,QACX,QAAQ,OAAO,KAAK,EACpB,QAAQ,SAAS,IAAI,EACrB,QAAQ,aAAa,OAAO;CAC/B,OAAO,IAAI,OAAO,IAAI,MAAM,EAAE,EAAE,KAAK,QAAQ;AAC/C;;;;AAKA,SAAgB,WAAW,KAAqB;CAC9C,OAAO,IACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,QAAQ;AAC3B;;;;;;ACvJA,SAAgB,gBAAgB,SAAiB,SAAiB,aAA6B;CAG7F,OAAO,GAAG,QAAQ,yBAFE,mBAAmB,OAEc,EAAE,WADhC,mBAAmB,WACqC;AACjF;;;;AAKA,SAAgB,eAAe,SAAsB,WAA+B;CAClF,OAAO;EACL,OAAO,QAAQ;EACf,QAAQ,QAAQ,QAAQ,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,EAAE;EACpD,QAAQ,QAAQ,QAAQ,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,KAAK,EAAE;EACrD,KAAK,QAAQ,QAAQ,MAAM,EAAE,KAAK,EAAE;EACpC,SAAS,QAAQ,QAAQ,MAAM,EAAE,KAAK,EAAE;EACxC,UAAU,KAAK,IAAI,IAAI;CACzB;AACF;;;;;;;;ACNA,eAAsB,kBACpB,QACA,KACA,aACA,UACA,SACoB;CACpB,MAAM,UAAU,OAAO,WAAW;CAClC,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,gDAAgD;CAGlE,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,UAAU,OAAO,WAAW;CAClC,MAAM,aAAa,OAAO,cAAc;CAExC,MAAM,cAAc,QAAQ;CAG5B,MAAM,eAAe,GAFD,KAAK,SAAS,IAAI,MAAM,UAEV,EAAE,IAAI,YAAY,IAD/B,SAAS,QAAQ,GAAG,SAAS,MAAM,GAAG,SAAS,SACC;CACrE,MAAM,eAAe,KAAK,KAAK,aAAa,YAAY;CACxD,MAAM,cAAc,KAAK,KAAK,aAAa,WAAW,YAAY;CAClE,MAAM,WAAW,KAAK,KAAK,aAAa,QAAQ,YAAY;CAG5D,MAAM,GAAG,SAAS,MAAM,KAAK,QAAQ,YAAY,GAAG,EAAE,WAAW,KAAK,CAAC;CACvE,MAAM,GAAG,SAAS,MAAM,KAAK,KAAK,aAAa,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;CAC9E,MAAM,GAAG,SAAS,MAAM,KAAK,KAAK,aAAa,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;CAE3E,IAAI,UAAiC;CACrC,IAAI,OAAoB;CAExB,IAAI;EACF,UAAU,MAAM,QAAQ,WAAW;GACjC,UAAU;IACR,OAAO,SAAS;IAChB,QAAQ,SAAS;GACnB;GACA,mBAAmB,SAAS,qBAAqB;EACnD,CAAC;EACD,OAAO,MAAM,QAAQ,QAAQ;EAG7B,MAAM,aAAa,gBAAgB,SAAS,IAAI,MAAM,WAAW;EACjE,MAAM,YAAY,QAAQ,iBAAkB,gBAA2B;EACvE,MAAM,KAAK,KAAK,YAAY,EAAE,UAAU,CAAC;EAGzC,MAAM,KAAK,gBAAgB,QAAQ,cAAc,EAAE,SAAS,IAAM,CAAC;EAGnE,MAAM,KAAK,eAAe,QAAQ,UAAU;EAG5C,IAAI,QAAQ,aAAa,SAAS,GAChC,KAAK,MAAM,YAAY,QAAQ,cAC7B,MAAM,KAAK,UAAU,QAAQ;GAC3B,SAAS,iBAAiB,GAAG,EAAE,SAAS,OAAO;IAC7C,GAAoB,MAAM,aAAa;GACzC,CAAC;EACH,GAAG,QAAQ;EAKf,IAAI,QAAQ,aAAa,SAAS,GAChC,KAAK,MAAM,YAAY,QAAQ,cAC7B,MAAM,KAAK,UAAU,QAAQ;GAC3B,SAAS,iBAAiB,GAAG,EAAE,SAAS,OAAO;IAC7C,MAAM,SAAS;IACf,OAAO,MAAM,aAAa;IAC1B,OAAO,MAAM,QAAQ;IACrB,OAAO,YAAY;GACrB,CAAC;EACH,GAAG,QAAQ;EAKf,MAAM,KAAK,WAAW;GACpB,MAAM;GACN,UAAU,QAAQ;EACpB,CAAC;EAKD,IAAI,CAAC,MAFqB,WAAW,YAAY,GAE/B;GAEhB,MAAM,GAAG,SAAS,SAAS,aAAa,YAAY;GACpD,OAAO;IACL,SAAS,IAAI;IACb;IACA;IACA,QAAQ;IACR;IACA;IACA,OAAO;GACT;EACF;EAGA,MAAM,mBAAmB,MAAM,cAAc,cAAc,aAAa,UAAU,UAAU;EAE5F,MAAM,SAAS,iBAAiB,kBAAkB,QAAQ;EAE1D,OAAO;GACL,SAAS,IAAI;GACb;GACA;GACA;GACA;GACA;GACA,UAAU,SAAS,KAAA,IAAY;GAC/B,gBAAgB,iBAAiB;GACjC,YAAY,iBAAiB;GAC7B,aAAa,iBAAiB;EAChC;CACF,SAAS,OAAO;EACd,OAAO;GACL,SAAS,IAAI;GACb;GACA;GACA,QAAQ;GACR;GACA,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC9D;CACF,UAAU;EACR,IAAI,MAAM,MAAM,KAAK,MAAM;EAC3B,IAAI,SAAS,MAAM,QAAQ,MAAM;CACnC;AACF;;;;;AAMA,eAAsB,cACpB,cACA,aACA,UACA,YAK8E;CAC9E,MAAM,WAAW,MAAM,QAAQ,YAAY;CAC3C,MAAM,UAAU,MAAM,QAAQ,WAAW;CAGzC,IAAI,SAAS,UAAU,QAAQ,SAAS,SAAS,WAAW,QAAQ,QAAQ;EAC1E,MAAM,QAAQ,KAAK,IAAI,SAAS,OAAO,QAAQ,KAAK;EACpD,MAAM,SAAS,KAAK,IAAI,SAAS,QAAQ,QAAQ,MAAM;EACvD,MAAM,OAAO,IAAI,IAAI;GAAE;GAAO;EAAO,CAAC;EAGtC,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK,QAAQ,KAAK,GAAG;GAC5C,KAAK,KAAK,KAAK;GACf,KAAK,KAAK,IAAI,KAAK;GACnB,KAAK,KAAK,IAAI,KAAK;GACnB,KAAK,KAAK,IAAI,KAAK;EACrB;EAEA,MAAM,SAAS,MAAM,QAAQ;EAE7B,OAAO;GACL,YAAY,QAAQ;GACpB,aAAa,QAAQ;GACrB,gBAAgB;EAClB;CACF;CAEA,MAAM,QAAQ,SAAS;CACvB,MAAM,SAAS,SAAS;CACxB,MAAM,cAAc,QAAQ;CAC5B,MAAM,OAAO,IAAI,IAAI;EAAE;EAAO;CAAO,CAAC;CAEtC,MAAM,kBAAkB,WAAW,gBAAgB;CACnD,MAAM,WAAW,WAAW,SAAS;CACrC,MAAM,YAAY,WAAW,aAAa;EAAE,GAAG;EAAK,GAAG;EAAG,GAAG;CAAE;CAG/D,IAAI,aAAa;CACjB,MAAM,YAAY;CAElB,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,KAC1B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KAAK;EAC9B,MAAM,OAAO,IAAI,QAAQ,KAAK;EAE9B,MAAM,KAAK,SAAS,KAAK;EACzB,MAAM,KAAK,SAAS,KAAK,MAAM;EAC/B,MAAM,KAAK,SAAS,KAAK,MAAM;EAC/B,MAAM,KAAK,WAAW,SAAS,KAAK,MAAM,KAAK;EAE/C,MAAM,KAAK,QAAQ,KAAK;EACxB,MAAM,KAAK,QAAQ,KAAK,MAAM;EAC9B,MAAM,KAAK,QAAQ,KAAK,MAAM;EAM9B,IAFc,WAAW,IAAI,IAAI,IAAI,IAAI,IAAI,IAAI,IAHtC,WAAW,QAAQ,KAAK,MAAM,KAAK,GAKtC,IAAI,YAAY,MAAM,KAE5B,IAAI,mBAAmB,cAAc,UAAU,SAAS,GAAG,GAAG,OAAO,MAAM,GAAG;GAE5E;GACA,KAAK,KAAK,OAAO;GACjB,KAAK,KAAK,MAAM,KAAK;GACrB,KAAK,KAAK,MAAM,KAAK;GACrB,KAAK,KAAK,MAAM,KAAK;EACvB,OAAO;GAEL;GACA,KAAK,KAAK,OAAO,UAAU;GAC3B,KAAK,KAAK,MAAM,KAAK,UAAU;GAC/B,KAAK,KAAK,MAAM,KAAK,UAAU;GAC/B,KAAK,KAAK,MAAM,KAAK;EACvB;OACK;GAEL,MAAM,OAAO,KAAK,OAAO,KAAK,KAAK,MAAM,CAAC;GAC1C,KAAK,KAAK,OAAO;GACjB,KAAK,KAAK,MAAM,KAAK;GACrB,KAAK,KAAK,MAAM,KAAK;GACrB,KAAK,KAAK,MAAM,KAAK;EACvB;CACF;CAIF,IAAI,aAAa,GACf,MAAM,SAAS,MAAM,QAAQ;CAG/B,MAAM,iBAAkB,aAAa,cAAe;CAEpD,OAAO;EACL;EACA;EACA;CACF;AACF;;;;;;ACvOA,IAAa,iBAAb,MAA4B;CAC1B;CACA;CACA;CACA;CACA,UAAkC;CAClC,YAA4B;CAE5B,YAAY,UAA8B,CAAC,GAAG;EAC5C,KAAK,UAAU;GACb,aAAa,QAAQ,eAAe;GACpC,WAAW,QAAQ,aAAa;GAChC,WAAW,QAAQ,aAAa,CAC9B;IAAE,OAAO;IAAM,QAAQ;IAAK,MAAM;GAAU,GAC5C;IAAE,OAAO;IAAK,QAAQ;IAAK,MAAM;GAAS,CAC5C;GACA,SAAS,wBAAwB,QAAQ,OAAO;EAClD;EACA,KAAK,UAAU;GACb,UAAU,QAAQ,SAAS,YAAY;GACvC,gBAAgB,QAAQ,SAAS,kBAAkB;GACnD,YAAY,QAAQ,SAAS,cAAc;GAC3C,cAAc,QAAQ,SAAS,gBAAgB;GAC/C,cAAc,QAAQ,SAAS,gBAAgB,CAAC;GAChD,cAAc,QAAQ,SAAS,gBAAgB,CAAC;EAClD;EACA,KAAK,aAAa,QAAQ,cAAc,CAAC;EACzC,KAAK,KAAK,QAAQ,MAAM,CAAC;CAC3B;;CAKA,aAA6B;EAC3B,OAAO,KAAK;CACd;;CAGA,aAAmC;EACjC,OAAO,KAAK;CACd;;CAGA,aAAsC;EACpC,OAAO,KAAK;CACd;;CAGA,gBAAkC;EAChC,OAAO,KAAK;CACd;;;;CAKA,MAAM,OAAsB;EAC1B,MAAM,EAAE,aAAa,MAAM,OAAO;EAClC,KAAK,UAAU,MAAM,SAAS,OAAO,EAAE,UAAU,KAAK,CAAC;EACvD,KAAK,YAAY,KAAK,IAAI;CAC5B;;;;CAKA,MAAM,QAAuB;EAC3B,IAAI,KAAK,SAAS;GAChB,MAAM,KAAK,QAAQ,MAAM;GACzB,KAAK,UAAU;EACjB;CACF;;;;CAKA,MAAM,QAAuB;EAC3B,OAAO,KAAK,KAAK;CACnB;;;;CAKA,MAAM,OAAsB;EAC1B,OAAO,KAAK,MAAM;CACpB;;;;CAKA,MAAM,YAAY,UAAyB,SAAuC;EAChF,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,MAAM,gDAAgD;EAGlE,MAAM,UAAU,KAAK,GAAG,WAAW;EAGnC,OAAO,mBAFM,cAAc,UAAU,KAAK,QAAQ,SAErB,GAAG,KAAK,QAAQ,SAAS,OAAO,QAAQ;GACnE,IAAI,SAA2B;GAC/B,IAAI,WAAW;GAEf,OAAO,YAAY,SAAS;IAC1B,SAAS,MAAM,KAAK,kBAAkB,IAAI,KAAK,IAAI,aAAa,IAAI,UAAU,OAAO;IACrF,IAAI,OAAO,UAAU,OAAO,SAAS,CAAC,OAAO,OAC3C;IAEF;IACA,IAAI,YAAY,SACd,QAAQ,IACN,eAAe,SAAS,GAAG,QAAQ,IAAI,KAAK,SAAS,IAAI,IAAI,IAAI,EAAE,GAAG,IAAI,aAC5E;GAEJ;GAEA,OAAO;EACT,CAAC;CACH;;;;CAKA,MAAM,SACJ,UACA,SACA,UACsB;EACtB,MAAM,UAAU,MAAM,KAAK,YAAY,UAAU,OAAO;EACxD,IAAI,UAAU,iBACZ,MAAM,KAAK,gBAAgB,OAAO;EAEpC,OAAO;CACT;;;;CAKA,MAAM,kBACJ,KACA,aACA,UACA,SACoB;EACpB,OAAO,kBAAkB,MAAM,KAAK,aAAa,UAAU,OAAO;CACpE;;;;CAKA,MAAM,WAAW,UAA4E;EAC3F,IAAI,CAAC,KAAK,SACR,MAAM,IAAI,MAAM,gDAAgD;EAElE,MAAM,UAAU,MAAM,KAAK,QAAQ,WAAW;GAC5C,UAAU;IAAE,OAAO,SAAS;IAAO,QAAQ,SAAS;GAAO;GAC3D,mBAAmB,SAAS,qBAAqB;EACnD,CAAC;EAED,OAAO;GAAE,MAAA,MADU,QAAQ,QAAQ;GACpB;EAAQ;CACzB;;;;CAKA,MAAM,gBAAgB,SAAuC;EAC3D,IAAI,UAAU;EACd,MAAM,cAAc,KAAK,QAAQ;EACjC,MAAM,aAAa,KAAK,KAAK,aAAa,SAAS;EAEnD,KAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,cAAc,KAAK,KAAK,YAAY,KAAK,SAAS,OAAO,YAAY,CAAC;GAE5E,IAAI,MAAM,WAAW,WAAW,GAAG;IACjC,MAAM,GAAG,SAAS,SAAS,aAAa,OAAO,YAAY;IAC3D;IACA,QAAQ,IAAI,kBAAkB,KAAK,SAAS,OAAO,YAAY,GAAG;GACpE;EACF;EAEA,OAAO;CACT;;;;CAKA,MAAM,eAAe,SAAsB,SAAmC;EAC5E,MAAM,YAAY,UACd,QAAQ,QAAQ,MAAM;GACpB,MAAM,OAAO,GAAG,KAAK,SAAS,EAAE,SAAS,UAAU,EAAE,GAAG,EAAE;GAC1D,OAAO,KAAK,SAAS,OAAO,KAAK,UAAU,MAAM,OAAO;EAC1D,CAAC,IACD,QAAQ,QAAQ,MAAM,CAAC,EAAE,UAAU,CAAC,EAAE,KAAK;EAE/C,OAAO,KAAK,gBAAgB,SAAS;CACvC;;;;CAKA,MAAM,aAAa,UAA0C;EAC3D,MAAM,cAAc,KAAK,QAAQ;EACjC,IAAI,UAAU;EAEd,IAAI;GACF,MAAM,QAAQ,MAAM,GAAG,SAAS,QAAQ,WAAW;GACnD,MAAM,6BAAa,IAAI,IAAY;GAEnC,KAAK,MAAM,OAAO,UAAU;IAC1B,MAAM,cAAc,KAAK,SAAS,IAAI,MAAM,UAAU;IACtD,KAAK,MAAM,WAAW,IAAI,UAAU;KAClC,IAAI,QAAQ,SAAS;KACrB,KAAK,MAAM,YAAY,KAAK,QAAQ,WAAW;MAC7C,MAAM,eAAe,SAAS,QAAQ,GAAG,SAAS,MAAM,GAAG,SAAS;MACpE,WAAW,IAAI,GAAG,YAAY,IAAI,QAAQ,KAAK,IAAI,aAAa,KAAK;KACvE;IACF;GACF;GAEA,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,MAAM,KAAK,CAAC,WAAW,IAAI,IAAI,GAAG;IAClD,MAAM,GAAG,SAAS,OAAO,KAAK,KAAK,aAAa,IAAI,CAAC;IACrD;IACA,QAAQ,IAAI,kBAAkB,MAAM;GACtC;EAEJ,QAAQ,CAER;EAEA,OAAO;CACT;;;;CAKA,WAAW,SAAkC;EAC3C,OAAO,eAAe,SAAS,KAAK,SAAS;CAC/C;AACF;AAEA,SAAgB,wBAAwB,SAAqC;CAC3E,IAAI,YAAY,KAAA,KAAa,CAAC,OAAO,SAAS,OAAO,GACnD,OAAO;CAET,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC;AACxC;AAEA,SAAS,cAAc,UAAyB,kBAA8C;CAC5F,MAAM,OAAiB,CAAC;CACxB,KAAK,MAAM,OAAO,UAChB,KAAK,MAAM,WAAW,IAAI,UAAU;EAClC,IAAI,QAAQ,SACV;EAGF,MAAM,YAAY,QAAQ,MAAM,WAC5B,CAAC,QAAQ,KAAK,QAA0B,IACxC;EAEJ,KAAK,MAAM,YAAY,WACrB,KAAK,KAAK;GAAE;GAAK,aAAa,QAAQ;GAAM;EAAS,CAAC;CAE1D;CAEF,OAAO;AACT;AAEA,eAAe,mBACb,MACA,aACA,QACc;CACd,MAAM,UAAU,MAAM,KAAe,EAAE,QAAQ,KAAK,OAAO,SAAS,IAAI;CACxE,IAAI,eAAe;CAEnB,eAAe,YAA2B;EACxC,OAAO,MAAM;GACX,MAAM,WAAW;GACjB,MAAM,MAAM,KAAK;GACjB,IAAI,CAAC,KACH;GAEF,QAAQ,YAAY,MAAM,OAAO,GAAG;EACtC;CACF;CAEA,MAAM,gBAAgB,KAAK,IAAI,wBAAwB,WAAW,GAAG,KAAK,MAAM;CAChF,MAAM,QAAQ,IAAI,MAAM,KAAK,EAAE,QAAQ,cAAc,SAAS,UAAU,CAAC,CAAC;CAC1E,OAAO,QAAQ,QAAQ,WAAwB,WAAW,IAAI;AAChE;;;;;;;;;;;;;ACnTA,SAAgB,kBAAkB,SAAsB,SAA6B;CACnF,MAAM,kBAAkB,OAAuB;EAC7C,IAAI,KAAK,KAAM,OAAO,GAAG,GAAG;EAC5B,MAAM,UAAU,KAAK,MAAM,KAAK,GAAI;EACpC,MAAM,UAAU,KAAK,MAAM,UAAU,EAAE;EACvC,IAAI,YAAY,GAAG,OAAO,GAAG,QAAQ;EACrC,OAAO,GAAG,QAAQ,IAAI,UAAU,GAAG;CACrC;CAEA,MAAM,6BAAY,IAAI,KAAK,GAAE,eAAe,SAAS;EACnD,MAAM;EACN,OAAO;EACP,KAAK;EACL,MAAM;EACN,QAAQ;CACV,CAAC;CAkPD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAlHC,eAAe,QAAQ,QAAQ,EAAE;;;;;;UAMjC,UAAU;;;;;;;yDAOqC,QAAQ,OAAO;yDACf,QAAQ,OAAO;sDAClB,QAAQ,IAAI;0DACR,QAAQ,QAAQ;;;MAIpE,QAAQ,WAAW,KAAK,QAAQ,YAAY,KAAK,QAAQ,QAAQ,IAC7D;;+CAEqC,QAAQ,MAAM;oBAEnD,GACL;;;oFAG+E,QAAQ,MAAM;mFACf,QAAQ,OAAO;mFACf,QAAQ,OAAO;6EACrB,QAAQ,IAAI;;;;;;;;;;QAWjF,QAAQ,WAAW,IACf,gEACA,QACG,KAAK,MAAM;EACV,MAAM,SAAS,EAAE,QAAQ,UAAU,EAAE,QAAQ,QAAQ,EAAE,SAAS,WAAW;EAC3E,MAAM,QAAQ,EAAE,QAAQ,UAAU,EAAE,QAAQ,QAAQ,EAAE,SAAS,WAAW;EAC1E,MAAM,UAAU,KAAK,SAAS,EAAE,SAAS,UAAU;EACnD,MAAM,eAAe,EAAE,SAAS,QAAQ,GAAG,EAAE,SAAS,MAAM,GAAG,EAAE,SAAS;EAE1E,IAAI,UAAU;EACd,IAAI,EAAE,OACJ,UAAU,qCAAqC,WAAW,EAAE,KAAK,EAAE;OAC9D,IAAI,EAAE,mBAAmB,KAAA,GAI9B,UAAU,qCAHY,EAAE,eAAe,QAAQ,CAGY,EAAE,KAFrC,EAAE,YAAY,eAAe,KAAK,IAEwB,KAD3D,EAAE,aAAa,eAAe,KAAK,IAC4C;EAGxG,IAAI,SAAS;EACb,IAAI,CAAC,EAAE,SAAS,CAAC,EAAE,UAAU,EAAE,UAC7B,SAAS,oDAAoD,EAAE,aAAa,yBAAyB,EAAE,YAAY,sBAAsB,EAAE,SAAS;sBAChJ,EAAE,eAAe,mHAAmH,EAAE,aAAa,kDAAkD,GAAG;sBACxM,EAAE,cAAc,kHAAkH,EAAE,YAAY,iDAAiD,GAAG;sBACpM,EAAE,WAAW,+GAA+G,EAAE,SAAS,8CAA8C,GAAG;;EAI9L,MAAM,UAAU,WAAW;EAE3B,OAAO,sBAAsB,OAAO,iBAAiB,OAAO;;;iDAG3B,WAAW,OAAO,EAAE,KAAK,WAAW,EAAE,WAAW,EAAE;iDACnD,WAAW,YAAY,EAAE;;iDAEzB,MAAM;;oBAEnC,UAAU,4BAA4B,UAAU,OAAO,UAAU,GAAG;;CAE1E,CAAC,EACA,KAAK,EAAE,EACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BP;;;;AAKA,SAAgB,sBAAsB,SAAsB,SAA6B;CACvF,OAAO,KAAK,UACV;EACE,4BAAW,IAAI,KAAK,GAAE,YAAY;EAClC;EACA,SAAS,QAAQ,KAAK,OAAO;GAC3B,KAAK,KAAK,SAAS,EAAE,SAAS,UAAU;GACxC,SAAS,EAAE;GACX,UAAU,EAAE,SAAS,QAAQ,GAAG,EAAE,SAAS,MAAM,GAAG,EAAE,SAAS;GAC/D,QAAQ,EAAE,QAAQ,UAAU,EAAE,QAAQ,QAAQ,EAAE,SAAS,WAAW;GACpE,gBAAgB,EAAE;GAClB,OAAO,EAAE;EACX,EAAE;CACJ,GACA,MACA,CACF;AACF;;;ACrRA,IAAA,cAAe"}
|