@vizejs/vite-plugin-musea 0.0.1-alpha.11 → 0.0.1-alpha.113
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-Bvx5TJb8.d.ts +61 -0
- package/dist/a11y-Bvx5TJb8.d.ts.map +1 -0
- package/dist/a11y-C6xqILwZ.js +305 -0
- package/dist/a11y-C6xqILwZ.js.map +1 -0
- package/dist/a11y.d.ts +3 -0
- package/dist/a11y.js +3 -0
- package/dist/autogen-D3Zjc3zI.d.ts +64 -0
- package/dist/autogen-D3Zjc3zI.d.ts.map +1 -0
- package/dist/autogen-ymQnARZK.js +193 -0
- package/dist/autogen-ymQnARZK.js.map +1 -0
- package/dist/autogen.d.ts +2 -0
- package/dist/autogen.js +3 -0
- package/dist/cli.js +207 -30
- package/dist/cli.js.map +1 -1
- package/dist/gallery/assets/abap-Cry0R76c.js +1 -0
- package/dist/gallery/assets/apex-GS4zZi0I.js +1 -0
- package/dist/gallery/assets/azcli-DMImymmY.js +1 -0
- package/dist/gallery/assets/bat-D6epFECU.js +1 -0
- package/dist/gallery/assets/bicep-7klDZ283.js +2 -0
- package/dist/gallery/assets/cameligo-PvLD8t4t.js +1 -0
- package/dist/gallery/assets/clojure-BTbSGpb3.js +1 -0
- package/dist/gallery/assets/codicon-DCmgc-ay.ttf +0 -0
- package/dist/gallery/assets/coffee-Bhl_9YuJ.js +1 -0
- package/dist/gallery/assets/cpp-CM5j04eT.js +1 -0
- package/dist/gallery/assets/csharp-Dh0Ee7SY.js +1 -0
- package/dist/gallery/assets/csp-CLRC61y6.js +1 -0
- package/dist/gallery/assets/css-B0t_muXd.js +3 -0
- package/dist/gallery/assets/css.worker-Cbw1kvi8.js +88 -0
- package/dist/gallery/assets/cssMode-CaF-1r5A.js +4 -0
- package/dist/gallery/assets/cypher-C5e5inIh.js +1 -0
- package/dist/gallery/assets/dart-DIK3l8YT.js +1 -0
- package/dist/gallery/assets/dockerfile-D7OAO0hl.js +1 -0
- package/dist/gallery/assets/ecl-CP7nM2KN.js +1 -0
- package/dist/gallery/assets/editor-B55U_qvj.css +1 -0
- package/dist/gallery/assets/editor-F8AxQWwE.css +1 -0
- package/dist/gallery/assets/editor.api-DXavAYrv.js +644 -0
- package/dist/gallery/assets/editor.main-BPGOQG9_.js +63 -0
- package/dist/gallery/assets/editor.worker-Cs7HTPcl.js +12 -0
- package/dist/gallery/assets/elixir-DNRIIj6-.js +1 -0
- package/dist/gallery/assets/flow9-BC5Cr9X0.js +1 -0
- package/dist/gallery/assets/freemarker2-BvLjdc1z.js +3 -0
- package/dist/gallery/assets/fsharp-52P4yqMh.js +1 -0
- package/dist/gallery/assets/go-yKE3zUfB.js +1 -0
- package/dist/gallery/assets/graphql-D3sNVCLc.js +1 -0
- package/dist/gallery/assets/handlebars-BHP-ru_f.js +1 -0
- package/dist/gallery/assets/hcl-BB7aW7AX.js +1 -0
- package/dist/gallery/assets/html-vs5ObZxz.js +1 -0
- package/dist/gallery/assets/html.worker-CYmk49z4.js +495 -0
- package/dist/gallery/assets/htmlMode-gtvFmrd-.js +4 -0
- package/dist/gallery/assets/index-8vvVUIZe.js +63 -0
- package/dist/gallery/assets/index-B-vh50zZ.css +1 -0
- package/dist/gallery/assets/ini-BdRufzJj.js +1 -0
- package/dist/gallery/assets/java-CeUu-z7Y.js +1 -0
- package/dist/gallery/assets/javascript-Dyi3DN3w.js +1 -0
- package/dist/gallery/assets/json.worker-CWR6J9Qf.js +51 -0
- package/dist/gallery/assets/jsonMode-FphWEj_M.js +10 -0
- package/dist/gallery/assets/julia-CXu-Fn93.js +1 -0
- package/dist/gallery/assets/kotlin-TwsjxLJ3.js +1 -0
- package/dist/gallery/assets/less-CviwWNG4.js +2 -0
- package/dist/gallery/assets/lexon-BTOivnjP.js +1 -0
- package/dist/gallery/assets/liquid-EbeZCh25.js +1 -0
- package/dist/gallery/assets/lua-6W3WJOvj.js +1 -0
- package/dist/gallery/assets/m3-tlthQ8Fo.js +1 -0
- package/dist/gallery/assets/markdown-CPR4Kr9O.js +1 -0
- package/dist/gallery/assets/mdx-CT3P33NE.js +1 -0
- package/dist/gallery/assets/mips-BfxZbsD8.js +1 -0
- package/dist/gallery/assets/monaco.contribution-G2ulgCOc.js +2 -0
- package/dist/gallery/assets/msdax-eKsr2VtO.js +1 -0
- package/dist/gallery/assets/mysql-D6-LO0bt.js +1 -0
- package/dist/gallery/assets/objective-c-DYtfYpNc.js +1 -0
- package/dist/gallery/assets/pascal-CPGyHbal.js +1 -0
- package/dist/gallery/assets/pascaligo-Dsp_VKxo.js +1 -0
- package/dist/gallery/assets/perl-CUVa2_Cu.js +1 -0
- package/dist/gallery/assets/pgsql-C2nbbU56.js +1 -0
- package/dist/gallery/assets/php-DxX2tlkL.js +1 -0
- package/dist/gallery/assets/pla-D55LHImG.js +1 -0
- package/dist/gallery/assets/postiats-Dw_nWtoT.js +1 -0
- package/dist/gallery/assets/powerquery-BldVOeNZ.js +1 -0
- package/dist/gallery/assets/powershell-fdqyoMut.js +1 -0
- package/dist/gallery/assets/protobuf-C-2cnAYL.js +2 -0
- package/dist/gallery/assets/pug-bDrVOc6m.js +1 -0
- package/dist/gallery/assets/python-CS5TXnQl.js +1 -0
- package/dist/gallery/assets/qsharp-B83Ol6AR.js +1 -0
- package/dist/gallery/assets/r-DAxg6zn-.js +1 -0
- package/dist/gallery/assets/razor-Bav3uDeU.js +1 -0
- package/dist/gallery/assets/redis-BSRYxJDu.js +1 -0
- package/dist/gallery/assets/redshift-BrtVU4Ki.js +1 -0
- package/dist/gallery/assets/restructuredtext-DdU6AjLQ.js +1 -0
- package/dist/gallery/assets/ruby-C-s7ovR-.js +1 -0
- package/dist/gallery/assets/rust-CmSb_pkG.js +1 -0
- package/dist/gallery/assets/sb-Bfo5Ukmr.js +1 -0
- package/dist/gallery/assets/scala-Cx2bkddK.js +1 -0
- package/dist/gallery/assets/scheme-DkT6GPaV.js +1 -0
- package/dist/gallery/assets/scss-DOPngiM2.js +3 -0
- package/dist/gallery/assets/shell-NYt6Xulf.js +1 -0
- package/dist/gallery/assets/solidity-CUiq_T3F.js +1 -0
- package/dist/gallery/assets/sophia-B4sI8Ij-.js +1 -0
- package/dist/gallery/assets/sparql-BNfhekQe.js +1 -0
- package/dist/gallery/assets/sql-Bzn3OZV3.js +1 -0
- package/dist/gallery/assets/st-CV0zI_0P.js +1 -0
- package/dist/gallery/assets/swift-CR3-zK7D.js +1 -0
- package/dist/gallery/assets/systemverilog-BxgPwTIi.js +1 -0
- package/dist/gallery/assets/tcl-BSnnsp36.js +1 -0
- package/dist/gallery/assets/ts.worker-B_5n269U.js +51339 -0
- package/dist/gallery/assets/tsMode-Czu8iBG1.js +11 -0
- package/dist/gallery/assets/twig-Bx06vatZ.js +1 -0
- package/dist/gallery/assets/typescript-r7pqYU1X.js +1 -0
- package/dist/gallery/assets/typespec-COSap3s7.js +1 -0
- package/dist/gallery/assets/vb-DU0VXhXP.js +1 -0
- package/dist/gallery/assets/wgsl-BegdTer-.js +298 -0
- package/dist/gallery/assets/xml-DSt5pfef.js +1 -0
- package/dist/gallery/assets/yaml-6SyqWhY0.js +1 -0
- package/dist/gallery/index.html +16 -0
- package/dist/index.d.ts +51 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1531 -65
- package/dist/index.js.map +1 -1
- package/dist/{vrt-DRwtnkE5.js → vrt-DP87vGIA.js} +222 -289
- package/dist/vrt-DP87vGIA.js.map +1 -0
- package/dist/vrt-Vb4aqPZE.d.ts +461 -0
- package/dist/vrt-Vb4aqPZE.d.ts.map +1 -0
- package/dist/vrt.d.ts +2 -2
- package/dist/vrt.js +1 -1
- package/package.json +28 -5
- package/dist/vrt-BfuTRv-J.d.ts +0 -217
- package/dist/vrt-BfuTRv-J.d.ts.map +0 -1
- package/dist/vrt-DRwtnkE5.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { MuseaA11yRunner } from "./a11y-C6xqILwZ.js";
|
|
2
|
+
import { generateArtFile, writeArtFile } from "./autogen-ymQnARZK.js";
|
|
3
|
+
import { MuseaVrtRunner, generateVrtJsonReport, generateVrtReport } from "./vrt-DP87vGIA.js";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { createRequire } from "node:module";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { vizeConfigStore } from "@vizejs/vite-plugin";
|
|
5
8
|
|
|
6
9
|
//#region src/style-dictionary.ts
|
|
7
10
|
/**
|
|
@@ -83,18 +86,21 @@ function extractSubcategories(obj) {
|
|
|
83
86
|
function isTokenValue(value) {
|
|
84
87
|
if (typeof value !== "object" || value === null) return false;
|
|
85
88
|
const obj = value;
|
|
86
|
-
return "value" in obj && (typeof obj.value === "string" || typeof obj.value === "number");
|
|
89
|
+
return "value" in obj && (typeof obj.value === "string" || typeof obj.value === "number") || "$value" in obj && (typeof obj.$value === "string" || typeof obj.$value === "number");
|
|
87
90
|
}
|
|
88
91
|
/**
|
|
89
92
|
* Normalize token to DesignToken interface.
|
|
90
93
|
*/
|
|
91
94
|
function normalizeToken(raw) {
|
|
92
|
-
|
|
93
|
-
value: raw.value,
|
|
94
|
-
type: raw.type,
|
|
95
|
+
const token = {
|
|
96
|
+
value: raw.value ?? raw.$value,
|
|
97
|
+
type: raw.type ?? raw.$type,
|
|
95
98
|
description: raw.description,
|
|
96
99
|
attributes: raw.attributes
|
|
97
100
|
};
|
|
101
|
+
if (raw.$tier === "primitive" || raw.$tier === "semantic") token.$tier = raw.$tier;
|
|
102
|
+
if (typeof raw.$reference === "string") token.$reference = raw.$reference;
|
|
103
|
+
return token;
|
|
98
104
|
}
|
|
99
105
|
/**
|
|
100
106
|
* Format category name for display.
|
|
@@ -103,6 +109,168 @@ function formatCategoryName(name) {
|
|
|
103
109
|
return name.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
104
110
|
}
|
|
105
111
|
/**
|
|
112
|
+
* Flatten nested categories into a flat map keyed by dot-path.
|
|
113
|
+
*/
|
|
114
|
+
function buildTokenMap(categories, prefix = []) {
|
|
115
|
+
const map = {};
|
|
116
|
+
for (const cat of categories) {
|
|
117
|
+
const catKey = cat.name.toLowerCase().replace(/\s+/g, "-");
|
|
118
|
+
const catPath = [...prefix, catKey];
|
|
119
|
+
for (const [name, token] of Object.entries(cat.tokens)) {
|
|
120
|
+
const dotPath = [...catPath, name].join(".");
|
|
121
|
+
map[dotPath] = token;
|
|
122
|
+
}
|
|
123
|
+
if (cat.subcategories) {
|
|
124
|
+
const subMap = buildTokenMap(cat.subcategories, catPath);
|
|
125
|
+
Object.assign(map, subMap);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return map;
|
|
129
|
+
}
|
|
130
|
+
const REFERENCE_PATTERN = /^\{(.+)\}$/;
|
|
131
|
+
const MAX_RESOLVE_DEPTH = 10;
|
|
132
|
+
/**
|
|
133
|
+
* Resolve references in categories, setting $tier, $reference, and $resolvedValue.
|
|
134
|
+
*/
|
|
135
|
+
function resolveReferences(categories, tokenMap) {
|
|
136
|
+
for (const cat of categories) {
|
|
137
|
+
for (const token of Object.values(cat.tokens)) resolveTokenReference(token, tokenMap);
|
|
138
|
+
if (cat.subcategories) resolveReferences(cat.subcategories, tokenMap);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function resolveTokenReference(token, tokenMap) {
|
|
142
|
+
if (typeof token.value === "string") {
|
|
143
|
+
const match = token.value.match(REFERENCE_PATTERN);
|
|
144
|
+
if (match) {
|
|
145
|
+
token.$tier = token.$tier ?? "semantic";
|
|
146
|
+
token.$reference = match[1];
|
|
147
|
+
token.$resolvedValue = resolveValue(match[1], tokenMap, 0, new Set());
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
token.$tier = token.$tier ?? "primitive";
|
|
152
|
+
}
|
|
153
|
+
function resolveValue(ref, tokenMap, depth, visited) {
|
|
154
|
+
if (depth >= MAX_RESOLVE_DEPTH || visited.has(ref)) return void 0;
|
|
155
|
+
visited.add(ref);
|
|
156
|
+
const target = tokenMap[ref];
|
|
157
|
+
if (!target) return void 0;
|
|
158
|
+
if (typeof target.value === "string") {
|
|
159
|
+
const match = target.value.match(REFERENCE_PATTERN);
|
|
160
|
+
if (match) return resolveValue(match[1], tokenMap, depth + 1, visited);
|
|
161
|
+
}
|
|
162
|
+
return target.value;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Read raw JSON token file.
|
|
166
|
+
*/
|
|
167
|
+
async function readRawTokenFile(tokensPath) {
|
|
168
|
+
const content = await fs.promises.readFile(tokensPath, "utf-8");
|
|
169
|
+
return JSON.parse(content);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Write raw JSON token file atomically (write tmp, rename).
|
|
173
|
+
*/
|
|
174
|
+
async function writeRawTokenFile(tokensPath, data) {
|
|
175
|
+
const tmpPath = tokensPath + ".tmp";
|
|
176
|
+
await fs.promises.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
177
|
+
await fs.promises.rename(tmpPath, tokensPath);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Set a token at a dot-separated path in the raw JSON structure.
|
|
181
|
+
*/
|
|
182
|
+
function setTokenAtPath(data, dotPath, token) {
|
|
183
|
+
const parts = dotPath.split(".");
|
|
184
|
+
let current = data;
|
|
185
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
186
|
+
const key = parts[i];
|
|
187
|
+
if (typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
188
|
+
current = current[key];
|
|
189
|
+
}
|
|
190
|
+
const leafKey = parts[parts.length - 1];
|
|
191
|
+
const raw = { value: token.value };
|
|
192
|
+
if (token.type) raw.type = token.type;
|
|
193
|
+
if (token.description) raw.description = token.description;
|
|
194
|
+
if (token.$tier) raw.$tier = token.$tier;
|
|
195
|
+
if (token.$reference) raw.$reference = token.$reference;
|
|
196
|
+
if (token.attributes) raw.attributes = token.attributes;
|
|
197
|
+
current[leafKey] = raw;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Delete a token at a dot-separated path, cleaning empty parents.
|
|
201
|
+
*/
|
|
202
|
+
function deleteTokenAtPath(data, dotPath) {
|
|
203
|
+
const parts = dotPath.split(".");
|
|
204
|
+
const parents = [];
|
|
205
|
+
let current = data;
|
|
206
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
207
|
+
const key = parts[i];
|
|
208
|
+
if (typeof current[key] !== "object" || current[key] === null) return false;
|
|
209
|
+
parents.push({
|
|
210
|
+
obj: current,
|
|
211
|
+
key
|
|
212
|
+
});
|
|
213
|
+
current = current[key];
|
|
214
|
+
}
|
|
215
|
+
const leafKey = parts[parts.length - 1];
|
|
216
|
+
if (!(leafKey in current)) return false;
|
|
217
|
+
delete current[leafKey];
|
|
218
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
219
|
+
const { obj, key } = parents[i];
|
|
220
|
+
const child = obj[key];
|
|
221
|
+
if (Object.keys(child).length === 0) delete obj[key];
|
|
222
|
+
else break;
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Validate that a semantic reference points to an existing token and has no cycles.
|
|
228
|
+
*/
|
|
229
|
+
function validateSemanticReference(tokenMap, reference, selfPath) {
|
|
230
|
+
if (!tokenMap[reference]) return {
|
|
231
|
+
valid: false,
|
|
232
|
+
error: `Reference target "${reference}" does not exist`
|
|
233
|
+
};
|
|
234
|
+
const visited = new Set();
|
|
235
|
+
if (selfPath) visited.add(selfPath);
|
|
236
|
+
let current = reference;
|
|
237
|
+
let depth = 0;
|
|
238
|
+
while (depth < MAX_RESOLVE_DEPTH) {
|
|
239
|
+
if (visited.has(current)) return {
|
|
240
|
+
valid: false,
|
|
241
|
+
error: `Circular reference detected at "${current}"`
|
|
242
|
+
};
|
|
243
|
+
visited.add(current);
|
|
244
|
+
const target = tokenMap[current];
|
|
245
|
+
if (!target) break;
|
|
246
|
+
if (typeof target.value === "string") {
|
|
247
|
+
const match = target.value.match(REFERENCE_PATTERN);
|
|
248
|
+
if (match) {
|
|
249
|
+
current = match[1];
|
|
250
|
+
depth++;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
if (depth >= MAX_RESOLVE_DEPTH) return {
|
|
257
|
+
valid: false,
|
|
258
|
+
error: "Reference chain too deep (max 10)"
|
|
259
|
+
};
|
|
260
|
+
return { valid: true };
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Find all tokens that reference the given path.
|
|
264
|
+
*/
|
|
265
|
+
function findDependentTokens(tokenMap, targetPath) {
|
|
266
|
+
const dependents = [];
|
|
267
|
+
for (const [path$1, token] of Object.entries(tokenMap)) if (typeof token.value === "string") {
|
|
268
|
+
const match = token.value.match(REFERENCE_PATTERN);
|
|
269
|
+
if (match && match[1] === targetPath) dependents.push(path$1);
|
|
270
|
+
}
|
|
271
|
+
return dependents;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
106
274
|
* Generate HTML documentation for tokens.
|
|
107
275
|
*/
|
|
108
276
|
function generateTokensHtml(categories) {
|
|
@@ -272,6 +440,97 @@ async function processStyleDictionary(config) {
|
|
|
272
440
|
}
|
|
273
441
|
};
|
|
274
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Normalize a token value for comparison.
|
|
445
|
+
* - Lowercase, trim
|
|
446
|
+
* - Leading-zero: `.5rem` → `0.5rem`
|
|
447
|
+
* - Short hex: `#fff` → `#ffffff`
|
|
448
|
+
*/
|
|
449
|
+
function normalizeTokenValue(value) {
|
|
450
|
+
let v = String(value).trim().toLowerCase();
|
|
451
|
+
const shortHex = v.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/);
|
|
452
|
+
if (shortHex) {
|
|
453
|
+
const [, r, g, b, a] = shortHex;
|
|
454
|
+
v = a ? `#${r}${r}${g}${g}${b}${b}${a}${a}` : `#${r}${r}${g}${g}${b}${b}`;
|
|
455
|
+
}
|
|
456
|
+
v = v.replace(/(?<![0-9])\.(\d)/g, "0.$1");
|
|
457
|
+
return v;
|
|
458
|
+
}
|
|
459
|
+
const STYLE_BLOCK_RE = /<style[^>]*>([\s\S]*?)<\/style>/g;
|
|
460
|
+
const CSS_PROPERTY_RE = /^\s*([\w-]+)\s*:\s*(.+?)\s*;?\s*$/;
|
|
461
|
+
/**
|
|
462
|
+
* Scan art file sources for token value matches in `<style>` blocks.
|
|
463
|
+
*/
|
|
464
|
+
function scanTokenUsage(artFiles, tokenMap) {
|
|
465
|
+
const valueLookup = new Map();
|
|
466
|
+
for (const [tokenPath, token] of Object.entries(tokenMap)) {
|
|
467
|
+
const rawValue = token.$resolvedValue ?? token.value;
|
|
468
|
+
const normalized = normalizeTokenValue(rawValue);
|
|
469
|
+
if (!normalized) continue;
|
|
470
|
+
const existing = valueLookup.get(normalized);
|
|
471
|
+
if (existing) existing.push(tokenPath);
|
|
472
|
+
else valueLookup.set(normalized, [tokenPath]);
|
|
473
|
+
}
|
|
474
|
+
const usageMap = {};
|
|
475
|
+
for (const [artPath, artInfo] of artFiles) {
|
|
476
|
+
let source;
|
|
477
|
+
try {
|
|
478
|
+
source = fs.readFileSync(artPath, "utf-8");
|
|
479
|
+
} catch {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
const allLines = source.split("\n");
|
|
483
|
+
const styleRegions = [];
|
|
484
|
+
let match;
|
|
485
|
+
STYLE_BLOCK_RE.lastIndex = 0;
|
|
486
|
+
while ((match = STYLE_BLOCK_RE.exec(source)) !== null) {
|
|
487
|
+
const beforeMatch = source.slice(0, match.index);
|
|
488
|
+
const startTag = source.slice(match.index, match.index + match[0].indexOf(match[1]));
|
|
489
|
+
const startLine = beforeMatch.split("\n").length + startTag.split("\n").length - 1;
|
|
490
|
+
styleRegions.push({
|
|
491
|
+
startLine,
|
|
492
|
+
content: match[1]
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
for (const region of styleRegions) {
|
|
496
|
+
const lines = region.content.split("\n");
|
|
497
|
+
for (let i = 0; i < lines.length; i++) {
|
|
498
|
+
const line = lines[i];
|
|
499
|
+
const propMatch = line.match(CSS_PROPERTY_RE);
|
|
500
|
+
if (!propMatch) continue;
|
|
501
|
+
const property = propMatch[1];
|
|
502
|
+
const valueStr = propMatch[2];
|
|
503
|
+
const valueParts = valueStr.split(/\s+/);
|
|
504
|
+
for (const part of valueParts) {
|
|
505
|
+
const normalizedPart = normalizeTokenValue(part);
|
|
506
|
+
const matchingTokens = valueLookup.get(normalizedPart);
|
|
507
|
+
if (!matchingTokens) continue;
|
|
508
|
+
const lineNumber = region.startLine + i;
|
|
509
|
+
const lineContent = allLines[lineNumber - 1]?.trim() ?? line.trim();
|
|
510
|
+
for (const tokenPath of matchingTokens) {
|
|
511
|
+
if (!usageMap[tokenPath]) usageMap[tokenPath] = [];
|
|
512
|
+
let entry = usageMap[tokenPath].find((e) => e.artPath === artPath);
|
|
513
|
+
if (!entry) {
|
|
514
|
+
entry = {
|
|
515
|
+
artPath,
|
|
516
|
+
artTitle: artInfo.metadata.title,
|
|
517
|
+
artCategory: artInfo.metadata.category,
|
|
518
|
+
matches: []
|
|
519
|
+
};
|
|
520
|
+
usageMap[tokenPath].push(entry);
|
|
521
|
+
}
|
|
522
|
+
if (!entry.matches.some((m) => m.line === lineNumber && m.property === property)) entry.matches.push({
|
|
523
|
+
line: lineNumber,
|
|
524
|
+
lineContent,
|
|
525
|
+
property
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return usageMap;
|
|
533
|
+
}
|
|
275
534
|
|
|
276
535
|
//#endregion
|
|
277
536
|
//#region src/index.ts
|
|
@@ -290,17 +549,106 @@ function loadNative() {
|
|
|
290
549
|
}
|
|
291
550
|
}
|
|
292
551
|
/**
|
|
552
|
+
* JS-based fallback for SFC analysis when native `analyzeSfc` is not available.
|
|
553
|
+
* Uses regex parsing to extract props and emits from Vue SFC source.
|
|
554
|
+
*/
|
|
555
|
+
function analyzeSfcFallback(source, _options) {
|
|
556
|
+
try {
|
|
557
|
+
const props = [];
|
|
558
|
+
const emits = [];
|
|
559
|
+
const scriptSetupMatch = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
|
|
560
|
+
if (!scriptSetupMatch) {
|
|
561
|
+
const scriptMatch = source.match(/<script[^>]*>([\s\S]*?)<\/script>/);
|
|
562
|
+
if (!scriptMatch) return {
|
|
563
|
+
props: [],
|
|
564
|
+
emits: []
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
const scriptContent = scriptSetupMatch?.[1] || "";
|
|
568
|
+
const propsMatch = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>\s*\(/);
|
|
569
|
+
const propsMatch2 = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>/);
|
|
570
|
+
const propsBody = propsMatch?.[1] || propsMatch2?.[1];
|
|
571
|
+
if (propsBody) {
|
|
572
|
+
const lines = propsBody.split("\n");
|
|
573
|
+
let i = 0;
|
|
574
|
+
while (i < lines.length) {
|
|
575
|
+
const line = lines[i].trim();
|
|
576
|
+
if (line.startsWith("/**") || line.startsWith("*") || line.startsWith("*/")) {
|
|
577
|
+
i++;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
const propMatch = line.match(/^(\w+)(\?)?:\s*(.+?)(?:;?\s*)$/);
|
|
581
|
+
if (propMatch) {
|
|
582
|
+
const name = propMatch[1];
|
|
583
|
+
const optional = !!propMatch[2];
|
|
584
|
+
let type = propMatch[3].replace(/;$/, "").trim();
|
|
585
|
+
const defaultPattern = new RegExp(`\\b${name}\\s*=\\s*([^,}\\n]+)`);
|
|
586
|
+
const defaultMatch = scriptContent.match(defaultPattern);
|
|
587
|
+
const defaultValue = defaultMatch ? defaultMatch[1].trim() : void 0;
|
|
588
|
+
props.push({
|
|
589
|
+
name,
|
|
590
|
+
type,
|
|
591
|
+
required: !optional && defaultValue === void 0,
|
|
592
|
+
...defaultValue !== void 0 ? { default_value: defaultValue } : {}
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
i++;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const emitsMatch = scriptContent.match(/defineEmits\s*<\s*\{([\s\S]*?)\}>/);
|
|
599
|
+
if (emitsMatch) {
|
|
600
|
+
const emitsBody = emitsMatch[1];
|
|
601
|
+
const emitRegex = /(\w+)\s*:/g;
|
|
602
|
+
let match;
|
|
603
|
+
while ((match = emitRegex.exec(emitsBody)) !== null) emits.push(match[1]);
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
props,
|
|
607
|
+
emits
|
|
608
|
+
};
|
|
609
|
+
} catch {
|
|
610
|
+
return {
|
|
611
|
+
props: [],
|
|
612
|
+
emits: []
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Build the theme config object from plugin options for runtime injection.
|
|
618
|
+
*/
|
|
619
|
+
function buildThemeConfig(theme) {
|
|
620
|
+
if (!theme) return void 0;
|
|
621
|
+
if (typeof theme === "string") return { default: theme };
|
|
622
|
+
const themes = Array.isArray(theme) ? theme : [theme];
|
|
623
|
+
const custom = {};
|
|
624
|
+
for (const t of themes) custom[t.name] = {
|
|
625
|
+
base: t.base,
|
|
626
|
+
colors: t.colors
|
|
627
|
+
};
|
|
628
|
+
return {
|
|
629
|
+
default: themes[0].name,
|
|
630
|
+
custom
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
293
634
|
* Create Musea Vite plugin.
|
|
294
635
|
*/
|
|
295
636
|
function musea(options = {}) {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
637
|
+
let include = options.include ?? ["**/*.art.vue"];
|
|
638
|
+
let exclude = options.exclude ?? ["node_modules/**", "dist/**"];
|
|
639
|
+
let basePath = options.basePath ?? "/__musea__";
|
|
640
|
+
let storybookCompat = options.storybookCompat ?? false;
|
|
300
641
|
const storybookOutDir = options.storybookOutDir ?? ".storybook/stories";
|
|
642
|
+
let inlineArt = options.inlineArt ?? false;
|
|
643
|
+
const tokensPath = options.tokensPath;
|
|
644
|
+
const themeConfig = buildThemeConfig(options.theme);
|
|
645
|
+
const previewCss = options.previewCss ?? [];
|
|
646
|
+
const previewSetup = options.previewSetup;
|
|
301
647
|
let config;
|
|
302
648
|
let server = null;
|
|
303
649
|
const artFiles = new Map();
|
|
650
|
+
let resolvedPreviewCss = [];
|
|
651
|
+
let resolvedPreviewSetup = null;
|
|
304
652
|
const mainPlugin = {
|
|
305
653
|
name: "vite-plugin-musea",
|
|
306
654
|
enforce: "pre",
|
|
@@ -309,15 +657,63 @@ function musea(options = {}) {
|
|
|
309
657
|
},
|
|
310
658
|
configResolved(resolvedConfig) {
|
|
311
659
|
config = resolvedConfig;
|
|
660
|
+
const vizeConfig = vizeConfigStore.get(resolvedConfig.root);
|
|
661
|
+
if (vizeConfig?.musea) {
|
|
662
|
+
const mc = vizeConfig.musea;
|
|
663
|
+
if (!options.include && mc.include) include = mc.include;
|
|
664
|
+
if (!options.exclude && mc.exclude) exclude = mc.exclude;
|
|
665
|
+
if (!options.basePath && mc.basePath) basePath = mc.basePath;
|
|
666
|
+
if (options.storybookCompat === void 0 && mc.storybookCompat !== void 0) storybookCompat = mc.storybookCompat;
|
|
667
|
+
if (options.inlineArt === void 0 && mc.inlineArt !== void 0) inlineArt = mc.inlineArt;
|
|
668
|
+
}
|
|
669
|
+
resolvedPreviewCss = previewCss.map((cssPath) => path.isAbsolute(cssPath) ? cssPath : path.resolve(resolvedConfig.root, cssPath));
|
|
670
|
+
if (previewSetup) resolvedPreviewSetup = path.isAbsolute(previewSetup) ? previewSetup : path.resolve(resolvedConfig.root, previewSetup);
|
|
312
671
|
},
|
|
313
672
|
configureServer(devServer) {
|
|
314
673
|
server = devServer;
|
|
315
674
|
devServer.middlewares.use(basePath, async (req, res, next) => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
675
|
+
const url = req.url || "/";
|
|
676
|
+
if (url === "/" || url === "/index.html" || url.startsWith("/tokens") || url.startsWith("/component/")) {
|
|
677
|
+
const galleryDistDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), "gallery");
|
|
678
|
+
const indexHtmlPath = path.join(galleryDistDir, "index.html");
|
|
679
|
+
try {
|
|
680
|
+
await fs.promises.access(indexHtmlPath);
|
|
681
|
+
let html = await fs.promises.readFile(indexHtmlPath, "utf-8");
|
|
682
|
+
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : "";
|
|
683
|
+
html = html.replace("</head>", `<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}</script></head>`);
|
|
684
|
+
res.setHeader("Content-Type", "text/html");
|
|
685
|
+
res.end(html);
|
|
686
|
+
return;
|
|
687
|
+
} catch {
|
|
688
|
+
const html = generateGalleryHtml(basePath, themeConfig);
|
|
689
|
+
res.setHeader("Content-Type", "text/html");
|
|
690
|
+
res.end(html);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (url.startsWith("/assets/")) {
|
|
695
|
+
const galleryDistDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), "gallery");
|
|
696
|
+
const filePath = path.join(galleryDistDir, url);
|
|
697
|
+
try {
|
|
698
|
+
const stat = await fs.promises.stat(filePath);
|
|
699
|
+
if (stat.isFile()) {
|
|
700
|
+
const content = await fs.promises.readFile(filePath);
|
|
701
|
+
const ext = path.extname(filePath);
|
|
702
|
+
const mimeTypes = {
|
|
703
|
+
".js": "application/javascript",
|
|
704
|
+
".css": "text/css",
|
|
705
|
+
".svg": "image/svg+xml",
|
|
706
|
+
".png": "image/png",
|
|
707
|
+
".ico": "image/x-icon",
|
|
708
|
+
".woff2": "font/woff2",
|
|
709
|
+
".woff": "font/woff"
|
|
710
|
+
};
|
|
711
|
+
res.setHeader("Content-Type", mimeTypes[ext] || "application/octet-stream");
|
|
712
|
+
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
713
|
+
res.end(content);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
} catch {}
|
|
321
717
|
}
|
|
322
718
|
next();
|
|
323
719
|
});
|
|
@@ -343,7 +739,7 @@ function musea(options = {}) {
|
|
|
343
739
|
return;
|
|
344
740
|
}
|
|
345
741
|
const variantComponentName = toPascalCase(variant.name);
|
|
346
|
-
const moduleCode = generatePreviewModule(art, variantComponentName, variant.name);
|
|
742
|
+
const moduleCode = generatePreviewModule(art, variantComponentName, variant.name, resolvedPreviewCss, resolvedPreviewSetup);
|
|
347
743
|
try {
|
|
348
744
|
const result = await devServer.transformRequest(`virtual:musea-preview:${artPath}:${variantName}`);
|
|
349
745
|
if (result) {
|
|
@@ -378,8 +774,7 @@ function musea(options = {}) {
|
|
|
378
774
|
res.end("Variant not found");
|
|
379
775
|
return;
|
|
380
776
|
}
|
|
381
|
-
const
|
|
382
|
-
const html = await devServer.transformIndexHtml(`${basePath}/preview?art=${encodeURIComponent(artPath)}&variant=${encodeURIComponent(variantName)}`, rawHtml);
|
|
777
|
+
const html = generatePreviewHtml(art, variant, basePath, config.base);
|
|
383
778
|
res.setHeader("Content-Type", "text/html");
|
|
384
779
|
res.end(html);
|
|
385
780
|
});
|
|
@@ -416,21 +811,525 @@ function musea(options = {}) {
|
|
|
416
811
|
}
|
|
417
812
|
});
|
|
418
813
|
devServer.middlewares.use(`${basePath}/api`, async (req, res, next) => {
|
|
419
|
-
|
|
814
|
+
const sendJson = (data, status = 200) => {
|
|
815
|
+
res.statusCode = status;
|
|
420
816
|
res.setHeader("Content-Type", "application/json");
|
|
421
|
-
res.end(JSON.stringify(
|
|
817
|
+
res.end(JSON.stringify(data));
|
|
818
|
+
};
|
|
819
|
+
const sendError = (message, status = 500) => {
|
|
820
|
+
sendJson({ error: message }, status);
|
|
821
|
+
};
|
|
822
|
+
if (req.url === "/arts" && req.method === "GET") {
|
|
823
|
+
sendJson(Array.from(artFiles.values()));
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
if (req.url === "/tokens/usage" && req.method === "GET") {
|
|
827
|
+
if (!tokensPath) {
|
|
828
|
+
sendJson({});
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
try {
|
|
832
|
+
const absoluteTokensPath = path.resolve(config.root, tokensPath);
|
|
833
|
+
const categories = await parseTokens(absoluteTokensPath);
|
|
834
|
+
const tokenMap = buildTokenMap(categories);
|
|
835
|
+
resolveReferences(categories, tokenMap);
|
|
836
|
+
const resolvedTokenMap = buildTokenMap(categories);
|
|
837
|
+
const usage = scanTokenUsage(artFiles, resolvedTokenMap);
|
|
838
|
+
sendJson(usage);
|
|
839
|
+
} catch (e) {
|
|
840
|
+
console.error("[musea] Failed to scan token usage:", e);
|
|
841
|
+
sendJson({});
|
|
842
|
+
}
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
if (req.url === "/tokens" && req.method === "GET") {
|
|
846
|
+
if (!tokensPath) {
|
|
847
|
+
sendJson({
|
|
848
|
+
categories: [],
|
|
849
|
+
tokenMap: {},
|
|
850
|
+
meta: {
|
|
851
|
+
filePath: "",
|
|
852
|
+
tokenCount: 0,
|
|
853
|
+
primitiveCount: 0,
|
|
854
|
+
semanticCount: 0
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
try {
|
|
860
|
+
const absoluteTokensPath = path.resolve(config.root, tokensPath);
|
|
861
|
+
const categories = await parseTokens(absoluteTokensPath);
|
|
862
|
+
const tokenMap = buildTokenMap(categories);
|
|
863
|
+
resolveReferences(categories, tokenMap);
|
|
864
|
+
const resolvedTokenMap = buildTokenMap(categories);
|
|
865
|
+
let primitiveCount = 0;
|
|
866
|
+
let semanticCount = 0;
|
|
867
|
+
for (const token of Object.values(resolvedTokenMap)) if (token.$tier === "semantic") semanticCount++;
|
|
868
|
+
else primitiveCount++;
|
|
869
|
+
sendJson({
|
|
870
|
+
categories,
|
|
871
|
+
tokenMap: resolvedTokenMap,
|
|
872
|
+
meta: {
|
|
873
|
+
filePath: absoluteTokensPath,
|
|
874
|
+
tokenCount: Object.keys(resolvedTokenMap).length,
|
|
875
|
+
primitiveCount,
|
|
876
|
+
semanticCount
|
|
877
|
+
}
|
|
878
|
+
});
|
|
879
|
+
} catch (e) {
|
|
880
|
+
console.error("[musea] Failed to load tokens:", e);
|
|
881
|
+
sendJson({
|
|
882
|
+
categories: [],
|
|
883
|
+
tokenMap: {},
|
|
884
|
+
error: String(e)
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (req.url === "/tokens" && req.method === "POST") {
|
|
890
|
+
if (!tokensPath) {
|
|
891
|
+
sendError("No tokens path configured", 400);
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
let body = "";
|
|
895
|
+
req.on("data", (chunk) => {
|
|
896
|
+
body += chunk;
|
|
897
|
+
});
|
|
898
|
+
req.on("end", async () => {
|
|
899
|
+
try {
|
|
900
|
+
const { path: dotPath, token } = JSON.parse(body);
|
|
901
|
+
if (!dotPath || !token || token.value === void 0) {
|
|
902
|
+
sendError("Missing required fields: path, token.value", 400);
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const absoluteTokensPath = path.resolve(config.root, tokensPath);
|
|
906
|
+
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
907
|
+
const currentCategories = await parseTokens(absoluteTokensPath);
|
|
908
|
+
const currentMap = buildTokenMap(currentCategories);
|
|
909
|
+
if (currentMap[dotPath]) {
|
|
910
|
+
sendError(`Token already exists at path "${dotPath}"`, 409);
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
if (token.$reference) {
|
|
914
|
+
const validation = validateSemanticReference(currentMap, token.$reference, dotPath);
|
|
915
|
+
if (!validation.valid) {
|
|
916
|
+
sendError(validation.error, 400);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
token.value = `{${token.$reference}}`;
|
|
920
|
+
token.$tier = "semantic";
|
|
921
|
+
}
|
|
922
|
+
setTokenAtPath(rawData, dotPath, token);
|
|
923
|
+
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
924
|
+
const categories = await parseTokens(absoluteTokensPath);
|
|
925
|
+
const tokenMap = buildTokenMap(categories);
|
|
926
|
+
resolveReferences(categories, tokenMap);
|
|
927
|
+
const resolvedTokenMap = buildTokenMap(categories);
|
|
928
|
+
sendJson({
|
|
929
|
+
categories,
|
|
930
|
+
tokenMap: resolvedTokenMap
|
|
931
|
+
}, 201);
|
|
932
|
+
} catch (e) {
|
|
933
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
if (req.url === "/tokens" && req.method === "PUT") {
|
|
939
|
+
if (!tokensPath) {
|
|
940
|
+
sendError("No tokens path configured", 400);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
let body = "";
|
|
944
|
+
req.on("data", (chunk) => {
|
|
945
|
+
body += chunk;
|
|
946
|
+
});
|
|
947
|
+
req.on("end", async () => {
|
|
948
|
+
try {
|
|
949
|
+
const { path: dotPath, token } = JSON.parse(body);
|
|
950
|
+
if (!dotPath || !token || token.value === void 0) {
|
|
951
|
+
sendError("Missing required fields: path, token.value", 400);
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
const absoluteTokensPath = path.resolve(config.root, tokensPath);
|
|
955
|
+
if (token.$reference) {
|
|
956
|
+
const currentCategories = await parseTokens(absoluteTokensPath);
|
|
957
|
+
const currentMap = buildTokenMap(currentCategories);
|
|
958
|
+
const validation = validateSemanticReference(currentMap, token.$reference, dotPath);
|
|
959
|
+
if (!validation.valid) {
|
|
960
|
+
sendError(validation.error, 400);
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
token.value = `{${token.$reference}}`;
|
|
964
|
+
token.$tier = "semantic";
|
|
965
|
+
}
|
|
966
|
+
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
967
|
+
setTokenAtPath(rawData, dotPath, token);
|
|
968
|
+
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
969
|
+
const categories = await parseTokens(absoluteTokensPath);
|
|
970
|
+
const tokenMap = buildTokenMap(categories);
|
|
971
|
+
resolveReferences(categories, tokenMap);
|
|
972
|
+
const resolvedTokenMap = buildTokenMap(categories);
|
|
973
|
+
sendJson({
|
|
974
|
+
categories,
|
|
975
|
+
tokenMap: resolvedTokenMap
|
|
976
|
+
});
|
|
977
|
+
} catch (e) {
|
|
978
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
979
|
+
}
|
|
980
|
+
});
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
if (req.url === "/tokens" && req.method === "DELETE") {
|
|
984
|
+
if (!tokensPath) {
|
|
985
|
+
sendError("No tokens path configured", 400);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
let body = "";
|
|
989
|
+
req.on("data", (chunk) => {
|
|
990
|
+
body += chunk;
|
|
991
|
+
});
|
|
992
|
+
req.on("end", async () => {
|
|
993
|
+
try {
|
|
994
|
+
const { path: dotPath } = JSON.parse(body);
|
|
995
|
+
if (!dotPath) {
|
|
996
|
+
sendError("Missing required field: path", 400);
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
const absoluteTokensPath = path.resolve(config.root, tokensPath);
|
|
1000
|
+
const currentCategories = await parseTokens(absoluteTokensPath);
|
|
1001
|
+
const currentMap = buildTokenMap(currentCategories);
|
|
1002
|
+
const dependents = findDependentTokens(currentMap, dotPath);
|
|
1003
|
+
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
1004
|
+
const deleted = deleteTokenAtPath(rawData, dotPath);
|
|
1005
|
+
if (!deleted) {
|
|
1006
|
+
sendError(`Token not found at path "${dotPath}"`, 404);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
1010
|
+
const categories = await parseTokens(absoluteTokensPath);
|
|
1011
|
+
const tokenMap = buildTokenMap(categories);
|
|
1012
|
+
resolveReferences(categories, tokenMap);
|
|
1013
|
+
const resolvedTokenMap = buildTokenMap(categories);
|
|
1014
|
+
sendJson({
|
|
1015
|
+
categories,
|
|
1016
|
+
tokenMap: resolvedTokenMap,
|
|
1017
|
+
dependentsWarning: dependents.length > 0 ? dependents : void 0
|
|
1018
|
+
});
|
|
1019
|
+
} catch (e) {
|
|
1020
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (req.url?.startsWith("/arts/") && req.method === "PUT") {
|
|
1026
|
+
const rest = req.url.slice(6);
|
|
1027
|
+
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
1028
|
+
if (sourceMatch) {
|
|
1029
|
+
const artPath = decodeURIComponent(sourceMatch[1]);
|
|
1030
|
+
const art = artFiles.get(artPath);
|
|
1031
|
+
if (!art) {
|
|
1032
|
+
sendError("Art not found", 404);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
let body = "";
|
|
1036
|
+
req.on("data", (chunk) => {
|
|
1037
|
+
body += chunk;
|
|
1038
|
+
});
|
|
1039
|
+
req.on("end", async () => {
|
|
1040
|
+
try {
|
|
1041
|
+
const { source } = JSON.parse(body);
|
|
1042
|
+
if (typeof source !== "string") {
|
|
1043
|
+
sendError("Missing required field: source", 400);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
await fs.promises.writeFile(artPath, source, "utf-8");
|
|
1047
|
+
await processArtFile(artPath);
|
|
1048
|
+
sendJson({ success: true });
|
|
1049
|
+
} catch (e) {
|
|
1050
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
next();
|
|
422
1056
|
return;
|
|
423
1057
|
}
|
|
424
1058
|
if (req.url?.startsWith("/arts/") && req.method === "GET") {
|
|
425
|
-
const
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
1059
|
+
const rest = req.url.slice(6);
|
|
1060
|
+
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
1061
|
+
const paletteMatch = rest.match(/^(.+)\/palette$/);
|
|
1062
|
+
const analysisMatch = rest.match(/^(.+)\/analysis$/);
|
|
1063
|
+
const docsMatch = rest.match(/^(.+)\/docs$/);
|
|
1064
|
+
const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
|
|
1065
|
+
if (sourceMatch) {
|
|
1066
|
+
const artPath$1 = decodeURIComponent(sourceMatch[1]);
|
|
1067
|
+
const art$1 = artFiles.get(artPath$1);
|
|
1068
|
+
if (!art$1) {
|
|
1069
|
+
sendError("Art not found", 404);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
try {
|
|
1073
|
+
const source = await fs.promises.readFile(artPath$1, "utf-8");
|
|
1074
|
+
sendJson({
|
|
1075
|
+
source,
|
|
1076
|
+
path: artPath$1
|
|
1077
|
+
});
|
|
1078
|
+
} catch (e) {
|
|
1079
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1080
|
+
}
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (paletteMatch) {
|
|
1084
|
+
const artPath$1 = decodeURIComponent(paletteMatch[1]);
|
|
1085
|
+
const art$1 = artFiles.get(artPath$1);
|
|
1086
|
+
if (!art$1) {
|
|
1087
|
+
sendError("Art not found", 404);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
try {
|
|
1091
|
+
const source = await fs.promises.readFile(artPath$1, "utf-8");
|
|
1092
|
+
const binding = loadNative();
|
|
1093
|
+
let palette;
|
|
1094
|
+
if (binding.generateArtPalette) palette = binding.generateArtPalette(source, { filename: artPath$1 });
|
|
1095
|
+
else palette = {
|
|
1096
|
+
title: art$1.metadata.title,
|
|
1097
|
+
controls: [],
|
|
1098
|
+
groups: [],
|
|
1099
|
+
json: "{}",
|
|
1100
|
+
typescript: ""
|
|
1101
|
+
};
|
|
1102
|
+
if (palette.controls.length === 0 && art$1.metadata.component) {
|
|
1103
|
+
const resolvedComponentPath = path.isAbsolute(art$1.metadata.component) ? art$1.metadata.component : path.resolve(path.dirname(artPath$1), art$1.metadata.component);
|
|
1104
|
+
try {
|
|
1105
|
+
const componentSource = await fs.promises.readFile(resolvedComponentPath, "utf-8");
|
|
1106
|
+
const analysis = binding.analyzeSfc ? binding.analyzeSfc(componentSource, { filename: resolvedComponentPath }) : analyzeSfcFallback(componentSource, { filename: resolvedComponentPath });
|
|
1107
|
+
if (analysis.props.length > 0) {
|
|
1108
|
+
palette.controls = analysis.props.map((prop) => {
|
|
1109
|
+
let control = "text";
|
|
1110
|
+
if (prop.type === "boolean") control = "boolean";
|
|
1111
|
+
else if (prop.type === "number") control = "number";
|
|
1112
|
+
else if (prop.type.includes("|") && !prop.type.includes("=>")) control = "select";
|
|
1113
|
+
const options$1 = [];
|
|
1114
|
+
if (control === "select") {
|
|
1115
|
+
const optionMatches = prop.type.match(/"([^"]+)"/g);
|
|
1116
|
+
if (optionMatches) for (const opt of optionMatches) {
|
|
1117
|
+
const val = opt.replace(/"/g, "");
|
|
1118
|
+
options$1.push({
|
|
1119
|
+
label: val,
|
|
1120
|
+
value: val
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return {
|
|
1125
|
+
name: prop.name,
|
|
1126
|
+
control,
|
|
1127
|
+
default_value: prop.default_value !== void 0 ? prop.default_value === "true" ? true : prop.default_value === "false" ? false : typeof prop.default_value === "string" && prop.default_value.startsWith("\"") ? prop.default_value.replace(/^"|"$/g, "") : prop.default_value : void 0,
|
|
1128
|
+
description: void 0,
|
|
1129
|
+
required: prop.required,
|
|
1130
|
+
options: options$1,
|
|
1131
|
+
range: void 0,
|
|
1132
|
+
group: void 0
|
|
1133
|
+
};
|
|
1134
|
+
});
|
|
1135
|
+
palette.json = JSON.stringify({
|
|
1136
|
+
title: palette.title,
|
|
1137
|
+
controls: palette.controls
|
|
1138
|
+
}, null, 2);
|
|
1139
|
+
palette.typescript = `export interface ${palette.title}Props {\n${palette.controls.map((c) => ` ${c.name}${c.required ? "" : "?"}: ${c.control === "boolean" ? "boolean" : c.control === "number" ? "number" : c.control === "select" ? c.options.map((o) => `"${String(o.value)}"`).join(" | ") : "string"};`).join("\n")}\n}\n`;
|
|
1140
|
+
}
|
|
1141
|
+
} catch {}
|
|
1142
|
+
}
|
|
1143
|
+
sendJson(palette);
|
|
1144
|
+
} catch (e) {
|
|
1145
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1146
|
+
}
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (analysisMatch) {
|
|
1150
|
+
const artPath$1 = decodeURIComponent(analysisMatch[1]);
|
|
1151
|
+
const art$1 = artFiles.get(artPath$1);
|
|
1152
|
+
if (!art$1) {
|
|
1153
|
+
sendError("Art not found", 404);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
try {
|
|
1157
|
+
const resolvedComponentPath = art$1.isInline && art$1.componentPath ? art$1.componentPath : art$1.metadata.component ? path.isAbsolute(art$1.metadata.component) ? art$1.metadata.component : path.resolve(path.dirname(artPath$1), art$1.metadata.component) : null;
|
|
1158
|
+
if (resolvedComponentPath) {
|
|
1159
|
+
const source = await fs.promises.readFile(resolvedComponentPath, "utf-8");
|
|
1160
|
+
const binding = loadNative();
|
|
1161
|
+
if (binding.analyzeSfc) {
|
|
1162
|
+
const analysis = binding.analyzeSfc(source, { filename: resolvedComponentPath });
|
|
1163
|
+
sendJson(analysis);
|
|
1164
|
+
} else {
|
|
1165
|
+
const analysis = analyzeSfcFallback(source, { filename: resolvedComponentPath });
|
|
1166
|
+
sendJson(analysis);
|
|
1167
|
+
}
|
|
1168
|
+
} else sendJson({
|
|
1169
|
+
props: [],
|
|
1170
|
+
emits: []
|
|
1171
|
+
});
|
|
1172
|
+
} catch (e) {
|
|
1173
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1174
|
+
}
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
if (docsMatch) {
|
|
1178
|
+
const artPath$1 = decodeURIComponent(docsMatch[1]);
|
|
1179
|
+
const art$1 = artFiles.get(artPath$1);
|
|
1180
|
+
if (!art$1) {
|
|
1181
|
+
sendError("Art not found", 404);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
try {
|
|
1185
|
+
const source = await fs.promises.readFile(artPath$1, "utf-8");
|
|
1186
|
+
const binding = loadNative();
|
|
1187
|
+
if (binding.generateArtDoc) {
|
|
1188
|
+
const doc = binding.generateArtDoc(source, { filename: artPath$1 });
|
|
1189
|
+
let markdown = doc.markdown || "";
|
|
1190
|
+
const componentName = art$1.metadata.title || "Component";
|
|
1191
|
+
markdown = markdown.replace(/<Self(\s|>|\/)/g, `<${componentName}$1`).replace(/<\/Self>/g, `</${componentName}>`);
|
|
1192
|
+
markdown = markdown.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
|
|
1193
|
+
const lines = code.split("\n");
|
|
1194
|
+
let minIndent = Infinity;
|
|
1195
|
+
for (const line of lines) if (line.trim()) {
|
|
1196
|
+
const indent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
1197
|
+
minIndent = Math.min(minIndent, indent);
|
|
1198
|
+
}
|
|
1199
|
+
if (minIndent === Infinity) minIndent = 0;
|
|
1200
|
+
let formatted;
|
|
1201
|
+
if (minIndent > 0) formatted = lines.map((line) => line.slice(minIndent)).join("\n");
|
|
1202
|
+
else {
|
|
1203
|
+
let restIndent = Infinity;
|
|
1204
|
+
for (let i = 1; i < lines.length; i++) if (lines[i].trim()) {
|
|
1205
|
+
const indent = lines[i].match(/^(\s*)/)?.[1].length || 0;
|
|
1206
|
+
restIndent = Math.min(restIndent, indent);
|
|
1207
|
+
}
|
|
1208
|
+
if (restIndent === Infinity || restIndent === 0) formatted = lines.join("\n");
|
|
1209
|
+
else formatted = lines.map((line, i) => i === 0 ? line : line.slice(restIndent)).join("\n");
|
|
1210
|
+
}
|
|
1211
|
+
return "```" + lang + "\n" + formatted + "```";
|
|
1212
|
+
});
|
|
1213
|
+
sendJson({
|
|
1214
|
+
...doc,
|
|
1215
|
+
markdown
|
|
1216
|
+
});
|
|
1217
|
+
} else sendJson({
|
|
1218
|
+
markdown: "",
|
|
1219
|
+
title: art$1.metadata.title,
|
|
1220
|
+
variant_count: art$1.variants.length
|
|
1221
|
+
});
|
|
1222
|
+
} catch (e) {
|
|
1223
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1224
|
+
}
|
|
1225
|
+
return;
|
|
433
1226
|
}
|
|
1227
|
+
if (a11yMatch) {
|
|
1228
|
+
const artPath$1 = decodeURIComponent(a11yMatch[1]);
|
|
1229
|
+
const _variantName = decodeURIComponent(a11yMatch[2]);
|
|
1230
|
+
const art$1 = artFiles.get(artPath$1);
|
|
1231
|
+
if (!art$1) {
|
|
1232
|
+
sendError("Art not found", 404);
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
sendJson({
|
|
1236
|
+
violations: [],
|
|
1237
|
+
passes: 0,
|
|
1238
|
+
incomplete: 0
|
|
1239
|
+
});
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
const artPath = decodeURIComponent(rest);
|
|
1243
|
+
const art = artFiles.get(artPath);
|
|
1244
|
+
if (art) sendJson(art);
|
|
1245
|
+
else sendError("Art not found", 404);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
if (req.url === "/preview-with-props" && req.method === "POST") {
|
|
1249
|
+
let body = "";
|
|
1250
|
+
req.on("data", (chunk) => {
|
|
1251
|
+
body += chunk;
|
|
1252
|
+
});
|
|
1253
|
+
req.on("end", () => {
|
|
1254
|
+
try {
|
|
1255
|
+
const { artPath: reqArtPath, variantName, props: propsOverride } = JSON.parse(body);
|
|
1256
|
+
const art = artFiles.get(reqArtPath);
|
|
1257
|
+
if (!art) {
|
|
1258
|
+
sendError("Art not found", 404);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const variant = art.variants.find((v) => v.name === variantName);
|
|
1262
|
+
if (!variant) {
|
|
1263
|
+
sendError("Variant not found", 404);
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
const variantComponentName = toPascalCase(variant.name);
|
|
1267
|
+
const moduleCode = generatePreviewModuleWithProps(art, variantComponentName, variant.name, propsOverride, resolvedPreviewCss, resolvedPreviewSetup);
|
|
1268
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
1269
|
+
res.end(moduleCode);
|
|
1270
|
+
} catch (e) {
|
|
1271
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
if (req.url === "/generate" && req.method === "POST") {
|
|
1277
|
+
let body = "";
|
|
1278
|
+
req.on("data", (chunk) => {
|
|
1279
|
+
body += chunk;
|
|
1280
|
+
});
|
|
1281
|
+
req.on("end", async () => {
|
|
1282
|
+
try {
|
|
1283
|
+
const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);
|
|
1284
|
+
const { generateArtFile: genArt } = await import("./autogen.js");
|
|
1285
|
+
const result = await genArt(reqComponentPath, autogenOptions);
|
|
1286
|
+
sendJson({
|
|
1287
|
+
generated: true,
|
|
1288
|
+
componentName: result.componentName,
|
|
1289
|
+
variants: result.variants,
|
|
1290
|
+
artFileContent: result.artFileContent
|
|
1291
|
+
});
|
|
1292
|
+
} catch (e) {
|
|
1293
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
if (req.url === "/run-vrt" && req.method === "POST") {
|
|
1299
|
+
let body = "";
|
|
1300
|
+
req.on("data", (chunk) => {
|
|
1301
|
+
body += chunk;
|
|
1302
|
+
});
|
|
1303
|
+
req.on("end", async () => {
|
|
1304
|
+
try {
|
|
1305
|
+
const { artPath, updateSnapshots } = JSON.parse(body);
|
|
1306
|
+
const { MuseaVrtRunner: MuseaVrtRunner$1 } = await import("./vrt.js");
|
|
1307
|
+
const runner = new MuseaVrtRunner$1({ snapshotDir: path.resolve(config.root, ".vize/snapshots") });
|
|
1308
|
+
const port = devServer.config.server.port || 5173;
|
|
1309
|
+
const baseUrl = `http://localhost:${port}`;
|
|
1310
|
+
let artsToTest = Array.from(artFiles.values());
|
|
1311
|
+
if (artPath) artsToTest = artsToTest.filter((a) => a.path === artPath);
|
|
1312
|
+
await runner.start();
|
|
1313
|
+
const results = await runner.runTests(artsToTest, baseUrl, { updateSnapshots });
|
|
1314
|
+
const summary = runner.getSummary(results);
|
|
1315
|
+
await runner.stop();
|
|
1316
|
+
sendJson({
|
|
1317
|
+
success: true,
|
|
1318
|
+
summary,
|
|
1319
|
+
results: results.map((r) => ({
|
|
1320
|
+
artPath: r.artPath,
|
|
1321
|
+
variantName: r.variantName,
|
|
1322
|
+
viewport: r.viewport.name,
|
|
1323
|
+
passed: r.passed,
|
|
1324
|
+
isNew: r.isNew,
|
|
1325
|
+
diffPercentage: r.diffPercentage,
|
|
1326
|
+
error: r.error
|
|
1327
|
+
}))
|
|
1328
|
+
});
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
434
1333
|
return;
|
|
435
1334
|
}
|
|
436
1335
|
next();
|
|
@@ -440,12 +1339,30 @@ function musea(options = {}) {
|
|
|
440
1339
|
await processArtFile(file);
|
|
441
1340
|
console.log(`[musea] Reloaded: ${path.relative(config.root, file)}`);
|
|
442
1341
|
}
|
|
1342
|
+
if (inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue")) {
|
|
1343
|
+
const hadArt = artFiles.has(file);
|
|
1344
|
+
const source = await fs.promises.readFile(file, "utf-8");
|
|
1345
|
+
if (source.includes("<art")) {
|
|
1346
|
+
await processArtFile(file);
|
|
1347
|
+
console.log(`[musea] Reloaded inline art: ${path.relative(config.root, file)}`);
|
|
1348
|
+
} else if (hadArt) {
|
|
1349
|
+
artFiles.delete(file);
|
|
1350
|
+
console.log(`[musea] Removed inline art: ${path.relative(config.root, file)}`);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
443
1353
|
});
|
|
444
1354
|
devServer.watcher.on("add", async (file) => {
|
|
445
1355
|
if (file.endsWith(".art.vue") && shouldProcess(file, include, exclude, config.root)) {
|
|
446
1356
|
await processArtFile(file);
|
|
447
1357
|
console.log(`[musea] Added: ${path.relative(config.root, file)}`);
|
|
448
1358
|
}
|
|
1359
|
+
if (inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue")) {
|
|
1360
|
+
const source = await fs.promises.readFile(file, "utf-8");
|
|
1361
|
+
if (source.includes("<art")) {
|
|
1362
|
+
await processArtFile(file);
|
|
1363
|
+
console.log(`[musea] Added inline art: ${path.relative(config.root, file)}`);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
449
1366
|
});
|
|
450
1367
|
devServer.watcher.on("unlink", (file) => {
|
|
451
1368
|
if (artFiles.has(file)) {
|
|
@@ -453,9 +1370,24 @@ function musea(options = {}) {
|
|
|
453
1370
|
console.log(`[musea] Removed: ${path.relative(config.root, file)}`);
|
|
454
1371
|
}
|
|
455
1372
|
});
|
|
1373
|
+
return () => {
|
|
1374
|
+
devServer.httpServer?.once("listening", () => {
|
|
1375
|
+
const address = devServer.httpServer?.address();
|
|
1376
|
+
if (address && typeof address === "object") {
|
|
1377
|
+
const protocol = devServer.config.server.https ? "https" : "http";
|
|
1378
|
+
const rawHost = address.address;
|
|
1379
|
+
const host = rawHost === "::" || rawHost === "::1" || rawHost === "0.0.0.0" || rawHost === "127.0.0.1" ? "localhost" : rawHost;
|
|
1380
|
+
const port = address.port;
|
|
1381
|
+
const url = `${protocol}://${host}:${port}${basePath}`;
|
|
1382
|
+
console.log();
|
|
1383
|
+
console.log(` \x1b[36m➜\x1b[0m \x1b[1mMusea Gallery:\x1b[0m \x1b[36m${url}\x1b[0m`);
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
};
|
|
456
1387
|
},
|
|
457
1388
|
async buildStart() {
|
|
458
|
-
|
|
1389
|
+
console.log(`[musea] config.root: ${config.root}, include: ${JSON.stringify(include)}`);
|
|
1390
|
+
const files = await scanArtFiles(config.root, include, exclude, inlineArt);
|
|
459
1391
|
console.log(`[musea] Found ${files.length} art files`);
|
|
460
1392
|
for (const file of files) await processArtFile(file);
|
|
461
1393
|
if (storybookCompat) await generateStorybookFiles(artFiles, config.root, storybookOutDir);
|
|
@@ -466,11 +1398,15 @@ function musea(options = {}) {
|
|
|
466
1398
|
if (id.startsWith("virtual:musea-preview:")) return "\0musea-preview:" + id.slice(22);
|
|
467
1399
|
if (id.startsWith("virtual:musea-art:")) {
|
|
468
1400
|
const artPath = id.slice(18);
|
|
469
|
-
if (artFiles.has(artPath)) return "\0musea-art:" + artPath;
|
|
1401
|
+
if (artFiles.has(artPath)) return "\0musea-art:" + artPath + "?musea-virtual";
|
|
470
1402
|
}
|
|
471
1403
|
if (id.endsWith(".art.vue")) {
|
|
472
1404
|
const resolved = path.resolve(config.root, id);
|
|
473
|
-
if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved;
|
|
1405
|
+
if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
1406
|
+
}
|
|
1407
|
+
if (inlineArt && id.endsWith(".vue") && !id.endsWith(".art.vue")) {
|
|
1408
|
+
const resolved = path.resolve(config.root, id);
|
|
1409
|
+
if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
474
1410
|
}
|
|
475
1411
|
return null;
|
|
476
1412
|
},
|
|
@@ -486,17 +1422,17 @@ function musea(options = {}) {
|
|
|
486
1422
|
const art = artFiles.get(artPath);
|
|
487
1423
|
if (art) {
|
|
488
1424
|
const variantComponentName = toPascalCase(variantName);
|
|
489
|
-
return generatePreviewModule(art, variantComponentName, variantName);
|
|
1425
|
+
return generatePreviewModule(art, variantComponentName, variantName, resolvedPreviewCss, resolvedPreviewSetup);
|
|
490
1426
|
}
|
|
491
1427
|
}
|
|
492
1428
|
}
|
|
493
1429
|
if (id.startsWith("\0musea-art:")) {
|
|
494
|
-
const artPath = id.slice(11);
|
|
1430
|
+
const artPath = id.slice(11).replace(/\?musea-virtual$/, "");
|
|
495
1431
|
const art = artFiles.get(artPath);
|
|
496
1432
|
if (art) return generateArtModule(art, artPath);
|
|
497
1433
|
}
|
|
498
1434
|
if (id.startsWith(VIRTUAL_MUSEA_PREFIX)) {
|
|
499
|
-
const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length);
|
|
1435
|
+
const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length).replace(/\?musea-virtual$/, "");
|
|
500
1436
|
const art = artFiles.get(realPath);
|
|
501
1437
|
if (art) return generateArtModule(art, realPath);
|
|
502
1438
|
}
|
|
@@ -505,6 +1441,12 @@ function musea(options = {}) {
|
|
|
505
1441
|
async handleHotUpdate(ctx) {
|
|
506
1442
|
const { file } = ctx;
|
|
507
1443
|
if (file.endsWith(".art.vue") && artFiles.has(file)) {
|
|
1444
|
+
await processArtFile(file);
|
|
1445
|
+
const virtualId = VIRTUAL_MUSEA_PREFIX + file + "?musea-virtual";
|
|
1446
|
+
const modules = server?.moduleGraph.getModulesByFile(virtualId);
|
|
1447
|
+
if (modules) return [...modules];
|
|
1448
|
+
}
|
|
1449
|
+
if (inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue") && artFiles.has(file)) {
|
|
508
1450
|
await processArtFile(file);
|
|
509
1451
|
const virtualId = VIRTUAL_MUSEA_PREFIX + file;
|
|
510
1452
|
const modules = server?.moduleGraph.getModulesByFile(virtualId);
|
|
@@ -518,12 +1460,14 @@ function musea(options = {}) {
|
|
|
518
1460
|
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
519
1461
|
const binding = loadNative();
|
|
520
1462
|
const parsed = binding.parseArt(source, { filename: filePath });
|
|
1463
|
+
if (!parsed.variants || parsed.variants.length === 0) return;
|
|
1464
|
+
const isInline = !filePath.endsWith(".art.vue");
|
|
521
1465
|
const info = {
|
|
522
1466
|
path: filePath,
|
|
523
1467
|
metadata: {
|
|
524
|
-
title: parsed.metadata.title,
|
|
1468
|
+
title: parsed.metadata.title || (isInline ? path.basename(filePath, ".vue") : ""),
|
|
525
1469
|
description: parsed.metadata.description,
|
|
526
|
-
component: parsed.metadata.component,
|
|
1470
|
+
component: isInline ? void 0 : parsed.metadata.component,
|
|
527
1471
|
category: parsed.metadata.category,
|
|
528
1472
|
tags: parsed.metadata.tags,
|
|
529
1473
|
status: parsed.metadata.status,
|
|
@@ -532,12 +1476,15 @@ function musea(options = {}) {
|
|
|
532
1476
|
variants: parsed.variants.map((v) => ({
|
|
533
1477
|
name: v.name,
|
|
534
1478
|
template: v.template,
|
|
535
|
-
isDefault: v.
|
|
536
|
-
skipVrt: v.
|
|
1479
|
+
isDefault: v.isDefault,
|
|
1480
|
+
skipVrt: v.skipVrt
|
|
537
1481
|
})),
|
|
538
|
-
hasScriptSetup: parsed.
|
|
539
|
-
|
|
540
|
-
|
|
1482
|
+
hasScriptSetup: isInline ? false : parsed.hasScriptSetup,
|
|
1483
|
+
scriptSetupContent: !isInline && parsed.hasScriptSetup ? extractScriptSetupContent(source) : void 0,
|
|
1484
|
+
hasScript: parsed.hasScript,
|
|
1485
|
+
styleCount: parsed.styleCount,
|
|
1486
|
+
isInline,
|
|
1487
|
+
componentPath: isInline ? filePath : void 0
|
|
541
1488
|
};
|
|
542
1489
|
artFiles.set(filePath, info);
|
|
543
1490
|
} catch (e) {
|
|
@@ -553,10 +1500,11 @@ function shouldProcess(file, include, exclude, root) {
|
|
|
553
1500
|
return false;
|
|
554
1501
|
}
|
|
555
1502
|
function matchGlob(filepath, pattern) {
|
|
556
|
-
const
|
|
1503
|
+
const PLACEHOLDER = "<<GLOBSTAR>>";
|
|
1504
|
+
const regex = pattern.replaceAll("**", PLACEHOLDER).replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replaceAll(PLACEHOLDER, ".*");
|
|
557
1505
|
return new RegExp(`^${regex}$`).test(filepath);
|
|
558
1506
|
}
|
|
559
|
-
async function scanArtFiles(root, include, exclude) {
|
|
1507
|
+
async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
|
|
560
1508
|
const files = [];
|
|
561
1509
|
async function scan(dir) {
|
|
562
1510
|
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
@@ -575,19 +1523,24 @@ async function scanArtFiles(root, include, exclude) {
|
|
|
575
1523
|
files.push(fullPath);
|
|
576
1524
|
break;
|
|
577
1525
|
}
|
|
1526
|
+
} else if (scanInlineArt && entry.isFile() && entry.name.endsWith(".vue") && !entry.name.endsWith(".art.vue")) {
|
|
1527
|
+
const content = await fs.promises.readFile(fullPath, "utf-8");
|
|
1528
|
+
if (content.includes("<art")) files.push(fullPath);
|
|
578
1529
|
}
|
|
579
1530
|
}
|
|
580
1531
|
}
|
|
581
1532
|
await scan(root);
|
|
582
1533
|
return files;
|
|
583
1534
|
}
|
|
584
|
-
function generateGalleryHtml(basePath) {
|
|
1535
|
+
function generateGalleryHtml(basePath, themeConfig) {
|
|
1536
|
+
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : "";
|
|
585
1537
|
return `<!DOCTYPE html>
|
|
586
1538
|
<html lang="en">
|
|
587
1539
|
<head>
|
|
588
1540
|
<meta charset="UTF-8">
|
|
589
1541
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
590
1542
|
<title>Musea - Component Gallery</title>
|
|
1543
|
+
<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}</script>
|
|
591
1544
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
592
1545
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
593
1546
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
@@ -1320,19 +2273,334 @@ export async function loadArts() {
|
|
|
1320
2273
|
}
|
|
1321
2274
|
`;
|
|
1322
2275
|
}
|
|
1323
|
-
|
|
2276
|
+
const MUSEA_ADDONS_INIT_CODE = `
|
|
2277
|
+
function __museaInitAddons(container, variantName) {
|
|
2278
|
+
// === DOM event capture ===
|
|
2279
|
+
// Note: mousemove, mouseenter, mouseleave, pointermove are excluded as they are too noisy
|
|
2280
|
+
const CAPTURE_EVENTS = ['click','dblclick','input','change','submit','focus','blur','keydown','keyup','mousedown','mouseup','wheel','contextmenu','pointerdown','pointerup'];
|
|
2281
|
+
for (const evt of CAPTURE_EVENTS) {
|
|
2282
|
+
container.addEventListener(evt, (e) => {
|
|
2283
|
+
// Extract raw event properties
|
|
2284
|
+
const rawEvent = {
|
|
2285
|
+
type: e.type,
|
|
2286
|
+
bubbles: e.bubbles,
|
|
2287
|
+
cancelable: e.cancelable,
|
|
2288
|
+
composed: e.composed,
|
|
2289
|
+
defaultPrevented: e.defaultPrevented,
|
|
2290
|
+
eventPhase: e.eventPhase,
|
|
2291
|
+
isTrusted: e.isTrusted,
|
|
2292
|
+
timeStamp: e.timeStamp,
|
|
2293
|
+
};
|
|
2294
|
+
// Mouse/Pointer event properties
|
|
2295
|
+
if ('clientX' in e) {
|
|
2296
|
+
rawEvent.clientX = e.clientX;
|
|
2297
|
+
rawEvent.clientY = e.clientY;
|
|
2298
|
+
rawEvent.screenX = e.screenX;
|
|
2299
|
+
rawEvent.screenY = e.screenY;
|
|
2300
|
+
rawEvent.pageX = e.pageX;
|
|
2301
|
+
rawEvent.pageY = e.pageY;
|
|
2302
|
+
rawEvent.offsetX = e.offsetX;
|
|
2303
|
+
rawEvent.offsetY = e.offsetY;
|
|
2304
|
+
rawEvent.button = e.button;
|
|
2305
|
+
rawEvent.buttons = e.buttons;
|
|
2306
|
+
rawEvent.altKey = e.altKey;
|
|
2307
|
+
rawEvent.ctrlKey = e.ctrlKey;
|
|
2308
|
+
rawEvent.metaKey = e.metaKey;
|
|
2309
|
+
rawEvent.shiftKey = e.shiftKey;
|
|
2310
|
+
}
|
|
2311
|
+
// Keyboard event properties
|
|
2312
|
+
if ('key' in e) {
|
|
2313
|
+
rawEvent.key = e.key;
|
|
2314
|
+
rawEvent.code = e.code;
|
|
2315
|
+
rawEvent.repeat = e.repeat;
|
|
2316
|
+
rawEvent.altKey = e.altKey;
|
|
2317
|
+
rawEvent.ctrlKey = e.ctrlKey;
|
|
2318
|
+
rawEvent.metaKey = e.metaKey;
|
|
2319
|
+
rawEvent.shiftKey = e.shiftKey;
|
|
2320
|
+
}
|
|
2321
|
+
// Input event properties
|
|
2322
|
+
if ('inputType' in e) {
|
|
2323
|
+
rawEvent.inputType = e.inputType;
|
|
2324
|
+
rawEvent.data = e.data;
|
|
2325
|
+
}
|
|
2326
|
+
// Wheel event properties
|
|
2327
|
+
if ('deltaX' in e) {
|
|
2328
|
+
rawEvent.deltaX = e.deltaX;
|
|
2329
|
+
rawEvent.deltaY = e.deltaY;
|
|
2330
|
+
rawEvent.deltaZ = e.deltaZ;
|
|
2331
|
+
rawEvent.deltaMode = e.deltaMode;
|
|
2332
|
+
}
|
|
2333
|
+
const payload = {
|
|
2334
|
+
name: evt,
|
|
2335
|
+
target: e.target?.tagName,
|
|
2336
|
+
timestamp: Date.now(),
|
|
2337
|
+
source: 'dom',
|
|
2338
|
+
rawEvent,
|
|
2339
|
+
variantName
|
|
2340
|
+
};
|
|
2341
|
+
if (e.target && 'value' in e.target) {
|
|
2342
|
+
payload.value = e.target.value;
|
|
2343
|
+
}
|
|
2344
|
+
window.parent.postMessage({ type: 'musea:event', payload }, '*');
|
|
2345
|
+
}, true);
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
// === Message handler for parent commands ===
|
|
2349
|
+
let measureActive = false;
|
|
2350
|
+
let measureOverlay = null;
|
|
2351
|
+
let measureLabel = null;
|
|
2352
|
+
|
|
2353
|
+
function toggleStyleById(id, enabled, css) {
|
|
2354
|
+
let el = document.getElementById(id);
|
|
2355
|
+
if (enabled) {
|
|
2356
|
+
if (!el) {
|
|
2357
|
+
el = document.createElement('style');
|
|
2358
|
+
el.id = id;
|
|
2359
|
+
el.textContent = css;
|
|
2360
|
+
document.head.appendChild(el);
|
|
2361
|
+
}
|
|
2362
|
+
} else {
|
|
2363
|
+
if (el) el.remove();
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
function createMeasureOverlay() {
|
|
2368
|
+
if (measureOverlay) return;
|
|
2369
|
+
measureOverlay = document.createElement('div');
|
|
2370
|
+
measureOverlay.id = 'musea-measure-overlay';
|
|
2371
|
+
measureOverlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:99999;';
|
|
2372
|
+
document.body.appendChild(measureOverlay);
|
|
2373
|
+
|
|
2374
|
+
measureLabel = document.createElement('div');
|
|
2375
|
+
measureLabel.className = 'musea-measure-label';
|
|
2376
|
+
measureLabel.style.cssText = 'position:fixed;background:#333;color:#fff;font-size:11px;padding:2px 6px;border-radius:3px;pointer-events:none;z-index:100000;display:none;';
|
|
2377
|
+
document.body.appendChild(measureLabel);
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
function removeMeasureOverlay() {
|
|
2381
|
+
if (measureOverlay) { measureOverlay.remove(); measureOverlay = null; }
|
|
2382
|
+
if (measureLabel) { measureLabel.remove(); measureLabel = null; }
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
function onMeasureMouseMove(e) {
|
|
2386
|
+
if (!measureActive || !measureOverlay) return;
|
|
2387
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
2388
|
+
if (!el || el === measureOverlay || el === measureLabel) return;
|
|
2389
|
+
|
|
2390
|
+
const rect = el.getBoundingClientRect();
|
|
2391
|
+
const cs = getComputedStyle(el);
|
|
2392
|
+
const mt = parseFloat(cs.marginTop) || 0;
|
|
2393
|
+
const mr = parseFloat(cs.marginRight) || 0;
|
|
2394
|
+
const mb = parseFloat(cs.marginBottom) || 0;
|
|
2395
|
+
const ml = parseFloat(cs.marginLeft) || 0;
|
|
2396
|
+
const bt = parseFloat(cs.borderTopWidth) || 0;
|
|
2397
|
+
const br = parseFloat(cs.borderRightWidth) || 0;
|
|
2398
|
+
const bb = parseFloat(cs.borderBottomWidth) || 0;
|
|
2399
|
+
const blw = parseFloat(cs.borderLeftWidth) || 0;
|
|
2400
|
+
const pt = parseFloat(cs.paddingTop) || 0;
|
|
2401
|
+
const pr = parseFloat(cs.paddingRight) || 0;
|
|
2402
|
+
const pb = parseFloat(cs.paddingBottom) || 0;
|
|
2403
|
+
const pl = parseFloat(cs.paddingLeft) || 0;
|
|
2404
|
+
|
|
2405
|
+
const cw = rect.width - blw - br - pl - pr;
|
|
2406
|
+
const ch = rect.height - bt - bb - pt - pb;
|
|
2407
|
+
|
|
2408
|
+
measureOverlay.innerHTML = ''
|
|
2409
|
+
// Margin
|
|
2410
|
+
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
2411
|
+
+ 'left:' + (rect.left - ml) + 'px;top:' + (rect.top - mt) + 'px;'
|
|
2412
|
+
+ 'width:' + (rect.width + ml + mr) + 'px;height:' + mt + 'px;"></div>'
|
|
2413
|
+
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
2414
|
+
+ 'left:' + (rect.left - ml) + 'px;top:' + (rect.bottom) + 'px;'
|
|
2415
|
+
+ 'width:' + (rect.width + ml + mr) + 'px;height:' + mb + 'px;"></div>'
|
|
2416
|
+
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
2417
|
+
+ 'left:' + (rect.left - ml) + 'px;top:' + rect.top + 'px;'
|
|
2418
|
+
+ 'width:' + ml + 'px;height:' + rect.height + 'px;"></div>'
|
|
2419
|
+
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
2420
|
+
+ 'left:' + rect.right + 'px;top:' + rect.top + 'px;'
|
|
2421
|
+
+ 'width:' + mr + 'px;height:' + rect.height + 'px;"></div>'
|
|
2422
|
+
// Border
|
|
2423
|
+
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
2424
|
+
+ 'left:' + rect.left + 'px;top:' + rect.top + 'px;'
|
|
2425
|
+
+ 'width:' + rect.width + 'px;height:' + bt + 'px;"></div>'
|
|
2426
|
+
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
2427
|
+
+ 'left:' + rect.left + 'px;top:' + (rect.bottom - bb) + 'px;'
|
|
2428
|
+
+ 'width:' + rect.width + 'px;height:' + bb + 'px;"></div>'
|
|
2429
|
+
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
2430
|
+
+ 'left:' + rect.left + 'px;top:' + (rect.top + bt) + 'px;'
|
|
2431
|
+
+ 'width:' + blw + 'px;height:' + (rect.height - bt - bb) + 'px;"></div>'
|
|
2432
|
+
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
2433
|
+
+ 'left:' + (rect.right - br) + 'px;top:' + (rect.top + bt) + 'px;'
|
|
2434
|
+
+ 'width:' + br + 'px;height:' + (rect.height - bt - bb) + 'px;"></div>'
|
|
2435
|
+
// Padding
|
|
2436
|
+
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
2437
|
+
+ 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt) + 'px;'
|
|
2438
|
+
+ 'width:' + (rect.width - blw - br) + 'px;height:' + pt + 'px;"></div>'
|
|
2439
|
+
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
2440
|
+
+ 'left:' + (rect.left + blw) + 'px;top:' + (rect.bottom - bb - pb) + 'px;'
|
|
2441
|
+
+ 'width:' + (rect.width - blw - br) + 'px;height:' + pb + 'px;"></div>'
|
|
2442
|
+
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
2443
|
+
+ 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt + pt) + 'px;'
|
|
2444
|
+
+ 'width:' + pl + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;"></div>'
|
|
2445
|
+
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
2446
|
+
+ 'left:' + (rect.right - br - pr) + 'px;top:' + (rect.top + bt + pt) + 'px;'
|
|
2447
|
+
+ 'width:' + pr + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;"></div>'
|
|
2448
|
+
// Content
|
|
2449
|
+
+ '<div style="position:fixed;background:rgba(100,149,237,0.3);'
|
|
2450
|
+
+ 'left:' + (rect.left + blw + pl) + 'px;top:' + (rect.top + bt + pt) + 'px;'
|
|
2451
|
+
+ 'width:' + cw + 'px;height:' + ch + 'px;"></div>';
|
|
2452
|
+
|
|
2453
|
+
// Label
|
|
2454
|
+
measureLabel.textContent = Math.round(rect.width) + ' x ' + Math.round(rect.height);
|
|
2455
|
+
measureLabel.style.display = 'block';
|
|
2456
|
+
measureLabel.style.left = (rect.right + 8) + 'px';
|
|
2457
|
+
measureLabel.style.top = rect.top + 'px';
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
window.addEventListener('message', (e) => {
|
|
2461
|
+
if (!e.data?.type?.startsWith('musea:')) return;
|
|
2462
|
+
const { type, payload } = e.data;
|
|
2463
|
+
switch (type) {
|
|
2464
|
+
case 'musea:set-background': {
|
|
2465
|
+
if (payload.pattern === 'checkerboard') {
|
|
2466
|
+
document.body.style.background = '';
|
|
2467
|
+
document.body.classList.add('musea-bg-checkerboard');
|
|
2468
|
+
} else {
|
|
2469
|
+
document.body.classList.remove('musea-bg-checkerboard');
|
|
2470
|
+
document.body.style.background = payload.color || '';
|
|
2471
|
+
}
|
|
2472
|
+
break;
|
|
2473
|
+
}
|
|
2474
|
+
case 'musea:toggle-outline': {
|
|
2475
|
+
toggleStyleById('musea-outline', payload.enabled,
|
|
2476
|
+
'* { outline: 1px solid rgba(255, 0, 0, 0.3) !important; }');
|
|
2477
|
+
break;
|
|
2478
|
+
}
|
|
2479
|
+
case 'musea:toggle-measure': {
|
|
2480
|
+
measureActive = payload.enabled;
|
|
2481
|
+
if (measureActive) {
|
|
2482
|
+
createMeasureOverlay();
|
|
2483
|
+
document.addEventListener('mousemove', onMeasureMouseMove);
|
|
2484
|
+
} else {
|
|
2485
|
+
document.removeEventListener('mousemove', onMeasureMouseMove);
|
|
2486
|
+
removeMeasureOverlay();
|
|
2487
|
+
}
|
|
2488
|
+
break;
|
|
2489
|
+
}
|
|
2490
|
+
case 'musea:set-props': {
|
|
2491
|
+
// Store props for remount - handled by preview module
|
|
2492
|
+
if (window.__museaSetProps) {
|
|
2493
|
+
window.__museaSetProps(payload.props || {});
|
|
2494
|
+
}
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
case 'musea:set-slots': {
|
|
2498
|
+
// Store slots for remount - handled by preview module
|
|
2499
|
+
if (window.__museaSetSlots) {
|
|
2500
|
+
window.__museaSetSlots(payload.slots || {});
|
|
2501
|
+
}
|
|
2502
|
+
break;
|
|
2503
|
+
}
|
|
2504
|
+
case 'musea:run-a11y': {
|
|
2505
|
+
// Run axe-core a11y test
|
|
2506
|
+
(async () => {
|
|
2507
|
+
try {
|
|
2508
|
+
// Dynamically load axe-core from CDN if not already loaded
|
|
2509
|
+
if (!window.axe) {
|
|
2510
|
+
const script = document.createElement('script');
|
|
2511
|
+
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.10.2/axe.min.js';
|
|
2512
|
+
script.crossOrigin = 'anonymous';
|
|
2513
|
+
await new Promise((resolve, reject) => {
|
|
2514
|
+
script.onload = resolve;
|
|
2515
|
+
script.onerror = reject;
|
|
2516
|
+
document.head.appendChild(script);
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
// Run axe-core on the .musea-variant container only (not the full document)
|
|
2520
|
+
const context = document.querySelector('.musea-variant') || document;
|
|
2521
|
+
const results = await window.axe.run(context, {
|
|
2522
|
+
// Run all rules without restrictions for comprehensive testing
|
|
2523
|
+
resultTypes: ['violations', 'incomplete', 'passes']
|
|
2524
|
+
});
|
|
2525
|
+
window.parent.postMessage({
|
|
2526
|
+
type: 'musea:a11y-result',
|
|
2527
|
+
payload: {
|
|
2528
|
+
violations: results.violations.map(v => ({
|
|
2529
|
+
id: v.id,
|
|
2530
|
+
impact: v.impact,
|
|
2531
|
+
description: v.description,
|
|
2532
|
+
helpUrl: v.helpUrl,
|
|
2533
|
+
nodes: v.nodes.map(n => ({
|
|
2534
|
+
html: n.html,
|
|
2535
|
+
target: n.target,
|
|
2536
|
+
failureSummary: n.failureSummary
|
|
2537
|
+
}))
|
|
2538
|
+
})),
|
|
2539
|
+
passes: results.passes.length,
|
|
2540
|
+
incomplete: results.incomplete.length
|
|
2541
|
+
}
|
|
2542
|
+
}, '*');
|
|
2543
|
+
} catch (err) {
|
|
2544
|
+
window.parent.postMessage({
|
|
2545
|
+
type: 'musea:a11y-result',
|
|
2546
|
+
payload: {
|
|
2547
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2548
|
+
violations: [],
|
|
2549
|
+
passes: 0,
|
|
2550
|
+
incomplete: 0
|
|
2551
|
+
}
|
|
2552
|
+
}, '*');
|
|
2553
|
+
}
|
|
2554
|
+
})();
|
|
2555
|
+
break;
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
// Notify parent that iframe is ready
|
|
2561
|
+
window.parent.postMessage({ type: 'musea:ready', payload: {} }, '*');
|
|
2562
|
+
}
|
|
2563
|
+
`;
|
|
2564
|
+
function generatePreviewModule(art, variantComponentName, variantName, cssImports = [], previewSetup = null) {
|
|
1324
2565
|
const artModuleId = `virtual:musea-art:${art.path}`;
|
|
1325
2566
|
const escapedVariantName = escapeTemplate(variantName);
|
|
2567
|
+
const cssImportStatements = cssImports.map((cssPath) => `import '${cssPath}';`).join("\n");
|
|
2568
|
+
const setupImport = previewSetup ? `import __museaPreviewSetup from '${previewSetup}';` : "";
|
|
2569
|
+
const setupCall = previewSetup ? "await __museaPreviewSetup(app);" : "";
|
|
1326
2570
|
return `
|
|
1327
|
-
|
|
2571
|
+
${cssImportStatements}
|
|
2572
|
+
${setupImport}
|
|
2573
|
+
import { createApp, reactive, h } from 'vue';
|
|
1328
2574
|
import * as artModule from '${artModuleId}';
|
|
1329
2575
|
|
|
1330
2576
|
const container = document.getElementById('app');
|
|
1331
2577
|
|
|
2578
|
+
${MUSEA_ADDONS_INIT_CODE}
|
|
2579
|
+
|
|
2580
|
+
let currentApp = null;
|
|
2581
|
+
const propsOverride = reactive({});
|
|
2582
|
+
const slotsOverride = reactive({ default: '' });
|
|
2583
|
+
|
|
2584
|
+
window.__museaSetProps = (props) => {
|
|
2585
|
+
// Clear old keys
|
|
2586
|
+
for (const key of Object.keys(propsOverride)) {
|
|
2587
|
+
delete propsOverride[key];
|
|
2588
|
+
}
|
|
2589
|
+
Object.assign(propsOverride, props);
|
|
2590
|
+
};
|
|
2591
|
+
|
|
2592
|
+
window.__museaSetSlots = (slots) => {
|
|
2593
|
+
for (const key of Object.keys(slotsOverride)) {
|
|
2594
|
+
delete slotsOverride[key];
|
|
2595
|
+
}
|
|
2596
|
+
Object.assign(slotsOverride, slots);
|
|
2597
|
+
};
|
|
2598
|
+
|
|
1332
2599
|
async function mount() {
|
|
1333
2600
|
try {
|
|
1334
2601
|
// Get the specific variant component
|
|
1335
2602
|
const VariantComponent = artModule['${variantComponentName}'];
|
|
2603
|
+
const RawComponent = artModule.__component__;
|
|
1336
2604
|
|
|
1337
2605
|
if (!VariantComponent) {
|
|
1338
2606
|
throw new Error('Variant component "${variantComponentName}" not found in art module');
|
|
@@ -1340,11 +2608,31 @@ async function mount() {
|
|
|
1340
2608
|
|
|
1341
2609
|
// Create and mount the app
|
|
1342
2610
|
const app = createApp(VariantComponent);
|
|
2611
|
+
${setupCall}
|
|
1343
2612
|
container.innerHTML = '';
|
|
1344
2613
|
container.className = 'musea-variant';
|
|
1345
2614
|
app.mount(container);
|
|
2615
|
+
currentApp = app;
|
|
1346
2616
|
|
|
1347
2617
|
console.log('[musea-preview] Mounted variant: ${escapedVariantName}');
|
|
2618
|
+
__museaInitAddons(container, '${escapedVariantName}');
|
|
2619
|
+
|
|
2620
|
+
// Override set-props to remount with raw component + props
|
|
2621
|
+
const TargetComponent = RawComponent || VariantComponent;
|
|
2622
|
+
window.__museaSetProps = (props) => {
|
|
2623
|
+
for (const key of Object.keys(propsOverride)) {
|
|
2624
|
+
delete propsOverride[key];
|
|
2625
|
+
}
|
|
2626
|
+
Object.assign(propsOverride, props);
|
|
2627
|
+
remountWithProps(TargetComponent);
|
|
2628
|
+
};
|
|
2629
|
+
window.__museaSetSlots = (slots) => {
|
|
2630
|
+
for (const key of Object.keys(slotsOverride)) {
|
|
2631
|
+
delete slotsOverride[key];
|
|
2632
|
+
}
|
|
2633
|
+
Object.assign(slotsOverride, slots);
|
|
2634
|
+
remountWithProps(TargetComponent);
|
|
2635
|
+
};
|
|
1348
2636
|
} catch (error) {
|
|
1349
2637
|
console.error('[musea-preview] Failed to mount:', error);
|
|
1350
2638
|
container.innerHTML = \`
|
|
@@ -1357,6 +2645,27 @@ async function mount() {
|
|
|
1357
2645
|
}
|
|
1358
2646
|
}
|
|
1359
2647
|
|
|
2648
|
+
async function remountWithProps(Component) {
|
|
2649
|
+
if (currentApp) {
|
|
2650
|
+
currentApp.unmount();
|
|
2651
|
+
}
|
|
2652
|
+
const app = createApp({
|
|
2653
|
+
setup() {
|
|
2654
|
+
return () => {
|
|
2655
|
+
const slotFns = {};
|
|
2656
|
+
for (const [name, content] of Object.entries(slotsOverride)) {
|
|
2657
|
+
if (content) slotFns[name] = () => h('span', { innerHTML: content });
|
|
2658
|
+
}
|
|
2659
|
+
return h(Component, { ...propsOverride }, slotFns);
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
});
|
|
2663
|
+
${setupCall}
|
|
2664
|
+
container.innerHTML = '';
|
|
2665
|
+
app.mount(container);
|
|
2666
|
+
currentApp = app;
|
|
2667
|
+
}
|
|
2668
|
+
|
|
1360
2669
|
mount();
|
|
1361
2670
|
`;
|
|
1362
2671
|
}
|
|
@@ -1364,32 +2673,121 @@ function generateManifestModule(artFiles) {
|
|
|
1364
2673
|
const arts = Array.from(artFiles.values());
|
|
1365
2674
|
return `export const arts = ${JSON.stringify(arts, null, 2)};`;
|
|
1366
2675
|
}
|
|
2676
|
+
/**
|
|
2677
|
+
* Extract the content of the first <script setup> block from a Vue SFC source.
|
|
2678
|
+
*/
|
|
2679
|
+
function extractScriptSetupContent(source) {
|
|
2680
|
+
const match = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
|
|
2681
|
+
return match?.[1]?.trim();
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* Parse script setup content into imports and setup body.
|
|
2685
|
+
* Returns the import lines, setup body lines, and all identifiers to expose.
|
|
2686
|
+
*/
|
|
2687
|
+
function parseScriptSetupForArt(content) {
|
|
2688
|
+
const lines = content.split("\n");
|
|
2689
|
+
const imports = [];
|
|
2690
|
+
const setupBody = [];
|
|
2691
|
+
const returnNames = new Set();
|
|
2692
|
+
for (const line of lines) {
|
|
2693
|
+
const trimmed = line.trim();
|
|
2694
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
2695
|
+
if (trimmed.startsWith("import ")) {
|
|
2696
|
+
imports.push(line);
|
|
2697
|
+
const defaultMatch = trimmed.match(/^import\s+(\w+)/);
|
|
2698
|
+
if (defaultMatch && defaultMatch[1] !== "type") returnNames.add(defaultMatch[1]);
|
|
2699
|
+
const namedMatch = trimmed.match(/\{([^}]+)\}/);
|
|
2700
|
+
if (namedMatch) for (const part of namedMatch[1].split(",")) {
|
|
2701
|
+
const name = part.trim().split(/\s+as\s+/).pop()?.trim();
|
|
2702
|
+
if (name && !name.startsWith("type ")) returnNames.add(name);
|
|
2703
|
+
}
|
|
2704
|
+
} else {
|
|
2705
|
+
setupBody.push(line);
|
|
2706
|
+
const constMatch = trimmed.match(/^(?:const|let|var)\s+(\w+)/);
|
|
2707
|
+
if (constMatch) returnNames.add(constMatch[1]);
|
|
2708
|
+
const destructMatch = trimmed.match(/^(?:const|let|var)\s+\{([^}]+)\}/);
|
|
2709
|
+
if (destructMatch) for (const part of destructMatch[1].split(",")) {
|
|
2710
|
+
const name = part.trim().split(/\s*:\s*/).shift()?.trim();
|
|
2711
|
+
if (name) returnNames.add(name);
|
|
2712
|
+
}
|
|
2713
|
+
const arrayMatch = trimmed.match(/^(?:const|let|var)\s+\[([^\]]+)\]/);
|
|
2714
|
+
if (arrayMatch) for (const part of arrayMatch[1].split(",")) {
|
|
2715
|
+
const name = part.trim();
|
|
2716
|
+
if (name && name !== "...") returnNames.add(name);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
returnNames.delete("type");
|
|
2721
|
+
return {
|
|
2722
|
+
imports,
|
|
2723
|
+
setupBody,
|
|
2724
|
+
returnNames: [...returnNames]
|
|
2725
|
+
};
|
|
2726
|
+
}
|
|
1367
2727
|
function generateArtModule(art, filePath) {
|
|
1368
|
-
|
|
1369
|
-
let
|
|
1370
|
-
if (
|
|
1371
|
-
|
|
1372
|
-
|
|
2728
|
+
let componentImportPath;
|
|
2729
|
+
let componentName;
|
|
2730
|
+
if (art.isInline && art.componentPath) {
|
|
2731
|
+
componentImportPath = art.componentPath;
|
|
2732
|
+
componentName = path.basename(art.componentPath, ".vue");
|
|
2733
|
+
} else if (art.metadata.component) {
|
|
2734
|
+
const comp = art.metadata.component;
|
|
2735
|
+
componentImportPath = path.isAbsolute(comp) ? comp : path.resolve(path.dirname(filePath), comp);
|
|
2736
|
+
componentName = path.basename(comp, ".vue");
|
|
1373
2737
|
}
|
|
1374
|
-
const
|
|
2738
|
+
const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
|
|
1375
2739
|
let code = `
|
|
1376
2740
|
// Auto-generated module for: ${path.basename(filePath)}
|
|
1377
2741
|
import { defineComponent, h } from 'vue';
|
|
1378
2742
|
`;
|
|
1379
|
-
if (
|
|
2743
|
+
if (scriptSetup) {
|
|
2744
|
+
const artDir = path.dirname(filePath);
|
|
2745
|
+
for (const imp of scriptSetup.imports) {
|
|
2746
|
+
const resolved = imp.replace(/from\s+(['"])(\.[^'"]+)\1/, (_match, quote, relPath) => {
|
|
2747
|
+
const absPath = path.resolve(artDir, relPath);
|
|
2748
|
+
return `from ${quote}${absPath}${quote}`;
|
|
2749
|
+
});
|
|
2750
|
+
code += `${resolved}\n`;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
if (componentImportPath && componentName) {
|
|
2754
|
+
const alreadyImported = scriptSetup?.imports.some((imp) => {
|
|
2755
|
+
if (imp.includes(`from '${componentImportPath}'`) || imp.includes(`from "${componentImportPath}"`)) return true;
|
|
2756
|
+
return new RegExp(`^import\\s+${componentName}[\\s,]`).test(imp.trim());
|
|
2757
|
+
});
|
|
2758
|
+
if (!alreadyImported) code += `import ${componentName} from '${componentImportPath}';\n`;
|
|
2759
|
+
code += `export const __component__ = ${componentName};\n`;
|
|
2760
|
+
}
|
|
1380
2761
|
code += `
|
|
1381
2762
|
export const metadata = ${JSON.stringify(art.metadata)};
|
|
1382
2763
|
export const variants = ${JSON.stringify(art.variants)};
|
|
1383
2764
|
`;
|
|
1384
2765
|
for (const variant of art.variants) {
|
|
1385
2766
|
const variantComponentName = toPascalCase(variant.name);
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
2767
|
+
let template = variant.template;
|
|
2768
|
+
if (componentName) template = template.replace(/<Self/g, `<${componentName}`).replace(/<\/Self>/g, `</${componentName}>`);
|
|
2769
|
+
const escapedTemplate = template.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
2770
|
+
const fullTemplate = `<div data-variant="${variant.name}">${escapedTemplate}</div>`;
|
|
2771
|
+
const componentNames = new Set();
|
|
2772
|
+
if (componentName) componentNames.add(componentName);
|
|
2773
|
+
if (scriptSetup) {
|
|
2774
|
+
for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.add(name);
|
|
2775
|
+
}
|
|
2776
|
+
const components = componentNames.size > 0 ? ` components: { ${[...componentNames].join(", ")} },\n` : "";
|
|
2777
|
+
if (scriptSetup && scriptSetup.setupBody.length > 0) code += `
|
|
2778
|
+
export const ${variantComponentName} = defineComponent({
|
|
1390
2779
|
name: '${variantComponentName}',
|
|
1391
|
-
|
|
2780
|
+
${components} setup() {
|
|
2781
|
+
${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
|
|
2782
|
+
return { ${scriptSetup.returnNames.join(", ")} };
|
|
2783
|
+
},
|
|
1392
2784
|
template: \`${fullTemplate}\`,
|
|
2785
|
+
});
|
|
2786
|
+
`;
|
|
2787
|
+
else if (componentName) code += `
|
|
2788
|
+
export const ${variantComponentName} = {
|
|
2789
|
+
name: '${variantComponentName}',
|
|
2790
|
+
${components} template: \`${fullTemplate}\`,
|
|
1393
2791
|
};
|
|
1394
2792
|
`;
|
|
1395
2793
|
else code += `
|
|
@@ -1425,14 +2823,63 @@ function toPascalCase(str) {
|
|
|
1425
2823
|
function escapeTemplate(str) {
|
|
1426
2824
|
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
1427
2825
|
}
|
|
1428
|
-
function
|
|
1429
|
-
const
|
|
2826
|
+
function generatePreviewModuleWithProps(art, variantComponentName, variantName, propsOverride, cssImports = [], previewSetup = null) {
|
|
2827
|
+
const artModuleId = `virtual:musea-art:${art.path}`;
|
|
2828
|
+
const escapedVariantName = escapeTemplate(variantName);
|
|
2829
|
+
const propsJson = JSON.stringify(propsOverride);
|
|
2830
|
+
const cssImportStatements = cssImports.map((cssPath) => `import '${cssPath}';`).join("\n");
|
|
2831
|
+
const setupImport = previewSetup ? `import __museaPreviewSetup from '${previewSetup}';` : "";
|
|
2832
|
+
const setupCall = previewSetup ? "await __museaPreviewSetup(app);" : "";
|
|
2833
|
+
return `
|
|
2834
|
+
${cssImportStatements}
|
|
2835
|
+
${setupImport}
|
|
2836
|
+
import { createApp, h } from 'vue';
|
|
2837
|
+
import * as artModule from '${artModuleId}';
|
|
2838
|
+
|
|
2839
|
+
const container = document.getElementById('app');
|
|
2840
|
+
const propsOverride = ${propsJson};
|
|
2841
|
+
|
|
2842
|
+
${MUSEA_ADDONS_INIT_CODE}
|
|
2843
|
+
|
|
2844
|
+
async function mount() {
|
|
2845
|
+
try {
|
|
2846
|
+
const VariantComponent = artModule['${variantComponentName}'];
|
|
2847
|
+
if (!VariantComponent) {
|
|
2848
|
+
throw new Error('Variant component "${variantComponentName}" not found');
|
|
2849
|
+
}
|
|
2850
|
+
|
|
2851
|
+
const WrappedComponent = {
|
|
2852
|
+
render() {
|
|
2853
|
+
return h(VariantComponent, propsOverride);
|
|
2854
|
+
}
|
|
2855
|
+
};
|
|
2856
|
+
|
|
2857
|
+
const app = createApp(WrappedComponent);
|
|
2858
|
+
${setupCall}
|
|
2859
|
+
container.innerHTML = '';
|
|
2860
|
+
container.className = 'musea-variant';
|
|
2861
|
+
app.mount(container);
|
|
2862
|
+
console.log('[musea-preview] Mounted variant: ${escapedVariantName} with props override');
|
|
2863
|
+
__museaInitAddons(container, '${escapedVariantName}');
|
|
2864
|
+
} catch (error) {
|
|
2865
|
+
console.error('[musea-preview] Failed to mount:', error);
|
|
2866
|
+
container.innerHTML = '<div class="musea-error"><div class="musea-error-title">Failed to render</div><div>' + error.message + '</div></div>';
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
mount();
|
|
2871
|
+
`;
|
|
2872
|
+
}
|
|
2873
|
+
function generatePreviewHtml(art, variant, _basePath, viteBase) {
|
|
2874
|
+
const previewModuleUrl = `${_basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;
|
|
2875
|
+
const base = (viteBase || "/").replace(/\/$/, "");
|
|
1430
2876
|
return `<!DOCTYPE html>
|
|
1431
2877
|
<html lang="en">
|
|
1432
2878
|
<head>
|
|
1433
2879
|
<meta charset="UTF-8">
|
|
1434
2880
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1435
2881
|
<title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>
|
|
2882
|
+
<script type="module" src="${base}/@vite/client"></script>
|
|
1436
2883
|
<style>
|
|
1437
2884
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1438
2885
|
html, body {
|
|
@@ -1444,10 +2891,6 @@ function generatePreviewHtml(art, variant, basePath) {
|
|
|
1444
2891
|
background: #ffffff;
|
|
1445
2892
|
}
|
|
1446
2893
|
.musea-variant {
|
|
1447
|
-
padding: 1.5rem;
|
|
1448
|
-
display: flex;
|
|
1449
|
-
align-items: center;
|
|
1450
|
-
justify-content: center;
|
|
1451
2894
|
min-height: 100vh;
|
|
1452
2895
|
}
|
|
1453
2896
|
.musea-error {
|
|
@@ -1489,6 +2932,29 @@ function generatePreviewHtml(art, variant, basePath) {
|
|
|
1489
2932
|
animation: spin 0.8s linear infinite;
|
|
1490
2933
|
}
|
|
1491
2934
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
2935
|
+
|
|
2936
|
+
/* Musea Addons: Checkerboard background for transparent mode */
|
|
2937
|
+
.musea-bg-checkerboard {
|
|
2938
|
+
background-image:
|
|
2939
|
+
linear-gradient(45deg, #ccc 25%, transparent 25%),
|
|
2940
|
+
linear-gradient(-45deg, #ccc 25%, transparent 25%),
|
|
2941
|
+
linear-gradient(45deg, transparent 75%, #ccc 75%),
|
|
2942
|
+
linear-gradient(-45deg, transparent 75%, #ccc 75%) !important;
|
|
2943
|
+
background-size: 20px 20px !important;
|
|
2944
|
+
background-position: 0 0, 0 10px, 10px -10px, -10px 0 !important;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
/* Musea Addons: Measure label */
|
|
2948
|
+
.musea-measure-label {
|
|
2949
|
+
position: fixed;
|
|
2950
|
+
background: #333;
|
|
2951
|
+
color: #fff;
|
|
2952
|
+
font-size: 11px;
|
|
2953
|
+
padding: 2px 6px;
|
|
2954
|
+
border-radius: 3px;
|
|
2955
|
+
pointer-events: none;
|
|
2956
|
+
z-index: 100000;
|
|
2957
|
+
}
|
|
1492
2958
|
</style>
|
|
1493
2959
|
</head>
|
|
1494
2960
|
<body>
|
|
@@ -1508,5 +2974,5 @@ function escapeHtml(str) {
|
|
|
1508
2974
|
var src_default = musea;
|
|
1509
2975
|
|
|
1510
2976
|
//#endregion
|
|
1511
|
-
export { MuseaVrtRunner, src_default as default, generateTokensHtml, generateTokensMarkdown, generateVrtJsonReport, generateVrtReport, musea, parseTokens, processStyleDictionary };
|
|
2977
|
+
export { MuseaA11yRunner, MuseaVrtRunner, buildTokenMap, src_default as default, generateArtFile, generateTokensHtml, generateTokensMarkdown, generateVrtJsonReport, generateVrtReport, musea, parseTokens, processStyleDictionary, resolveReferences, scanTokenUsage, writeArtFile };
|
|
1512
2978
|
//# sourceMappingURL=index.js.map
|