@vizejs/vite-plugin-musea 0.429.1 → 0.430.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/a11y/index.mjs +1 -1
- package/dist/{a11y-Ci9EEv6F.mjs → a11y-BbFK942j.mjs} +66 -15
- package/dist/a11y-BbFK942j.mjs.map +1 -0
- package/dist/api-gMwahZKm.d.mts +112 -0
- package/dist/api-gMwahZKm.d.mts.map +1 -0
- package/dist/cli/index.d.mts +1 -1
- package/dist/cli/index.d.mts.map +1 -1
- package/dist/cli/index.mjs +79 -33
- package/dist/cli/index.mjs.map +1 -1
- package/dist/gallery/assets/{MonacoEditor-BIjYdDrA.js → MonacoEditor-CnzIyKMy.js} +2 -2
- package/dist/gallery/assets/{cssMode-C5PB9d_X.js → cssMode-DZ_me5CG.js} +1 -1
- package/dist/gallery/assets/{editor.api-tyf2wJeZ.js → editor.api-CjD4Hpms.js} +1 -1
- package/dist/gallery/assets/{freemarker2-Df2SP4C4.js → freemarker2-F7z49Hfe.js} +1 -1
- package/dist/gallery/assets/{handlebars-CbFr-5KQ.js → handlebars-Dt4eGgf-.js} +1 -1
- package/dist/gallery/assets/{html-C1ubjnt5.js → html-B1zmbcur.js} +1 -1
- package/dist/gallery/assets/{htmlMode-9FYaGqmo.js → htmlMode-DmDwpA4v.js} +1 -1
- package/dist/gallery/assets/{index-D81pMC9f.css → index-C4vm-yqv.css} +1 -1
- package/dist/gallery/assets/{index-BjW1cVGf.js → index-vJ3ftuqF.js} +3 -3
- package/dist/gallery/assets/{javascript-DjzYR9ls.js → javascript-CLKbyvz8.js} +1 -1
- package/dist/gallery/assets/{jsonMode-BRxSdIjW.js → jsonMode-sms9-L2I.js} +1 -1
- package/dist/gallery/assets/{liquid-DD0LqlcF.js → liquid-CesACmVT.js} +1 -1
- package/dist/gallery/assets/{lspLanguageFeatures-C0f4qlAU.js → lspLanguageFeatures-C8BR-41u.js} +1 -1
- package/dist/gallery/assets/{mdx-DtcWR2Lx.js → mdx-Cd93KUo_.js} +1 -1
- package/dist/gallery/assets/{python-C1OlrT9L.js → python-8gi7tgMv.js} +1 -1
- package/dist/gallery/assets/{razor-BN2Zj1B9.js → razor-sOyhBXSy.js} +1 -1
- package/dist/gallery/assets/{register-CTEjv03I.js → register-C-_4TcWq.js} +2 -2
- package/dist/gallery/assets/{toggleHighContrast-BELgo5Kd.js → toggleHighContrast-BCHobRx5.js} +1 -1
- package/dist/gallery/assets/{tsMode-Bd8a8shj.js → tsMode-BkQQD7P8.js} +1 -1
- package/dist/gallery/assets/{typescript-DLlFIuFu.js → typescript-XiwOdN8G.js} +1 -1
- package/dist/gallery/assets/{vs-YCtNwrNl.js → vs-Coo3fK7w.js} +3 -3
- package/dist/gallery/assets/{workers-CBv4Qjp-.js → workers-Cypb28_F.js} +1 -1
- package/dist/gallery/assets/{xml-wUPbJH_s.js → xml-CN1ixSLs.js} +1 -1
- package/dist/gallery/assets/{yaml-B52zgDg-.js → yaml-l30MdsZA.js} +1 -1
- package/dist/gallery/index.html +2 -2
- package/dist/{gallery-qE5deHL0.mjs → gallery-CIcrB07g.mjs} +2 -2
- package/dist/{gallery-qE5deHL0.mjs.map → gallery-CIcrB07g.mjs.map} +1 -1
- package/dist/index-C5evpfmF.d.mts +81 -0
- package/dist/index-C5evpfmF.d.mts.map +1 -0
- package/dist/index.d.mts +5 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +33 -9
- package/dist/index.mjs.map +1 -1
- package/dist/options-CZYdDtTW.mjs +581 -0
- package/dist/options-CZYdDtTW.mjs.map +1 -0
- package/dist/{plugin-CIrKPW8K.d.mts → plugin-DkFXWcCD.d.mts} +11 -2
- package/dist/plugin-DkFXWcCD.d.mts.map +1 -0
- package/dist/{template-DNhEgY8-.mjs → template-DDl7rsgo.mjs} +2 -2
- package/dist/{template-DNhEgY8-.mjs.map → template-DDl7rsgo.mjs.map} +1 -1
- package/dist/utils-DeMLVlNS.mjs +628 -0
- package/dist/utils-DeMLVlNS.mjs.map +1 -0
- package/dist/{vrt-DVub2B6r.d.mts → vrt-3T_e4bJ3.d.mts} +7 -1
- package/dist/vrt-3T_e4bJ3.d.mts.map +1 -0
- package/dist/{vrt-BPwRc95_.d.mts → vrt-6iDBfqNg.d.mts} +2 -2
- package/dist/{vrt-BPwRc95_.d.mts.map → vrt-6iDBfqNg.d.mts.map} +1 -1
- package/dist/{vrt-UxzWFygI.mjs → vrt-VuGK9wTX.mjs} +11 -4
- package/dist/vrt-VuGK9wTX.mjs.map +1 -0
- package/dist/vrt.d.mts +1 -1
- package/dist/vrt.mjs +1 -1
- package/package.json +4 -4
- package/dist/a11y-Ci9EEv6F.mjs.map +0 -1
- package/dist/index-HVtO9FK5.d.mts +0 -170
- package/dist/index-HVtO9FK5.d.mts.map +0 -1
- package/dist/options-Cd7bzLGr.mjs +0 -1142
- package/dist/options-Cd7bzLGr.mjs.map +0 -1
- package/dist/plugin-CIrKPW8K.d.mts.map +0 -1
- package/dist/vrt-DVub2B6r.d.mts.map +0 -1
- package/dist/vrt-UxzWFygI.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-C5evpfmF.mjs";
|
|
2
2
|
export { A11ySummary, MuseaA11yRunner, computeA11ySummary, generateA11yHtmlReport, generateA11yJsonReport, resolveAxeSource };
|
package/dist/a11y/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as generateA11yJsonReport, i as generateA11yHtmlReport, n as resolveAxeSource, r as computeA11ySummary, t as MuseaA11yRunner } from "../a11y-
|
|
1
|
+
import { a as generateA11yJsonReport, i as generateA11yHtmlReport, n as resolveAxeSource, r as computeA11ySummary, t as MuseaA11yRunner } from "../a11y-BbFK942j.mjs";
|
|
2
2
|
export { MuseaA11yRunner, computeA11ySummary, generateA11yHtmlReport, generateA11yJsonReport, resolveAxeSource };
|
|
@@ -1,4 +1,21 @@
|
|
|
1
|
+
import { t as escapeHtml$1 } from "./utils-DeMLVlNS.mjs";
|
|
1
2
|
import path from "node:path";
|
|
3
|
+
//#region src/a11y/node-report.ts
|
|
4
|
+
function nodeDetails(result) {
|
|
5
|
+
return (result.targets ?? []).map((node) => {
|
|
6
|
+
const selectors = node.target.map((target) => typeof target === "string" ? target : JSON.stringify(target)).join(", ");
|
|
7
|
+
const checks = JSON.stringify({
|
|
8
|
+
any: node.any,
|
|
9
|
+
all: node.all,
|
|
10
|
+
none: node.none
|
|
11
|
+
}, null, 2);
|
|
12
|
+
return `<details class="node-details"><summary><code>${escapeHtml$1(selectors)}</code></summary>
|
|
13
|
+
<pre>${escapeHtml$1(node.html)}</pre>
|
|
14
|
+
${node.failureSummary ? `<pre>${escapeHtml$1(node.failureSummary)}</pre>` : ""}
|
|
15
|
+
<pre>${escapeHtml$1(checks)}</pre></details>`;
|
|
16
|
+
}).join("");
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
2
19
|
//#region src/a11y/report.ts
|
|
3
20
|
/**
|
|
4
21
|
* Compute a11y summary statistics from results.
|
|
@@ -47,13 +64,19 @@ function generateA11yHtmlReport(results, summary) {
|
|
|
47
64
|
</div>
|
|
48
65
|
<p style="padding:0.75rem 1rem;color:#f87171"><code>${escapeHtml(r.error ?? "")}</code></p>
|
|
49
66
|
</div>`).join("");
|
|
50
|
-
const resultItems = results.filter((r) => r.violations.length > 0).map((r) => {
|
|
67
|
+
const resultItems = results.filter((r) => r.violations.length > 0 || r.incomplete > 0).map((r) => {
|
|
51
68
|
const artName = path.basename(r.artPath, ".art.vue");
|
|
52
|
-
const violationRows = r.violations.map((v) =>
|
|
69
|
+
const violationRows = [...r.violations.map((v) => ({
|
|
70
|
+
v,
|
|
71
|
+
review: false
|
|
72
|
+
})), ...(r.incompleteResults ?? []).map((v) => ({
|
|
73
|
+
v,
|
|
74
|
+
review: true
|
|
75
|
+
}))].map(({ v, review }) => `
|
|
53
76
|
<tr>
|
|
54
|
-
<td><span style="color:${impactColor(v.impact)};font-weight:600;text-transform:uppercase;font-size:0.6875rem">${escapeHtml(v.impact)}</span></td>
|
|
77
|
+
<td><span style="color:${impactColor(v.impact ?? "unknown")};font-weight:600;text-transform:uppercase;font-size:0.6875rem">${escapeHtml(review ? "needs review" : v.impact ?? "unknown")}</span></td>
|
|
55
78
|
<td><code>${escapeHtml(v.id)}</code></td>
|
|
56
|
-
<td>${escapeHtml(v.description)}</td>
|
|
79
|
+
<td>${escapeHtml(v.description)}${nodeDetails(v)}</td>
|
|
57
80
|
<td>${v.nodes}</td>
|
|
58
81
|
<td>${v.helpUrl ? `<a href="${escapeHtml(v.helpUrl)}" target="_blank" style="color:#60a5fa">docs</a>` : ""}</td>
|
|
59
82
|
</tr>`).join("");
|
|
@@ -62,7 +85,7 @@ function generateA11yHtmlReport(results, summary) {
|
|
|
62
85
|
<div class="result-header">
|
|
63
86
|
<div class="result-info">
|
|
64
87
|
<span class="result-name">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</span>
|
|
65
|
-
<span class="result-count">${r.violations.length} violation(s)</span>
|
|
88
|
+
<span class="result-count">${r.violations.length} violation(s), ${r.incomplete} need review</span>
|
|
66
89
|
</div>
|
|
67
90
|
</div>
|
|
68
91
|
<table class="violations-table">
|
|
@@ -124,6 +147,8 @@ function generateA11yHtmlReport(results, summary) {
|
|
|
124
147
|
.violations-table th { padding: 0.75rem 1rem; text-align: left; color: var(--musea-text-muted); font-weight: 500; font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.08em; border-bottom: 1px solid var(--musea-border); }
|
|
125
148
|
.violations-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--musea-border); }
|
|
126
149
|
.violations-table code { background: var(--musea-bg-tertiary); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.75rem; }
|
|
150
|
+
.node-details { margin-top: 0.5rem; }
|
|
151
|
+
.node-details pre { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 0.5rem; }
|
|
127
152
|
.all-clear { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 2rem; text-align: center; }
|
|
128
153
|
.all-clear-text { color: #4ade80; font-weight: 600; }
|
|
129
154
|
</style>
|
|
@@ -142,7 +167,7 @@ function generateA11yHtmlReport(results, summary) {
|
|
|
142
167
|
<div class="stat minor"><div class="stat-value">${summary.minorCount}</div><div class="stat-label">Minor</div></div>
|
|
143
168
|
<div class="stat critical"><div class="stat-value">${summary.erroredVariants}</div><div class="stat-label">Not audited</div></div>
|
|
144
169
|
</div>
|
|
145
|
-
${summary.totalViolations === 0 && summary.erroredVariants === 0 ? `<div class="all-clear"><div class="all-clear-text">No accessibility violations found across ${summary.totalVariants} variant(s)</div></div>` : `<div class="results">${errorItems}${resultItems}</div>`}
|
|
170
|
+
${summary.totalViolations === 0 && summary.erroredVariants === 0 && !results.some((r) => r.incomplete > 0) ? `<div class="all-clear"><div class="all-clear-text">No accessibility violations found across ${summary.totalVariants} variant(s)</div></div>` : `<div class="results">${errorItems}${resultItems}</div>`}
|
|
146
171
|
</main>
|
|
147
172
|
</body>
|
|
148
173
|
</html>`;
|
|
@@ -161,6 +186,7 @@ function generateA11yJsonReport(results) {
|
|
|
161
186
|
violations: r.violations,
|
|
162
187
|
passes: r.passes,
|
|
163
188
|
incomplete: r.incomplete,
|
|
189
|
+
...r.incompleteResults === void 0 ? {} : { incompleteResults: r.incompleteResults },
|
|
164
190
|
...r.error === void 0 ? {} : { error: r.error }
|
|
165
191
|
}))
|
|
166
192
|
}, null, 2);
|
|
@@ -267,18 +293,43 @@ var MuseaA11yRunner = class {
|
|
|
267
293
|
const axeResult = await page.evaluate((opts) => {
|
|
268
294
|
return window.axe.run(document, opts);
|
|
269
295
|
}, runOptions);
|
|
296
|
+
const mapResult = (result) => ({
|
|
297
|
+
id: result.id,
|
|
298
|
+
impact: result.impact ?? null,
|
|
299
|
+
description: result.description,
|
|
300
|
+
helpUrl: result.helpUrl,
|
|
301
|
+
nodes: result.nodes.length,
|
|
302
|
+
targets: result.nodes.map((node) => ({
|
|
303
|
+
target: node.target,
|
|
304
|
+
html: node.html,
|
|
305
|
+
failureSummary: node.failureSummary,
|
|
306
|
+
any: node.any.map(({ id, impact, message, data }) => ({
|
|
307
|
+
id,
|
|
308
|
+
impact,
|
|
309
|
+
message,
|
|
310
|
+
data
|
|
311
|
+
})),
|
|
312
|
+
all: node.all.map(({ id, impact, message, data }) => ({
|
|
313
|
+
id,
|
|
314
|
+
impact,
|
|
315
|
+
message,
|
|
316
|
+
data
|
|
317
|
+
})),
|
|
318
|
+
none: node.none.map(({ id, impact, message, data }) => ({
|
|
319
|
+
id,
|
|
320
|
+
impact,
|
|
321
|
+
message,
|
|
322
|
+
data
|
|
323
|
+
}))
|
|
324
|
+
}))
|
|
325
|
+
});
|
|
270
326
|
return {
|
|
271
327
|
artPath,
|
|
272
328
|
variantName,
|
|
273
|
-
violations: axeResult.violations.map(
|
|
274
|
-
id: v.id,
|
|
275
|
-
impact: v.impact,
|
|
276
|
-
description: v.description,
|
|
277
|
-
helpUrl: v.helpUrl,
|
|
278
|
-
nodes: v.nodes.length
|
|
279
|
-
})),
|
|
329
|
+
violations: axeResult.violations.map(mapResult),
|
|
280
330
|
passes: axeResult.passes.length,
|
|
281
|
-
incomplete: axeResult.incomplete.length
|
|
331
|
+
incomplete: axeResult.incomplete.length,
|
|
332
|
+
incompleteResults: axeResult.incomplete.map(mapResult)
|
|
282
333
|
};
|
|
283
334
|
}
|
|
284
335
|
/**
|
|
@@ -335,4 +386,4 @@ var MuseaA11yRunner = class {
|
|
|
335
386
|
//#endregion
|
|
336
387
|
export { generateA11yJsonReport as a, generateA11yHtmlReport as i, resolveAxeSource as n, computeA11ySummary as r, MuseaA11yRunner as t };
|
|
337
388
|
|
|
338
|
-
//# sourceMappingURL=a11y-
|
|
389
|
+
//# sourceMappingURL=a11y-BbFK942j.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"a11y-BbFK942j.mjs","names":["escapeHtml"],"sources":["../src/a11y/node-report.ts","../src/a11y/report.ts","../src/a11y/index.ts"],"sourcesContent":["import type { A11yViolation } from \"../types/index.js\";\nimport { escapeHtml } from \"../utils.js\";\n\nexport function nodeDetails(result: A11yViolation): string {\n return (result.targets ?? [])\n .map((node) => {\n const selectors = node.target\n .map((target) => (typeof target === \"string\" ? target : JSON.stringify(target)))\n .join(\", \");\n const checks = JSON.stringify({ any: node.any, all: node.all, none: node.none }, null, 2);\n return `<details class=\"node-details\"><summary><code>${escapeHtml(selectors)}</code></summary>\n <pre>${escapeHtml(node.html)}</pre>\n ${node.failureSummary ? `<pre>${escapeHtml(node.failureSummary)}</pre>` : \"\"}\n <pre>${escapeHtml(checks)}</pre></details>`;\n })\n .join(\"\");\n}\n","/**\n * A11y report generation for Musea.\n *\n * Generates HTML and JSON reports from accessibility audit results,\n * plus summary statistics computation.\n */\n\nimport type { A11yResult } from \"../types/index.js\";\nimport type { A11ySummary } from \"./index.js\";\nimport path from \"node:path\";\nimport { nodeDetails } from \"./node-report.js\";\n\n/**\n * Compute a11y summary statistics from results.\n */\nexport function computeA11ySummary(results: A11yResult[]): A11ySummary {\n const components = new Set(results.map((r) => r.artPath));\n const allViolations = results.flatMap((r) => r.violations);\n\n return {\n totalComponents: components.size,\n totalVariants: results.length,\n erroredVariants: results.filter((r) => r.error !== undefined).length,\n totalViolations: allViolations.length,\n criticalCount: allViolations.filter((v) => v.impact === \"critical\").length,\n seriousCount: allViolations.filter((v) => v.impact === \"serious\").length,\n moderateCount: allViolations.filter((v) => v.impact === \"moderate\").length,\n minorCount: allViolations.filter((v) => v.impact === \"minor\").length,\n };\n}\n\n/**\n * Generate HTML report from a11y results.\n */\nexport function generateA11yHtmlReport(results: A11yResult[], summary: A11ySummary): string {\n const timestamp = new Date().toLocaleString(\"ja-JP\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n\n const impactColor = (impact: string): string => {\n switch (impact) {\n case \"critical\":\n return \"#f87171\";\n case \"serious\":\n return \"#fb923c\";\n case \"moderate\":\n return \"#fbbf24\";\n case \"minor\":\n return \"#60a5fa\";\n default:\n return \"#7b8494\";\n }\n };\n\n const errorItems = results\n .filter((r) => r.error !== undefined)\n .map(\n (r) => `\n <div class=\"result\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <span class=\"result-name\">${escapeHtml(path.basename(r.artPath, \".art.vue\"))} / ${escapeHtml(r.variantName)}</span>\n <span class=\"result-count\">audit did not run</span>\n </div>\n </div>\n <p style=\"padding:0.75rem 1rem;color:#f87171\"><code>${escapeHtml(r.error ?? \"\")}</code></p>\n </div>`,\n )\n .join(\"\");\n\n const resultItems = results\n .filter((r) => r.violations.length > 0 || r.incomplete > 0)\n .map((r) => {\n const artName = path.basename(r.artPath, \".art.vue\");\n const findings = [\n ...r.violations.map((v) => ({ v, review: false })),\n ...(r.incompleteResults ?? []).map((v) => ({ v, review: true })),\n ];\n const violationRows = findings\n .map(\n ({ v, review }) => `\n <tr>\n <td><span style=\"color:${impactColor(v.impact ?? \"unknown\")};font-weight:600;text-transform:uppercase;font-size:0.6875rem\">${escapeHtml(review ? \"needs review\" : (v.impact ?? \"unknown\"))}</span></td>\n <td><code>${escapeHtml(v.id)}</code></td>\n <td>${escapeHtml(v.description)}${nodeDetails(v)}</td>\n <td>${v.nodes}</td>\n <td>${v.helpUrl ? `<a href=\"${escapeHtml(v.helpUrl)}\" target=\"_blank\" style=\"color:#60a5fa\">docs</a>` : \"\"}</td>\n </tr>`,\n )\n .join(\"\");\n\n return `\n <div class=\"result\">\n <div class=\"result-header\">\n <div class=\"result-info\">\n <span class=\"result-name\">${escapeHtml(artName)} / ${escapeHtml(r.variantName)}</span>\n <span class=\"result-count\">${r.violations.length} violation(s), ${r.incomplete} need review</span>\n </div>\n </div>\n <table class=\"violations-table\">\n <thead><tr><th>Impact</th><th>Rule</th><th>Description</th><th>Nodes</th><th>Help</th></tr></thead>\n <tbody>${violationRows}</tbody>\n </table>\n </div>`;\n })\n .join(\"\");\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>A11y Report - Musea</title>\n <style>\n :root {\n --musea-bg-primary: #0d0d0d;\n --musea-bg-secondary: #1a1815;\n --musea-bg-tertiary: #252220;\n --musea-accent: #a34828;\n --musea-text: #e6e9f0;\n --musea-text-muted: #7b8494;\n --musea-border: #3a3530;\n }\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n min-height: 100vh;\n line-height: 1.5;\n }\n .header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n padding: 1rem 2rem;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .logo { font-size: 1.25rem; font-weight: 700; color: var(--musea-accent); }\n .header-meta { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .main { max-width: 1200px; margin: 0 auto; padding: 2rem; }\n .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 1rem; margin-bottom: 2rem; }\n .stat { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; padding: 1rem; text-align: center; }\n .stat-value { font-size: 1.75rem; font-weight: 700; font-variant-numeric: tabular-nums; }\n .stat-label { color: var(--musea-text-muted); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; }\n .stat.critical .stat-value { color: #f87171; }\n .stat.serious .stat-value { color: #fb923c; }\n .stat.moderate .stat-value { color: #fbbf24; }\n .stat.minor .stat-value { color: #60a5fa; }\n .stat.total .stat-value { color: var(--musea-text); }\n .results { display: flex; flex-direction: column; gap: 1rem; }\n .result { background: var(--musea-bg-secondary); border: 1px solid var(--musea-border); border-radius: 8px; overflow: hidden; }\n .result-header { padding: 1rem; background: var(--musea-bg-tertiary); display: flex; justify-content: space-between; align-items: center; }\n .result-name { font-weight: 600; }\n .result-count { color: var(--musea-text-muted); font-size: 0.8125rem; }\n .violations-table { width: 100%; border-collapse: collapse; font-size: 0.8125rem; }\n .violations-table th { padding: 0.75rem 1rem; text-align: left; color: var(--musea-text-muted); font-weight: 500; font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.08em; border-bottom: 1px solid var(--musea-border); }\n .violations-table td { padding: 0.75rem 1rem; border-bottom: 1px solid var(--musea-border); }\n .violations-table code { background: var(--musea-bg-tertiary); padding: 0.125rem 0.375rem; border-radius: 3px; font-size: 0.75rem; }\n .node-details { margin-top: 0.5rem; }\n .node-details pre { white-space: pre-wrap; overflow-wrap: anywhere; margin-top: 0.5rem; }\n .all-clear { background: rgba(74, 222, 128, 0.1); border: 1px solid rgba(74, 222, 128, 0.2); border-radius: 8px; padding: 2rem; text-align: center; }\n .all-clear-text { color: #4ade80; font-weight: 600; }\n </style>\n</head>\n<body>\n <header class=\"header\">\n <div><span class=\"logo\">Musea</span> <span style=\"color:var(--musea-text-muted);font-size:0.875rem;margin-left:1rem\">Accessibility Report</span></div>\n <div class=\"header-meta\">${timestamp}</div>\n </header>\n <main class=\"main\">\n <div class=\"summary\">\n <div class=\"stat total\"><div class=\"stat-value\">${summary.totalViolations}</div><div class=\"stat-label\">Violations</div></div>\n <div class=\"stat critical\"><div class=\"stat-value\">${summary.criticalCount}</div><div class=\"stat-label\">Critical</div></div>\n <div class=\"stat serious\"><div class=\"stat-value\">${summary.seriousCount}</div><div class=\"stat-label\">Serious</div></div>\n <div class=\"stat moderate\"><div class=\"stat-value\">${summary.moderateCount}</div><div class=\"stat-label\">Moderate</div></div>\n <div class=\"stat minor\"><div class=\"stat-value\">${summary.minorCount}</div><div class=\"stat-label\">Minor</div></div>\n <div class=\"stat critical\"><div class=\"stat-value\">${summary.erroredVariants}</div><div class=\"stat-label\">Not audited</div></div>\n </div>\n ${\n summary.totalViolations === 0 &&\n summary.erroredVariants === 0 &&\n !results.some((r) => r.incomplete > 0)\n ? `<div class=\"all-clear\"><div class=\"all-clear-text\">No accessibility violations found across ${summary.totalVariants} variant(s)</div></div>`\n : `<div class=\"results\">${errorItems}${resultItems}</div>`\n }\n </main>\n</body>\n</html>`;\n}\n\n/**\n * Generate JSON report for CI integration.\n */\nexport function generateA11yJsonReport(results: A11yResult[]): string {\n const summary = computeA11ySummary(results);\n return JSON.stringify(\n {\n timestamp: new Date().toISOString(),\n summary,\n results: results.map((r) => ({\n art: path.basename(r.artPath, \".art.vue\"),\n variant: r.variantName,\n violations: r.violations,\n passes: r.passes,\n incomplete: r.incomplete,\n ...(r.incompleteResults === undefined ? {} : { incompleteResults: r.incompleteResults }),\n ...(r.error === undefined ? {} : { error: r.error }),\n })),\n },\n null,\n 2,\n );\n}\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n","/**\n * Accessibility (a11y) testing module for Musea.\n * Uses axe-core for automated accessibility auditing via Playwright.\n */\n\nimport type { Page } from \"playwright\";\nimport type { AxeResults, Result } from \"axe-core\";\nimport type {\n ArtFileInfo,\n A11yResult,\n A11yViolation,\n A11yOptions,\n ViewportConfig,\n} from \"../types/index.js\";\nimport type { MuseaVrtRunner } from \"../vrt.js\";\n\nimport { computeA11ySummary, generateA11yHtmlReport, generateA11yJsonReport } from \"./report.js\";\n\n// Re-export report functions so consumers importing from a11y.js still work\nexport { computeA11ySummary, generateA11yHtmlReport, generateA11yJsonReport } from \"./report.js\";\n\n/**\n * A11y audit summary.\n */\nexport interface A11ySummary {\n totalComponents: number;\n totalVariants: number;\n /** Variants whose audit could not run at all; see `A11yResult.error`. */\n erroredVariants: number;\n totalViolations: number;\n criticalCount: number;\n seriousCount: number;\n moderateCount: number;\n minorCount: number;\n}\n\n/**\n * Read axe-core's injectable browser bundle.\n *\n * axe-core ships CommonJS (`\"main\": \"axe.js\"`, no `exports` map), so under\n * ESM its exports arrive on the namespace's `default` and `source` is not\n * interop-detected as a named export: `(await import(\"axe-core\")).source`\n * is `undefined`. Injecting that injects nothing, leaves `window.axe`\n * undefined, and the audit dies later as `Cannot read properties of\n * undefined (reading 'run')` — a failure that points at the wrong line. So\n * read both shapes and fail here, where the cause is still legible.\n */\nexport async function resolveAxeSource(): Promise<string> {\n let namespace: { source?: unknown; default?: { source?: unknown } };\n try {\n namespace = await import(\"axe-core\");\n } catch (cause) {\n throw new Error(\n \"axe-core is not installed. Install it as a peer dependency: npm install axe-core\",\n { cause },\n );\n }\n\n const source = namespace.source ?? namespace.default?.source;\n if (typeof source !== \"string\" || source.length === 0) {\n throw new Error(\n \"axe-core is installed but exposes no `source` bundle to inject. \" +\n \"Expected a non-empty string on either the module namespace or its \" +\n \"default export; check that the installed axe-core version is >= 4.\",\n );\n }\n return source;\n}\n\n/**\n * A11y runner using axe-core via Playwright.\n */\nexport class MuseaA11yRunner {\n private options: Required<A11yOptions>;\n\n constructor(options: A11yOptions = {}) {\n this.options = {\n enabled: options.enabled ?? true,\n includeRules: options.includeRules ?? [],\n excludeRules: options.excludeRules ?? [],\n level: options.level ?? \"AA\",\n };\n }\n\n /**\n * Run a11y audits on all art file variants.\n * Reuses VRT runner's browser if available.\n */\n async runAudits(\n artFiles: ArtFileInfo[],\n baseUrl: string,\n vrtRunner?: MuseaVrtRunner,\n ): Promise<A11yResult[]> {\n const results: A11yResult[] = [];\n const defaultViewport: ViewportConfig = { width: 1280, height: 720, name: \"desktop\" };\n\n for (const art of artFiles) {\n for (const variant of art.variants) {\n if (variant.skipVrt) continue;\n\n let page: Page | null = null;\n let context: { page: Page; context: { close(): Promise<void> } } | null = null;\n\n try {\n if (vrtRunner) {\n context = await vrtRunner.createPage(defaultViewport);\n page = context.page;\n } else {\n // Standalone mode: launch own browser\n const { chromium } = await import(\"playwright\");\n const browser = await chromium.launch({ headless: true });\n const ctx = await browser.newContext({\n viewport: { width: defaultViewport.width, height: defaultViewport.height },\n });\n page = await ctx.newPage();\n context = { page, context: ctx };\n }\n\n const variantUrl = this.buildVariantUrl(baseUrl, art.path, variant.name);\n await page.goto(variantUrl, { waitUntil: \"networkidle\" });\n await page.waitForSelector(\".musea-variant\", { timeout: 10000 });\n await page.waitForTimeout(200);\n\n const result = await this.auditPage(page, art.path, variant.name);\n results.push(result);\n } catch (error) {\n results.push({\n artPath: art.path,\n variantName: variant.name,\n violations: [],\n passes: 0,\n incomplete: 0,\n error: error instanceof Error ? error.message : String(error),\n });\n } finally {\n if (context) {\n await context.context.close();\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Audit a single page using axe-core.\n */\n async auditPage(page: Page, artPath: string, variantName: string): Promise<A11yResult> {\n // Inject axe-core into the page\n const axeSource = await resolveAxeSource();\n await page.evaluate(axeSource);\n\n // Build axe-core run options\n const runOptions = this.buildAxeOptions();\n\n // Run axe-core\n const axeResult = (await page.evaluate((opts) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return (window as any).axe.run(document, opts);\n }, runOptions)) as AxeResults;\n\n // Map to our result format\n const mapResult = (result: Result): A11yViolation => ({\n id: result.id,\n impact: result.impact ?? null,\n description: result.description,\n helpUrl: result.helpUrl,\n nodes: result.nodes.length,\n targets: result.nodes.map((node) => ({\n target: node.target,\n html: node.html,\n failureSummary: node.failureSummary,\n any: node.any.map(({ id, impact, message, data }) => ({ id, impact, message, data })),\n all: node.all.map(({ id, impact, message, data }) => ({ id, impact, message, data })),\n none: node.none.map(({ id, impact, message, data }) => ({ id, impact, message, data })),\n })),\n });\n const violations = axeResult.violations.map(mapResult);\n\n return {\n artPath,\n variantName,\n violations,\n passes: axeResult.passes.length,\n incomplete: axeResult.incomplete.length,\n incompleteResults: axeResult.incomplete.map(mapResult),\n };\n }\n\n /**\n * Get summary statistics from results.\n */\n getSummary(results: A11yResult[]): A11ySummary {\n return computeA11ySummary(results);\n }\n\n /**\n * Generate HTML report.\n */\n generateHtmlReport(results: A11yResult[]): string {\n const summary = this.getSummary(results);\n return generateA11yHtmlReport(results, summary);\n }\n\n /**\n * Generate JSON report for CI integration.\n */\n generateJsonReport(results: A11yResult[]): string {\n return generateA11yJsonReport(results);\n }\n\n /**\n * Build axe-core run options from configuration.\n */\n private buildAxeOptions(): Record<string, unknown> {\n const runOnly: Record<string, unknown> = {};\n\n // Set WCAG level\n const tags: string[] = [];\n switch (this.options.level) {\n case \"A\":\n tags.push(\"wcag2a\", \"wcag21a\");\n break;\n case \"AA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n case \"AAA\":\n tags.push(\"wcag2a\", \"wcag2aa\", \"wcag2aaa\", \"wcag21a\", \"wcag21aa\", \"wcag22aa\");\n break;\n }\n\n if (tags.length > 0) {\n runOnly.type = \"tag\";\n runOnly.values = tags;\n }\n\n const rules: Record<string, { enabled: boolean }> = {};\n\n for (const ruleId of this.options.includeRules) {\n rules[ruleId] = { enabled: true };\n }\n for (const ruleId of this.options.excludeRules) {\n rules[ruleId] = { enabled: false };\n }\n\n return {\n ...(Object.keys(runOnly).length > 0 ? { runOnly } : {}),\n ...(Object.keys(rules).length > 0 ? { rules } : {}),\n };\n }\n\n private buildVariantUrl(baseUrl: string, artPath: string, variantName: string): string {\n const encodedPath = encodeURIComponent(artPath);\n const encodedVariant = encodeURIComponent(variantName);\n return `${baseUrl}/__musea__/preview?art=${encodedPath}&variant=${encodedVariant}`;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
//#region src/types/api.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Palette API response.
|
|
4
|
+
*/
|
|
5
|
+
interface PaletteApiResponse {
|
|
6
|
+
title: string;
|
|
7
|
+
controls: PaletteControl[];
|
|
8
|
+
groups: string[];
|
|
9
|
+
json: string;
|
|
10
|
+
typescript: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Single prop control definition.
|
|
14
|
+
*/
|
|
15
|
+
interface PaletteControl {
|
|
16
|
+
name: string;
|
|
17
|
+
control: ControlKind;
|
|
18
|
+
default_value?: unknown;
|
|
19
|
+
description?: string;
|
|
20
|
+
required: boolean;
|
|
21
|
+
options: Array<{
|
|
22
|
+
label: string;
|
|
23
|
+
value: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
range?: {
|
|
26
|
+
min: number;
|
|
27
|
+
max: number;
|
|
28
|
+
step?: number;
|
|
29
|
+
};
|
|
30
|
+
group?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Supported control kinds for the props panel.
|
|
34
|
+
*/
|
|
35
|
+
type ControlKind = "text" | "number" | "boolean" | "range" | "select" | "radio" | "color" | "date" | "object" | "array" | "file" | "raw";
|
|
36
|
+
/**
|
|
37
|
+
* Analysis API response (Props/Emits info).
|
|
38
|
+
*/
|
|
39
|
+
interface AnalysisApiResponse {
|
|
40
|
+
props: Array<{
|
|
41
|
+
name: string;
|
|
42
|
+
type: string;
|
|
43
|
+
required: boolean;
|
|
44
|
+
default_value?: unknown;
|
|
45
|
+
}>;
|
|
46
|
+
emits: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Accessibility testing options.
|
|
50
|
+
*/
|
|
51
|
+
interface A11yOptions {
|
|
52
|
+
/** Enable a11y auditing during VRT */
|
|
53
|
+
enabled?: boolean;
|
|
54
|
+
/** axe-core rules to include */
|
|
55
|
+
includeRules?: string[];
|
|
56
|
+
/** axe-core rules to exclude */
|
|
57
|
+
excludeRules?: string[];
|
|
58
|
+
/** WCAG level (A, AA, AAA) */
|
|
59
|
+
level?: "A" | "AA" | "AAA";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Accessibility audit result.
|
|
63
|
+
*/
|
|
64
|
+
interface A11yResult {
|
|
65
|
+
artPath: string;
|
|
66
|
+
variantName: string;
|
|
67
|
+
violations: A11yViolation[];
|
|
68
|
+
passes: number;
|
|
69
|
+
incomplete: number;
|
|
70
|
+
/** Rules requiring manual review, with the same per-node details as violations. */
|
|
71
|
+
incompleteResults?: A11yViolation[];
|
|
72
|
+
/**
|
|
73
|
+
* Why the audit could not run, when it could not run.
|
|
74
|
+
*
|
|
75
|
+
* A variant that failed to audit measured *nothing* about accessibility,
|
|
76
|
+
* so it reports no violations. Recording the failure here instead of as a
|
|
77
|
+
* synthetic `critical` violation keeps the counts honest: a broken run
|
|
78
|
+
* reads as broken rather than as a component with critical defects.
|
|
79
|
+
*/
|
|
80
|
+
error?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Single accessibility violation.
|
|
84
|
+
*/
|
|
85
|
+
interface A11yViolation {
|
|
86
|
+
id: string;
|
|
87
|
+
impact: "minor" | "moderate" | "serious" | "critical" | null;
|
|
88
|
+
description: string;
|
|
89
|
+
helpUrl: string;
|
|
90
|
+
nodes: number;
|
|
91
|
+
/** Per-node results from axe; optional for older stored reports. */
|
|
92
|
+
targets?: A11yNode[];
|
|
93
|
+
}
|
|
94
|
+
interface A11yNode {
|
|
95
|
+
/** Selectors retain axe's nested arrays for iframe/shadow DOM targets. */
|
|
96
|
+
target: (string | string[])[];
|
|
97
|
+
html: string;
|
|
98
|
+
failureSummary?: string;
|
|
99
|
+
any: A11yCheck[];
|
|
100
|
+
all: A11yCheck[];
|
|
101
|
+
none: A11yCheck[];
|
|
102
|
+
}
|
|
103
|
+
interface A11yCheck {
|
|
104
|
+
id: string;
|
|
105
|
+
impact: string;
|
|
106
|
+
message: string;
|
|
107
|
+
/** Measured values such as foreground/background colors and contrast ratios. */
|
|
108
|
+
data?: unknown;
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { PaletteApiResponse as i, A11yResult as n, AnalysisApiResponse as r, A11yOptions as t };
|
|
112
|
+
//# sourceMappingURL=api-gMwahZKm.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-gMwahZKm.d.mts","names":[],"sources":["../src/types/api.ts"],"mappings":";;AAGA;;UAAiB,kBAAA;EACf,KAAA;EACA,QAAA,EAAU,cAAc;EACxB,MAAA;EACA,IAAA;EACA,UAAA;AAAA;;;AAAU;UAMK,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;AAAZ;;;AAAA,UAiBiB,mBAAA;EACf,KAAA,EAAO,KAAK;IACV,IAAA;IACA,IAAA;IACA,QAAA;IACA,aAAA;EAAA;EAEF,KAAA;AAAA;;;;UAUe,WAAA;EAVV;EAYL,OAAA;EAFe;EAIf,YAAA;;EAEA,YAAA;EAJA;EAMA,KAAA;AAAA;;;AAAK;UAMU,UAAA;EACf,OAAA;EACA,WAAA;EACA,UAAA,EAAY,aAAA;EACZ,MAAA;EACA,UAAA;EAFA;EAIA,iBAAA,GAAoB,aAAa;EAHjC;;;;;;AAYK;AAMP;EANE,KAAA;AAAA;;;;UAMe,aAAA;EACf,EAAA;EACA,MAAA;EACA,WAAA;EACA,OAAA;EACA,KAAA;EAEkB;EAAlB,OAAA,GAAU,QAAQ;AAAA;AAAA,UAGH,QAAA;EAKV;EAHL,MAAA;EACA,IAAA;EACA,cAAA;EACA,GAAA,EAAK,SAAA;EACL,GAAA,EAAK,SAAA;EACL,IAAA,EAAM,SAAA;AAAA;AAAA,UAGS,SAAA;EACf,EAAA;EACA,MAAA;EACA,OAAA;EANA;EAQA,IAAA;AAAA"}
|
package/dist/cli/index.d.mts
CHANGED
package/dist/cli/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/cli/index.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/cli/index.ts"],"mappings":";;;KAkCK,OAAA;AAAA,UAEY,UAAA;EACf,OAAA,EAAS,OAAA;EACT,MAAA;EACA,MAAA;EACA,MAAA;EACA,SAAA;EACA,iBAAA;EACA,OAAA;EACA,eAAA;EACA,IAAA;EACA,EAAA;EACA,IAAA;EACA,IAAA;EACA,OAAA;EACA,OAAA;EACA,aAAA;EACA,GAAA,GAAM,eAAe;AAAA;AAAA,iBAGP,SAAA,CAAU,IAAA,aAAiB,UAAU"}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,37 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { a as scanArtFiles } from "../utils-DeMLVlNS.mjs";
|
|
3
|
+
import { n as readMuseaOptions, r as processMuseaArtFile } from "../options-CZYdDtTW.mjs";
|
|
4
|
+
import { a as normalizeVrtWorkerCount, i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport } from "../vrt-VuGK9wTX.mjs";
|
|
4
5
|
import fs, { realpathSync } from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
import { loadConfigFromFile } from "vite";
|
|
8
9
|
//#region src/cli/utils.ts
|
|
9
|
-
/**
|
|
10
|
-
* CLI utility functions for art file scanning and parsing.
|
|
11
|
-
*
|
|
12
|
-
* Extracted from cli.ts to keep file sizes manageable.
|
|
13
|
-
*/
|
|
14
|
-
/** Recursively scan a directory for .art.vue files. */
|
|
15
|
-
async function scanArtFiles(root) {
|
|
16
|
-
const files = [];
|
|
17
|
-
async function scan(dir) {
|
|
18
|
-
let entries;
|
|
19
|
-
try {
|
|
20
|
-
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
21
|
-
} catch (error) {
|
|
22
|
-
if (error.code === "EACCES") return;
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
for (const entry of entries) {
|
|
26
|
-
const fullPath = path.join(dir, entry.name);
|
|
27
|
-
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
|
28
|
-
if (entry.isDirectory()) await scan(fullPath);
|
|
29
|
-
else if (entry.isFile() && entry.name.endsWith(".art.vue")) files.push(fullPath);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
await scan(root);
|
|
33
|
-
return files;
|
|
34
|
-
}
|
|
35
10
|
/** Parse a single .art.vue file into an ArtFileInfo structure. */
|
|
36
11
|
async function parseArtFile(filePath) {
|
|
37
12
|
return processMuseaArtFile(filePath, {
|
|
@@ -54,16 +29,34 @@ function isArtFileInput(filePath) {
|
|
|
54
29
|
return filePath.endsWith(".art.vue");
|
|
55
30
|
}
|
|
56
31
|
function createVrtOptions(options) {
|
|
57
|
-
const configured = options.vrt
|
|
32
|
+
const configured = withoutA11y(options.vrt);
|
|
58
33
|
const vrtOptions = {
|
|
59
34
|
...configured,
|
|
60
|
-
snapshotDir:
|
|
35
|
+
snapshotDir: resolveVrtSnapshotDir(options, configured.snapshotDir),
|
|
61
36
|
threshold: options.thresholdProvided ? options.threshold : configured.threshold ?? options.threshold
|
|
62
37
|
};
|
|
63
38
|
const workers = options.workersProvided ? options.workers : configured.workers;
|
|
64
39
|
if (workers !== void 0) vrtOptions.workers = normalizeVrtWorkerCount(workers);
|
|
65
40
|
return vrtOptions;
|
|
66
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* `vrt.snapshotDir` is resolved against the config file's directory.
|
|
44
|
+
* When it is unset, baselines stay under `<--output>/snapshots`.
|
|
45
|
+
*/
|
|
46
|
+
function resolveVrtSnapshotDir(options, snapshotDir) {
|
|
47
|
+
if (!snapshotDir) return path.join(options.output, "snapshots");
|
|
48
|
+
return path.resolve(path.dirname(path.resolve(options.config)), snapshotDir);
|
|
49
|
+
}
|
|
50
|
+
function withoutA11y(vrt) {
|
|
51
|
+
if (!vrt) return {};
|
|
52
|
+
const configured = { ...vrt };
|
|
53
|
+
delete configured.a11y;
|
|
54
|
+
return configured;
|
|
55
|
+
}
|
|
56
|
+
/** Options object `musea-vrt --a11y` passes to `MuseaA11yRunner`. */
|
|
57
|
+
function createA11yRunner(options, Runner) {
|
|
58
|
+
return new Runner(options.vrt?.a11y);
|
|
59
|
+
}
|
|
67
60
|
async function runVrt(options, artFiles) {
|
|
68
61
|
const totalVariants = artFiles.reduce((sum, art) => sum + art.variants.filter((v) => !v.skipVrt).length, 0);
|
|
69
62
|
console.log(` Testing ${totalVariants} variant(s) across ${artFiles.length} art file(s)\n`);
|
|
@@ -93,7 +86,7 @@ async function runVrt(options, artFiles) {
|
|
|
93
86
|
console.log(" Running accessibility audits...\n");
|
|
94
87
|
try {
|
|
95
88
|
const { MuseaA11yRunner } = await import("../a11y/index.mjs");
|
|
96
|
-
const a11yRunner =
|
|
89
|
+
const a11yRunner = createA11yRunner(options, MuseaA11yRunner);
|
|
97
90
|
const a11yResults = await a11yRunner.runAudits(artFiles, options.baseUrl, runner);
|
|
98
91
|
const a11ySummary = a11yRunner.getSummary(a11yResults);
|
|
99
92
|
console.log(" A11y Results:");
|
|
@@ -215,7 +208,33 @@ async function runGenerate(options) {
|
|
|
215
208
|
}
|
|
216
209
|
//#endregion
|
|
217
210
|
//#region src/cli/config.ts
|
|
211
|
+
const DEFAULT_INCLUDE = ["**/*.art.vue"];
|
|
212
|
+
const DEFAULT_EXCLUDE = ["node_modules/**", "dist/**"];
|
|
213
|
+
const VIZE_CONFIG_NAMES = [
|
|
214
|
+
"vize.config.ts",
|
|
215
|
+
"vize.config.mts",
|
|
216
|
+
"vize.config.js",
|
|
217
|
+
"vize.config.mjs",
|
|
218
|
+
"vize.config.cjs"
|
|
219
|
+
];
|
|
218
220
|
async function loadMuseaVrtOptions(configPath, cwd = process.cwd()) {
|
|
221
|
+
const loaded = await loadViteConfig(configPath, cwd);
|
|
222
|
+
if (!loaded) return void 0;
|
|
223
|
+
return readPluginMuseaOptions(loaded.plugins)?.vrt;
|
|
224
|
+
}
|
|
225
|
+
async function loadMuseaFileSet(configPath, cwd = process.cwd()) {
|
|
226
|
+
const loaded = await loadViteConfig(configPath, cwd);
|
|
227
|
+
const pluginOptions = loaded ? readPluginMuseaOptions(loaded.plugins) : void 0;
|
|
228
|
+
const vizeOptions = pluginOptions?.include ? void 0 : await readVizeMuseaOptions(loaded?.configDir ?? cwd);
|
|
229
|
+
const include = pluginOptions?.include ?? vizeOptions?.include ?? DEFAULT_INCLUDE;
|
|
230
|
+
const exclude = pluginOptions?.exclude ?? vizeOptions?.exclude ?? DEFAULT_EXCLUDE;
|
|
231
|
+
return {
|
|
232
|
+
root: loaded?.root ?? cwd,
|
|
233
|
+
include,
|
|
234
|
+
exclude
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function loadViteConfig(configPath, cwd) {
|
|
219
238
|
const resolvedConfigPath = path.isAbsolute(configPath) ? configPath : path.resolve(cwd, configPath);
|
|
220
239
|
if (!await fileExists(resolvedConfigPath)) return void 0;
|
|
221
240
|
const loaded = await loadConfigFromFile({
|
|
@@ -227,9 +246,35 @@ async function loadMuseaVrtOptions(configPath, cwd = process.cwd()) {
|
|
|
227
246
|
if (!loaded) return void 0;
|
|
228
247
|
const plugins = [];
|
|
229
248
|
await collectPlugins(loaded.config.plugins, plugins);
|
|
249
|
+
const configDir = path.dirname(resolvedConfigPath);
|
|
250
|
+
const configuredRoot = loaded.config.root;
|
|
251
|
+
return {
|
|
252
|
+
root: configuredRoot ? path.resolve(configDir, configuredRoot) : configDir,
|
|
253
|
+
configDir,
|
|
254
|
+
plugins
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function readPluginMuseaOptions(plugins) {
|
|
230
258
|
for (const plugin of plugins) {
|
|
231
259
|
const options = readMuseaOptions(plugin);
|
|
232
|
-
if (options
|
|
260
|
+
if (options) return options;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function readVizeMuseaOptions(dir) {
|
|
264
|
+
for (const name of VIZE_CONFIG_NAMES) {
|
|
265
|
+
const file = path.join(dir, name);
|
|
266
|
+
if (!await fileExists(file)) continue;
|
|
267
|
+
try {
|
|
268
|
+
const musea = ((await loadConfigFromFile({
|
|
269
|
+
command: "serve",
|
|
270
|
+
mode: "development",
|
|
271
|
+
isPreview: false,
|
|
272
|
+
isSsrBuild: false
|
|
273
|
+
}, file))?.config)?.musea;
|
|
274
|
+
if (musea?.include || musea?.exclude) return musea;
|
|
275
|
+
} catch {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
233
278
|
}
|
|
234
279
|
}
|
|
235
280
|
async function collectPlugins(input, plugins) {
|
|
@@ -442,8 +487,9 @@ async function main() {
|
|
|
442
487
|
return;
|
|
443
488
|
}
|
|
444
489
|
options.vrt = await loadMuseaVrtOptions(options.config, cwd);
|
|
490
|
+
const fileSet = await loadMuseaFileSet(options.config, cwd);
|
|
445
491
|
console.log(" Scanning for art files...");
|
|
446
|
-
const artFilePaths = await scanArtFiles(
|
|
492
|
+
const artFilePaths = await scanArtFiles(fileSet.root, fileSet.include, fileSet.exclude);
|
|
447
493
|
if (artFilePaths.length === 0) {
|
|
448
494
|
console.log(" No art files found.\n");
|
|
449
495
|
process.exit(0);
|