@vizejs/vite-plugin-musea 0.49.0 → 0.58.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -21
- package/dist/a11y/index.d.mts +0 -2
- package/dist/a11y/index.mjs +0 -2
- package/dist/a11y-DNCg2qCB.mjs +0 -318
- package/dist/a11y-DNCg2qCB.mjs.map +0 -1
- package/dist/autogen/index.d.mts +0 -66
- package/dist/autogen/index.d.mts.map +0 -1
- package/dist/autogen/index.mjs +0 -2
- package/dist/autogen-3-y1d0ou.mjs +0 -213
- package/dist/autogen-3-y1d0ou.mjs.map +0 -1
- package/dist/cli/index.d.mts +0 -40
- package/dist/cli/index.d.mts.map +0 -1
- package/dist/cli/index.mjs +0 -407
- package/dist/cli/index.mjs.map +0 -1
- package/dist/gallery/assets/abap-DVwoIrM0.js +0 -1
- package/dist/gallery/assets/apex-DnfrpC_v.js +0 -1
- package/dist/gallery/assets/azcli-CE6n6ErR.js +0 -1
- package/dist/gallery/assets/bat-ainFW1qj.js +0 -1
- package/dist/gallery/assets/bicep-Lzdk7NqX.js +0 -2
- package/dist/gallery/assets/cameligo-BjyZ5cgY.js +0 -1
- package/dist/gallery/assets/clojure-B-6owjux.js +0 -1
- package/dist/gallery/assets/codicon-DCmgc-ay.ttf +0 -0
- package/dist/gallery/assets/coffee-npIvPqmH.js +0 -1
- package/dist/gallery/assets/cpp--A8GPZYM.js +0 -1
- package/dist/gallery/assets/csharp-Daa9qY-p.js +0 -1
- package/dist/gallery/assets/csp-N0NfYrCc.js +0 -1
- package/dist/gallery/assets/css-CUssoHYv.js +0 -3
- package/dist/gallery/assets/css.worker-Cbw1kvi8.js +0 -88
- package/dist/gallery/assets/cssMode-cT8muvO_.js +0 -4
- package/dist/gallery/assets/cypher-CmQpMFeE.js +0 -1
- package/dist/gallery/assets/dart-DOjZJNmR.js +0 -1
- package/dist/gallery/assets/dockerfile-CxIfCbxU.js +0 -1
- package/dist/gallery/assets/ecl-AnA3JP56.js +0 -1
- package/dist/gallery/assets/editor-B55U_qvj.css +0 -1
- package/dist/gallery/assets/editor-F8AxQWwE.css +0 -1
- package/dist/gallery/assets/editor.api-aFfQJDkw.js +0 -644
- package/dist/gallery/assets/editor.main-DMqE48tW.js +0 -63
- package/dist/gallery/assets/editor.worker-Cs7HTPcl.js +0 -12
- package/dist/gallery/assets/elixir-D-N3eh22.js +0 -1
- package/dist/gallery/assets/flow9-BR6FT9qg.js +0 -1
- package/dist/gallery/assets/freemarker2-B6IBKB9r.js +0 -3
- package/dist/gallery/assets/fsharp-DR0IQ95q.js +0 -1
- package/dist/gallery/assets/go-B27OpVON.js +0 -1
- package/dist/gallery/assets/graphql-Cw7HtomI.js +0 -1
- package/dist/gallery/assets/handlebars-zNX2LtzG.js +0 -1
- package/dist/gallery/assets/hcl-CDDd0gYG.js +0 -1
- package/dist/gallery/assets/html-BKYZbazO.js +0 -1
- package/dist/gallery/assets/html.worker-CYmk49z4.js +0 -495
- package/dist/gallery/assets/htmlMode-Bl1BwXnQ.js +0 -4
- package/dist/gallery/assets/index-9OXG8DGI.css +0 -1
- package/dist/gallery/assets/index-Dltd3znx.js +0 -63
- package/dist/gallery/assets/ini-ChiSjCUM.js +0 -1
- package/dist/gallery/assets/java-CKVuuvX6.js +0 -1
- package/dist/gallery/assets/javascript-DwBI_Z0J.js +0 -1
- package/dist/gallery/assets/json.worker-Ci2wf3HT.js +0 -51
- package/dist/gallery/assets/jsonMode-CBK55pb7.js +0 -10
- package/dist/gallery/assets/julia-BcKGx43g.js +0 -1
- package/dist/gallery/assets/kotlin-C7EpOAJu.js +0 -1
- package/dist/gallery/assets/less-BFpYPxgE.js +0 -2
- package/dist/gallery/assets/lexon-DDPF3See.js +0 -1
- package/dist/gallery/assets/liquid-D87ggETD.js +0 -1
- package/dist/gallery/assets/lua-CmzM4S9z.js +0 -1
- package/dist/gallery/assets/m3-C75GLUav.js +0 -1
- package/dist/gallery/assets/markdown-B6XL0Y9j.js +0 -1
- package/dist/gallery/assets/mdx-CBs_aoHd.js +0 -1
- package/dist/gallery/assets/mips-BG4Fy7Bl.js +0 -1
- package/dist/gallery/assets/monaco.contribution-BHYQJQ-0.js +0 -2
- package/dist/gallery/assets/msdax-H0aqYz0U.js +0 -1
- package/dist/gallery/assets/mysql-CDbOhBhf.js +0 -1
- package/dist/gallery/assets/objective-c-DKE6-VEf.js +0 -1
- package/dist/gallery/assets/pascal-DBuqflGM.js +0 -1
- package/dist/gallery/assets/pascaligo-BVtulzHb.js +0 -1
- package/dist/gallery/assets/perl-xkTv78ng.js +0 -1
- package/dist/gallery/assets/pgsql-Cxti3J5E.js +0 -1
- package/dist/gallery/assets/php-Bh5BD3dg.js +0 -1
- package/dist/gallery/assets/pla-DSsYzlXV.js +0 -1
- package/dist/gallery/assets/postiats-De0qivlp.js +0 -1
- package/dist/gallery/assets/powerquery-KGKq89F-.js +0 -1
- package/dist/gallery/assets/powershell-Djwhihrv.js +0 -1
- package/dist/gallery/assets/protobuf-Jbp01qUU.js +0 -2
- package/dist/gallery/assets/pug-BntfJCN7.js +0 -1
- package/dist/gallery/assets/python-CWI5d6bd.js +0 -1
- package/dist/gallery/assets/qsharp-CHH1r_aq.js +0 -1
- package/dist/gallery/assets/r-BbeUcBN9.js +0 -1
- package/dist/gallery/assets/razor-CPcSAg2x.js +0 -1
- package/dist/gallery/assets/redis-DR9m_VtD.js +0 -1
- package/dist/gallery/assets/redshift-D97Qa-FW.js +0 -1
- package/dist/gallery/assets/restructuredtext-DQ1MtboI.js +0 -1
- package/dist/gallery/assets/ruby-ByLGeogt.js +0 -1
- package/dist/gallery/assets/rust-CIqtS9ON.js +0 -1
- package/dist/gallery/assets/sb-ByVTEZ1d.js +0 -1
- package/dist/gallery/assets/scala-DvkPypTh.js +0 -1
- package/dist/gallery/assets/scheme-CQy1Ya2H.js +0 -1
- package/dist/gallery/assets/scss-DLIO8qmP.js +0 -3
- package/dist/gallery/assets/shell-BZaILY8J.js +0 -1
- package/dist/gallery/assets/solidity-D80FpOWz.js +0 -1
- package/dist/gallery/assets/sophia-DXh1T4eB.js +0 -1
- package/dist/gallery/assets/sparql-DHSgmKlJ.js +0 -1
- package/dist/gallery/assets/sql-9GboOSCN.js +0 -1
- package/dist/gallery/assets/st--m1Z2h3c.js +0 -1
- package/dist/gallery/assets/swift-DMo7Bf1r.js +0 -1
- package/dist/gallery/assets/systemverilog-D6kP5wsA.js +0 -1
- package/dist/gallery/assets/tcl-HAhMyY2Y.js +0 -1
- package/dist/gallery/assets/ts.worker-lmYMVzBW.js +0 -51339
- package/dist/gallery/assets/tsMode-1ZmI8w72.js +0 -11
- package/dist/gallery/assets/twig-RNzllx71.js +0 -1
- package/dist/gallery/assets/typescript-CVZCJg8D.js +0 -1
- package/dist/gallery/assets/typespec-DeyXqKVJ.js +0 -1
- package/dist/gallery/assets/vb-BfpeX2r9.js +0 -1
- package/dist/gallery/assets/wgsl-B52428dy.js +0 -298
- package/dist/gallery/assets/xml-B_rQCZJa.js +0 -1
- package/dist/gallery/assets/yaml-gB0iHXGe.js +0 -1
- package/dist/gallery/index.html +0 -19
- package/dist/index-BWuuTDDw.d.mts +0 -151
- package/dist/index-BWuuTDDw.d.mts.map +0 -1
- package/dist/index.d.mts +0 -253
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -2700
- package/dist/index.mjs.map +0 -1
- package/dist/vrt-B4uxOrnN.d.mts +0 -285
- package/dist/vrt-B4uxOrnN.d.mts.map +0 -1
- package/dist/vrt-CjFf5GR0.mjs +0 -767
- package/dist/vrt-CjFf5GR0.mjs.map +0 -1
- package/dist/vrt.d.mts +0 -2
- package/dist/vrt.mjs +0 -2
package/dist/index.mjs
DELETED
|
@@ -1,2700 +0,0 @@
|
|
|
1
|
-
import { i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport } from "./vrt-CjFf5GR0.mjs";
|
|
2
|
-
import { t as MuseaA11yRunner } from "./a11y-DNCg2qCB.mjs";
|
|
3
|
-
import { n as writeArtFile, t as generateArtFile } from "./autogen-3-y1d0ou.mjs";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import fs from "node:fs";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import { vizeConfigStore } from "@vizejs/vite-plugin";
|
|
8
|
-
//#region src/native-loader.ts
|
|
9
|
-
/**
|
|
10
|
-
* Native binding loader for @vizejs/native.
|
|
11
|
-
*
|
|
12
|
-
* Provides lazy-loading of the native Rust-based parser and a JS fallback
|
|
13
|
-
* for SFC analysis when the native `analyzeSfc` function is unavailable.
|
|
14
|
-
*/
|
|
15
|
-
let native = null;
|
|
16
|
-
function loadNative() {
|
|
17
|
-
if (native) return native;
|
|
18
|
-
const require = createRequire(import.meta.url);
|
|
19
|
-
try {
|
|
20
|
-
native = require("@vizejs/native");
|
|
21
|
-
return native;
|
|
22
|
-
} catch (e) {
|
|
23
|
-
throw new Error(`Failed to load @vizejs/native. Make sure it's installed and built:\n${String(e)}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* JS-based fallback for SFC analysis when native `analyzeSfc` is not available.
|
|
28
|
-
* Uses regex parsing to extract props and emits from Vue SFC source.
|
|
29
|
-
*/
|
|
30
|
-
function analyzeSfcFallback(source, _options) {
|
|
31
|
-
try {
|
|
32
|
-
const props = [];
|
|
33
|
-
const emits = [];
|
|
34
|
-
const scriptSetupMatch = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
|
|
35
|
-
if (!scriptSetupMatch) {
|
|
36
|
-
if (!source.match(/<script[^>]*>([\s\S]*?)<\/script>/)) return {
|
|
37
|
-
props: [],
|
|
38
|
-
emits: []
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
const scriptContent = scriptSetupMatch?.[1] || "";
|
|
42
|
-
const propsMatch = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>\s*\(/);
|
|
43
|
-
const propsMatch2 = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}>/);
|
|
44
|
-
const propsBody = propsMatch?.[1] || propsMatch2?.[1];
|
|
45
|
-
if (propsBody) {
|
|
46
|
-
const lines = propsBody.split("\n");
|
|
47
|
-
let i = 0;
|
|
48
|
-
while (i < lines.length) {
|
|
49
|
-
const line = lines[i].trim();
|
|
50
|
-
if (line.startsWith("/**") || line.startsWith("*") || line.startsWith("*/")) {
|
|
51
|
-
i++;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const propMatch = line.match(/^(\w+)(\?)?:\s*(.+?)(?:;?\s*)$/);
|
|
55
|
-
if (propMatch) {
|
|
56
|
-
const name = propMatch[1];
|
|
57
|
-
const optional = !!propMatch[2];
|
|
58
|
-
let type = propMatch[3].replace(/;$/, "").trim();
|
|
59
|
-
const defaultPattern = new RegExp(`\\b${name}\\s*=\\s*([^,}\\n]+)`);
|
|
60
|
-
const defaultMatch = scriptContent.match(defaultPattern);
|
|
61
|
-
const defaultValue = defaultMatch ? defaultMatch[1].trim() : void 0;
|
|
62
|
-
props.push({
|
|
63
|
-
name,
|
|
64
|
-
type,
|
|
65
|
-
required: !optional && defaultValue === void 0,
|
|
66
|
-
...defaultValue !== void 0 ? { default_value: defaultValue } : {}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
i++;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const emitsMatch = scriptContent.match(/defineEmits\s*<\s*\{([\s\S]*?)\}>/);
|
|
73
|
-
if (emitsMatch) {
|
|
74
|
-
const emitsBody = emitsMatch[1];
|
|
75
|
-
const emitRegex = /(\w+)\s*:/g;
|
|
76
|
-
let match;
|
|
77
|
-
while ((match = emitRegex.exec(emitsBody)) !== null) emits.push(match[1]);
|
|
78
|
-
}
|
|
79
|
-
return {
|
|
80
|
-
props,
|
|
81
|
-
emits
|
|
82
|
-
};
|
|
83
|
-
} catch {
|
|
84
|
-
return {
|
|
85
|
-
props: [],
|
|
86
|
-
emits: []
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
//#endregion
|
|
91
|
-
//#region src/utils.ts
|
|
92
|
-
/**
|
|
93
|
-
* Shared utility functions for the Musea Vite plugin.
|
|
94
|
-
*/
|
|
95
|
-
function shouldProcess(file, include, exclude, root) {
|
|
96
|
-
const relative = path.relative(root, file);
|
|
97
|
-
for (const pattern of exclude) if (matchGlob(relative, pattern)) return false;
|
|
98
|
-
for (const pattern of include) if (matchGlob(relative, pattern)) return true;
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
function matchGlob(filepath, pattern) {
|
|
102
|
-
const PLACEHOLDER = "<<GLOBSTAR>>";
|
|
103
|
-
const regex = pattern.replaceAll("**", PLACEHOLDER).replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replaceAll(PLACEHOLDER, ".*");
|
|
104
|
-
return new RegExp(`^${regex}$`).test(filepath);
|
|
105
|
-
}
|
|
106
|
-
async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
|
|
107
|
-
const files = [];
|
|
108
|
-
async function scan(dir) {
|
|
109
|
-
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
110
|
-
for (const entry of entries) {
|
|
111
|
-
const fullPath = path.join(dir, entry.name);
|
|
112
|
-
const relative = path.relative(root, fullPath);
|
|
113
|
-
let excluded = false;
|
|
114
|
-
for (const pattern of exclude) if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {
|
|
115
|
-
excluded = true;
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
if (excluded) continue;
|
|
119
|
-
if (entry.isDirectory()) await scan(fullPath);
|
|
120
|
-
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
121
|
-
for (const pattern of include) if (matchGlob(relative, pattern)) {
|
|
122
|
-
files.push(fullPath);
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
} else if (scanInlineArt && entry.isFile() && entry.name.endsWith(".vue") && !entry.name.endsWith(".art.vue")) {
|
|
126
|
-
if ((await fs.promises.readFile(fullPath, "utf-8")).includes("<art")) files.push(fullPath);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
await scan(root);
|
|
131
|
-
return files;
|
|
132
|
-
}
|
|
133
|
-
async function generateStorybookFiles(artFiles, root, outDir) {
|
|
134
|
-
const binding = loadNative();
|
|
135
|
-
const outputDir = path.resolve(root, outDir);
|
|
136
|
-
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
137
|
-
for (const [filePath, _art] of artFiles) try {
|
|
138
|
-
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
139
|
-
const csf = binding.artToCsf(source, { filename: filePath });
|
|
140
|
-
const outputPath = path.join(outputDir, csf.filename);
|
|
141
|
-
await fs.promises.writeFile(outputPath, csf.code, "utf-8");
|
|
142
|
-
console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);
|
|
143
|
-
} catch (e) {
|
|
144
|
-
console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function toPascalCase(str) {
|
|
148
|
-
return str.split(/[\s\-_]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
149
|
-
}
|
|
150
|
-
function escapeTemplate(str) {
|
|
151
|
-
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
152
|
-
}
|
|
153
|
-
function escapeHtml(str) {
|
|
154
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Build the theme config object from plugin options for runtime injection.
|
|
158
|
-
*/
|
|
159
|
-
function buildThemeConfig(theme) {
|
|
160
|
-
if (!theme) return void 0;
|
|
161
|
-
if (typeof theme === "string") return { default: theme };
|
|
162
|
-
const themes = Array.isArray(theme) ? theme : [theme];
|
|
163
|
-
const custom = {};
|
|
164
|
-
for (const t of themes) custom[t.name] = {
|
|
165
|
-
base: t.base,
|
|
166
|
-
colors: t.colors
|
|
167
|
-
};
|
|
168
|
-
return {
|
|
169
|
-
default: themes[0].name,
|
|
170
|
-
custom
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
//#endregion
|
|
174
|
-
//#region src/art-module.ts
|
|
175
|
-
/**
|
|
176
|
-
* Art module generation for Musea.
|
|
177
|
-
*
|
|
178
|
-
* Generates the virtual ES modules that represent parsed `.art.vue` files,
|
|
179
|
-
* including variant component definitions and script setup handling.
|
|
180
|
-
*/
|
|
181
|
-
/**
|
|
182
|
-
* Extract the content of the first <script setup> block from a Vue SFC source.
|
|
183
|
-
*/
|
|
184
|
-
function extractScriptSetupContent(source) {
|
|
185
|
-
return source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
|
|
186
|
-
}
|
|
187
|
-
/**
|
|
188
|
-
* Parse script setup content into imports and setup body.
|
|
189
|
-
* Returns the import lines, setup body lines, and all identifiers to expose.
|
|
190
|
-
*/
|
|
191
|
-
function parseScriptSetupForArt(content) {
|
|
192
|
-
const lines = content.split("\n");
|
|
193
|
-
const imports = [];
|
|
194
|
-
const setupBody = [];
|
|
195
|
-
const returnNames = /* @__PURE__ */ new Set();
|
|
196
|
-
for (const line of lines) {
|
|
197
|
-
const trimmed = line.trim();
|
|
198
|
-
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
199
|
-
if (trimmed.startsWith("import ")) {
|
|
200
|
-
imports.push(line);
|
|
201
|
-
const defaultMatch = trimmed.match(/^import\s+(\w+)/);
|
|
202
|
-
if (defaultMatch && defaultMatch[1] !== "type") returnNames.add(defaultMatch[1]);
|
|
203
|
-
const namedMatch = trimmed.match(/\{([^}]+)\}/);
|
|
204
|
-
if (namedMatch) for (const part of namedMatch[1].split(",")) {
|
|
205
|
-
const name = part.trim().split(/\s+as\s+/).pop()?.trim();
|
|
206
|
-
if (name && !name.startsWith("type ")) returnNames.add(name);
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
setupBody.push(line);
|
|
210
|
-
const constMatch = trimmed.match(/^(?:const|let|var)\s+(\w+)/);
|
|
211
|
-
if (constMatch) returnNames.add(constMatch[1]);
|
|
212
|
-
const destructMatch = trimmed.match(/^(?:const|let|var)\s+\{([^}]+)\}/);
|
|
213
|
-
if (destructMatch) for (const part of destructMatch[1].split(",")) {
|
|
214
|
-
const name = part.trim().split(/\s*:\s*/).shift()?.trim();
|
|
215
|
-
if (name) returnNames.add(name);
|
|
216
|
-
}
|
|
217
|
-
const arrayMatch = trimmed.match(/^(?:const|let|var)\s+\[([^\]]+)\]/);
|
|
218
|
-
if (arrayMatch) for (const part of arrayMatch[1].split(",")) {
|
|
219
|
-
const name = part.trim();
|
|
220
|
-
if (name && name !== "...") returnNames.add(name);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
returnNames.delete("type");
|
|
225
|
-
return {
|
|
226
|
-
imports,
|
|
227
|
-
setupBody,
|
|
228
|
-
returnNames: [...returnNames]
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
function generateArtModule(art, filePath) {
|
|
232
|
-
let componentImportPath;
|
|
233
|
-
let componentName;
|
|
234
|
-
if (art.isInline && art.componentPath) {
|
|
235
|
-
componentImportPath = art.componentPath;
|
|
236
|
-
componentName = path.basename(art.componentPath, ".vue");
|
|
237
|
-
} else if (art.metadata.component) {
|
|
238
|
-
const comp = art.metadata.component;
|
|
239
|
-
componentImportPath = path.isAbsolute(comp) ? comp : path.resolve(path.dirname(filePath), comp);
|
|
240
|
-
componentName = path.basename(comp, ".vue");
|
|
241
|
-
}
|
|
242
|
-
const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
|
|
243
|
-
let code = `
|
|
244
|
-
// Auto-generated module for: ${path.basename(filePath)}
|
|
245
|
-
import { defineComponent, h } from 'vue';
|
|
246
|
-
`;
|
|
247
|
-
if (scriptSetup) {
|
|
248
|
-
const artDir = path.dirname(filePath);
|
|
249
|
-
for (const imp of scriptSetup.imports) {
|
|
250
|
-
const resolved = imp.replace(/from\s+(['"])(\.[^'"]+)\1/, (_match, quote, relPath) => {
|
|
251
|
-
return `from ${quote}${path.resolve(artDir, relPath)}${quote}`;
|
|
252
|
-
});
|
|
253
|
-
code += `${resolved}\n`;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (componentImportPath && componentName) {
|
|
257
|
-
if (!scriptSetup?.imports.some((imp) => {
|
|
258
|
-
if (imp.includes(`from '${componentImportPath}'`) || imp.includes(`from "${componentImportPath}"`)) return true;
|
|
259
|
-
return new RegExp(`^import\\s+${componentName}[\\s,]`).test(imp.trim());
|
|
260
|
-
})) code += `import ${componentName} from '${componentImportPath}';\n`;
|
|
261
|
-
code += `export const __component__ = ${componentName};\n`;
|
|
262
|
-
}
|
|
263
|
-
code += `
|
|
264
|
-
export const metadata = ${JSON.stringify(art.metadata)};
|
|
265
|
-
export const variants = ${JSON.stringify(art.variants)};
|
|
266
|
-
`;
|
|
267
|
-
for (const variant of art.variants) {
|
|
268
|
-
const variantComponentName = toPascalCase(variant.name);
|
|
269
|
-
let template = variant.template;
|
|
270
|
-
if (componentName) template = template.replace(/<Self/g, `<${componentName}`).replace(/<\/Self>/g, `</${componentName}>`);
|
|
271
|
-
const escapedTemplate = template.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
272
|
-
const fullTemplate = `<div data-variant="${variant.name}">${escapedTemplate}</div>`;
|
|
273
|
-
const componentNames = /* @__PURE__ */ new Set();
|
|
274
|
-
if (componentName) componentNames.add(componentName);
|
|
275
|
-
if (scriptSetup) {
|
|
276
|
-
for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.add(name);
|
|
277
|
-
}
|
|
278
|
-
const components = componentNames.size > 0 ? ` components: { ${[...componentNames].join(", ")} },\n` : "";
|
|
279
|
-
if (scriptSetup && scriptSetup.setupBody.length > 0) code += `
|
|
280
|
-
export const ${variantComponentName} = defineComponent({
|
|
281
|
-
name: '${variantComponentName}',
|
|
282
|
-
${components} setup() {
|
|
283
|
-
${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
|
|
284
|
-
return { ${scriptSetup.returnNames.join(", ")} };
|
|
285
|
-
},
|
|
286
|
-
template: \`${fullTemplate}\`,
|
|
287
|
-
});
|
|
288
|
-
`;
|
|
289
|
-
else if (componentName) code += `
|
|
290
|
-
export const ${variantComponentName} = {
|
|
291
|
-
name: '${variantComponentName}',
|
|
292
|
-
${components} template: \`${fullTemplate}\`,
|
|
293
|
-
};
|
|
294
|
-
`;
|
|
295
|
-
else code += `
|
|
296
|
-
export const ${variantComponentName} = {
|
|
297
|
-
name: '${variantComponentName}',
|
|
298
|
-
template: \`${fullTemplate}\`,
|
|
299
|
-
};
|
|
300
|
-
`;
|
|
301
|
-
}
|
|
302
|
-
const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];
|
|
303
|
-
if (defaultVariant) code += `
|
|
304
|
-
export default ${toPascalCase(defaultVariant.name)};
|
|
305
|
-
`;
|
|
306
|
-
return code;
|
|
307
|
-
}
|
|
308
|
-
//#endregion
|
|
309
|
-
//#region src/gallery/styles-base.css?inline
|
|
310
|
-
var styles_base_default = ":root {\n --musea-bg-primary: #e6e2d6;\n --musea-bg-secondary: #ddd9cd;\n --musea-bg-tertiary: #d4d0c4;\n --musea-bg-elevated: #e6e2d6;\n --musea-accent: #121212;\n --musea-accent-hover: #2a2a2a;\n --musea-accent-subtle: #12121214;\n --musea-text: #121212;\n --musea-text-secondary: #3a3a3a;\n --musea-text-muted: #6b6b6b;\n --musea-border: #c8c4b8;\n --musea-border-subtle: #d4d0c4;\n --musea-success: #16a34a;\n --musea-shadow: 0 4px 24px #00000014;\n --musea-radius-sm: 4px;\n --musea-radius-md: 6px;\n --musea-radius-lg: 8px;\n --musea-transition: .15s ease;\n}\n\n* {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nbody {\n background: var(--musea-bg-primary);\n color: var(--musea-text);\n -webkit-font-smoothing: antialiased;\n min-height: 100vh;\n font-family: Helvetica Neue, Helvetica, Arial, sans-serif;\n line-height: 1.5;\n}\n";
|
|
311
|
-
//#endregion
|
|
312
|
-
//#region src/gallery/styles-layout.css?inline
|
|
313
|
-
var styles_layout_default = ".header {\n background: var(--musea-bg-secondary);\n border-bottom: 1px solid var(--musea-border);\n z-index: 100;\n justify-content: space-between;\n align-items: center;\n height: 56px;\n padding: 0 1.5rem;\n display: flex;\n position: sticky;\n top: 0;\n}\n\n.header-left {\n align-items: center;\n gap: 1.5rem;\n display: flex;\n}\n\n.logo {\n color: var(--musea-accent);\n align-items: center;\n gap: .5rem;\n font-size: 1.125rem;\n font-weight: 700;\n text-decoration: none;\n display: flex;\n}\n\n.logo-svg {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n}\n\n.logo-icon svg {\n width: 16px;\n height: 16px;\n color: var(--musea-text);\n}\n\n.header-subtitle {\n color: var(--musea-text-muted);\n border-left: 1px solid var(--musea-border);\n padding-left: 1.5rem;\n font-size: .8125rem;\n font-weight: 500;\n}\n\n.search-container {\n width: 280px;\n position: relative;\n}\n\n.search-input {\n background: var(--musea-bg-tertiary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-md);\n width: 100%;\n color: var(--musea-text);\n transition: border-color var(--musea-transition),\n background var(--musea-transition);\n outline: none;\n padding: .5rem .75rem .5rem 2.25rem;\n font-size: .8125rem;\n}\n\n.search-input::placeholder {\n color: var(--musea-text-muted);\n}\n\n.search-input:focus {\n border-color: var(--musea-accent);\n background: var(--musea-bg-elevated);\n}\n\n.search-icon {\n color: var(--musea-text-muted);\n pointer-events: none;\n position: absolute;\n top: 50%;\n left: .75rem;\n transform: translateY(-50%);\n}\n\n.main {\n grid-template-columns: 260px 1fr;\n min-height: calc(100vh - 56px);\n display: grid;\n}\n\n.sidebar {\n background: var(--musea-bg-secondary);\n border-right: 1px solid var(--musea-border);\n overflow: hidden auto;\n}\n\n.sidebar::-webkit-scrollbar {\n width: 6px;\n}\n\n.sidebar::-webkit-scrollbar-track {\n background: none;\n}\n\n.sidebar::-webkit-scrollbar-thumb {\n background: var(--musea-border);\n border-radius: 3px;\n}\n\n.sidebar-section {\n padding: .75rem;\n}\n\n.category-header {\n text-transform: uppercase;\n letter-spacing: .08em;\n color: var(--musea-text-muted);\n cursor: pointer;\n user-select: none;\n border-radius: var(--musea-radius-sm);\n transition: background var(--musea-transition);\n align-items: center;\n gap: .5rem;\n padding: .625rem .75rem;\n font-size: .6875rem;\n font-weight: 600;\n display: flex;\n}\n\n.category-header:hover {\n background: var(--musea-bg-tertiary);\n}\n\n.category-icon {\n width: 16px;\n height: 16px;\n transition: transform var(--musea-transition);\n}\n\n.category-header.collapsed .category-icon {\n transform: rotate(-90deg);\n}\n\n.category-count {\n background: var(--musea-bg-tertiary);\n border-radius: 4px;\n margin-left: auto;\n padding: .125rem .375rem;\n font-size: .625rem;\n}\n\n.art-list {\n margin-top: .25rem;\n list-style: none;\n}\n\n.art-item {\n border-radius: var(--musea-radius-sm);\n cursor: pointer;\n color: var(--musea-text-secondary);\n transition: all var(--musea-transition);\n align-items: center;\n gap: .625rem;\n padding: .5rem .75rem .5rem 1.75rem;\n font-size: .8125rem;\n display: flex;\n position: relative;\n}\n\n.art-item:before {\n content: \"\";\n background: var(--musea-border);\n width: 6px;\n height: 6px;\n transition: background var(--musea-transition);\n border-radius: 50%;\n position: absolute;\n top: 50%;\n left: .75rem;\n transform: translateY(-50%);\n}\n\n.art-item:hover {\n background: var(--musea-bg-tertiary);\n color: var(--musea-text);\n}\n\n.art-item:hover:before {\n background: var(--musea-text-muted);\n}\n\n.art-item.active {\n background: var(--musea-accent-subtle);\n color: var(--musea-accent-hover);\n}\n\n.art-item.active:before {\n background: var(--musea-accent);\n}\n\n.art-variant-count {\n color: var(--musea-text-muted);\n opacity: 0;\n transition: opacity var(--musea-transition);\n margin-left: auto;\n font-size: .6875rem;\n}\n\n.art-item:hover .art-variant-count {\n opacity: 1;\n}\n\n.content {\n background: var(--musea-bg-primary);\n overflow-y: auto;\n}\n\n.content-inner {\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n}\n\n.content-header {\n margin-bottom: 2rem;\n}\n\n.content-title {\n margin-bottom: .5rem;\n font-size: 1.5rem;\n font-weight: 700;\n}\n\n.content-description {\n color: var(--musea-text-muted);\n max-width: 600px;\n font-size: .9375rem;\n}\n\n.content-meta {\n align-items: center;\n gap: 1rem;\n margin-top: 1rem;\n display: flex;\n}\n\n.meta-tag {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-sm);\n color: var(--musea-text-muted);\n align-items: center;\n gap: .375rem;\n padding: .25rem .625rem;\n font-size: .75rem;\n display: inline-flex;\n}\n\n.meta-tag svg {\n width: 12px;\n height: 12px;\n}\n";
|
|
314
|
-
//#endregion
|
|
315
|
-
//#region src/gallery/styles-components.css?inline
|
|
316
|
-
var styles_components_default = ".gallery {\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 1.25rem;\n display: grid;\n}\n\n.variant-card {\n background: var(--musea-bg-secondary);\n border: 1px solid var(--musea-border);\n border-radius: var(--musea-radius-lg);\n transition: all var(--musea-transition);\n overflow: hidden;\n}\n\n.variant-card:hover {\n border-color: var(--musea-text-muted);\n box-shadow: var(--musea-shadow);\n transform: translateY(-2px);\n}\n\n.variant-preview {\n aspect-ratio: 16 / 10;\n background: var(--musea-bg-tertiary);\n justify-content: center;\n align-items: center;\n display: flex;\n position: relative;\n overflow: hidden;\n}\n\n.variant-preview iframe {\n background: #fff;\n border: none;\n width: 100%;\n height: 100%;\n}\n\n.variant-preview-placeholder {\n color: var(--musea-text-muted);\n text-align: center;\n padding: 1rem;\n font-size: .8125rem;\n}\n\n.variant-preview-code {\n color: var(--musea-text-muted);\n background: var(--musea-bg-primary);\n width: 100%;\n max-height: 100%;\n padding: 1rem;\n font-family: JetBrains Mono, SF Mono, Fira Code, monospace;\n font-size: .75rem;\n overflow: auto;\n}\n\n.variant-info {\n border-top: 1px solid var(--musea-border);\n justify-content: space-between;\n align-items: center;\n padding: 1rem;\n display: flex;\n}\n\n.variant-name {\n font-size: .875rem;\n font-weight: 600;\n}\n\n.variant-badge {\n text-transform: uppercase;\n letter-spacing: .04em;\n background: var(--musea-accent-subtle);\n color: var(--musea-accent);\n border-radius: 4px;\n padding: .1875rem .5rem;\n font-size: .625rem;\n font-weight: 600;\n}\n\n.variant-actions {\n gap: .5rem;\n display: flex;\n}\n\n.variant-action-btn {\n background: var(--musea-bg-tertiary);\n border-radius: var(--musea-radius-sm);\n width: 28px;\n height: 28px;\n color: var(--musea-text-muted);\n cursor: pointer;\n transition: all var(--musea-transition);\n border: none;\n justify-content: center;\n align-items: center;\n display: flex;\n}\n\n.variant-action-btn:hover {\n background: var(--musea-bg-elevated);\n color: var(--musea-text);\n}\n\n.variant-action-btn svg {\n width: 14px;\n height: 14px;\n}\n\n.empty-state {\n text-align: center;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n min-height: 400px;\n padding: 2rem;\n display: flex;\n}\n\n.empty-state-icon {\n background: var(--musea-bg-secondary);\n border-radius: var(--musea-radius-lg);\n justify-content: center;\n align-items: center;\n width: 80px;\n height: 80px;\n margin-bottom: 1.5rem;\n display: flex;\n}\n\n.empty-state-icon svg {\n width: 40px;\n height: 40px;\n color: var(--musea-text-muted);\n}\n\n.empty-state-title {\n margin-bottom: .5rem;\n font-size: 1.125rem;\n font-weight: 600;\n}\n\n.empty-state-text {\n color: var(--musea-text-muted);\n max-width: 300px;\n font-size: .875rem;\n}\n\n.loading {\n min-height: 200px;\n color: var(--musea-text-muted);\n justify-content: center;\n align-items: center;\n gap: .75rem;\n display: flex;\n}\n\n.loading-spinner {\n border: 2px solid var(--musea-border);\n border-top-color: var(--musea-accent);\n border-radius: 50%;\n width: 20px;\n height: 20px;\n animation: .8s linear infinite spin;\n}\n\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (width <= 768px) {\n .main {\n grid-template-columns: 1fr;\n }\n\n .sidebar, .header-subtitle {\n display: none;\n }\n}\n";
|
|
317
|
-
//#endregion
|
|
318
|
-
//#region src/gallery/styles.ts
|
|
319
|
-
/**
|
|
320
|
-
* CSS theme variables and style definitions for the Musea gallery.
|
|
321
|
-
*
|
|
322
|
-
* CSS is split into separate .css files and imported as text
|
|
323
|
-
* via tsdown's `?inline` support.
|
|
324
|
-
*/
|
|
325
|
-
/**
|
|
326
|
-
* Generate the full gallery CSS styles string.
|
|
327
|
-
*/
|
|
328
|
-
function generateGalleryStyles() {
|
|
329
|
-
return `${styles_base_default}\n${styles_layout_default}\n${styles_components_default}`;
|
|
330
|
-
}
|
|
331
|
-
//#endregion
|
|
332
|
-
//#region src/gallery/template.ts
|
|
333
|
-
/**
|
|
334
|
-
* HTML structure and inline JS generation for the Musea gallery.
|
|
335
|
-
*
|
|
336
|
-
* Extracted from gallery.ts to keep file sizes manageable.
|
|
337
|
-
*/
|
|
338
|
-
/**
|
|
339
|
-
* Generate the gallery HTML body (header, sidebar, content, and inline script).
|
|
340
|
-
*/
|
|
341
|
-
function generateGalleryBody(basePath) {
|
|
342
|
-
return `
|
|
343
|
-
<header class="header">
|
|
344
|
-
<div class="header-left">
|
|
345
|
-
<a href="${basePath}" class="logo">
|
|
346
|
-
<svg class="logo-svg" width="32" height="32" viewBox="0 0 200 200" fill="none">
|
|
347
|
-
<g transform="translate(30, 25) scale(1.2)">
|
|
348
|
-
<g transform="translate(15, 10) skewX(-15)">
|
|
349
|
-
<path d="M 65 0 L 40 60 L 70 20 L 65 0 Z" fill="currentColor"/>
|
|
350
|
-
<path d="M 20 0 L 40 60 L 53 13 L 20 0 Z" fill="currentColor"/>
|
|
351
|
-
</g>
|
|
352
|
-
</g>
|
|
353
|
-
<g transform="translate(110, 120)">
|
|
354
|
-
<line x1="5" y1="10" x2="5" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
|
355
|
-
<line x1="60" y1="10" x2="60" y2="50" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
|
356
|
-
<path d="M 0 10 L 32.5 0 L 65 10" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
357
|
-
<rect x="15" y="18" width="14" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
|
|
358
|
-
<rect x="36" y="18" width="14" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.7"/>
|
|
359
|
-
<rect x="23" y="35" width="18" height="12" rx="1" fill="none" stroke="currentColor" stroke-width="1.5" opacity="0.6"/>
|
|
360
|
-
</g>
|
|
361
|
-
</svg>
|
|
362
|
-
Musea
|
|
363
|
-
</a>
|
|
364
|
-
<span class="header-subtitle">Component Gallery</span>
|
|
365
|
-
</div>
|
|
366
|
-
<div class="search-container">
|
|
367
|
-
<svg class="search-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
368
|
-
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
|
|
369
|
-
</svg>
|
|
370
|
-
<input type="text" class="search-input" placeholder="Search components..." id="search">
|
|
371
|
-
</div>
|
|
372
|
-
</header>
|
|
373
|
-
|
|
374
|
-
<main class="main">
|
|
375
|
-
<aside class="sidebar" id="sidebar">
|
|
376
|
-
<div class="loading">
|
|
377
|
-
<div class="loading-spinner"></div>
|
|
378
|
-
Loading...
|
|
379
|
-
</div>
|
|
380
|
-
</aside>
|
|
381
|
-
<section class="content" id="content">
|
|
382
|
-
<div class="empty-state">
|
|
383
|
-
<div class="empty-state-icon">
|
|
384
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
385
|
-
<path d="M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z"/>
|
|
386
|
-
<path d="M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z"/>
|
|
387
|
-
<path d="M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z"/>
|
|
388
|
-
</svg>
|
|
389
|
-
</div>
|
|
390
|
-
<div class="empty-state-title">Select a component</div>
|
|
391
|
-
<div class="empty-state-text">Choose a component from the sidebar to view its variants and documentation</div>
|
|
392
|
-
</div>
|
|
393
|
-
</section>
|
|
394
|
-
</main>`;
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Generate the gallery inline script (SPA logic).
|
|
398
|
-
*/
|
|
399
|
-
function generateGalleryScript(basePath) {
|
|
400
|
-
return `
|
|
401
|
-
const basePath = '${basePath}';
|
|
402
|
-
let arts = [];
|
|
403
|
-
let selectedArt = null;
|
|
404
|
-
let searchQuery = '';
|
|
405
|
-
|
|
406
|
-
async function loadArts() {
|
|
407
|
-
try {
|
|
408
|
-
const res = await fetch(basePath + '/api/arts');
|
|
409
|
-
arts = await res.json();
|
|
410
|
-
renderSidebar();
|
|
411
|
-
} catch (e) {
|
|
412
|
-
console.error('Failed to load arts:', e);
|
|
413
|
-
document.getElementById('sidebar').innerHTML = '<div class="loading">Failed to load</div>';
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function renderSidebar() {
|
|
418
|
-
const sidebar = document.getElementById('sidebar');
|
|
419
|
-
const categories = {};
|
|
420
|
-
|
|
421
|
-
const filtered = searchQuery
|
|
422
|
-
? arts.filter(a => a.metadata.title.toLowerCase().includes(searchQuery.toLowerCase()))
|
|
423
|
-
: arts;
|
|
424
|
-
|
|
425
|
-
for (const art of filtered) {
|
|
426
|
-
const cat = art.metadata.category || 'Components';
|
|
427
|
-
if (!categories[cat]) categories[cat] = [];
|
|
428
|
-
categories[cat].push(art);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
if (Object.keys(categories).length === 0) {
|
|
432
|
-
sidebar.innerHTML = '<div class="sidebar-section"><div class="loading">No components found</div></div>';
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
let html = '';
|
|
437
|
-
for (const [category, items] of Object.entries(categories)) {
|
|
438
|
-
html += '<div class="sidebar-section">';
|
|
439
|
-
html += '<div class="category-header" data-category="' + category + '">';
|
|
440
|
-
html += '<svg class="category-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>';
|
|
441
|
-
html += '<span>' + category + '</span>';
|
|
442
|
-
html += '<span class="category-count">' + items.length + '</span>';
|
|
443
|
-
html += '</div>';
|
|
444
|
-
html += '<ul class="art-list" data-category="' + category + '">';
|
|
445
|
-
for (const art of items) {
|
|
446
|
-
const active = selectedArt?.path === art.path ? 'active' : '';
|
|
447
|
-
const variantCount = art.variants?.length || 0;
|
|
448
|
-
html += '<li class="art-item ' + active + '" data-path="' + art.path + '">';
|
|
449
|
-
html += '<span>' + escapeHtml(art.metadata.title) + '</span>';
|
|
450
|
-
html += '<span class="art-variant-count">' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';
|
|
451
|
-
html += '</li>';
|
|
452
|
-
}
|
|
453
|
-
html += '</ul>';
|
|
454
|
-
html += '</div>';
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
sidebar.innerHTML = html;
|
|
458
|
-
|
|
459
|
-
sidebar.querySelectorAll('.art-item').forEach(item => {
|
|
460
|
-
item.addEventListener('click', () => {
|
|
461
|
-
const artPath = item.dataset.path;
|
|
462
|
-
selectedArt = arts.find(a => a.path === artPath);
|
|
463
|
-
renderSidebar();
|
|
464
|
-
renderContent();
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
sidebar.querySelectorAll('.category-header').forEach(header => {
|
|
469
|
-
header.addEventListener('click', () => {
|
|
470
|
-
header.classList.toggle('collapsed');
|
|
471
|
-
const list = sidebar.querySelector('.art-list[data-category="' + header.dataset.category + '"]');
|
|
472
|
-
if (list) list.style.display = header.classList.contains('collapsed') ? 'none' : 'block';
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function renderContent() {
|
|
478
|
-
const content = document.getElementById('content');
|
|
479
|
-
if (!selectedArt) {
|
|
480
|
-
content.innerHTML = \`
|
|
481
|
-
<div class="empty-state">
|
|
482
|
-
<div class="empty-state-icon">
|
|
483
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
484
|
-
<path d="M4 5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V5Z"/>
|
|
485
|
-
<path d="M4 13a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6Z"/>
|
|
486
|
-
<path d="M16 13a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-6Z"/>
|
|
487
|
-
</svg>
|
|
488
|
-
</div>
|
|
489
|
-
<div class="empty-state-title">Select a component</div>
|
|
490
|
-
<div class="empty-state-text">Choose a component from the sidebar to view its variants</div>
|
|
491
|
-
</div>
|
|
492
|
-
\`;
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const meta = selectedArt.metadata;
|
|
497
|
-
const tags = meta.tags || [];
|
|
498
|
-
const variantCount = selectedArt.variants?.length || 0;
|
|
499
|
-
|
|
500
|
-
let html = '<div class="content-inner">';
|
|
501
|
-
html += '<div class="content-header">';
|
|
502
|
-
html += '<h1 class="content-title">' + escapeHtml(meta.title) + '</h1>';
|
|
503
|
-
if (meta.description) {
|
|
504
|
-
html += '<p class="content-description">' + escapeHtml(meta.description) + '</p>';
|
|
505
|
-
}
|
|
506
|
-
html += '<div class="content-meta">';
|
|
507
|
-
html += '<span class="meta-tag"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>' + variantCount + ' variant' + (variantCount !== 1 ? 's' : '') + '</span>';
|
|
508
|
-
if (meta.category) {
|
|
509
|
-
html += '<span class="meta-tag"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>' + escapeHtml(meta.category) + '</span>';
|
|
510
|
-
}
|
|
511
|
-
for (const tag of tags) {
|
|
512
|
-
html += '<span class="meta-tag">#' + escapeHtml(tag) + '</span>';
|
|
513
|
-
}
|
|
514
|
-
html += '</div>';
|
|
515
|
-
html += '</div>';
|
|
516
|
-
|
|
517
|
-
html += '<div class="gallery">';
|
|
518
|
-
for (const variant of selectedArt.variants) {
|
|
519
|
-
const previewUrl = basePath + '/preview?art=' + encodeURIComponent(selectedArt.path) + '&variant=' + encodeURIComponent(variant.name);
|
|
520
|
-
|
|
521
|
-
html += '<div class="variant-card">';
|
|
522
|
-
html += '<div class="variant-preview">';
|
|
523
|
-
html += '<iframe src="' + previewUrl + '" loading="lazy" title="' + escapeHtml(variant.name) + '"></iframe>';
|
|
524
|
-
html += '</div>';
|
|
525
|
-
html += '<div class="variant-info">';
|
|
526
|
-
html += '<div>';
|
|
527
|
-
html += '<span class="variant-name">' + escapeHtml(variant.name) + '</span>';
|
|
528
|
-
if (variant.isDefault) html += ' <span class="variant-badge">Default</span>';
|
|
529
|
-
html += '</div>';
|
|
530
|
-
html += '<div class="variant-actions">';
|
|
531
|
-
html += '<button class="variant-action-btn" title="Open in new tab" onclick="window.open(\\'' + previewUrl + '\\', \\'_blank\\')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg></button>';
|
|
532
|
-
html += '</div>';
|
|
533
|
-
html += '</div>';
|
|
534
|
-
html += '</div>';
|
|
535
|
-
}
|
|
536
|
-
html += '</div>';
|
|
537
|
-
html += '</div>';
|
|
538
|
-
|
|
539
|
-
content.innerHTML = html;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
function escapeHtml(str) {
|
|
543
|
-
if (!str) return '';
|
|
544
|
-
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Search
|
|
548
|
-
document.getElementById('search').addEventListener('input', (e) => {
|
|
549
|
-
searchQuery = e.target.value;
|
|
550
|
-
renderSidebar();
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
// Keyboard shortcut for search
|
|
554
|
-
document.addEventListener('keydown', (e) => {
|
|
555
|
-
if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
|
|
556
|
-
e.preventDefault();
|
|
557
|
-
document.getElementById('search').focus();
|
|
558
|
-
}
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
loadArts();`;
|
|
562
|
-
}
|
|
563
|
-
//#endregion
|
|
564
|
-
//#region src/gallery/index.ts
|
|
565
|
-
/**
|
|
566
|
-
* Gallery HTML generation for the Musea component gallery.
|
|
567
|
-
*
|
|
568
|
-
* Contains the inline gallery SPA template (used as a fallback when the
|
|
569
|
-
* pre-built gallery is not available) and the gallery virtual module.
|
|
570
|
-
*/
|
|
571
|
-
/**
|
|
572
|
-
* Generate the inline gallery HTML page.
|
|
573
|
-
*/
|
|
574
|
-
function generateGalleryHtml(basePath, themeConfig) {
|
|
575
|
-
return `<!DOCTYPE html>
|
|
576
|
-
<html lang="en">
|
|
577
|
-
<head>
|
|
578
|
-
<meta charset="UTF-8">
|
|
579
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
580
|
-
<title>Musea - Component Gallery</title>
|
|
581
|
-
<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : ""}<\/script>
|
|
582
|
-
<style>${generateGalleryStyles()}
|
|
583
|
-
</style>
|
|
584
|
-
</head>
|
|
585
|
-
<body>${generateGalleryBody(basePath)}
|
|
586
|
-
|
|
587
|
-
<script type="module">${generateGalleryScript(basePath)}
|
|
588
|
-
<\/script>
|
|
589
|
-
</body>
|
|
590
|
-
</html>`;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Generate the virtual gallery module code.
|
|
594
|
-
*/
|
|
595
|
-
function generateGalleryModule(basePath) {
|
|
596
|
-
return `
|
|
597
|
-
export const basePath = '${basePath}';
|
|
598
|
-
export async function loadArts() {
|
|
599
|
-
const res = await fetch(basePath + '/api/arts');
|
|
600
|
-
return res.json();
|
|
601
|
-
}
|
|
602
|
-
`;
|
|
603
|
-
}
|
|
604
|
-
//#endregion
|
|
605
|
-
//#region src/preview/addons.ts
|
|
606
|
-
/**
|
|
607
|
-
* Addon initialization code for Musea preview iframes.
|
|
608
|
-
*
|
|
609
|
-
* Contains DOM event capture, measure overlay, and message handler logic
|
|
610
|
-
* injected into preview modules.
|
|
611
|
-
*
|
|
612
|
-
* Extracted from preview.ts to keep file sizes manageable.
|
|
613
|
-
*/
|
|
614
|
-
/**
|
|
615
|
-
* Addon initialization code injected into preview iframe modules.
|
|
616
|
-
* Shared between generatePreviewModule and generatePreviewModuleWithProps.
|
|
617
|
-
*/
|
|
618
|
-
const MUSEA_ADDONS_INIT_CODE = `
|
|
619
|
-
function __museaInitAddons(container, variantName) {
|
|
620
|
-
// === DOM event capture ===
|
|
621
|
-
// Note: mousemove, mouseenter, mouseleave, pointermove are excluded as they are too noisy
|
|
622
|
-
const CAPTURE_EVENTS = ['click','dblclick','input','change','submit','focus','blur','keydown','keyup','mousedown','mouseup','wheel','contextmenu','pointerdown','pointerup'];
|
|
623
|
-
for (const evt of CAPTURE_EVENTS) {
|
|
624
|
-
container.addEventListener(evt, (e) => {
|
|
625
|
-
// Extract raw event properties
|
|
626
|
-
const rawEvent = {
|
|
627
|
-
type: e.type,
|
|
628
|
-
bubbles: e.bubbles,
|
|
629
|
-
cancelable: e.cancelable,
|
|
630
|
-
composed: e.composed,
|
|
631
|
-
defaultPrevented: e.defaultPrevented,
|
|
632
|
-
eventPhase: e.eventPhase,
|
|
633
|
-
isTrusted: e.isTrusted,
|
|
634
|
-
timeStamp: e.timeStamp,
|
|
635
|
-
};
|
|
636
|
-
// Mouse/Pointer event properties
|
|
637
|
-
if ('clientX' in e) {
|
|
638
|
-
rawEvent.clientX = e.clientX;
|
|
639
|
-
rawEvent.clientY = e.clientY;
|
|
640
|
-
rawEvent.screenX = e.screenX;
|
|
641
|
-
rawEvent.screenY = e.screenY;
|
|
642
|
-
rawEvent.pageX = e.pageX;
|
|
643
|
-
rawEvent.pageY = e.pageY;
|
|
644
|
-
rawEvent.offsetX = e.offsetX;
|
|
645
|
-
rawEvent.offsetY = e.offsetY;
|
|
646
|
-
rawEvent.button = e.button;
|
|
647
|
-
rawEvent.buttons = e.buttons;
|
|
648
|
-
rawEvent.altKey = e.altKey;
|
|
649
|
-
rawEvent.ctrlKey = e.ctrlKey;
|
|
650
|
-
rawEvent.metaKey = e.metaKey;
|
|
651
|
-
rawEvent.shiftKey = e.shiftKey;
|
|
652
|
-
}
|
|
653
|
-
// Keyboard event properties
|
|
654
|
-
if ('key' in e) {
|
|
655
|
-
rawEvent.key = e.key;
|
|
656
|
-
rawEvent.code = e.code;
|
|
657
|
-
rawEvent.repeat = e.repeat;
|
|
658
|
-
rawEvent.altKey = e.altKey;
|
|
659
|
-
rawEvent.ctrlKey = e.ctrlKey;
|
|
660
|
-
rawEvent.metaKey = e.metaKey;
|
|
661
|
-
rawEvent.shiftKey = e.shiftKey;
|
|
662
|
-
}
|
|
663
|
-
// Input event properties
|
|
664
|
-
if ('inputType' in e) {
|
|
665
|
-
rawEvent.inputType = e.inputType;
|
|
666
|
-
rawEvent.data = e.data;
|
|
667
|
-
}
|
|
668
|
-
// Wheel event properties
|
|
669
|
-
if ('deltaX' in e) {
|
|
670
|
-
rawEvent.deltaX = e.deltaX;
|
|
671
|
-
rawEvent.deltaY = e.deltaY;
|
|
672
|
-
rawEvent.deltaZ = e.deltaZ;
|
|
673
|
-
rawEvent.deltaMode = e.deltaMode;
|
|
674
|
-
}
|
|
675
|
-
const payload = {
|
|
676
|
-
name: evt,
|
|
677
|
-
target: e.target?.tagName,
|
|
678
|
-
timestamp: Date.now(),
|
|
679
|
-
source: 'dom',
|
|
680
|
-
rawEvent,
|
|
681
|
-
variantName
|
|
682
|
-
};
|
|
683
|
-
if (e.target && 'value' in e.target) {
|
|
684
|
-
payload.value = e.target.value;
|
|
685
|
-
}
|
|
686
|
-
window.parent.postMessage({ type: 'musea:event', payload }, '*');
|
|
687
|
-
}, true);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
// === Message handler for parent commands ===
|
|
691
|
-
let measureActive = false;
|
|
692
|
-
let measureOverlay = null;
|
|
693
|
-
let measureLabel = null;
|
|
694
|
-
|
|
695
|
-
function toggleStyleById(id, enabled, css) {
|
|
696
|
-
let el = document.getElementById(id);
|
|
697
|
-
if (enabled) {
|
|
698
|
-
if (!el) {
|
|
699
|
-
el = document.createElement('style');
|
|
700
|
-
el.id = id;
|
|
701
|
-
el.textContent = css;
|
|
702
|
-
document.head.appendChild(el);
|
|
703
|
-
}
|
|
704
|
-
} else {
|
|
705
|
-
if (el) el.remove();
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
function createMeasureOverlay() {
|
|
710
|
-
if (measureOverlay) return;
|
|
711
|
-
measureOverlay = document.createElement('div');
|
|
712
|
-
measureOverlay.id = 'musea-measure-overlay';
|
|
713
|
-
measureOverlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:99999;';
|
|
714
|
-
document.body.appendChild(measureOverlay);
|
|
715
|
-
|
|
716
|
-
measureLabel = document.createElement('div');
|
|
717
|
-
measureLabel.className = 'musea-measure-label';
|
|
718
|
-
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;';
|
|
719
|
-
document.body.appendChild(measureLabel);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
function removeMeasureOverlay() {
|
|
723
|
-
if (measureOverlay) { measureOverlay.remove(); measureOverlay = null; }
|
|
724
|
-
if (measureLabel) { measureLabel.remove(); measureLabel = null; }
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function onMeasureMouseMove(e) {
|
|
728
|
-
if (!measureActive || !measureOverlay) return;
|
|
729
|
-
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
730
|
-
if (!el || el === measureOverlay || el === measureLabel) return;
|
|
731
|
-
|
|
732
|
-
const rect = el.getBoundingClientRect();
|
|
733
|
-
const cs = getComputedStyle(el);
|
|
734
|
-
const mt = parseFloat(cs.marginTop) || 0;
|
|
735
|
-
const mr = parseFloat(cs.marginRight) || 0;
|
|
736
|
-
const mb = parseFloat(cs.marginBottom) || 0;
|
|
737
|
-
const ml = parseFloat(cs.marginLeft) || 0;
|
|
738
|
-
const bt = parseFloat(cs.borderTopWidth) || 0;
|
|
739
|
-
const br = parseFloat(cs.borderRightWidth) || 0;
|
|
740
|
-
const bb = parseFloat(cs.borderBottomWidth) || 0;
|
|
741
|
-
const blw = parseFloat(cs.borderLeftWidth) || 0;
|
|
742
|
-
const pt = parseFloat(cs.paddingTop) || 0;
|
|
743
|
-
const pr = parseFloat(cs.paddingRight) || 0;
|
|
744
|
-
const pb = parseFloat(cs.paddingBottom) || 0;
|
|
745
|
-
const pl = parseFloat(cs.paddingLeft) || 0;
|
|
746
|
-
|
|
747
|
-
const cw = rect.width - blw - br - pl - pr;
|
|
748
|
-
const ch = rect.height - bt - bb - pt - pb;
|
|
749
|
-
|
|
750
|
-
measureOverlay.innerHTML = ''
|
|
751
|
-
// Margin
|
|
752
|
-
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
753
|
-
+ 'left:' + (rect.left - ml) + 'px;top:' + (rect.top - mt) + 'px;'
|
|
754
|
-
+ 'width:' + (rect.width + ml + mr) + 'px;height:' + mt + 'px;"></div>'
|
|
755
|
-
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
756
|
-
+ 'left:' + (rect.left - ml) + 'px;top:' + (rect.bottom) + 'px;'
|
|
757
|
-
+ 'width:' + (rect.width + ml + mr) + 'px;height:' + mb + 'px;"></div>'
|
|
758
|
-
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
759
|
-
+ 'left:' + (rect.left - ml) + 'px;top:' + rect.top + 'px;'
|
|
760
|
-
+ 'width:' + ml + 'px;height:' + rect.height + 'px;"></div>'
|
|
761
|
-
+ '<div style="position:fixed;background:rgba(255,165,0,0.3);'
|
|
762
|
-
+ 'left:' + rect.right + 'px;top:' + rect.top + 'px;'
|
|
763
|
-
+ 'width:' + mr + 'px;height:' + rect.height + 'px;"></div>'
|
|
764
|
-
// Border
|
|
765
|
-
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
766
|
-
+ 'left:' + rect.left + 'px;top:' + rect.top + 'px;'
|
|
767
|
-
+ 'width:' + rect.width + 'px;height:' + bt + 'px;"></div>'
|
|
768
|
-
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
769
|
-
+ 'left:' + rect.left + 'px;top:' + (rect.bottom - bb) + 'px;'
|
|
770
|
-
+ 'width:' + rect.width + 'px;height:' + bb + 'px;"></div>'
|
|
771
|
-
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
772
|
-
+ 'left:' + rect.left + 'px;top:' + (rect.top + bt) + 'px;'
|
|
773
|
-
+ 'width:' + blw + 'px;height:' + (rect.height - bt - bb) + 'px;"></div>'
|
|
774
|
-
+ '<div style="position:fixed;background:rgba(255,255,0,0.3);'
|
|
775
|
-
+ 'left:' + (rect.right - br) + 'px;top:' + (rect.top + bt) + 'px;'
|
|
776
|
-
+ 'width:' + br + 'px;height:' + (rect.height - bt - bb) + 'px;"></div>'
|
|
777
|
-
// Padding
|
|
778
|
-
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
779
|
-
+ 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt) + 'px;'
|
|
780
|
-
+ 'width:' + (rect.width - blw - br) + 'px;height:' + pt + 'px;"></div>'
|
|
781
|
-
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
782
|
-
+ 'left:' + (rect.left + blw) + 'px;top:' + (rect.bottom - bb - pb) + 'px;'
|
|
783
|
-
+ 'width:' + (rect.width - blw - br) + 'px;height:' + pb + 'px;"></div>'
|
|
784
|
-
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
785
|
-
+ 'left:' + (rect.left + blw) + 'px;top:' + (rect.top + bt + pt) + 'px;'
|
|
786
|
-
+ 'width:' + pl + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;"></div>'
|
|
787
|
-
+ '<div style="position:fixed;background:rgba(144,238,144,0.3);'
|
|
788
|
-
+ 'left:' + (rect.right - br - pr) + 'px;top:' + (rect.top + bt + pt) + 'px;'
|
|
789
|
-
+ 'width:' + pr + 'px;height:' + (rect.height - bt - bb - pt - pb) + 'px;"></div>'
|
|
790
|
-
// Content
|
|
791
|
-
+ '<div style="position:fixed;background:rgba(100,149,237,0.3);'
|
|
792
|
-
+ 'left:' + (rect.left + blw + pl) + 'px;top:' + (rect.top + bt + pt) + 'px;'
|
|
793
|
-
+ 'width:' + cw + 'px;height:' + ch + 'px;"></div>';
|
|
794
|
-
|
|
795
|
-
// Label
|
|
796
|
-
measureLabel.textContent = Math.round(rect.width) + ' x ' + Math.round(rect.height);
|
|
797
|
-
measureLabel.style.display = 'block';
|
|
798
|
-
measureLabel.style.left = (rect.right + 8) + 'px';
|
|
799
|
-
measureLabel.style.top = rect.top + 'px';
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
window.addEventListener('message', (e) => {
|
|
803
|
-
if (!e.data?.type?.startsWith('musea:')) return;
|
|
804
|
-
const { type, payload } = e.data;
|
|
805
|
-
switch (type) {
|
|
806
|
-
case 'musea:set-background': {
|
|
807
|
-
if (payload.pattern === 'checkerboard') {
|
|
808
|
-
document.body.style.background = '';
|
|
809
|
-
document.body.classList.add('musea-bg-checkerboard');
|
|
810
|
-
} else {
|
|
811
|
-
document.body.classList.remove('musea-bg-checkerboard');
|
|
812
|
-
document.body.style.background = payload.color || '';
|
|
813
|
-
}
|
|
814
|
-
break;
|
|
815
|
-
}
|
|
816
|
-
case 'musea:toggle-outline': {
|
|
817
|
-
toggleStyleById('musea-outline', payload.enabled,
|
|
818
|
-
'* { outline: 1px solid rgba(255, 0, 0, 0.3) !important; }');
|
|
819
|
-
break;
|
|
820
|
-
}
|
|
821
|
-
case 'musea:toggle-measure': {
|
|
822
|
-
measureActive = payload.enabled;
|
|
823
|
-
if (measureActive) {
|
|
824
|
-
createMeasureOverlay();
|
|
825
|
-
document.addEventListener('mousemove', onMeasureMouseMove);
|
|
826
|
-
} else {
|
|
827
|
-
document.removeEventListener('mousemove', onMeasureMouseMove);
|
|
828
|
-
removeMeasureOverlay();
|
|
829
|
-
}
|
|
830
|
-
break;
|
|
831
|
-
}
|
|
832
|
-
case 'musea:set-props': {
|
|
833
|
-
// Store props for remount - handled by preview module
|
|
834
|
-
if (window.__museaSetProps) {
|
|
835
|
-
window.__museaSetProps(payload.props || {});
|
|
836
|
-
}
|
|
837
|
-
break;
|
|
838
|
-
}
|
|
839
|
-
case 'musea:set-slots': {
|
|
840
|
-
// Store slots for remount - handled by preview module
|
|
841
|
-
if (window.__museaSetSlots) {
|
|
842
|
-
window.__museaSetSlots(payload.slots || {});
|
|
843
|
-
}
|
|
844
|
-
break;
|
|
845
|
-
}
|
|
846
|
-
case 'musea:run-a11y': {
|
|
847
|
-
// Run axe-core a11y test
|
|
848
|
-
(async () => {
|
|
849
|
-
try {
|
|
850
|
-
// Dynamically load axe-core from local vendor route if not already loaded
|
|
851
|
-
if (!window.axe) {
|
|
852
|
-
const script = document.createElement('script');
|
|
853
|
-
const _basePath = location.pathname.replace(/\\/preview$/, '');
|
|
854
|
-
script.src = _basePath + '/vendor/axe-core.min.js';
|
|
855
|
-
await new Promise((resolve, reject) => {
|
|
856
|
-
script.onload = resolve;
|
|
857
|
-
script.onerror = reject;
|
|
858
|
-
document.head.appendChild(script);
|
|
859
|
-
});
|
|
860
|
-
}
|
|
861
|
-
// Run axe-core on the .musea-variant container only (not the full document)
|
|
862
|
-
const context = document.querySelector('.musea-variant') || document;
|
|
863
|
-
const results = await window.axe.run(context, {
|
|
864
|
-
// Run all rules without restrictions for comprehensive testing
|
|
865
|
-
resultTypes: ['violations', 'incomplete', 'passes']
|
|
866
|
-
});
|
|
867
|
-
window.parent.postMessage({
|
|
868
|
-
type: 'musea:a11y-result',
|
|
869
|
-
payload: {
|
|
870
|
-
violations: results.violations.map(v => ({
|
|
871
|
-
id: v.id,
|
|
872
|
-
impact: v.impact,
|
|
873
|
-
description: v.description,
|
|
874
|
-
helpUrl: v.helpUrl,
|
|
875
|
-
nodes: v.nodes.map(n => ({
|
|
876
|
-
html: n.html,
|
|
877
|
-
target: n.target,
|
|
878
|
-
failureSummary: n.failureSummary
|
|
879
|
-
}))
|
|
880
|
-
})),
|
|
881
|
-
passes: results.passes.length,
|
|
882
|
-
incomplete: results.incomplete.length
|
|
883
|
-
}
|
|
884
|
-
}, '*');
|
|
885
|
-
} catch (err) {
|
|
886
|
-
window.parent.postMessage({
|
|
887
|
-
type: 'musea:a11y-result',
|
|
888
|
-
payload: {
|
|
889
|
-
error: err instanceof Error ? err.message : String(err),
|
|
890
|
-
violations: [],
|
|
891
|
-
passes: 0,
|
|
892
|
-
incomplete: 0
|
|
893
|
-
}
|
|
894
|
-
}, '*');
|
|
895
|
-
}
|
|
896
|
-
})();
|
|
897
|
-
break;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
// Notify parent that iframe is ready
|
|
903
|
-
window.parent.postMessage({ type: 'musea:ready', payload: {} }, '*');
|
|
904
|
-
}
|
|
905
|
-
`;
|
|
906
|
-
//#endregion
|
|
907
|
-
//#region src/preview/html.ts
|
|
908
|
-
function generatePreviewHtml(art, variant, _basePath, viteBase) {
|
|
909
|
-
const previewModuleUrl = `${_basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;
|
|
910
|
-
const base = (viteBase || "/").replace(/\/$/, "");
|
|
911
|
-
return `<!DOCTYPE html>
|
|
912
|
-
<html lang="en">
|
|
913
|
-
<head>
|
|
914
|
-
<meta charset="UTF-8">
|
|
915
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
916
|
-
<title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>
|
|
917
|
-
<script type="module" src="${base}/@vite/client"><\/script>
|
|
918
|
-
<style>
|
|
919
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
920
|
-
html, body {
|
|
921
|
-
width: 100%;
|
|
922
|
-
height: 100%;
|
|
923
|
-
}
|
|
924
|
-
body {
|
|
925
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
926
|
-
background: #ffffff;
|
|
927
|
-
}
|
|
928
|
-
.musea-variant {
|
|
929
|
-
min-height: 100vh;
|
|
930
|
-
}
|
|
931
|
-
.musea-error {
|
|
932
|
-
color: #dc2626;
|
|
933
|
-
background: #fef2f2;
|
|
934
|
-
border: 1px solid #fecaca;
|
|
935
|
-
border-radius: 8px;
|
|
936
|
-
padding: 1rem;
|
|
937
|
-
font-size: 0.875rem;
|
|
938
|
-
max-width: 400px;
|
|
939
|
-
}
|
|
940
|
-
.musea-error-title {
|
|
941
|
-
font-weight: 600;
|
|
942
|
-
margin-bottom: 0.5rem;
|
|
943
|
-
}
|
|
944
|
-
.musea-error pre {
|
|
945
|
-
font-family: monospace;
|
|
946
|
-
font-size: 0.75rem;
|
|
947
|
-
white-space: pre-wrap;
|
|
948
|
-
word-break: break-all;
|
|
949
|
-
margin-top: 0.5rem;
|
|
950
|
-
padding: 0.5rem;
|
|
951
|
-
background: #fff;
|
|
952
|
-
border-radius: 4px;
|
|
953
|
-
}
|
|
954
|
-
.musea-loading {
|
|
955
|
-
display: flex;
|
|
956
|
-
align-items: center;
|
|
957
|
-
gap: 0.75rem;
|
|
958
|
-
color: #6b7280;
|
|
959
|
-
font-size: 0.875rem;
|
|
960
|
-
}
|
|
961
|
-
.musea-spinner {
|
|
962
|
-
width: 20px;
|
|
963
|
-
height: 20px;
|
|
964
|
-
border: 2px solid #e5e7eb;
|
|
965
|
-
border-top-color: #3b82f6;
|
|
966
|
-
border-radius: 50%;
|
|
967
|
-
animation: spin 0.8s linear infinite;
|
|
968
|
-
}
|
|
969
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
970
|
-
|
|
971
|
-
/* Musea Addons: Checkerboard background for transparent mode */
|
|
972
|
-
.musea-bg-checkerboard {
|
|
973
|
-
background-image:
|
|
974
|
-
linear-gradient(45deg, #ccc 25%, transparent 25%),
|
|
975
|
-
linear-gradient(-45deg, #ccc 25%, transparent 25%),
|
|
976
|
-
linear-gradient(45deg, transparent 75%, #ccc 75%),
|
|
977
|
-
linear-gradient(-45deg, transparent 75%, #ccc 75%) !important;
|
|
978
|
-
background-size: 20px 20px !important;
|
|
979
|
-
background-position: 0 0, 0 10px, 10px -10px, -10px 0 !important;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
/* Musea Addons: Measure label */
|
|
983
|
-
.musea-measure-label {
|
|
984
|
-
position: fixed;
|
|
985
|
-
background: #333;
|
|
986
|
-
color: #fff;
|
|
987
|
-
font-size: 11px;
|
|
988
|
-
padding: 2px 6px;
|
|
989
|
-
border-radius: 3px;
|
|
990
|
-
pointer-events: none;
|
|
991
|
-
z-index: 100000;
|
|
992
|
-
}
|
|
993
|
-
</style>
|
|
994
|
-
</head>
|
|
995
|
-
<body>
|
|
996
|
-
<div id="app" class="musea-variant" data-art="${escapeHtml(art.path)}" data-variant="${escapeHtml(variant.name)}">
|
|
997
|
-
<div class="musea-loading">
|
|
998
|
-
<div class="musea-spinner"></div>
|
|
999
|
-
Loading component...
|
|
1000
|
-
</div>
|
|
1001
|
-
</div>
|
|
1002
|
-
<script type="module" src="${previewModuleUrl}"><\/script>
|
|
1003
|
-
</body>
|
|
1004
|
-
</html>`;
|
|
1005
|
-
}
|
|
1006
|
-
//#endregion
|
|
1007
|
-
//#region src/preview/index.ts
|
|
1008
|
-
function generatePreviewModule(art, variantComponentName, variantName, cssImports = [], previewSetup = null) {
|
|
1009
|
-
const artModuleId = `virtual:musea-art:${art.path}`;
|
|
1010
|
-
const escapedVariantName = escapeTemplate(variantName);
|
|
1011
|
-
const cssImportStatements = cssImports.map((cssPath) => `import '${cssPath}';`).join("\n");
|
|
1012
|
-
const setupImport = previewSetup ? `import __museaPreviewSetup from '${previewSetup}';` : "";
|
|
1013
|
-
const setupCall = previewSetup ? "await __museaPreviewSetup(app);" : "";
|
|
1014
|
-
return `
|
|
1015
|
-
${cssImportStatements}
|
|
1016
|
-
${setupImport}
|
|
1017
|
-
import { createApp, reactive, h } from 'vue';
|
|
1018
|
-
import * as artModule from '${artModuleId}';
|
|
1019
|
-
|
|
1020
|
-
const container = document.getElementById('app');
|
|
1021
|
-
|
|
1022
|
-
${MUSEA_ADDONS_INIT_CODE}
|
|
1023
|
-
|
|
1024
|
-
let currentApp = null;
|
|
1025
|
-
const propsOverride = reactive({});
|
|
1026
|
-
const slotsOverride = reactive({ default: '' });
|
|
1027
|
-
|
|
1028
|
-
window.__museaSetProps = (props) => {
|
|
1029
|
-
// Clear old keys
|
|
1030
|
-
for (const key of Object.keys(propsOverride)) {
|
|
1031
|
-
delete propsOverride[key];
|
|
1032
|
-
}
|
|
1033
|
-
Object.assign(propsOverride, props);
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
window.__museaSetSlots = (slots) => {
|
|
1037
|
-
for (const key of Object.keys(slotsOverride)) {
|
|
1038
|
-
delete slotsOverride[key];
|
|
1039
|
-
}
|
|
1040
|
-
Object.assign(slotsOverride, slots);
|
|
1041
|
-
};
|
|
1042
|
-
|
|
1043
|
-
async function mount() {
|
|
1044
|
-
try {
|
|
1045
|
-
// Get the specific variant component
|
|
1046
|
-
const VariantComponent = artModule['${variantComponentName}'];
|
|
1047
|
-
const RawComponent = artModule.__component__;
|
|
1048
|
-
|
|
1049
|
-
if (!VariantComponent) {
|
|
1050
|
-
throw new Error('Variant component "${variantComponentName}" not found in art module');
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
// Create and mount the app
|
|
1054
|
-
const app = createApp(VariantComponent);
|
|
1055
|
-
${setupCall}
|
|
1056
|
-
container.innerHTML = '';
|
|
1057
|
-
container.className = 'musea-variant';
|
|
1058
|
-
app.mount(container);
|
|
1059
|
-
currentApp = app;
|
|
1060
|
-
|
|
1061
|
-
console.log('[musea-preview] Mounted variant: ${escapedVariantName}');
|
|
1062
|
-
__museaInitAddons(container, '${escapedVariantName}');
|
|
1063
|
-
|
|
1064
|
-
// Override set-props to remount with raw component + props
|
|
1065
|
-
const TargetComponent = RawComponent || VariantComponent;
|
|
1066
|
-
window.__museaSetProps = (props) => {
|
|
1067
|
-
for (const key of Object.keys(propsOverride)) {
|
|
1068
|
-
delete propsOverride[key];
|
|
1069
|
-
}
|
|
1070
|
-
Object.assign(propsOverride, props);
|
|
1071
|
-
remountWithProps(TargetComponent);
|
|
1072
|
-
};
|
|
1073
|
-
window.__museaSetSlots = (slots) => {
|
|
1074
|
-
for (const key of Object.keys(slotsOverride)) {
|
|
1075
|
-
delete slotsOverride[key];
|
|
1076
|
-
}
|
|
1077
|
-
Object.assign(slotsOverride, slots);
|
|
1078
|
-
remountWithProps(TargetComponent);
|
|
1079
|
-
};
|
|
1080
|
-
} catch (error) {
|
|
1081
|
-
console.error('[musea-preview] Failed to mount:', error);
|
|
1082
|
-
container.innerHTML = \`
|
|
1083
|
-
<div class="musea-error">
|
|
1084
|
-
<div class="musea-error-title">Failed to render component</div>
|
|
1085
|
-
<div>\${error.message}</div>
|
|
1086
|
-
<pre>\${error.stack || ''}</pre>
|
|
1087
|
-
</div>
|
|
1088
|
-
\`;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
async function remountWithProps(Component) {
|
|
1093
|
-
if (currentApp) {
|
|
1094
|
-
currentApp.unmount();
|
|
1095
|
-
}
|
|
1096
|
-
const app = createApp({
|
|
1097
|
-
setup() {
|
|
1098
|
-
return () => {
|
|
1099
|
-
const slotFns = {};
|
|
1100
|
-
for (const [name, content] of Object.entries(slotsOverride)) {
|
|
1101
|
-
if (content) slotFns[name] = () => h('span', { innerHTML: content });
|
|
1102
|
-
}
|
|
1103
|
-
return h(Component, { ...propsOverride }, slotFns);
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
});
|
|
1107
|
-
${setupCall}
|
|
1108
|
-
container.innerHTML = '';
|
|
1109
|
-
app.mount(container);
|
|
1110
|
-
currentApp = app;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
mount();
|
|
1114
|
-
`;
|
|
1115
|
-
}
|
|
1116
|
-
function generatePreviewModuleWithProps(art, variantComponentName, variantName, propsOverride, cssImports = [], previewSetup = null) {
|
|
1117
|
-
const artModuleId = `virtual:musea-art:${art.path}`;
|
|
1118
|
-
const escapedVariantName = escapeTemplate(variantName);
|
|
1119
|
-
const propsJson = JSON.stringify(propsOverride);
|
|
1120
|
-
return `
|
|
1121
|
-
${cssImports.map((cssPath) => `import '${cssPath}';`).join("\n")}
|
|
1122
|
-
${previewSetup ? `import __museaPreviewSetup from '${previewSetup}';` : ""}
|
|
1123
|
-
import { createApp, h } from 'vue';
|
|
1124
|
-
import * as artModule from '${artModuleId}';
|
|
1125
|
-
|
|
1126
|
-
const container = document.getElementById('app');
|
|
1127
|
-
const propsOverride = ${propsJson};
|
|
1128
|
-
|
|
1129
|
-
${MUSEA_ADDONS_INIT_CODE}
|
|
1130
|
-
|
|
1131
|
-
async function mount() {
|
|
1132
|
-
try {
|
|
1133
|
-
const VariantComponent = artModule['${variantComponentName}'];
|
|
1134
|
-
if (!VariantComponent) {
|
|
1135
|
-
throw new Error('Variant component "${variantComponentName}" not found');
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
const WrappedComponent = {
|
|
1139
|
-
render() {
|
|
1140
|
-
return h(VariantComponent, propsOverride);
|
|
1141
|
-
}
|
|
1142
|
-
};
|
|
1143
|
-
|
|
1144
|
-
const app = createApp(WrappedComponent);
|
|
1145
|
-
${previewSetup ? "await __museaPreviewSetup(app);" : ""}
|
|
1146
|
-
container.innerHTML = '';
|
|
1147
|
-
container.className = 'musea-variant';
|
|
1148
|
-
app.mount(container);
|
|
1149
|
-
console.log('[musea-preview] Mounted variant: ${escapedVariantName} with props override');
|
|
1150
|
-
__museaInitAddons(container, '${escapedVariantName}');
|
|
1151
|
-
} catch (error) {
|
|
1152
|
-
console.error('[musea-preview] Failed to mount:', error);
|
|
1153
|
-
container.innerHTML = '<div class="musea-error"><div class="musea-error-title">Failed to render</div><div>' + error.message + '</div></div>';
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
mount();
|
|
1158
|
-
`;
|
|
1159
|
-
}
|
|
1160
|
-
//#endregion
|
|
1161
|
-
//#region src/server-middleware.ts
|
|
1162
|
-
/**
|
|
1163
|
-
* Register all Musea middleware on the given dev server.
|
|
1164
|
-
*
|
|
1165
|
-
* This sets up:
|
|
1166
|
-
* - Gallery SPA route (serves built SPA or inline HTML fallback)
|
|
1167
|
-
* - Gallery static assets (/assets/)
|
|
1168
|
-
* - axe-core vendor script
|
|
1169
|
-
* - Preview module route
|
|
1170
|
-
* - VRT preview route
|
|
1171
|
-
* - Art module route
|
|
1172
|
-
*/
|
|
1173
|
-
function registerMiddleware(devServer, ctx) {
|
|
1174
|
-
const { basePath, themeConfig, artFiles } = ctx;
|
|
1175
|
-
devServer.middlewares.use(basePath, async (req, res, next) => {
|
|
1176
|
-
const url = req.url || "/";
|
|
1177
|
-
if (url === "/" || url === "/index.html" || url.startsWith("/tokens") || url.startsWith("/component/") || url.startsWith("/tests")) {
|
|
1178
|
-
const galleryDistDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), "gallery");
|
|
1179
|
-
const indexHtmlPath = path.join(galleryDistDir, "index.html");
|
|
1180
|
-
try {
|
|
1181
|
-
await fs.promises.access(indexHtmlPath);
|
|
1182
|
-
let html = await fs.promises.readFile(indexHtmlPath, "utf-8");
|
|
1183
|
-
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : "";
|
|
1184
|
-
html = html.replace("</head>", `<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}<\/script></head>`);
|
|
1185
|
-
res.setHeader("Content-Type", "text/html");
|
|
1186
|
-
res.end(html);
|
|
1187
|
-
return;
|
|
1188
|
-
} catch {
|
|
1189
|
-
const html = generateGalleryHtml(basePath, themeConfig);
|
|
1190
|
-
res.setHeader("Content-Type", "text/html");
|
|
1191
|
-
res.end(html);
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
}
|
|
1195
|
-
if (url.startsWith("/assets/")) {
|
|
1196
|
-
const galleryDistDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), "gallery");
|
|
1197
|
-
const filePath = path.join(galleryDistDir, url);
|
|
1198
|
-
try {
|
|
1199
|
-
if ((await fs.promises.stat(filePath)).isFile()) {
|
|
1200
|
-
const content = await fs.promises.readFile(filePath);
|
|
1201
|
-
const ext = path.extname(filePath);
|
|
1202
|
-
res.setHeader("Content-Type", {
|
|
1203
|
-
".js": "application/javascript",
|
|
1204
|
-
".css": "text/css",
|
|
1205
|
-
".svg": "image/svg+xml",
|
|
1206
|
-
".png": "image/png",
|
|
1207
|
-
".ico": "image/x-icon",
|
|
1208
|
-
".woff2": "font/woff2",
|
|
1209
|
-
".woff": "font/woff"
|
|
1210
|
-
}[ext] || "application/octet-stream");
|
|
1211
|
-
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
|
1212
|
-
res.end(content);
|
|
1213
|
-
return;
|
|
1214
|
-
}
|
|
1215
|
-
} catch {}
|
|
1216
|
-
}
|
|
1217
|
-
next();
|
|
1218
|
-
});
|
|
1219
|
-
devServer.middlewares.use(`${basePath}/vendor/axe-core.min.js`, async (_req, res, _next) => {
|
|
1220
|
-
try {
|
|
1221
|
-
const axeCorePath = createRequire(import.meta.url).resolve("axe-core/axe.min.js");
|
|
1222
|
-
const content = await fs.promises.readFile(axeCorePath, "utf-8");
|
|
1223
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
1224
|
-
res.setHeader("Cache-Control", "public, max-age=86400");
|
|
1225
|
-
res.end(content);
|
|
1226
|
-
} catch {
|
|
1227
|
-
res.statusCode = 404;
|
|
1228
|
-
res.end("axe-core not installed");
|
|
1229
|
-
}
|
|
1230
|
-
});
|
|
1231
|
-
devServer.middlewares.use(`${basePath}/preview-module`, async (req, res, _next) => {
|
|
1232
|
-
const url = new URL(req.url || "", `http://localhost`);
|
|
1233
|
-
const artPath = url.searchParams.get("art");
|
|
1234
|
-
const variantName = url.searchParams.get("variant");
|
|
1235
|
-
if (!artPath || !variantName) {
|
|
1236
|
-
res.statusCode = 400;
|
|
1237
|
-
res.end("Missing art or variant parameter");
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
const art = artFiles.get(artPath);
|
|
1241
|
-
if (!art) {
|
|
1242
|
-
res.statusCode = 404;
|
|
1243
|
-
res.end("Art not found");
|
|
1244
|
-
return;
|
|
1245
|
-
}
|
|
1246
|
-
const variant = art.variants.find((v) => v.name === variantName);
|
|
1247
|
-
if (!variant) {
|
|
1248
|
-
res.statusCode = 404;
|
|
1249
|
-
res.end("Variant not found");
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
const moduleCode = generatePreviewModule(art, toPascalCase(variant.name), variant.name, ctx.resolvedPreviewCss, ctx.resolvedPreviewSetup);
|
|
1253
|
-
try {
|
|
1254
|
-
const result = await devServer.transformRequest(`virtual:musea-preview:${artPath}:${variantName}`);
|
|
1255
|
-
if (result) {
|
|
1256
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
1257
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
1258
|
-
res.end(result.code);
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
} catch {}
|
|
1262
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
1263
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
1264
|
-
res.end(moduleCode);
|
|
1265
|
-
});
|
|
1266
|
-
devServer.middlewares.use(`${basePath}/preview`, async (req, res, _next) => {
|
|
1267
|
-
const url = new URL(req.url || "", `http://localhost`);
|
|
1268
|
-
const artPath = url.searchParams.get("art");
|
|
1269
|
-
const variantName = url.searchParams.get("variant");
|
|
1270
|
-
if (!artPath || !variantName) {
|
|
1271
|
-
res.statusCode = 400;
|
|
1272
|
-
res.end("Missing art or variant parameter");
|
|
1273
|
-
return;
|
|
1274
|
-
}
|
|
1275
|
-
const art = artFiles.get(artPath);
|
|
1276
|
-
if (!art) {
|
|
1277
|
-
res.statusCode = 404;
|
|
1278
|
-
res.end("Art not found");
|
|
1279
|
-
return;
|
|
1280
|
-
}
|
|
1281
|
-
const variant = art.variants.find((v) => v.name === variantName);
|
|
1282
|
-
if (!variant) {
|
|
1283
|
-
res.statusCode = 404;
|
|
1284
|
-
res.end("Variant not found");
|
|
1285
|
-
return;
|
|
1286
|
-
}
|
|
1287
|
-
const config = devServer.config;
|
|
1288
|
-
const html = generatePreviewHtml(art, variant, basePath, config.base);
|
|
1289
|
-
res.setHeader("Content-Type", "text/html");
|
|
1290
|
-
res.end(html);
|
|
1291
|
-
});
|
|
1292
|
-
devServer.middlewares.use(`${basePath}/art`, async (req, res, next) => {
|
|
1293
|
-
const url = new URL(req.url || "", "http://localhost");
|
|
1294
|
-
const artPath = decodeURIComponent(url.pathname.slice(1));
|
|
1295
|
-
if (!artPath) {
|
|
1296
|
-
next();
|
|
1297
|
-
return;
|
|
1298
|
-
}
|
|
1299
|
-
const art = artFiles.get(artPath);
|
|
1300
|
-
if (!art) {
|
|
1301
|
-
res.statusCode = 404;
|
|
1302
|
-
res.end("Art not found: " + artPath);
|
|
1303
|
-
return;
|
|
1304
|
-
}
|
|
1305
|
-
try {
|
|
1306
|
-
const virtualId = `virtual:musea-art:${artPath}`;
|
|
1307
|
-
const result = await devServer.transformRequest(virtualId);
|
|
1308
|
-
if (result) {
|
|
1309
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
1310
|
-
res.setHeader("Cache-Control", "no-cache");
|
|
1311
|
-
res.end(result.code);
|
|
1312
|
-
} else {
|
|
1313
|
-
const moduleCode = generateArtModule(art, artPath);
|
|
1314
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
1315
|
-
res.end(moduleCode);
|
|
1316
|
-
}
|
|
1317
|
-
} catch (err) {
|
|
1318
|
-
console.error("[musea] Failed to transform art module:", err);
|
|
1319
|
-
const moduleCode = generateArtModule(art, artPath);
|
|
1320
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
1321
|
-
res.end(moduleCode);
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
|
-
}
|
|
1325
|
-
//#endregion
|
|
1326
|
-
//#region src/tokens/parser.ts
|
|
1327
|
-
/**
|
|
1328
|
-
* Token parsing utilities for Style Dictionary integration.
|
|
1329
|
-
*
|
|
1330
|
-
* Reads and parses design token files (JSON) and directories,
|
|
1331
|
-
* flattening nested structures into categorized token collections.
|
|
1332
|
-
*/
|
|
1333
|
-
/**
|
|
1334
|
-
* Parse Style Dictionary tokens file.
|
|
1335
|
-
*/
|
|
1336
|
-
async function parseTokens(tokensPath) {
|
|
1337
|
-
const absolutePath = path.resolve(tokensPath);
|
|
1338
|
-
if ((await fs.promises.stat(absolutePath)).isDirectory()) return parseTokenDirectory(absolutePath);
|
|
1339
|
-
const content = await fs.promises.readFile(absolutePath, "utf-8");
|
|
1340
|
-
return flattenTokens(JSON.parse(content));
|
|
1341
|
-
}
|
|
1342
|
-
/**
|
|
1343
|
-
* Parse tokens from a directory.
|
|
1344
|
-
*/
|
|
1345
|
-
async function parseTokenDirectory(dirPath) {
|
|
1346
|
-
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
1347
|
-
const categories = [];
|
|
1348
|
-
for (const entry of entries) if (entry.isFile() && (entry.name.endsWith(".json") || entry.name.endsWith(".tokens.json"))) {
|
|
1349
|
-
const filePath = path.join(dirPath, entry.name);
|
|
1350
|
-
const content = await fs.promises.readFile(filePath, "utf-8");
|
|
1351
|
-
const tokens = JSON.parse(content);
|
|
1352
|
-
const categoryName = path.basename(entry.name, path.extname(entry.name)).replace(".tokens", "");
|
|
1353
|
-
categories.push({
|
|
1354
|
-
name: formatCategoryName(categoryName),
|
|
1355
|
-
tokens: extractTokens(tokens),
|
|
1356
|
-
subcategories: extractSubcategories(tokens)
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
|
-
return categories;
|
|
1360
|
-
}
|
|
1361
|
-
/**
|
|
1362
|
-
* Flatten nested token structure into categories.
|
|
1363
|
-
*/
|
|
1364
|
-
function flattenTokens(tokens, prefix = []) {
|
|
1365
|
-
const categories = [];
|
|
1366
|
-
for (const [key, value] of Object.entries(tokens)) {
|
|
1367
|
-
if (isTokenValue(value)) continue;
|
|
1368
|
-
if (typeof value === "object" && value !== null) {
|
|
1369
|
-
const categoryTokens = extractTokens(value);
|
|
1370
|
-
const subcategories = flattenTokens(value, [...prefix, key]);
|
|
1371
|
-
if (Object.keys(categoryTokens).length > 0 || subcategories.length > 0) categories.push({
|
|
1372
|
-
name: formatCategoryName(key),
|
|
1373
|
-
tokens: categoryTokens,
|
|
1374
|
-
subcategories: subcategories.length > 0 ? subcategories : void 0
|
|
1375
|
-
});
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
return categories;
|
|
1379
|
-
}
|
|
1380
|
-
/**
|
|
1381
|
-
* Extract token values from an object.
|
|
1382
|
-
*/
|
|
1383
|
-
function extractTokens(obj) {
|
|
1384
|
-
const tokens = {};
|
|
1385
|
-
for (const [key, value] of Object.entries(obj)) if (isTokenValue(value)) tokens[key] = normalizeToken(value);
|
|
1386
|
-
return tokens;
|
|
1387
|
-
}
|
|
1388
|
-
/**
|
|
1389
|
-
* Extract subcategories from an object.
|
|
1390
|
-
*/
|
|
1391
|
-
function extractSubcategories(obj) {
|
|
1392
|
-
const subcategories = [];
|
|
1393
|
-
for (const [key, value] of Object.entries(obj)) if (!isTokenValue(value) && typeof value === "object" && value !== null) {
|
|
1394
|
-
const categoryTokens = extractTokens(value);
|
|
1395
|
-
const nested = extractSubcategories(value);
|
|
1396
|
-
if (Object.keys(categoryTokens).length > 0 || nested && nested.length > 0) subcategories.push({
|
|
1397
|
-
name: formatCategoryName(key),
|
|
1398
|
-
tokens: categoryTokens,
|
|
1399
|
-
subcategories: nested
|
|
1400
|
-
});
|
|
1401
|
-
}
|
|
1402
|
-
return subcategories.length > 0 ? subcategories : void 0;
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
|
-
* Check if a value is a token definition.
|
|
1406
|
-
*/
|
|
1407
|
-
function isTokenValue(value) {
|
|
1408
|
-
if (typeof value !== "object" || value === null) return false;
|
|
1409
|
-
const obj = value;
|
|
1410
|
-
return "value" in obj && (typeof obj.value === "string" || typeof obj.value === "number") || "$value" in obj && (typeof obj.$value === "string" || typeof obj.$value === "number");
|
|
1411
|
-
}
|
|
1412
|
-
/**
|
|
1413
|
-
* Normalize token to DesignToken interface.
|
|
1414
|
-
*/
|
|
1415
|
-
function normalizeToken(raw) {
|
|
1416
|
-
const token = {
|
|
1417
|
-
value: raw.value ?? raw.$value,
|
|
1418
|
-
type: raw.type ?? raw.$type,
|
|
1419
|
-
description: raw.description,
|
|
1420
|
-
attributes: raw.attributes
|
|
1421
|
-
};
|
|
1422
|
-
if (raw.$tier === "primitive" || raw.$tier === "semantic") token.$tier = raw.$tier;
|
|
1423
|
-
if (typeof raw.$reference === "string") token.$reference = raw.$reference;
|
|
1424
|
-
return token;
|
|
1425
|
-
}
|
|
1426
|
-
/**
|
|
1427
|
-
* Format category name for display.
|
|
1428
|
-
*/
|
|
1429
|
-
function formatCategoryName(name) {
|
|
1430
|
-
return name.replace(/[-_]/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").split(" ").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(" ");
|
|
1431
|
-
}
|
|
1432
|
-
//#endregion
|
|
1433
|
-
//#region src/tokens/usage.ts
|
|
1434
|
-
/**
|
|
1435
|
-
* Token usage scanning and value normalization.
|
|
1436
|
-
*
|
|
1437
|
-
* Scans art file `<style>` blocks for CSS property values that match
|
|
1438
|
-
* design token values, and provides value normalization utilities.
|
|
1439
|
-
*/
|
|
1440
|
-
/**
|
|
1441
|
-
* Normalize a token value for comparison.
|
|
1442
|
-
* - Lowercase, trim
|
|
1443
|
-
* - Leading-zero: `.5rem` -> `0.5rem`
|
|
1444
|
-
* - Short hex: `#fff` -> `#ffffff`
|
|
1445
|
-
*/
|
|
1446
|
-
function normalizeTokenValue(value) {
|
|
1447
|
-
let v = String(value).trim().toLowerCase();
|
|
1448
|
-
const shortHex = v.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/);
|
|
1449
|
-
if (shortHex) {
|
|
1450
|
-
const [, r, g, b, a] = shortHex;
|
|
1451
|
-
v = a ? `#${r}${r}${g}${g}${b}${b}${a}${a}` : `#${r}${r}${g}${g}${b}${b}`;
|
|
1452
|
-
}
|
|
1453
|
-
v = v.replace(/(?<![0-9])\.(\d)/g, "0.$1");
|
|
1454
|
-
return v;
|
|
1455
|
-
}
|
|
1456
|
-
const STYLE_BLOCK_RE = /<style[^>]*>([\s\S]*?)<\/style>/g;
|
|
1457
|
-
const CSS_PROPERTY_RE = /^\s*([\w-]+)\s*:\s*(.+?)\s*;?\s*$/;
|
|
1458
|
-
/**
|
|
1459
|
-
* Scan art file sources for token value matches in `<style>` blocks.
|
|
1460
|
-
*/
|
|
1461
|
-
function scanTokenUsage(artFiles, tokenMap) {
|
|
1462
|
-
const valueLookup = /* @__PURE__ */ new Map();
|
|
1463
|
-
for (const [tokenPath, token] of Object.entries(tokenMap)) {
|
|
1464
|
-
const normalized = normalizeTokenValue(token.$resolvedValue ?? token.value);
|
|
1465
|
-
if (!normalized) continue;
|
|
1466
|
-
const existing = valueLookup.get(normalized);
|
|
1467
|
-
if (existing) existing.push(tokenPath);
|
|
1468
|
-
else valueLookup.set(normalized, [tokenPath]);
|
|
1469
|
-
}
|
|
1470
|
-
const usageMap = {};
|
|
1471
|
-
for (const [artPath, artInfo] of artFiles) {
|
|
1472
|
-
let source;
|
|
1473
|
-
try {
|
|
1474
|
-
source = fs.readFileSync(artPath, "utf-8");
|
|
1475
|
-
} catch {
|
|
1476
|
-
continue;
|
|
1477
|
-
}
|
|
1478
|
-
const allLines = source.split("\n");
|
|
1479
|
-
const styleRegions = [];
|
|
1480
|
-
let match;
|
|
1481
|
-
STYLE_BLOCK_RE.lastIndex = 0;
|
|
1482
|
-
while ((match = STYLE_BLOCK_RE.exec(source)) !== null) {
|
|
1483
|
-
const beforeMatch = source.slice(0, match.index);
|
|
1484
|
-
const startTag = source.slice(match.index, match.index + match[0].indexOf(match[1]));
|
|
1485
|
-
const startLine = beforeMatch.split("\n").length + startTag.split("\n").length - 1;
|
|
1486
|
-
styleRegions.push({
|
|
1487
|
-
startLine,
|
|
1488
|
-
content: match[1]
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
for (const region of styleRegions) {
|
|
1492
|
-
const lines = region.content.split("\n");
|
|
1493
|
-
for (let i = 0; i < lines.length; i++) {
|
|
1494
|
-
const line = lines[i];
|
|
1495
|
-
const propMatch = line.match(CSS_PROPERTY_RE);
|
|
1496
|
-
if (!propMatch) continue;
|
|
1497
|
-
const property = propMatch[1];
|
|
1498
|
-
const valueParts = propMatch[2].split(/\s+/);
|
|
1499
|
-
for (const part of valueParts) {
|
|
1500
|
-
const normalizedPart = normalizeTokenValue(part);
|
|
1501
|
-
const matchingTokens = valueLookup.get(normalizedPart);
|
|
1502
|
-
if (!matchingTokens) continue;
|
|
1503
|
-
const lineNumber = region.startLine + i;
|
|
1504
|
-
const lineContent = allLines[lineNumber - 1]?.trim() ?? line.trim();
|
|
1505
|
-
for (const tokenPath of matchingTokens) {
|
|
1506
|
-
if (!usageMap[tokenPath]) usageMap[tokenPath] = [];
|
|
1507
|
-
let entry = usageMap[tokenPath].find((e) => e.artPath === artPath);
|
|
1508
|
-
if (!entry) {
|
|
1509
|
-
entry = {
|
|
1510
|
-
artPath,
|
|
1511
|
-
artTitle: artInfo.metadata.title,
|
|
1512
|
-
artCategory: artInfo.metadata.category,
|
|
1513
|
-
matches: []
|
|
1514
|
-
};
|
|
1515
|
-
usageMap[tokenPath].push(entry);
|
|
1516
|
-
}
|
|
1517
|
-
if (!entry.matches.some((m) => m.line === lineNumber && m.property === property)) entry.matches.push({
|
|
1518
|
-
line: lineNumber,
|
|
1519
|
-
lineContent,
|
|
1520
|
-
property
|
|
1521
|
-
});
|
|
1522
|
-
}
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
return usageMap;
|
|
1528
|
-
}
|
|
1529
|
-
//#endregion
|
|
1530
|
-
//#region src/tokens/resolver.ts
|
|
1531
|
-
/**
|
|
1532
|
-
* Token resolution, CRUD operations, and validation.
|
|
1533
|
-
*
|
|
1534
|
-
* Handles building flat token maps from categories, resolving reference chains,
|
|
1535
|
-
* reading/writing raw token files, and validating semantic references.
|
|
1536
|
-
*/
|
|
1537
|
-
const REFERENCE_PATTERN = /^\{(.+)\}$/;
|
|
1538
|
-
const MAX_RESOLVE_DEPTH = 10;
|
|
1539
|
-
/**
|
|
1540
|
-
* Flatten nested categories into a flat map keyed by dot-path.
|
|
1541
|
-
*/
|
|
1542
|
-
function buildTokenMap(categories, prefix = []) {
|
|
1543
|
-
const map = {};
|
|
1544
|
-
for (const cat of categories) {
|
|
1545
|
-
const catKey = cat.name.toLowerCase().replace(/\s+/g, "-");
|
|
1546
|
-
const catPath = [...prefix, catKey];
|
|
1547
|
-
for (const [name, token] of Object.entries(cat.tokens)) {
|
|
1548
|
-
const dotPath = [...catPath, name].join(".");
|
|
1549
|
-
map[dotPath] = token;
|
|
1550
|
-
}
|
|
1551
|
-
if (cat.subcategories) {
|
|
1552
|
-
const subMap = buildTokenMap(cat.subcategories, catPath);
|
|
1553
|
-
Object.assign(map, subMap);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
return map;
|
|
1557
|
-
}
|
|
1558
|
-
/**
|
|
1559
|
-
* Resolve references in categories, setting $tier, $reference, and $resolvedValue.
|
|
1560
|
-
*/
|
|
1561
|
-
function resolveReferences(categories, tokenMap) {
|
|
1562
|
-
for (const cat of categories) {
|
|
1563
|
-
for (const token of Object.values(cat.tokens)) resolveTokenReference(token, tokenMap);
|
|
1564
|
-
if (cat.subcategories) resolveReferences(cat.subcategories, tokenMap);
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
function resolveTokenReference(token, tokenMap) {
|
|
1568
|
-
if (typeof token.value === "string") {
|
|
1569
|
-
const match = token.value.match(REFERENCE_PATTERN);
|
|
1570
|
-
if (match) {
|
|
1571
|
-
token.$tier = token.$tier ?? "semantic";
|
|
1572
|
-
token.$reference = match[1];
|
|
1573
|
-
token.$resolvedValue = resolveValue(match[1], tokenMap, 0, /* @__PURE__ */ new Set());
|
|
1574
|
-
return;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
token.$tier = token.$tier ?? "primitive";
|
|
1578
|
-
}
|
|
1579
|
-
function resolveValue(ref, tokenMap, depth, visited) {
|
|
1580
|
-
if (depth >= MAX_RESOLVE_DEPTH || visited.has(ref)) return void 0;
|
|
1581
|
-
visited.add(ref);
|
|
1582
|
-
const target = tokenMap[ref];
|
|
1583
|
-
if (!target) return void 0;
|
|
1584
|
-
if (typeof target.value === "string") {
|
|
1585
|
-
const match = target.value.match(REFERENCE_PATTERN);
|
|
1586
|
-
if (match) return resolveValue(match[1], tokenMap, depth + 1, visited);
|
|
1587
|
-
}
|
|
1588
|
-
return target.value;
|
|
1589
|
-
}
|
|
1590
|
-
/**
|
|
1591
|
-
* Read raw JSON token file.
|
|
1592
|
-
*/
|
|
1593
|
-
async function readRawTokenFile(tokensPath) {
|
|
1594
|
-
const content = await fs.promises.readFile(tokensPath, "utf-8");
|
|
1595
|
-
return JSON.parse(content);
|
|
1596
|
-
}
|
|
1597
|
-
/**
|
|
1598
|
-
* Write raw JSON token file atomically (write tmp, rename).
|
|
1599
|
-
*/
|
|
1600
|
-
async function writeRawTokenFile(tokensPath, data) {
|
|
1601
|
-
const tmpPath = tokensPath + ".tmp";
|
|
1602
|
-
await fs.promises.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
1603
|
-
await fs.promises.rename(tmpPath, tokensPath);
|
|
1604
|
-
}
|
|
1605
|
-
/**
|
|
1606
|
-
* Set a token at a dot-separated path in the raw JSON structure.
|
|
1607
|
-
*/
|
|
1608
|
-
function setTokenAtPath(data, dotPath, token) {
|
|
1609
|
-
const parts = dotPath.split(".");
|
|
1610
|
-
let current = data;
|
|
1611
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1612
|
-
const key = parts[i];
|
|
1613
|
-
if (typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
1614
|
-
current = current[key];
|
|
1615
|
-
}
|
|
1616
|
-
const leafKey = parts[parts.length - 1];
|
|
1617
|
-
const raw = { value: token.value };
|
|
1618
|
-
if (token.type) raw.type = token.type;
|
|
1619
|
-
if (token.description) raw.description = token.description;
|
|
1620
|
-
if (token.$tier) raw.$tier = token.$tier;
|
|
1621
|
-
if (token.$reference) raw.$reference = token.$reference;
|
|
1622
|
-
if (token.attributes) raw.attributes = token.attributes;
|
|
1623
|
-
current[leafKey] = raw;
|
|
1624
|
-
}
|
|
1625
|
-
/**
|
|
1626
|
-
* Delete a token at a dot-separated path, cleaning empty parents.
|
|
1627
|
-
*/
|
|
1628
|
-
function deleteTokenAtPath(data, dotPath) {
|
|
1629
|
-
const parts = dotPath.split(".");
|
|
1630
|
-
const parents = [];
|
|
1631
|
-
let current = data;
|
|
1632
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1633
|
-
const key = parts[i];
|
|
1634
|
-
if (typeof current[key] !== "object" || current[key] === null) return false;
|
|
1635
|
-
parents.push({
|
|
1636
|
-
obj: current,
|
|
1637
|
-
key
|
|
1638
|
-
});
|
|
1639
|
-
current = current[key];
|
|
1640
|
-
}
|
|
1641
|
-
const leafKey = parts[parts.length - 1];
|
|
1642
|
-
if (!(leafKey in current)) return false;
|
|
1643
|
-
delete current[leafKey];
|
|
1644
|
-
for (let i = parents.length - 1; i >= 0; i--) {
|
|
1645
|
-
const { obj, key } = parents[i];
|
|
1646
|
-
const child = obj[key];
|
|
1647
|
-
if (Object.keys(child).length === 0) delete obj[key];
|
|
1648
|
-
else break;
|
|
1649
|
-
}
|
|
1650
|
-
return true;
|
|
1651
|
-
}
|
|
1652
|
-
/**
|
|
1653
|
-
* Validate that a semantic reference points to an existing token and has no cycles.
|
|
1654
|
-
*/
|
|
1655
|
-
function validateSemanticReference(tokenMap, reference, selfPath) {
|
|
1656
|
-
if (!tokenMap[reference]) return {
|
|
1657
|
-
valid: false,
|
|
1658
|
-
error: `Reference target "${reference}" does not exist`
|
|
1659
|
-
};
|
|
1660
|
-
const visited = /* @__PURE__ */ new Set();
|
|
1661
|
-
if (selfPath) visited.add(selfPath);
|
|
1662
|
-
let current = reference;
|
|
1663
|
-
let depth = 0;
|
|
1664
|
-
while (depth < MAX_RESOLVE_DEPTH) {
|
|
1665
|
-
if (visited.has(current)) return {
|
|
1666
|
-
valid: false,
|
|
1667
|
-
error: `Circular reference detected at "${current}"`
|
|
1668
|
-
};
|
|
1669
|
-
visited.add(current);
|
|
1670
|
-
const target = tokenMap[current];
|
|
1671
|
-
if (!target) break;
|
|
1672
|
-
if (typeof target.value === "string") {
|
|
1673
|
-
const match = target.value.match(REFERENCE_PATTERN);
|
|
1674
|
-
if (match) {
|
|
1675
|
-
current = match[1];
|
|
1676
|
-
depth++;
|
|
1677
|
-
continue;
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
break;
|
|
1681
|
-
}
|
|
1682
|
-
if (depth >= MAX_RESOLVE_DEPTH) return {
|
|
1683
|
-
valid: false,
|
|
1684
|
-
error: "Reference chain too deep (max 10)"
|
|
1685
|
-
};
|
|
1686
|
-
return { valid: true };
|
|
1687
|
-
}
|
|
1688
|
-
/**
|
|
1689
|
-
* Find all tokens that reference the given path.
|
|
1690
|
-
*/
|
|
1691
|
-
function findDependentTokens(tokenMap, targetPath) {
|
|
1692
|
-
const dependents = [];
|
|
1693
|
-
for (const [path, token] of Object.entries(tokenMap)) if (typeof token.value === "string") {
|
|
1694
|
-
const match = token.value.match(REFERENCE_PATTERN);
|
|
1695
|
-
if (match && match[1] === targetPath) dependents.push(path);
|
|
1696
|
-
}
|
|
1697
|
-
return dependents;
|
|
1698
|
-
}
|
|
1699
|
-
//#endregion
|
|
1700
|
-
//#region src/tokens/generator.ts
|
|
1701
|
-
/**
|
|
1702
|
-
* Token documentation generators for Style Dictionary integration.
|
|
1703
|
-
*
|
|
1704
|
-
* Generates HTML, Markdown, and JSON documentation from parsed token categories,
|
|
1705
|
-
* and provides the main processStyleDictionary orchestrator function.
|
|
1706
|
-
*/
|
|
1707
|
-
/**
|
|
1708
|
-
* Generate HTML documentation for tokens.
|
|
1709
|
-
*/
|
|
1710
|
-
function generateTokensHtml(categories) {
|
|
1711
|
-
const renderToken = (name, token) => {
|
|
1712
|
-
return `
|
|
1713
|
-
<div class="token">
|
|
1714
|
-
<div class="token-preview">
|
|
1715
|
-
${typeof token.value === "string" && (token.value.startsWith("#") || token.value.startsWith("rgb") || token.value.startsWith("hsl") || token.type === "color") ? `<div class="color-swatch" style="background: ${token.value}"></div>` : ""}
|
|
1716
|
-
</div>
|
|
1717
|
-
<div class="token-info">
|
|
1718
|
-
<div class="token-name">${name}</div>
|
|
1719
|
-
<div class="token-value">${token.value}</div>
|
|
1720
|
-
${token.description ? `<div class="token-description">${token.description}</div>` : ""}
|
|
1721
|
-
</div>
|
|
1722
|
-
</div>
|
|
1723
|
-
`;
|
|
1724
|
-
};
|
|
1725
|
-
const renderCategory = (category, level = 2) => {
|
|
1726
|
-
const heading = `h${Math.min(level, 6)}`;
|
|
1727
|
-
let html = `<${heading}>${category.name}</${heading}>`;
|
|
1728
|
-
html += "<div class=\"tokens-grid\">";
|
|
1729
|
-
for (const [name, token] of Object.entries(category.tokens)) html += renderToken(name, token);
|
|
1730
|
-
html += "</div>";
|
|
1731
|
-
if (category.subcategories) for (const sub of category.subcategories) html += renderCategory(sub, level + 1);
|
|
1732
|
-
return html;
|
|
1733
|
-
};
|
|
1734
|
-
return `<!DOCTYPE html>
|
|
1735
|
-
<html lang="en">
|
|
1736
|
-
<head>
|
|
1737
|
-
<meta charset="UTF-8">
|
|
1738
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1739
|
-
<title>Design Tokens - Musea</title>
|
|
1740
|
-
<style>
|
|
1741
|
-
:root {
|
|
1742
|
-
--musea-bg: #0d0d0d;
|
|
1743
|
-
--musea-bg-secondary: #1a1815;
|
|
1744
|
-
--musea-text: #e6e9f0;
|
|
1745
|
-
--musea-text-muted: #7b8494;
|
|
1746
|
-
--musea-accent: #a34828;
|
|
1747
|
-
--musea-border: #3a3530;
|
|
1748
|
-
}
|
|
1749
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1750
|
-
body {
|
|
1751
|
-
font-family: 'Inter', -apple-system, sans-serif;
|
|
1752
|
-
background: var(--musea-bg);
|
|
1753
|
-
color: var(--musea-text);
|
|
1754
|
-
line-height: 1.6;
|
|
1755
|
-
padding: 2rem;
|
|
1756
|
-
}
|
|
1757
|
-
h1 { margin-bottom: 2rem; color: var(--musea-accent); }
|
|
1758
|
-
h2 { margin: 2rem 0 1rem; padding-bottom: 0.5rem; border-bottom: 1px solid var(--musea-border); }
|
|
1759
|
-
h3, h4, h5, h6 { margin: 1.5rem 0 0.75rem; }
|
|
1760
|
-
.tokens-grid {
|
|
1761
|
-
display: grid;
|
|
1762
|
-
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
1763
|
-
gap: 1rem;
|
|
1764
|
-
margin-bottom: 1.5rem;
|
|
1765
|
-
}
|
|
1766
|
-
.token {
|
|
1767
|
-
background: var(--musea-bg-secondary);
|
|
1768
|
-
border: 1px solid var(--musea-border);
|
|
1769
|
-
border-radius: 8px;
|
|
1770
|
-
padding: 1rem;
|
|
1771
|
-
display: flex;
|
|
1772
|
-
gap: 1rem;
|
|
1773
|
-
align-items: center;
|
|
1774
|
-
}
|
|
1775
|
-
.token-preview {
|
|
1776
|
-
flex-shrink: 0;
|
|
1777
|
-
width: 48px;
|
|
1778
|
-
height: 48px;
|
|
1779
|
-
display: flex;
|
|
1780
|
-
align-items: center;
|
|
1781
|
-
justify-content: center;
|
|
1782
|
-
}
|
|
1783
|
-
.color-swatch {
|
|
1784
|
-
width: 48px;
|
|
1785
|
-
height: 48px;
|
|
1786
|
-
border-radius: 8px;
|
|
1787
|
-
border: 1px solid var(--musea-border);
|
|
1788
|
-
}
|
|
1789
|
-
.token-info {
|
|
1790
|
-
flex: 1;
|
|
1791
|
-
min-width: 0;
|
|
1792
|
-
}
|
|
1793
|
-
.token-name {
|
|
1794
|
-
font-weight: 600;
|
|
1795
|
-
font-family: 'JetBrains Mono', monospace;
|
|
1796
|
-
font-size: 0.875rem;
|
|
1797
|
-
}
|
|
1798
|
-
.token-value {
|
|
1799
|
-
color: var(--musea-text-muted);
|
|
1800
|
-
font-family: 'JetBrains Mono', monospace;
|
|
1801
|
-
font-size: 0.75rem;
|
|
1802
|
-
word-break: break-all;
|
|
1803
|
-
}
|
|
1804
|
-
.token-description {
|
|
1805
|
-
color: var(--musea-text-muted);
|
|
1806
|
-
font-size: 0.75rem;
|
|
1807
|
-
margin-top: 0.25rem;
|
|
1808
|
-
}
|
|
1809
|
-
</style>
|
|
1810
|
-
</head>
|
|
1811
|
-
<body>
|
|
1812
|
-
<h1>Design Tokens</h1>
|
|
1813
|
-
${categories.map((cat) => renderCategory(cat)).join("")}
|
|
1814
|
-
</body>
|
|
1815
|
-
</html>`;
|
|
1816
|
-
}
|
|
1817
|
-
/**
|
|
1818
|
-
* Generate Markdown documentation for tokens.
|
|
1819
|
-
*/
|
|
1820
|
-
function generateTokensMarkdown(categories) {
|
|
1821
|
-
const renderCategory = (category, level = 2) => {
|
|
1822
|
-
let md = `\n${"#".repeat(level)} ${category.name}\n\n`;
|
|
1823
|
-
if (Object.keys(category.tokens).length > 0) {
|
|
1824
|
-
md += "| Token | Value | Description |\n";
|
|
1825
|
-
md += "|-------|-------|-------------|\n";
|
|
1826
|
-
for (const [name, token] of Object.entries(category.tokens)) {
|
|
1827
|
-
const desc = token.description || "-";
|
|
1828
|
-
md += `| \`${name}\` | \`${token.value}\` | ${desc} |\n`;
|
|
1829
|
-
}
|
|
1830
|
-
md += "\n";
|
|
1831
|
-
}
|
|
1832
|
-
if (category.subcategories) for (const sub of category.subcategories) md += renderCategory(sub, level + 1);
|
|
1833
|
-
return md;
|
|
1834
|
-
};
|
|
1835
|
-
let markdown = "# Design Tokens\n\n";
|
|
1836
|
-
markdown += `> Generated by Musea on ${(/* @__PURE__ */ new Date()).toISOString()}\n`;
|
|
1837
|
-
for (const category of categories) markdown += renderCategory(category);
|
|
1838
|
-
return markdown;
|
|
1839
|
-
}
|
|
1840
|
-
/**
|
|
1841
|
-
* Style Dictionary plugin for Musea.
|
|
1842
|
-
* Parses tokens and generates documentation in the specified format.
|
|
1843
|
-
*/
|
|
1844
|
-
async function processStyleDictionary(config) {
|
|
1845
|
-
const categories = await parseTokens(config.tokensPath);
|
|
1846
|
-
const outputDir = config.outputDir ?? ".vize/tokens";
|
|
1847
|
-
const outputFormat = config.outputFormat ?? "html";
|
|
1848
|
-
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
1849
|
-
let content;
|
|
1850
|
-
let filename;
|
|
1851
|
-
switch (outputFormat) {
|
|
1852
|
-
case "html":
|
|
1853
|
-
content = generateTokensHtml(categories);
|
|
1854
|
-
filename = "tokens.html";
|
|
1855
|
-
break;
|
|
1856
|
-
case "markdown":
|
|
1857
|
-
content = generateTokensMarkdown(categories);
|
|
1858
|
-
filename = "tokens.md";
|
|
1859
|
-
break;
|
|
1860
|
-
default:
|
|
1861
|
-
content = JSON.stringify({ categories }, null, 2);
|
|
1862
|
-
filename = "tokens.json";
|
|
1863
|
-
}
|
|
1864
|
-
const outputPath = path.join(outputDir, filename);
|
|
1865
|
-
await fs.promises.writeFile(outputPath, content, "utf-8");
|
|
1866
|
-
console.log(`[musea] Generated token documentation: ${outputPath}`);
|
|
1867
|
-
return {
|
|
1868
|
-
categories,
|
|
1869
|
-
metadata: {
|
|
1870
|
-
name: path.basename(config.tokensPath),
|
|
1871
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1872
|
-
}
|
|
1873
|
-
};
|
|
1874
|
-
}
|
|
1875
|
-
//#endregion
|
|
1876
|
-
//#region src/api-tokens.ts
|
|
1877
|
-
/**
|
|
1878
|
-
* Musea gallery API token route handlers.
|
|
1879
|
-
*
|
|
1880
|
-
* Handles GET/POST/PUT/DELETE for /api/tokens endpoints:
|
|
1881
|
-
* - GET /tokens -- list all resolved design tokens
|
|
1882
|
-
* - GET /tokens/usage -- token usage across art files
|
|
1883
|
-
* - POST /tokens -- create a new token
|
|
1884
|
-
* - PUT /tokens -- update an existing token
|
|
1885
|
-
* - DELETE /tokens -- delete a token
|
|
1886
|
-
*/
|
|
1887
|
-
/** GET /api/tokens/usage */
|
|
1888
|
-
async function handleTokensUsage(ctx, sendJson) {
|
|
1889
|
-
if (!ctx.tokensPath) {
|
|
1890
|
-
sendJson({});
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
try {
|
|
1894
|
-
const categories = await parseTokens(path.resolve(ctx.config.root, ctx.tokensPath));
|
|
1895
|
-
resolveReferences(categories, buildTokenMap(categories));
|
|
1896
|
-
const resolvedTokenMap = buildTokenMap(categories);
|
|
1897
|
-
sendJson(scanTokenUsage(ctx.artFiles, resolvedTokenMap));
|
|
1898
|
-
} catch (e) {
|
|
1899
|
-
console.error("[musea] Failed to scan token usage:", e);
|
|
1900
|
-
sendJson({});
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
/** GET /api/tokens */
|
|
1904
|
-
async function handleTokensGet(ctx, sendJson) {
|
|
1905
|
-
if (!ctx.tokensPath) {
|
|
1906
|
-
sendJson({
|
|
1907
|
-
categories: [],
|
|
1908
|
-
tokenMap: {},
|
|
1909
|
-
meta: {
|
|
1910
|
-
filePath: "",
|
|
1911
|
-
tokenCount: 0,
|
|
1912
|
-
primitiveCount: 0,
|
|
1913
|
-
semanticCount: 0
|
|
1914
|
-
}
|
|
1915
|
-
});
|
|
1916
|
-
return;
|
|
1917
|
-
}
|
|
1918
|
-
try {
|
|
1919
|
-
const absoluteTokensPath = path.resolve(ctx.config.root, ctx.tokensPath);
|
|
1920
|
-
const categories = await parseTokens(absoluteTokensPath);
|
|
1921
|
-
resolveReferences(categories, buildTokenMap(categories));
|
|
1922
|
-
const resolvedTokenMap = buildTokenMap(categories);
|
|
1923
|
-
let primitiveCount = 0;
|
|
1924
|
-
let semanticCount = 0;
|
|
1925
|
-
for (const token of Object.values(resolvedTokenMap)) if (token.$tier === "semantic") semanticCount++;
|
|
1926
|
-
else primitiveCount++;
|
|
1927
|
-
sendJson({
|
|
1928
|
-
categories,
|
|
1929
|
-
tokenMap: resolvedTokenMap,
|
|
1930
|
-
meta: {
|
|
1931
|
-
filePath: absoluteTokensPath,
|
|
1932
|
-
tokenCount: Object.keys(resolvedTokenMap).length,
|
|
1933
|
-
primitiveCount,
|
|
1934
|
-
semanticCount
|
|
1935
|
-
}
|
|
1936
|
-
});
|
|
1937
|
-
} catch (e) {
|
|
1938
|
-
console.error("[musea] Failed to load tokens:", e);
|
|
1939
|
-
sendJson({
|
|
1940
|
-
categories: [],
|
|
1941
|
-
tokenMap: {},
|
|
1942
|
-
error: String(e)
|
|
1943
|
-
});
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
/** POST /api/tokens (create) */
|
|
1947
|
-
async function handleTokensCreate(ctx, readBody, sendJson, sendError) {
|
|
1948
|
-
if (!ctx.tokensPath) {
|
|
1949
|
-
sendError("No tokens path configured", 400);
|
|
1950
|
-
return;
|
|
1951
|
-
}
|
|
1952
|
-
const body = await readBody();
|
|
1953
|
-
try {
|
|
1954
|
-
const { path: dotPath, token } = JSON.parse(body);
|
|
1955
|
-
if (!dotPath || !token || token.value === void 0) {
|
|
1956
|
-
sendError("Missing required fields: path, token.value", 400);
|
|
1957
|
-
return;
|
|
1958
|
-
}
|
|
1959
|
-
const absoluteTokensPath = path.resolve(ctx.config.root, ctx.tokensPath);
|
|
1960
|
-
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
1961
|
-
const currentMap = buildTokenMap(await parseTokens(absoluteTokensPath));
|
|
1962
|
-
if (currentMap[dotPath]) {
|
|
1963
|
-
sendError(`Token already exists at path "${dotPath}"`, 409);
|
|
1964
|
-
return;
|
|
1965
|
-
}
|
|
1966
|
-
if (token.$reference) {
|
|
1967
|
-
const validation = validateSemanticReference(currentMap, token.$reference, dotPath);
|
|
1968
|
-
if (!validation.valid) {
|
|
1969
|
-
sendError(validation.error, 400);
|
|
1970
|
-
return;
|
|
1971
|
-
}
|
|
1972
|
-
token.value = `{${token.$reference}}`;
|
|
1973
|
-
token.$tier = "semantic";
|
|
1974
|
-
}
|
|
1975
|
-
setTokenAtPath(rawData, dotPath, token);
|
|
1976
|
-
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
1977
|
-
const categories = await parseTokens(absoluteTokensPath);
|
|
1978
|
-
resolveReferences(categories, buildTokenMap(categories));
|
|
1979
|
-
sendJson({
|
|
1980
|
-
categories,
|
|
1981
|
-
tokenMap: buildTokenMap(categories)
|
|
1982
|
-
}, 201);
|
|
1983
|
-
} catch (e) {
|
|
1984
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
1985
|
-
}
|
|
1986
|
-
}
|
|
1987
|
-
/** PUT /api/tokens (update) */
|
|
1988
|
-
async function handleTokensUpdate(ctx, readBody, sendJson, sendError) {
|
|
1989
|
-
if (!ctx.tokensPath) {
|
|
1990
|
-
sendError("No tokens path configured", 400);
|
|
1991
|
-
return;
|
|
1992
|
-
}
|
|
1993
|
-
const body = await readBody();
|
|
1994
|
-
try {
|
|
1995
|
-
const { path: dotPath, token } = JSON.parse(body);
|
|
1996
|
-
if (!dotPath || !token || token.value === void 0) {
|
|
1997
|
-
sendError("Missing required fields: path, token.value", 400);
|
|
1998
|
-
return;
|
|
1999
|
-
}
|
|
2000
|
-
const absoluteTokensPath = path.resolve(ctx.config.root, ctx.tokensPath);
|
|
2001
|
-
if (token.$reference) {
|
|
2002
|
-
const validation = validateSemanticReference(buildTokenMap(await parseTokens(absoluteTokensPath)), token.$reference, dotPath);
|
|
2003
|
-
if (!validation.valid) {
|
|
2004
|
-
sendError(validation.error, 400);
|
|
2005
|
-
return;
|
|
2006
|
-
}
|
|
2007
|
-
token.value = `{${token.$reference}}`;
|
|
2008
|
-
token.$tier = "semantic";
|
|
2009
|
-
}
|
|
2010
|
-
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2011
|
-
setTokenAtPath(rawData, dotPath, token);
|
|
2012
|
-
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
2013
|
-
const categories = await parseTokens(absoluteTokensPath);
|
|
2014
|
-
resolveReferences(categories, buildTokenMap(categories));
|
|
2015
|
-
sendJson({
|
|
2016
|
-
categories,
|
|
2017
|
-
tokenMap: buildTokenMap(categories)
|
|
2018
|
-
});
|
|
2019
|
-
} catch (e) {
|
|
2020
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
/** DELETE /api/tokens */
|
|
2024
|
-
async function handleTokensDelete(ctx, readBody, sendJson, sendError) {
|
|
2025
|
-
if (!ctx.tokensPath) {
|
|
2026
|
-
sendError("No tokens path configured", 400);
|
|
2027
|
-
return;
|
|
2028
|
-
}
|
|
2029
|
-
const body = await readBody();
|
|
2030
|
-
try {
|
|
2031
|
-
const { path: dotPath } = JSON.parse(body);
|
|
2032
|
-
if (!dotPath) {
|
|
2033
|
-
sendError("Missing required field: path", 400);
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
const absoluteTokensPath = path.resolve(ctx.config.root, ctx.tokensPath);
|
|
2037
|
-
const dependents = findDependentTokens(buildTokenMap(await parseTokens(absoluteTokensPath)), dotPath);
|
|
2038
|
-
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2039
|
-
if (!deleteTokenAtPath(rawData, dotPath)) {
|
|
2040
|
-
sendError(`Token not found at path "${dotPath}"`, 404);
|
|
2041
|
-
return;
|
|
2042
|
-
}
|
|
2043
|
-
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
2044
|
-
const categories = await parseTokens(absoluteTokensPath);
|
|
2045
|
-
resolveReferences(categories, buildTokenMap(categories));
|
|
2046
|
-
sendJson({
|
|
2047
|
-
categories,
|
|
2048
|
-
tokenMap: buildTokenMap(categories),
|
|
2049
|
-
dependentsWarning: dependents.length > 0 ? dependents : void 0
|
|
2050
|
-
});
|
|
2051
|
-
} catch (e) {
|
|
2052
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
//#endregion
|
|
2056
|
-
//#region src/api-routes/handler-palette.ts
|
|
2057
|
-
/**
|
|
2058
|
-
* Palette handler for the Musea gallery API.
|
|
2059
|
-
*
|
|
2060
|
-
* Handles GET /api/arts/:path/palette endpoint.
|
|
2061
|
-
*/
|
|
2062
|
-
/** GET /api/arts/:path/palette */
|
|
2063
|
-
async function handleArtPalette(ctx, match, sendJson, sendError) {
|
|
2064
|
-
const artPath = decodeURIComponent(match[1]);
|
|
2065
|
-
const art = ctx.artFiles.get(artPath);
|
|
2066
|
-
if (!art) {
|
|
2067
|
-
sendError("Art not found", 404);
|
|
2068
|
-
return;
|
|
2069
|
-
}
|
|
2070
|
-
try {
|
|
2071
|
-
const source = await fs.promises.readFile(artPath, "utf-8");
|
|
2072
|
-
const binding = loadNative();
|
|
2073
|
-
let palette;
|
|
2074
|
-
if (binding.generateArtPalette) palette = binding.generateArtPalette(source, { filename: artPath });
|
|
2075
|
-
else palette = {
|
|
2076
|
-
title: art.metadata.title,
|
|
2077
|
-
controls: [],
|
|
2078
|
-
groups: [],
|
|
2079
|
-
json: "{}",
|
|
2080
|
-
typescript: ""
|
|
2081
|
-
};
|
|
2082
|
-
if (palette.controls.length === 0 && art.metadata.component) {
|
|
2083
|
-
const resolvedComponentPath = path.isAbsolute(art.metadata.component) ? art.metadata.component : path.resolve(path.dirname(artPath), art.metadata.component);
|
|
2084
|
-
try {
|
|
2085
|
-
const componentSource = await fs.promises.readFile(resolvedComponentPath, "utf-8");
|
|
2086
|
-
const analysis = binding.analyzeSfc ? binding.analyzeSfc(componentSource, { filename: resolvedComponentPath }) : analyzeSfcFallback(componentSource, { filename: resolvedComponentPath });
|
|
2087
|
-
if (analysis.props.length > 0) {
|
|
2088
|
-
palette.controls = analysis.props.map((prop) => {
|
|
2089
|
-
let control = "text";
|
|
2090
|
-
if (prop.type === "boolean") control = "boolean";
|
|
2091
|
-
else if (prop.type === "number") control = "number";
|
|
2092
|
-
else if (prop.type.includes("|") && !prop.type.includes("=>")) control = "select";
|
|
2093
|
-
const options = [];
|
|
2094
|
-
if (control === "select") {
|
|
2095
|
-
const optionMatches = prop.type.match(/"([^"]+)"/g);
|
|
2096
|
-
if (optionMatches) for (const opt of optionMatches) {
|
|
2097
|
-
const val = opt.replace(/"/g, "");
|
|
2098
|
-
options.push({
|
|
2099
|
-
label: val,
|
|
2100
|
-
value: val
|
|
2101
|
-
});
|
|
2102
|
-
}
|
|
2103
|
-
}
|
|
2104
|
-
return {
|
|
2105
|
-
name: prop.name,
|
|
2106
|
-
control,
|
|
2107
|
-
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,
|
|
2108
|
-
description: void 0,
|
|
2109
|
-
required: prop.required,
|
|
2110
|
-
options,
|
|
2111
|
-
range: void 0,
|
|
2112
|
-
group: void 0
|
|
2113
|
-
};
|
|
2114
|
-
});
|
|
2115
|
-
palette.json = JSON.stringify({
|
|
2116
|
-
title: palette.title,
|
|
2117
|
-
controls: palette.controls
|
|
2118
|
-
}, null, 2);
|
|
2119
|
-
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`;
|
|
2120
|
-
}
|
|
2121
|
-
} catch {}
|
|
2122
|
-
}
|
|
2123
|
-
sendJson(palette);
|
|
2124
|
-
} catch (e) {
|
|
2125
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
//#endregion
|
|
2129
|
-
//#region src/api-routes/handlers.ts
|
|
2130
|
-
/**
|
|
2131
|
-
* Individual route handler functions for the Musea gallery API.
|
|
2132
|
-
*
|
|
2133
|
-
* Extracted from api-routes.ts to keep file sizes manageable.
|
|
2134
|
-
* These handle GET /api/arts/:path/... sub-routes.
|
|
2135
|
-
*/
|
|
2136
|
-
/** GET /api/arts/:path/source */
|
|
2137
|
-
async function handleArtSource(ctx, match, sendJson, sendError) {
|
|
2138
|
-
const artPath = decodeURIComponent(match[1]);
|
|
2139
|
-
if (!ctx.artFiles.get(artPath)) {
|
|
2140
|
-
sendError("Art not found", 404);
|
|
2141
|
-
return;
|
|
2142
|
-
}
|
|
2143
|
-
try {
|
|
2144
|
-
sendJson({
|
|
2145
|
-
source: await fs.promises.readFile(artPath, "utf-8"),
|
|
2146
|
-
path: artPath
|
|
2147
|
-
});
|
|
2148
|
-
} catch (e) {
|
|
2149
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
/** GET /api/arts/:path/analysis */
|
|
2153
|
-
async function handleArtAnalysis(ctx, match, sendJson, sendError) {
|
|
2154
|
-
const artPath = decodeURIComponent(match[1]);
|
|
2155
|
-
const art = ctx.artFiles.get(artPath);
|
|
2156
|
-
if (!art) {
|
|
2157
|
-
sendError("Art not found", 404);
|
|
2158
|
-
return;
|
|
2159
|
-
}
|
|
2160
|
-
try {
|
|
2161
|
-
const resolvedComponentPath = art.isInline && art.componentPath ? art.componentPath : art.metadata.component ? path.isAbsolute(art.metadata.component) ? art.metadata.component : path.resolve(path.dirname(artPath), art.metadata.component) : null;
|
|
2162
|
-
if (resolvedComponentPath) {
|
|
2163
|
-
const source = await fs.promises.readFile(resolvedComponentPath, "utf-8");
|
|
2164
|
-
const binding = loadNative();
|
|
2165
|
-
if (binding.analyzeSfc) sendJson(binding.analyzeSfc(source, { filename: resolvedComponentPath }));
|
|
2166
|
-
else sendJson(analyzeSfcFallback(source, { filename: resolvedComponentPath }));
|
|
2167
|
-
} else sendJson({
|
|
2168
|
-
props: [],
|
|
2169
|
-
emits: []
|
|
2170
|
-
});
|
|
2171
|
-
} catch (e) {
|
|
2172
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2173
|
-
}
|
|
2174
|
-
}
|
|
2175
|
-
/** GET /api/arts/:path/docs */
|
|
2176
|
-
async function handleArtDocs(ctx, match, sendJson, sendError) {
|
|
2177
|
-
const artPath = decodeURIComponent(match[1]);
|
|
2178
|
-
const art = ctx.artFiles.get(artPath);
|
|
2179
|
-
if (!art) {
|
|
2180
|
-
sendError("Art not found", 404);
|
|
2181
|
-
return;
|
|
2182
|
-
}
|
|
2183
|
-
try {
|
|
2184
|
-
const source = await fs.promises.readFile(artPath, "utf-8");
|
|
2185
|
-
const binding = loadNative();
|
|
2186
|
-
if (binding.generateArtDoc) {
|
|
2187
|
-
const doc = binding.generateArtDoc(source, { filename: artPath });
|
|
2188
|
-
let markdown = doc.markdown || "";
|
|
2189
|
-
const componentName = art.metadata.title || "Component";
|
|
2190
|
-
markdown = markdown.replace(/<Self(\s|>|\/)/g, `<${componentName}$1`).replace(/<\/Self>/g, `</${componentName}>`);
|
|
2191
|
-
markdown = markdown.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, lang, code) => {
|
|
2192
|
-
const lines = code.split("\n");
|
|
2193
|
-
let minIndent = Infinity;
|
|
2194
|
-
for (const line of lines) if (line.trim()) {
|
|
2195
|
-
const indent = line.match(/^(\s*)/)?.[1].length || 0;
|
|
2196
|
-
minIndent = Math.min(minIndent, indent);
|
|
2197
|
-
}
|
|
2198
|
-
if (minIndent === Infinity) minIndent = 0;
|
|
2199
|
-
let formatted;
|
|
2200
|
-
if (minIndent > 0) formatted = lines.map((line) => line.slice(minIndent)).join("\n");
|
|
2201
|
-
else {
|
|
2202
|
-
let restIndent = Infinity;
|
|
2203
|
-
for (let i = 1; i < lines.length; i++) if (lines[i].trim()) {
|
|
2204
|
-
const indent = lines[i].match(/^(\s*)/)?.[1].length || 0;
|
|
2205
|
-
restIndent = Math.min(restIndent, indent);
|
|
2206
|
-
}
|
|
2207
|
-
if (restIndent === Infinity || restIndent === 0) formatted = lines.join("\n");
|
|
2208
|
-
else formatted = lines.map((line, i) => i === 0 ? line : line.slice(restIndent)).join("\n");
|
|
2209
|
-
}
|
|
2210
|
-
return "```" + lang + "\n" + formatted + "```";
|
|
2211
|
-
});
|
|
2212
|
-
sendJson({
|
|
2213
|
-
...doc,
|
|
2214
|
-
markdown
|
|
2215
|
-
});
|
|
2216
|
-
} else sendJson({
|
|
2217
|
-
markdown: "",
|
|
2218
|
-
title: art.metadata.title,
|
|
2219
|
-
variant_count: art.variants.length
|
|
2220
|
-
});
|
|
2221
|
-
} catch (e) {
|
|
2222
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
/** GET /api/arts/:path/variants/:name/a11y */
|
|
2226
|
-
function handleArtA11y(ctx, match, sendJson, sendError) {
|
|
2227
|
-
const artPath = decodeURIComponent(match[1]);
|
|
2228
|
-
decodeURIComponent(match[2]);
|
|
2229
|
-
if (!ctx.artFiles.get(artPath)) {
|
|
2230
|
-
sendError("Art not found", 404);
|
|
2231
|
-
return;
|
|
2232
|
-
}
|
|
2233
|
-
sendJson({
|
|
2234
|
-
violations: [],
|
|
2235
|
-
passes: 0,
|
|
2236
|
-
incomplete: 0
|
|
2237
|
-
});
|
|
2238
|
-
}
|
|
2239
|
-
//#endregion
|
|
2240
|
-
//#region src/api-routes/post-handlers.ts
|
|
2241
|
-
/** POST /api/preview-with-props */
|
|
2242
|
-
function handlePreviewWithProps(ctx, body, res, sendJson, sendError) {
|
|
2243
|
-
try {
|
|
2244
|
-
const { artPath: reqArtPath, variantName, props: propsOverride } = JSON.parse(body);
|
|
2245
|
-
const art = ctx.artFiles.get(reqArtPath);
|
|
2246
|
-
if (!art) {
|
|
2247
|
-
sendError("Art not found", 404);
|
|
2248
|
-
return;
|
|
2249
|
-
}
|
|
2250
|
-
const variant = art.variants.find((v) => v.name === variantName);
|
|
2251
|
-
if (!variant) {
|
|
2252
|
-
sendError("Variant not found", 404);
|
|
2253
|
-
return;
|
|
2254
|
-
}
|
|
2255
|
-
const moduleCode = generatePreviewModuleWithProps(art, toPascalCase(variant.name), variant.name, propsOverride, ctx.resolvedPreviewCss, ctx.resolvedPreviewSetup);
|
|
2256
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
2257
|
-
res.end(moduleCode);
|
|
2258
|
-
} catch (e) {
|
|
2259
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
/** POST /api/generate */
|
|
2263
|
-
async function handleGenerate(body, sendJson, sendError) {
|
|
2264
|
-
try {
|
|
2265
|
-
const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);
|
|
2266
|
-
const { generateArtFile: genArt } = await import("./autogen/index.mjs");
|
|
2267
|
-
const result = await genArt(reqComponentPath, autogenOptions);
|
|
2268
|
-
sendJson({
|
|
2269
|
-
generated: true,
|
|
2270
|
-
componentName: result.componentName,
|
|
2271
|
-
variants: result.variants,
|
|
2272
|
-
artFileContent: result.artFileContent
|
|
2273
|
-
});
|
|
2274
|
-
} catch (e) {
|
|
2275
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
/** POST /api/run-vrt */
|
|
2279
|
-
async function handleRunVrt(ctx, body, sendJson, sendError) {
|
|
2280
|
-
try {
|
|
2281
|
-
const { artPath, updateSnapshots } = JSON.parse(body);
|
|
2282
|
-
const { MuseaVrtRunner } = await import("./vrt.mjs");
|
|
2283
|
-
const runner = new MuseaVrtRunner({ snapshotDir: path.resolve(ctx.config.root, ".vize/snapshots") });
|
|
2284
|
-
const baseUrl = `http://localhost:${ctx.getDevServerPort()}`;
|
|
2285
|
-
let artsToTest = Array.from(ctx.artFiles.values());
|
|
2286
|
-
if (artPath) artsToTest = artsToTest.filter((a) => a.path === artPath);
|
|
2287
|
-
await runner.start();
|
|
2288
|
-
const results = await runner.runTests(artsToTest, baseUrl, { updateSnapshots });
|
|
2289
|
-
const summary = runner.getSummary(results);
|
|
2290
|
-
await runner.stop();
|
|
2291
|
-
sendJson({
|
|
2292
|
-
success: true,
|
|
2293
|
-
summary,
|
|
2294
|
-
results: results.map((r) => ({
|
|
2295
|
-
artPath: r.artPath,
|
|
2296
|
-
variantName: r.variantName,
|
|
2297
|
-
viewport: r.viewport.name,
|
|
2298
|
-
passed: r.passed,
|
|
2299
|
-
isNew: r.isNew,
|
|
2300
|
-
diffPercentage: r.diffPercentage,
|
|
2301
|
-
error: r.error
|
|
2302
|
-
}))
|
|
2303
|
-
});
|
|
2304
|
-
} catch (e) {
|
|
2305
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
//#endregion
|
|
2309
|
-
//#region src/api-routes/index.ts
|
|
2310
|
-
/** Helper to read the full request body as a string. */
|
|
2311
|
-
function collectBody(req) {
|
|
2312
|
-
return new Promise((resolve) => {
|
|
2313
|
-
let body = "";
|
|
2314
|
-
req.on("data", (chunk) => {
|
|
2315
|
-
body += chunk;
|
|
2316
|
-
});
|
|
2317
|
-
req.on("end", () => resolve(body));
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
|
-
/**
|
|
2321
|
-
* Create the API middleware handler for the Musea gallery.
|
|
2322
|
-
*
|
|
2323
|
-
* Returns a Connect-compatible middleware function that handles all
|
|
2324
|
-
* `/api/...` sub-routes under the configured basePath.
|
|
2325
|
-
*/
|
|
2326
|
-
function createApiMiddleware(ctx) {
|
|
2327
|
-
return async (req, res, next) => {
|
|
2328
|
-
const sendJson = (data, status = 200) => {
|
|
2329
|
-
res.statusCode = status;
|
|
2330
|
-
res.setHeader("Content-Type", "application/json");
|
|
2331
|
-
res.end(JSON.stringify(data));
|
|
2332
|
-
};
|
|
2333
|
-
const sendError = (message, status = 500) => {
|
|
2334
|
-
sendJson({ error: message }, status);
|
|
2335
|
-
};
|
|
2336
|
-
const readBody = () => collectBody(req);
|
|
2337
|
-
const url = req.url || "/";
|
|
2338
|
-
if (url === "/arts" && req.method === "GET") {
|
|
2339
|
-
sendJson(Array.from(ctx.artFiles.values()));
|
|
2340
|
-
return;
|
|
2341
|
-
}
|
|
2342
|
-
if (url === "/tokens/usage" && req.method === "GET") {
|
|
2343
|
-
await handleTokensUsage(ctx, sendJson);
|
|
2344
|
-
return;
|
|
2345
|
-
}
|
|
2346
|
-
if (url === "/tokens" && req.method === "GET") {
|
|
2347
|
-
await handleTokensGet(ctx, sendJson);
|
|
2348
|
-
return;
|
|
2349
|
-
}
|
|
2350
|
-
if (url === "/tokens" && req.method === "POST") {
|
|
2351
|
-
await handleTokensCreate(ctx, readBody, sendJson, sendError);
|
|
2352
|
-
return;
|
|
2353
|
-
}
|
|
2354
|
-
if (url === "/tokens" && req.method === "PUT") {
|
|
2355
|
-
await handleTokensUpdate(ctx, readBody, sendJson, sendError);
|
|
2356
|
-
return;
|
|
2357
|
-
}
|
|
2358
|
-
if (url === "/tokens" && req.method === "DELETE") {
|
|
2359
|
-
await handleTokensDelete(ctx, readBody, sendJson, sendError);
|
|
2360
|
-
return;
|
|
2361
|
-
}
|
|
2362
|
-
if (url?.startsWith("/arts/") && req.method === "PUT") {
|
|
2363
|
-
const sourceMatch = url.slice(6).match(/^(.+)\/source$/);
|
|
2364
|
-
if (sourceMatch) {
|
|
2365
|
-
const artPath = decodeURIComponent(sourceMatch[1]);
|
|
2366
|
-
if (!ctx.artFiles.get(artPath)) {
|
|
2367
|
-
sendError("Art not found", 404);
|
|
2368
|
-
return;
|
|
2369
|
-
}
|
|
2370
|
-
const body = await collectBody(req);
|
|
2371
|
-
try {
|
|
2372
|
-
const { source } = JSON.parse(body);
|
|
2373
|
-
if (typeof source !== "string") {
|
|
2374
|
-
sendError("Missing required field: source", 400);
|
|
2375
|
-
return;
|
|
2376
|
-
}
|
|
2377
|
-
await fs.promises.writeFile(artPath, source, "utf-8");
|
|
2378
|
-
await ctx.processArtFile(artPath);
|
|
2379
|
-
sendJson({ success: true });
|
|
2380
|
-
} catch (e) {
|
|
2381
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2382
|
-
}
|
|
2383
|
-
return;
|
|
2384
|
-
}
|
|
2385
|
-
next();
|
|
2386
|
-
return;
|
|
2387
|
-
}
|
|
2388
|
-
if (url?.startsWith("/arts/") && req.method === "GET") {
|
|
2389
|
-
const rest = url.slice(6);
|
|
2390
|
-
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2391
|
-
const paletteMatch = rest.match(/^(.+)\/palette$/);
|
|
2392
|
-
const analysisMatch = rest.match(/^(.+)\/analysis$/);
|
|
2393
|
-
const docsMatch = rest.match(/^(.+)\/docs$/);
|
|
2394
|
-
const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
|
|
2395
|
-
if (sourceMatch) {
|
|
2396
|
-
await handleArtSource(ctx, sourceMatch, sendJson, sendError);
|
|
2397
|
-
return;
|
|
2398
|
-
}
|
|
2399
|
-
if (paletteMatch) {
|
|
2400
|
-
await handleArtPalette(ctx, paletteMatch, sendJson, sendError);
|
|
2401
|
-
return;
|
|
2402
|
-
}
|
|
2403
|
-
if (analysisMatch) {
|
|
2404
|
-
await handleArtAnalysis(ctx, analysisMatch, sendJson, sendError);
|
|
2405
|
-
return;
|
|
2406
|
-
}
|
|
2407
|
-
if (docsMatch) {
|
|
2408
|
-
await handleArtDocs(ctx, docsMatch, sendJson, sendError);
|
|
2409
|
-
return;
|
|
2410
|
-
}
|
|
2411
|
-
if (a11yMatch) {
|
|
2412
|
-
handleArtA11y(ctx, a11yMatch, sendJson, sendError);
|
|
2413
|
-
return;
|
|
2414
|
-
}
|
|
2415
|
-
const artPath = decodeURIComponent(rest);
|
|
2416
|
-
const art = ctx.artFiles.get(artPath);
|
|
2417
|
-
if (art) sendJson(art);
|
|
2418
|
-
else sendError("Art not found", 404);
|
|
2419
|
-
return;
|
|
2420
|
-
}
|
|
2421
|
-
if (req.method === "POST") {
|
|
2422
|
-
const body = await collectBody(req);
|
|
2423
|
-
if (url === "/preview-with-props") {
|
|
2424
|
-
handlePreviewWithProps(ctx, body, res, sendJson, sendError);
|
|
2425
|
-
return;
|
|
2426
|
-
}
|
|
2427
|
-
if (url === "/generate") {
|
|
2428
|
-
await handleGenerate(body, sendJson, sendError);
|
|
2429
|
-
return;
|
|
2430
|
-
}
|
|
2431
|
-
if (url === "/run-vrt") {
|
|
2432
|
-
await handleRunVrt(ctx, body, sendJson, sendError);
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
next();
|
|
2437
|
-
};
|
|
2438
|
-
}
|
|
2439
|
-
//#endregion
|
|
2440
|
-
//#region src/manifest.ts
|
|
2441
|
-
/**
|
|
2442
|
-
* Generate the virtual manifest module code containing all art file metadata.
|
|
2443
|
-
*/
|
|
2444
|
-
function generateManifestModule(artFiles) {
|
|
2445
|
-
const arts = Array.from(artFiles.values());
|
|
2446
|
-
return `export const arts = ${JSON.stringify(arts, null, 2)};`;
|
|
2447
|
-
}
|
|
2448
|
-
//#endregion
|
|
2449
|
-
//#region src/plugin/virtual.ts
|
|
2450
|
-
/**
|
|
2451
|
-
* Virtual module handling for the Musea Vite plugin.
|
|
2452
|
-
*
|
|
2453
|
-
* Contains `resolveId`, `load`, and `handleHotUpdate` hooks that
|
|
2454
|
-
* manage virtual modules for gallery, manifest, preview, and art files.
|
|
2455
|
-
*/
|
|
2456
|
-
const VIRTUAL_MUSEA_PREFIX = "\0musea:";
|
|
2457
|
-
const VIRTUAL_GALLERY = "\0musea-gallery";
|
|
2458
|
-
const VIRTUAL_MANIFEST = "\0musea-manifest";
|
|
2459
|
-
function createResolveId(state) {
|
|
2460
|
-
return function resolveId(id) {
|
|
2461
|
-
const root = state.getConfigRoot();
|
|
2462
|
-
if (id === "\0musea-gallery") return VIRTUAL_GALLERY;
|
|
2463
|
-
if (id === "\0musea-manifest") return VIRTUAL_MANIFEST;
|
|
2464
|
-
if (id.startsWith("virtual:musea-preview:")) return "\0musea-preview:" + id.slice(22);
|
|
2465
|
-
if (id.startsWith("virtual:musea-art:")) {
|
|
2466
|
-
const artPath = id.slice(18);
|
|
2467
|
-
if (state.artFiles.has(artPath)) return "\0musea-art:" + artPath + "?musea-virtual";
|
|
2468
|
-
}
|
|
2469
|
-
if (id.endsWith(".art.vue")) {
|
|
2470
|
-
const resolved = path.resolve(root, id);
|
|
2471
|
-
if (state.artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
2472
|
-
}
|
|
2473
|
-
if (state.inlineArt && id.endsWith(".vue") && !id.endsWith(".art.vue")) {
|
|
2474
|
-
const resolved = path.resolve(root, id);
|
|
2475
|
-
if (state.artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
2476
|
-
}
|
|
2477
|
-
return null;
|
|
2478
|
-
};
|
|
2479
|
-
}
|
|
2480
|
-
function createLoad(state) {
|
|
2481
|
-
return function load(id) {
|
|
2482
|
-
if (id === "\0musea-gallery") return generateGalleryModule(state.basePath);
|
|
2483
|
-
if (id === "\0musea-manifest") return generateManifestModule(state.artFiles);
|
|
2484
|
-
if (id.startsWith("\0musea-preview:")) {
|
|
2485
|
-
const rest = id.slice(15);
|
|
2486
|
-
const lastColonIndex = rest.lastIndexOf(":");
|
|
2487
|
-
if (lastColonIndex !== -1) {
|
|
2488
|
-
const artPath = rest.slice(0, lastColonIndex);
|
|
2489
|
-
const variantName = rest.slice(lastColonIndex + 1);
|
|
2490
|
-
const art = state.artFiles.get(artPath);
|
|
2491
|
-
if (art) return generatePreviewModule(art, toPascalCase(variantName), variantName, state.resolvedPreviewCss, state.resolvedPreviewSetup);
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
if (id.startsWith("\0musea-art:")) {
|
|
2495
|
-
const artPath = id.slice(11).replace(/\?musea-virtual$/, "");
|
|
2496
|
-
const art = state.artFiles.get(artPath);
|
|
2497
|
-
if (art) return generateArtModule(art, artPath);
|
|
2498
|
-
}
|
|
2499
|
-
if (id.startsWith("\0musea:")) {
|
|
2500
|
-
const realPath = id.slice(7).replace(/\?musea-virtual$/, "");
|
|
2501
|
-
const art = state.artFiles.get(realPath);
|
|
2502
|
-
if (art) return generateArtModule(art, realPath);
|
|
2503
|
-
}
|
|
2504
|
-
return null;
|
|
2505
|
-
};
|
|
2506
|
-
}
|
|
2507
|
-
function createHandleHotUpdate(state) {
|
|
2508
|
-
return async function handleHotUpdate(ctx) {
|
|
2509
|
-
const { file } = ctx;
|
|
2510
|
-
if (file.endsWith(".art.vue") && state.artFiles.has(file)) {
|
|
2511
|
-
await state.processArtFile(file);
|
|
2512
|
-
const virtualId = VIRTUAL_MUSEA_PREFIX + file + "?musea-virtual";
|
|
2513
|
-
const modules = state.getServer()?.moduleGraph.getModulesByFile(virtualId);
|
|
2514
|
-
if (modules) return [...modules];
|
|
2515
|
-
}
|
|
2516
|
-
if (state.inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue") && state.artFiles.has(file)) {
|
|
2517
|
-
await state.processArtFile(file);
|
|
2518
|
-
const virtualId = VIRTUAL_MUSEA_PREFIX + file;
|
|
2519
|
-
const modules = state.getServer()?.moduleGraph.getModulesByFile(virtualId);
|
|
2520
|
-
if (modules) return [...modules];
|
|
2521
|
-
}
|
|
2522
|
-
};
|
|
2523
|
-
}
|
|
2524
|
-
//#endregion
|
|
2525
|
-
//#region src/plugin/index.ts
|
|
2526
|
-
/**
|
|
2527
|
-
* Create Musea Vite plugin.
|
|
2528
|
-
*/
|
|
2529
|
-
function musea(options = {}) {
|
|
2530
|
-
let include = options.include ?? ["**/*.art.vue"];
|
|
2531
|
-
let exclude = options.exclude ?? ["node_modules/**", "dist/**"];
|
|
2532
|
-
let basePath = options.basePath ?? "/__musea__";
|
|
2533
|
-
let storybookCompat = options.storybookCompat ?? false;
|
|
2534
|
-
const storybookOutDir = options.storybookOutDir ?? ".storybook/stories";
|
|
2535
|
-
let inlineArt = options.inlineArt ?? false;
|
|
2536
|
-
const tokensPath = options.tokensPath;
|
|
2537
|
-
const themeConfig = buildThemeConfig(options.theme);
|
|
2538
|
-
const previewCss = options.previewCss ?? [];
|
|
2539
|
-
const previewSetup = options.previewSetup;
|
|
2540
|
-
let config;
|
|
2541
|
-
let server = null;
|
|
2542
|
-
const artFiles = /* @__PURE__ */ new Map();
|
|
2543
|
-
let resolvedPreviewCss = [];
|
|
2544
|
-
let resolvedPreviewSetup = null;
|
|
2545
|
-
const virtualState = {
|
|
2546
|
-
basePath,
|
|
2547
|
-
get inlineArt() {
|
|
2548
|
-
return inlineArt;
|
|
2549
|
-
},
|
|
2550
|
-
artFiles,
|
|
2551
|
-
resolvedPreviewCss,
|
|
2552
|
-
resolvedPreviewSetup,
|
|
2553
|
-
getConfigRoot: () => config.root,
|
|
2554
|
-
getServer: () => server,
|
|
2555
|
-
processArtFile
|
|
2556
|
-
};
|
|
2557
|
-
const mainPlugin = {
|
|
2558
|
-
name: "vite-plugin-musea",
|
|
2559
|
-
enforce: "pre",
|
|
2560
|
-
config() {
|
|
2561
|
-
return { resolve: { alias: { vue: "vue/dist/vue.esm-bundler.js" } } };
|
|
2562
|
-
},
|
|
2563
|
-
configResolved(resolvedConfig) {
|
|
2564
|
-
config = resolvedConfig;
|
|
2565
|
-
const vizeConfig = vizeConfigStore.get(resolvedConfig.root);
|
|
2566
|
-
if (vizeConfig?.musea) {
|
|
2567
|
-
const mc = vizeConfig.musea;
|
|
2568
|
-
if (!options.include && mc.include) include = mc.include;
|
|
2569
|
-
if (!options.exclude && mc.exclude) exclude = mc.exclude;
|
|
2570
|
-
if (!options.basePath && mc.basePath) basePath = mc.basePath;
|
|
2571
|
-
if (options.storybookCompat === void 0 && mc.storybookCompat !== void 0) storybookCompat = mc.storybookCompat;
|
|
2572
|
-
if (options.inlineArt === void 0 && mc.inlineArt !== void 0) inlineArt = mc.inlineArt;
|
|
2573
|
-
}
|
|
2574
|
-
virtualState.basePath = basePath;
|
|
2575
|
-
resolvedPreviewCss = previewCss.map((cssPath) => path.isAbsolute(cssPath) ? cssPath : path.resolve(resolvedConfig.root, cssPath));
|
|
2576
|
-
if (previewSetup) resolvedPreviewSetup = path.isAbsolute(previewSetup) ? previewSetup : path.resolve(resolvedConfig.root, previewSetup);
|
|
2577
|
-
virtualState.resolvedPreviewCss = resolvedPreviewCss;
|
|
2578
|
-
virtualState.resolvedPreviewSetup = resolvedPreviewSetup;
|
|
2579
|
-
},
|
|
2580
|
-
configureServer(devServer) {
|
|
2581
|
-
server = devServer;
|
|
2582
|
-
registerMiddleware(devServer, {
|
|
2583
|
-
basePath,
|
|
2584
|
-
themeConfig,
|
|
2585
|
-
artFiles,
|
|
2586
|
-
resolvedPreviewCss,
|
|
2587
|
-
resolvedPreviewSetup
|
|
2588
|
-
});
|
|
2589
|
-
devServer.middlewares.use(`${basePath}/api`, createApiMiddleware({
|
|
2590
|
-
config,
|
|
2591
|
-
artFiles,
|
|
2592
|
-
tokensPath,
|
|
2593
|
-
basePath,
|
|
2594
|
-
resolvedPreviewCss,
|
|
2595
|
-
resolvedPreviewSetup,
|
|
2596
|
-
processArtFile,
|
|
2597
|
-
getDevServerPort: () => devServer.config.server.port || 5173
|
|
2598
|
-
}));
|
|
2599
|
-
devServer.watcher.on("change", async (file) => {
|
|
2600
|
-
if (file.endsWith(".art.vue") && shouldProcess(file, include, exclude, config.root)) {
|
|
2601
|
-
await processArtFile(file);
|
|
2602
|
-
console.log(`[musea] Reloaded: ${path.relative(config.root, file)}`);
|
|
2603
|
-
}
|
|
2604
|
-
if (inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue")) {
|
|
2605
|
-
const hadArt = artFiles.has(file);
|
|
2606
|
-
if ((await fs.promises.readFile(file, "utf-8")).includes("<art")) {
|
|
2607
|
-
await processArtFile(file);
|
|
2608
|
-
console.log(`[musea] Reloaded inline art: ${path.relative(config.root, file)}`);
|
|
2609
|
-
} else if (hadArt) {
|
|
2610
|
-
artFiles.delete(file);
|
|
2611
|
-
console.log(`[musea] Removed inline art: ${path.relative(config.root, file)}`);
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
});
|
|
2615
|
-
devServer.watcher.on("add", async (file) => {
|
|
2616
|
-
if (file.endsWith(".art.vue") && shouldProcess(file, include, exclude, config.root)) {
|
|
2617
|
-
await processArtFile(file);
|
|
2618
|
-
console.log(`[musea] Added: ${path.relative(config.root, file)}`);
|
|
2619
|
-
}
|
|
2620
|
-
if (inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue")) {
|
|
2621
|
-
if ((await fs.promises.readFile(file, "utf-8")).includes("<art")) {
|
|
2622
|
-
await processArtFile(file);
|
|
2623
|
-
console.log(`[musea] Added inline art: ${path.relative(config.root, file)}`);
|
|
2624
|
-
}
|
|
2625
|
-
}
|
|
2626
|
-
});
|
|
2627
|
-
devServer.watcher.on("unlink", (file) => {
|
|
2628
|
-
if (artFiles.has(file)) {
|
|
2629
|
-
artFiles.delete(file);
|
|
2630
|
-
console.log(`[musea] Removed: ${path.relative(config.root, file)}`);
|
|
2631
|
-
}
|
|
2632
|
-
});
|
|
2633
|
-
return () => {
|
|
2634
|
-
devServer.httpServer?.once("listening", () => {
|
|
2635
|
-
const address = devServer.httpServer?.address();
|
|
2636
|
-
if (address && typeof address === "object") {
|
|
2637
|
-
const protocol = devServer.config.server.https ? "https" : "http";
|
|
2638
|
-
const rawHost = address.address;
|
|
2639
|
-
const url = `${protocol}://${rawHost === "::" || rawHost === "::1" || rawHost === "0.0.0.0" || rawHost === "127.0.0.1" ? "localhost" : rawHost}:${address.port}${basePath}`;
|
|
2640
|
-
console.log();
|
|
2641
|
-
console.log(` \x1b[36m➜\x1b[0m \x1b[1mMusea Gallery:\x1b[0m \x1b[36m${url}\x1b[0m`);
|
|
2642
|
-
}
|
|
2643
|
-
});
|
|
2644
|
-
};
|
|
2645
|
-
},
|
|
2646
|
-
async buildStart() {
|
|
2647
|
-
console.log(`[musea] config.root: ${config.root}, include: ${JSON.stringify(include)}`);
|
|
2648
|
-
const files = await scanArtFiles(config.root, include, exclude, inlineArt);
|
|
2649
|
-
console.log(`[musea] Found ${files.length} art files`);
|
|
2650
|
-
for (const file of files) await processArtFile(file);
|
|
2651
|
-
if (storybookCompat) await generateStorybookFiles(artFiles, config.root, storybookOutDir);
|
|
2652
|
-
},
|
|
2653
|
-
resolveId: createResolveId(virtualState),
|
|
2654
|
-
load: createLoad(virtualState),
|
|
2655
|
-
handleHotUpdate: createHandleHotUpdate(virtualState)
|
|
2656
|
-
};
|
|
2657
|
-
async function processArtFile(filePath) {
|
|
2658
|
-
try {
|
|
2659
|
-
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
2660
|
-
const parsed = loadNative().parseArt(source, { filename: filePath });
|
|
2661
|
-
if (!parsed.variants || parsed.variants.length === 0) return;
|
|
2662
|
-
const isInline = !filePath.endsWith(".art.vue");
|
|
2663
|
-
const info = {
|
|
2664
|
-
path: filePath,
|
|
2665
|
-
metadata: {
|
|
2666
|
-
title: parsed.metadata.title || (isInline ? path.basename(filePath, ".vue") : ""),
|
|
2667
|
-
description: parsed.metadata.description,
|
|
2668
|
-
component: isInline ? void 0 : parsed.metadata.component,
|
|
2669
|
-
category: parsed.metadata.category,
|
|
2670
|
-
tags: parsed.metadata.tags,
|
|
2671
|
-
status: parsed.metadata.status,
|
|
2672
|
-
order: parsed.metadata.order
|
|
2673
|
-
},
|
|
2674
|
-
variants: parsed.variants.map((v) => ({
|
|
2675
|
-
name: v.name,
|
|
2676
|
-
template: v.template,
|
|
2677
|
-
isDefault: v.isDefault,
|
|
2678
|
-
skipVrt: v.skipVrt
|
|
2679
|
-
})),
|
|
2680
|
-
hasScriptSetup: isInline ? false : parsed.hasScriptSetup,
|
|
2681
|
-
scriptSetupContent: !isInline && parsed.hasScriptSetup ? extractScriptSetupContent(source) : void 0,
|
|
2682
|
-
hasScript: parsed.hasScript,
|
|
2683
|
-
styleCount: parsed.styleCount,
|
|
2684
|
-
isInline,
|
|
2685
|
-
componentPath: isInline ? filePath : void 0
|
|
2686
|
-
};
|
|
2687
|
-
artFiles.set(filePath, info);
|
|
2688
|
-
} catch (e) {
|
|
2689
|
-
console.error(`[musea] Failed to process ${filePath}:`, e);
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
return [mainPlugin];
|
|
2693
|
-
}
|
|
2694
|
-
//#endregion
|
|
2695
|
-
//#region src/index.ts
|
|
2696
|
-
var src_default = musea;
|
|
2697
|
-
//#endregion
|
|
2698
|
-
export { MuseaA11yRunner, MuseaVrtRunner, buildTokenMap, src_default as default, generateArtFile, generateTokensHtml, generateTokensMarkdown, generateVrtJsonReport, generateVrtReport, musea, parseTokens, processStyleDictionary, resolveReferences, scanTokenUsage, writeArtFile };
|
|
2699
|
-
|
|
2700
|
-
//# sourceMappingURL=index.mjs.map
|