@vizejs/vite-plugin-musea 0.13.0 → 0.15.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/dist/{a11y-C6xqILwZ.js → a11y-7maCHrYD.js} +164 -143
- package/dist/a11y-7maCHrYD.js.map +1 -0
- package/dist/a11y-CjpWs0s0.js +3 -0
- package/dist/autogen-Dx-SIBf_.js +3 -0
- package/dist/{autogen-ymQnARZK.js → autogen-dfLosbY_.js} +97 -85
- package/dist/autogen-dfLosbY_.js.map +1 -0
- package/dist/cli/index.d.ts +61 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/{cli.js → cli/index.js} +146 -138
- package/dist/cli/index.js.map +1 -0
- package/dist/gallery/assets/{cssMode-CcXMra0m.js → cssMode-Bh8Yx3kR.js} +1 -1
- package/dist/gallery/assets/{editor.api-DngZ07MW.js → editor.api-CmduODkO.js} +1 -1
- package/dist/gallery/assets/{editor.main-B7YVyVYi.js → editor.main-BGzZ6mZ7.js} +2 -2
- package/dist/gallery/assets/{freemarker2-VkSVdked.js → freemarker2-Cn3sOnVM.js} +1 -1
- package/dist/gallery/assets/{handlebars-YowBDP4W.js → handlebars-Bxtt_XDm.js} +1 -1
- package/dist/gallery/assets/{html-DJtSoeLQ.js → html-wxaydg4H.js} +1 -1
- package/dist/gallery/assets/{htmlMode-CfXIwSy5.js → htmlMode-EOxgEq0U.js} +1 -1
- package/dist/gallery/assets/{index-0_abCQYn.css → index-Cp7AWs0x.css} +1 -1
- package/dist/gallery/assets/{index-D94E3YZ4.js → index-DiHal_rf.js} +3 -3
- package/dist/gallery/assets/{javascript-OR93O4ZK.js → javascript-DN4Z2eWk.js} +1 -1
- package/dist/gallery/assets/{jsonMode-Cgol9YnW.js → jsonMode-tuRq32My.js} +1 -1
- package/dist/gallery/assets/{liquid-CrwTQQjB.js → liquid-D4J1rGOp.js} +1 -1
- package/dist/gallery/assets/{mdx-DwR18PX6.js → mdx-DxeOF6WC.js} +1 -1
- package/dist/gallery/assets/{monaco.contribution-CtStVxRd.js → monaco.contribution-C457czx3.js} +2 -2
- package/dist/gallery/assets/{python-D86nvZ4A.js → python-HLp-R9eA.js} +1 -1
- package/dist/gallery/assets/{razor-1784i4Ae.js → razor-2U4-SYvG.js} +1 -1
- package/dist/gallery/assets/{tsMode-DMkSHunc.js → tsMode-jWuA-Omz.js} +1 -1
- package/dist/gallery/assets/{typescript-Bu4A5btR.js → typescript-rOoZv0aH.js} +1 -1
- package/dist/gallery/assets/{xml-Sny7sZAt.js → xml-BfA65ILD.js} +1 -1
- package/dist/gallery/assets/{yaml-DKD1HvU8.js → yaml-CddGDANN.js} +1 -1
- package/dist/gallery/index.html +2 -2
- package/dist/index.css +496 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +342 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +925 -1320
- package/dist/index.js.map +1 -1
- package/dist/{vrt-CrjRhMVE.js → vrt-5_c9P1YY.js} +224 -184
- package/dist/vrt-5_c9P1YY.js.map +1 -0
- package/dist/{vrt-BuMkTrLK.d.ts → vrt-D6OumJUH.d.ts} +76 -260
- package/dist/vrt-D6OumJUH.d.ts.map +1 -0
- package/dist/vrt.d.ts +1 -1
- package/dist/vrt.js +1 -1
- package/package.json +7 -7
- package/dist/a11y-C6xqILwZ.js.map +0 -1
- package/dist/a11y-cQIJXM5k.d.ts +0 -61
- package/dist/a11y-cQIJXM5k.d.ts.map +0 -1
- package/dist/a11y.d.ts +0 -3
- package/dist/a11y.js +0 -3
- package/dist/autogen-D3Zjc3zI.d.ts +0 -64
- package/dist/autogen-D3Zjc3zI.d.ts.map +0 -1
- package/dist/autogen-ymQnARZK.js.map +0 -1
- package/dist/autogen.d.ts +0 -2
- package/dist/autogen.js +0 -3
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/vrt-BuMkTrLK.d.ts.map +0 -1
- package/dist/vrt-CrjRhMVE.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
1
|
+
import { MuseaVrtRunner, generateVrtJsonReport, generateVrtReport } from "./vrt-5_c9P1YY.js";
|
|
2
|
+
import { MuseaA11yRunner } from "./a11y-7maCHrYD.js";
|
|
3
|
+
import { generateArtFile, writeArtFile } from "./autogen-dfLosbY_.js";
|
|
6
4
|
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
7
6
|
import { vizeConfigStore } from "@vizejs/vite-plugin";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
8
|
|
|
9
9
|
//#region src/native-loader.ts
|
|
10
10
|
let native = null;
|
|
@@ -85,514 +85,253 @@ function analyzeSfcFallback(source, _options) {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
//#endregion
|
|
88
|
-
//#region src/
|
|
88
|
+
//#region src/utils.ts
|
|
89
|
+
function shouldProcess(file, include, exclude, root) {
|
|
90
|
+
const relative = path.relative(root, file);
|
|
91
|
+
for (const pattern of exclude) if (matchGlob(relative, pattern)) return false;
|
|
92
|
+
for (const pattern of include) if (matchGlob(relative, pattern)) return true;
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
function matchGlob(filepath, pattern) {
|
|
96
|
+
const PLACEHOLDER = "<<GLOBSTAR>>";
|
|
97
|
+
const regex = pattern.replaceAll("**", PLACEHOLDER).replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replaceAll(PLACEHOLDER, ".*");
|
|
98
|
+
return new RegExp(`^${regex}$`).test(filepath);
|
|
99
|
+
}
|
|
100
|
+
async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
|
|
101
|
+
const files = [];
|
|
102
|
+
async function scan(dir) {
|
|
103
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const fullPath = path.join(dir, entry.name);
|
|
106
|
+
const relative = path.relative(root, fullPath);
|
|
107
|
+
let excluded = false;
|
|
108
|
+
for (const pattern of exclude) if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {
|
|
109
|
+
excluded = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
if (excluded) continue;
|
|
113
|
+
if (entry.isDirectory()) await scan(fullPath);
|
|
114
|
+
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
115
|
+
for (const pattern of include) if (matchGlob(relative, pattern)) {
|
|
116
|
+
files.push(fullPath);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
} else if (scanInlineArt && entry.isFile() && entry.name.endsWith(".vue") && !entry.name.endsWith(".art.vue")) {
|
|
120
|
+
const content = await fs.promises.readFile(fullPath, "utf-8");
|
|
121
|
+
if (content.includes("<art")) files.push(fullPath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
await scan(root);
|
|
126
|
+
return files;
|
|
127
|
+
}
|
|
128
|
+
async function generateStorybookFiles(artFiles, root, outDir) {
|
|
129
|
+
const binding = loadNative();
|
|
130
|
+
const outputDir = path.resolve(root, outDir);
|
|
131
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
132
|
+
for (const [filePath, _art] of artFiles) try {
|
|
133
|
+
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
134
|
+
const csf = binding.artToCsf(source, { filename: filePath });
|
|
135
|
+
const outputPath = path.join(outputDir, csf.filename);
|
|
136
|
+
await fs.promises.writeFile(outputPath, csf.code, "utf-8");
|
|
137
|
+
console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);
|
|
138
|
+
} catch (e) {
|
|
139
|
+
console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function toPascalCase(str) {
|
|
143
|
+
return str.split(/[\s\-_]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
144
|
+
}
|
|
145
|
+
function escapeTemplate(str) {
|
|
146
|
+
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
147
|
+
}
|
|
148
|
+
function escapeHtml(str) {
|
|
149
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
150
|
+
}
|
|
89
151
|
/**
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* Contains the inline gallery SPA template (used as a fallback when the
|
|
93
|
-
* pre-built gallery is not available) and the gallery virtual module.
|
|
152
|
+
* Build the theme config object from plugin options for runtime injection.
|
|
94
153
|
*/
|
|
154
|
+
function buildThemeConfig(theme) {
|
|
155
|
+
if (!theme) return void 0;
|
|
156
|
+
if (typeof theme === "string") return { default: theme };
|
|
157
|
+
const themes = Array.isArray(theme) ? theme : [theme];
|
|
158
|
+
const custom = {};
|
|
159
|
+
for (const t of themes) custom[t.name] = {
|
|
160
|
+
base: t.base,
|
|
161
|
+
colors: t.colors
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
default: themes[0].name,
|
|
165
|
+
custom
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region src/art-module.ts
|
|
95
171
|
/**
|
|
96
|
-
*
|
|
172
|
+
* Extract the content of the first <script setup> block from a Vue SFC source.
|
|
97
173
|
*/
|
|
98
|
-
function
|
|
99
|
-
const
|
|
100
|
-
return
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
/* Layout */
|
|
226
|
-
.main {
|
|
227
|
-
display: grid;
|
|
228
|
-
grid-template-columns: 260px 1fr;
|
|
229
|
-
min-height: calc(100vh - 56px);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/* Sidebar */
|
|
233
|
-
.sidebar {
|
|
234
|
-
background: var(--musea-bg-secondary);
|
|
235
|
-
border-right: 1px solid var(--musea-border);
|
|
236
|
-
overflow-y: auto;
|
|
237
|
-
overflow-x: hidden;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.sidebar::-webkit-scrollbar {
|
|
241
|
-
width: 6px;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.sidebar::-webkit-scrollbar-track {
|
|
245
|
-
background: transparent;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.sidebar::-webkit-scrollbar-thumb {
|
|
249
|
-
background: var(--musea-border);
|
|
250
|
-
border-radius: 3px;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
.sidebar-section {
|
|
254
|
-
padding: 0.75rem;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.category-header {
|
|
258
|
-
display: flex;
|
|
259
|
-
align-items: center;
|
|
260
|
-
gap: 0.5rem;
|
|
261
|
-
padding: 0.625rem 0.75rem;
|
|
262
|
-
font-size: 0.6875rem;
|
|
263
|
-
font-weight: 600;
|
|
264
|
-
text-transform: uppercase;
|
|
265
|
-
letter-spacing: 0.08em;
|
|
266
|
-
color: var(--musea-text-muted);
|
|
267
|
-
cursor: pointer;
|
|
268
|
-
user-select: none;
|
|
269
|
-
border-radius: var(--musea-radius-sm);
|
|
270
|
-
transition: background var(--musea-transition);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.category-header:hover {
|
|
274
|
-
background: var(--musea-bg-tertiary);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
.category-icon {
|
|
278
|
-
width: 16px;
|
|
279
|
-
height: 16px;
|
|
280
|
-
transition: transform var(--musea-transition);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
.category-header.collapsed .category-icon {
|
|
284
|
-
transform: rotate(-90deg);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
.category-count {
|
|
288
|
-
margin-left: auto;
|
|
289
|
-
background: var(--musea-bg-tertiary);
|
|
290
|
-
padding: 0.125rem 0.375rem;
|
|
291
|
-
border-radius: 4px;
|
|
292
|
-
font-size: 0.625rem;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
.art-list {
|
|
296
|
-
list-style: none;
|
|
297
|
-
margin-top: 0.25rem;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
.art-item {
|
|
301
|
-
display: flex;
|
|
302
|
-
align-items: center;
|
|
303
|
-
gap: 0.625rem;
|
|
304
|
-
padding: 0.5rem 0.75rem 0.5rem 1.75rem;
|
|
305
|
-
border-radius: var(--musea-radius-sm);
|
|
306
|
-
cursor: pointer;
|
|
307
|
-
font-size: 0.8125rem;
|
|
308
|
-
color: var(--musea-text-secondary);
|
|
309
|
-
transition: all var(--musea-transition);
|
|
310
|
-
position: relative;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
.art-item::before {
|
|
314
|
-
content: '';
|
|
315
|
-
position: absolute;
|
|
316
|
-
left: 0.75rem;
|
|
317
|
-
top: 50%;
|
|
318
|
-
transform: translateY(-50%);
|
|
319
|
-
width: 6px;
|
|
320
|
-
height: 6px;
|
|
321
|
-
border-radius: 50%;
|
|
322
|
-
background: var(--musea-border);
|
|
323
|
-
transition: background var(--musea-transition);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
.art-item:hover {
|
|
327
|
-
background: var(--musea-bg-tertiary);
|
|
328
|
-
color: var(--musea-text);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
.art-item:hover::before {
|
|
332
|
-
background: var(--musea-text-muted);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
.art-item.active {
|
|
336
|
-
background: var(--musea-accent-subtle);
|
|
337
|
-
color: var(--musea-accent-hover);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
.art-item.active::before {
|
|
341
|
-
background: var(--musea-accent);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
.art-variant-count {
|
|
345
|
-
margin-left: auto;
|
|
346
|
-
font-size: 0.6875rem;
|
|
347
|
-
color: var(--musea-text-muted);
|
|
348
|
-
opacity: 0;
|
|
349
|
-
transition: opacity var(--musea-transition);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
.art-item:hover .art-variant-count {
|
|
353
|
-
opacity: 1;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/* Content */
|
|
357
|
-
.content {
|
|
358
|
-
background: var(--musea-bg-primary);
|
|
359
|
-
overflow-y: auto;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
.content-inner {
|
|
363
|
-
max-width: 1400px;
|
|
364
|
-
margin: 0 auto;
|
|
365
|
-
padding: 2rem;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
.content-header {
|
|
369
|
-
margin-bottom: 2rem;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
.content-title {
|
|
373
|
-
font-size: 1.5rem;
|
|
374
|
-
font-weight: 700;
|
|
375
|
-
margin-bottom: 0.5rem;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.content-description {
|
|
379
|
-
color: var(--musea-text-muted);
|
|
380
|
-
font-size: 0.9375rem;
|
|
381
|
-
max-width: 600px;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
.content-meta {
|
|
385
|
-
display: flex;
|
|
386
|
-
align-items: center;
|
|
387
|
-
gap: 1rem;
|
|
388
|
-
margin-top: 1rem;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
.meta-tag {
|
|
392
|
-
display: inline-flex;
|
|
393
|
-
align-items: center;
|
|
394
|
-
gap: 0.375rem;
|
|
395
|
-
padding: 0.25rem 0.625rem;
|
|
396
|
-
background: var(--musea-bg-secondary);
|
|
397
|
-
border: 1px solid var(--musea-border);
|
|
398
|
-
border-radius: var(--musea-radius-sm);
|
|
399
|
-
font-size: 0.75rem;
|
|
400
|
-
color: var(--musea-text-muted);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
.meta-tag svg {
|
|
404
|
-
width: 12px;
|
|
405
|
-
height: 12px;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/* Gallery Grid */
|
|
409
|
-
.gallery {
|
|
410
|
-
display: grid;
|
|
411
|
-
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
412
|
-
gap: 1.25rem;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/* Variant Card */
|
|
416
|
-
.variant-card {
|
|
417
|
-
background: var(--musea-bg-secondary);
|
|
418
|
-
border: 1px solid var(--musea-border);
|
|
419
|
-
border-radius: var(--musea-radius-lg);
|
|
420
|
-
overflow: hidden;
|
|
421
|
-
transition: all var(--musea-transition);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
.variant-card:hover {
|
|
425
|
-
border-color: var(--musea-text-muted);
|
|
426
|
-
box-shadow: var(--musea-shadow);
|
|
427
|
-
transform: translateY(-2px);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
.variant-preview {
|
|
431
|
-
aspect-ratio: 16 / 10;
|
|
432
|
-
background: var(--musea-bg-tertiary);
|
|
433
|
-
display: flex;
|
|
434
|
-
align-items: center;
|
|
435
|
-
justify-content: center;
|
|
436
|
-
position: relative;
|
|
437
|
-
overflow: hidden;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
.variant-preview iframe {
|
|
441
|
-
width: 100%;
|
|
442
|
-
height: 100%;
|
|
443
|
-
border: none;
|
|
444
|
-
background: white;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
.variant-preview-placeholder {
|
|
448
|
-
color: var(--musea-text-muted);
|
|
449
|
-
font-size: 0.8125rem;
|
|
450
|
-
text-align: center;
|
|
451
|
-
padding: 1rem;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
.variant-preview-code {
|
|
455
|
-
font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
|
|
456
|
-
font-size: 0.75rem;
|
|
457
|
-
color: var(--musea-text-muted);
|
|
458
|
-
background: var(--musea-bg-primary);
|
|
459
|
-
padding: 1rem;
|
|
460
|
-
overflow: auto;
|
|
461
|
-
max-height: 100%;
|
|
462
|
-
width: 100%;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.variant-info {
|
|
466
|
-
padding: 1rem;
|
|
467
|
-
border-top: 1px solid var(--musea-border);
|
|
468
|
-
display: flex;
|
|
469
|
-
align-items: center;
|
|
470
|
-
justify-content: space-between;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
.variant-name {
|
|
474
|
-
font-weight: 600;
|
|
475
|
-
font-size: 0.875rem;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
.variant-badge {
|
|
479
|
-
font-size: 0.625rem;
|
|
480
|
-
font-weight: 600;
|
|
481
|
-
text-transform: uppercase;
|
|
482
|
-
letter-spacing: 0.04em;
|
|
483
|
-
padding: 0.1875rem 0.5rem;
|
|
484
|
-
border-radius: 4px;
|
|
485
|
-
background: var(--musea-accent-subtle);
|
|
486
|
-
color: var(--musea-accent);
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
.variant-actions {
|
|
490
|
-
display: flex;
|
|
491
|
-
gap: 0.5rem;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
.variant-action-btn {
|
|
495
|
-
width: 28px;
|
|
496
|
-
height: 28px;
|
|
497
|
-
border: none;
|
|
498
|
-
background: var(--musea-bg-tertiary);
|
|
499
|
-
border-radius: var(--musea-radius-sm);
|
|
500
|
-
color: var(--musea-text-muted);
|
|
501
|
-
cursor: pointer;
|
|
502
|
-
display: flex;
|
|
503
|
-
align-items: center;
|
|
504
|
-
justify-content: center;
|
|
505
|
-
transition: all var(--musea-transition);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
.variant-action-btn:hover {
|
|
509
|
-
background: var(--musea-bg-elevated);
|
|
510
|
-
color: var(--musea-text);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
.variant-action-btn svg {
|
|
514
|
-
width: 14px;
|
|
515
|
-
height: 14px;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
/* Empty State */
|
|
519
|
-
.empty-state {
|
|
520
|
-
display: flex;
|
|
521
|
-
flex-direction: column;
|
|
522
|
-
align-items: center;
|
|
523
|
-
justify-content: center;
|
|
524
|
-
min-height: 400px;
|
|
525
|
-
text-align: center;
|
|
526
|
-
padding: 2rem;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
.empty-state-icon {
|
|
530
|
-
width: 80px;
|
|
531
|
-
height: 80px;
|
|
532
|
-
background: var(--musea-bg-secondary);
|
|
533
|
-
border-radius: var(--musea-radius-lg);
|
|
534
|
-
display: flex;
|
|
535
|
-
align-items: center;
|
|
536
|
-
justify-content: center;
|
|
537
|
-
margin-bottom: 1.5rem;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
.empty-state-icon svg {
|
|
541
|
-
width: 40px;
|
|
542
|
-
height: 40px;
|
|
543
|
-
color: var(--musea-text-muted);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
.empty-state-title {
|
|
547
|
-
font-size: 1.125rem;
|
|
548
|
-
font-weight: 600;
|
|
549
|
-
margin-bottom: 0.5rem;
|
|
550
|
-
}
|
|
174
|
+
function extractScriptSetupContent(source) {
|
|
175
|
+
const match = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
|
|
176
|
+
return match?.[1]?.trim();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Parse script setup content into imports and setup body.
|
|
180
|
+
* Returns the import lines, setup body lines, and all identifiers to expose.
|
|
181
|
+
*/
|
|
182
|
+
function parseScriptSetupForArt(content) {
|
|
183
|
+
const lines = content.split("\n");
|
|
184
|
+
const imports = [];
|
|
185
|
+
const setupBody = [];
|
|
186
|
+
const returnNames = new Set();
|
|
187
|
+
for (const line of lines) {
|
|
188
|
+
const trimmed = line.trim();
|
|
189
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
190
|
+
if (trimmed.startsWith("import ")) {
|
|
191
|
+
imports.push(line);
|
|
192
|
+
const defaultMatch = trimmed.match(/^import\s+(\w+)/);
|
|
193
|
+
if (defaultMatch && defaultMatch[1] !== "type") returnNames.add(defaultMatch[1]);
|
|
194
|
+
const namedMatch = trimmed.match(/\{([^}]+)\}/);
|
|
195
|
+
if (namedMatch) for (const part of namedMatch[1].split(",")) {
|
|
196
|
+
const name = part.trim().split(/\s+as\s+/).pop()?.trim();
|
|
197
|
+
if (name && !name.startsWith("type ")) returnNames.add(name);
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
setupBody.push(line);
|
|
201
|
+
const constMatch = trimmed.match(/^(?:const|let|var)\s+(\w+)/);
|
|
202
|
+
if (constMatch) returnNames.add(constMatch[1]);
|
|
203
|
+
const destructMatch = trimmed.match(/^(?:const|let|var)\s+\{([^}]+)\}/);
|
|
204
|
+
if (destructMatch) for (const part of destructMatch[1].split(",")) {
|
|
205
|
+
const name = part.trim().split(/\s*:\s*/).shift()?.trim();
|
|
206
|
+
if (name) returnNames.add(name);
|
|
207
|
+
}
|
|
208
|
+
const arrayMatch = trimmed.match(/^(?:const|let|var)\s+\[([^\]]+)\]/);
|
|
209
|
+
if (arrayMatch) for (const part of arrayMatch[1].split(",")) {
|
|
210
|
+
const name = part.trim();
|
|
211
|
+
if (name && name !== "...") returnNames.add(name);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
returnNames.delete("type");
|
|
216
|
+
return {
|
|
217
|
+
imports,
|
|
218
|
+
setupBody,
|
|
219
|
+
returnNames: [...returnNames]
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function generateArtModule(art, filePath) {
|
|
223
|
+
let componentImportPath;
|
|
224
|
+
let componentName;
|
|
225
|
+
if (art.isInline && art.componentPath) {
|
|
226
|
+
componentImportPath = art.componentPath;
|
|
227
|
+
componentName = path.basename(art.componentPath, ".vue");
|
|
228
|
+
} else if (art.metadata.component) {
|
|
229
|
+
const comp = art.metadata.component;
|
|
230
|
+
componentImportPath = path.isAbsolute(comp) ? comp : path.resolve(path.dirname(filePath), comp);
|
|
231
|
+
componentName = path.basename(comp, ".vue");
|
|
232
|
+
}
|
|
233
|
+
const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
|
|
234
|
+
let code = `
|
|
235
|
+
// Auto-generated module for: ${path.basename(filePath)}
|
|
236
|
+
import { defineComponent, h } from 'vue';
|
|
237
|
+
`;
|
|
238
|
+
if (scriptSetup) {
|
|
239
|
+
const artDir = path.dirname(filePath);
|
|
240
|
+
for (const imp of scriptSetup.imports) {
|
|
241
|
+
const resolved = imp.replace(/from\s+(['"])(\.[^'"]+)\1/, (_match, quote, relPath) => {
|
|
242
|
+
const absPath = path.resolve(artDir, relPath);
|
|
243
|
+
return `from ${quote}${absPath}${quote}`;
|
|
244
|
+
});
|
|
245
|
+
code += `${resolved}\n`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (componentImportPath && componentName) {
|
|
249
|
+
const alreadyImported = scriptSetup?.imports.some((imp) => {
|
|
250
|
+
if (imp.includes(`from '${componentImportPath}'`) || imp.includes(`from "${componentImportPath}"`)) return true;
|
|
251
|
+
return new RegExp(`^import\\s+${componentName}[\\s,]`).test(imp.trim());
|
|
252
|
+
});
|
|
253
|
+
if (!alreadyImported) code += `import ${componentName} from '${componentImportPath}';\n`;
|
|
254
|
+
code += `export const __component__ = ${componentName};\n`;
|
|
255
|
+
}
|
|
256
|
+
code += `
|
|
257
|
+
export const metadata = ${JSON.stringify(art.metadata)};
|
|
258
|
+
export const variants = ${JSON.stringify(art.variants)};
|
|
259
|
+
`;
|
|
260
|
+
for (const variant of art.variants) {
|
|
261
|
+
const variantComponentName = toPascalCase(variant.name);
|
|
262
|
+
let template = variant.template;
|
|
263
|
+
if (componentName) template = template.replace(/<Self/g, `<${componentName}`).replace(/<\/Self>/g, `</${componentName}>`);
|
|
264
|
+
const escapedTemplate = template.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
265
|
+
const fullTemplate = `<div data-variant="${variant.name}">${escapedTemplate}</div>`;
|
|
266
|
+
const componentNames = new Set();
|
|
267
|
+
if (componentName) componentNames.add(componentName);
|
|
268
|
+
if (scriptSetup) {
|
|
269
|
+
for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.add(name);
|
|
270
|
+
}
|
|
271
|
+
const components = componentNames.size > 0 ? ` components: { ${[...componentNames].join(", ")} },\n` : "";
|
|
272
|
+
if (scriptSetup && scriptSetup.setupBody.length > 0) code += `
|
|
273
|
+
export const ${variantComponentName} = defineComponent({
|
|
274
|
+
name: '${variantComponentName}',
|
|
275
|
+
${components} setup() {
|
|
276
|
+
${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
|
|
277
|
+
return { ${scriptSetup.returnNames.join(", ")} };
|
|
278
|
+
},
|
|
279
|
+
template: \`${fullTemplate}\`,
|
|
280
|
+
});
|
|
281
|
+
`;
|
|
282
|
+
else if (componentName) code += `
|
|
283
|
+
export const ${variantComponentName} = {
|
|
284
|
+
name: '${variantComponentName}',
|
|
285
|
+
${components} template: \`${fullTemplate}\`,
|
|
286
|
+
};
|
|
287
|
+
`;
|
|
288
|
+
else code += `
|
|
289
|
+
export const ${variantComponentName} = {
|
|
290
|
+
name: '${variantComponentName}',
|
|
291
|
+
template: \`${fullTemplate}\`,
|
|
292
|
+
};
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];
|
|
296
|
+
if (defaultVariant) code += `
|
|
297
|
+
export default ${toPascalCase(defaultVariant.name)};
|
|
298
|
+
`;
|
|
299
|
+
return code;
|
|
300
|
+
}
|
|
551
301
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
max-width: 300px;
|
|
556
|
-
}
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/gallery/styles-base.css
|
|
304
|
+
var styles_base_default = {};
|
|
557
305
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
align-items: center;
|
|
562
|
-
justify-content: center;
|
|
563
|
-
min-height: 200px;
|
|
564
|
-
color: var(--musea-text-muted);
|
|
565
|
-
gap: 0.75rem;
|
|
566
|
-
}
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/gallery/styles-layout.css
|
|
308
|
+
var styles_layout_default = {};
|
|
567
309
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
border: 2px solid var(--musea-border);
|
|
572
|
-
border-top-color: var(--musea-accent);
|
|
573
|
-
border-radius: 50%;
|
|
574
|
-
animation: spin 0.8s linear infinite;
|
|
575
|
-
}
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/gallery/styles-components.css
|
|
312
|
+
var styles_components_default = {};
|
|
576
313
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/gallery/styles.ts
|
|
316
|
+
/**
|
|
317
|
+
* Generate the full gallery CSS styles string.
|
|
318
|
+
*/
|
|
319
|
+
function generateGalleryStyles() {
|
|
320
|
+
return `${styles_base_default}\n${styles_layout_default}\n${styles_components_default}`;
|
|
321
|
+
}
|
|
580
322
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
</style>
|
|
594
|
-
</head>
|
|
595
|
-
<body>
|
|
323
|
+
//#endregion
|
|
324
|
+
//#region src/gallery/template.ts
|
|
325
|
+
/**
|
|
326
|
+
* HTML structure and inline JS generation for the Musea gallery.
|
|
327
|
+
*
|
|
328
|
+
* Extracted from gallery.ts to keep file sizes manageable.
|
|
329
|
+
*/
|
|
330
|
+
/**
|
|
331
|
+
* Generate the gallery HTML body (header, sidebar, content, and inline script).
|
|
332
|
+
*/
|
|
333
|
+
function generateGalleryBody(basePath) {
|
|
334
|
+
return `
|
|
596
335
|
<header class="header">
|
|
597
336
|
<div class="header-left">
|
|
598
337
|
<a href="${basePath}" class="logo">
|
|
@@ -644,9 +383,13 @@ function generateGalleryHtml(basePath, themeConfig) {
|
|
|
644
383
|
<div class="empty-state-text">Choose a component from the sidebar to view its variants and documentation</div>
|
|
645
384
|
</div>
|
|
646
385
|
</section>
|
|
647
|
-
</main
|
|
648
|
-
|
|
649
|
-
|
|
386
|
+
</main>`;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Generate the gallery inline script (SPA logic).
|
|
390
|
+
*/
|
|
391
|
+
function generateGalleryScript(basePath) {
|
|
392
|
+
return `
|
|
650
393
|
const basePath = '${basePath}';
|
|
651
394
|
let arts = [];
|
|
652
395
|
let selectedArt = null;
|
|
@@ -807,7 +550,29 @@ function generateGalleryHtml(basePath, themeConfig) {
|
|
|
807
550
|
}
|
|
808
551
|
});
|
|
809
552
|
|
|
810
|
-
loadArts()
|
|
553
|
+
loadArts();`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
//#endregion
|
|
557
|
+
//#region src/gallery/index.ts
|
|
558
|
+
/**
|
|
559
|
+
* Generate the inline gallery HTML page.
|
|
560
|
+
*/
|
|
561
|
+
function generateGalleryHtml(basePath, themeConfig) {
|
|
562
|
+
const themeScript = themeConfig ? `window.__MUSEA_THEME_CONFIG__=${JSON.stringify(themeConfig)};` : "";
|
|
563
|
+
return `<!DOCTYPE html>
|
|
564
|
+
<html lang="en">
|
|
565
|
+
<head>
|
|
566
|
+
<meta charset="UTF-8">
|
|
567
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
568
|
+
<title>Musea - Component Gallery</title>
|
|
569
|
+
<script>window.__MUSEA_BASE_PATH__='${basePath}';${themeScript}</script>
|
|
570
|
+
<style>${generateGalleryStyles()}
|
|
571
|
+
</style>
|
|
572
|
+
</head>
|
|
573
|
+
<body>${generateGalleryBody(basePath)}
|
|
574
|
+
|
|
575
|
+
<script type="module">${generateGalleryScript(basePath)}
|
|
811
576
|
</script>
|
|
812
577
|
</body>
|
|
813
578
|
</html>`;
|
|
@@ -826,89 +591,19 @@ export async function loadArts() {
|
|
|
826
591
|
}
|
|
827
592
|
|
|
828
593
|
//#endregion
|
|
829
|
-
//#region src/
|
|
830
|
-
function shouldProcess(file, include, exclude, root) {
|
|
831
|
-
const relative = path.relative(root, file);
|
|
832
|
-
for (const pattern of exclude) if (matchGlob(relative, pattern)) return false;
|
|
833
|
-
for (const pattern of include) if (matchGlob(relative, pattern)) return true;
|
|
834
|
-
return false;
|
|
835
|
-
}
|
|
836
|
-
function matchGlob(filepath, pattern) {
|
|
837
|
-
const PLACEHOLDER = "<<GLOBSTAR>>";
|
|
838
|
-
const regex = pattern.replaceAll("**", PLACEHOLDER).replace(/\./g, "\\.").replace(/\*/g, "[^/]*").replaceAll(PLACEHOLDER, ".*");
|
|
839
|
-
return new RegExp(`^${regex}$`).test(filepath);
|
|
840
|
-
}
|
|
841
|
-
async function scanArtFiles(root, include, exclude, scanInlineArt = false) {
|
|
842
|
-
const files = [];
|
|
843
|
-
async function scan(dir) {
|
|
844
|
-
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
845
|
-
for (const entry of entries) {
|
|
846
|
-
const fullPath = path.join(dir, entry.name);
|
|
847
|
-
const relative = path.relative(root, fullPath);
|
|
848
|
-
let excluded = false;
|
|
849
|
-
for (const pattern of exclude) if (matchGlob(relative, pattern) || matchGlob(entry.name, pattern)) {
|
|
850
|
-
excluded = true;
|
|
851
|
-
break;
|
|
852
|
-
}
|
|
853
|
-
if (excluded) continue;
|
|
854
|
-
if (entry.isDirectory()) await scan(fullPath);
|
|
855
|
-
else if (entry.isFile() && entry.name.endsWith(".art.vue")) {
|
|
856
|
-
for (const pattern of include) if (matchGlob(relative, pattern)) {
|
|
857
|
-
files.push(fullPath);
|
|
858
|
-
break;
|
|
859
|
-
}
|
|
860
|
-
} else if (scanInlineArt && entry.isFile() && entry.name.endsWith(".vue") && !entry.name.endsWith(".art.vue")) {
|
|
861
|
-
const content = await fs.promises.readFile(fullPath, "utf-8");
|
|
862
|
-
if (content.includes("<art")) files.push(fullPath);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
await scan(root);
|
|
867
|
-
return files;
|
|
868
|
-
}
|
|
869
|
-
async function generateStorybookFiles(artFiles, root, outDir) {
|
|
870
|
-
const binding = loadNative();
|
|
871
|
-
const outputDir = path.resolve(root, outDir);
|
|
872
|
-
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
873
|
-
for (const [filePath, _art] of artFiles) try {
|
|
874
|
-
const source = await fs.promises.readFile(filePath, "utf-8");
|
|
875
|
-
const csf = binding.artToCsf(source, { filename: filePath });
|
|
876
|
-
const outputPath = path.join(outputDir, csf.filename);
|
|
877
|
-
await fs.promises.writeFile(outputPath, csf.code, "utf-8");
|
|
878
|
-
console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);
|
|
879
|
-
} catch (e) {
|
|
880
|
-
console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
function toPascalCase(str) {
|
|
884
|
-
return str.split(/[\s\-_]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
885
|
-
}
|
|
886
|
-
function escapeTemplate(str) {
|
|
887
|
-
return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\n/g, "\\n");
|
|
888
|
-
}
|
|
889
|
-
function escapeHtml(str) {
|
|
890
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
891
|
-
}
|
|
594
|
+
//#region src/preview/addons.ts
|
|
892
595
|
/**
|
|
893
|
-
*
|
|
596
|
+
* Addon initialization code for Musea preview iframes.
|
|
597
|
+
*
|
|
598
|
+
* Contains DOM event capture, measure overlay, and message handler logic
|
|
599
|
+
* injected into preview modules.
|
|
600
|
+
*
|
|
601
|
+
* Extracted from preview.ts to keep file sizes manageable.
|
|
602
|
+
*/
|
|
603
|
+
/**
|
|
604
|
+
* Addon initialization code injected into preview iframe modules.
|
|
605
|
+
* Shared between generatePreviewModule and generatePreviewModuleWithProps.
|
|
894
606
|
*/
|
|
895
|
-
function buildThemeConfig(theme) {
|
|
896
|
-
if (!theme) return void 0;
|
|
897
|
-
if (typeof theme === "string") return { default: theme };
|
|
898
|
-
const themes = Array.isArray(theme) ? theme : [theme];
|
|
899
|
-
const custom = {};
|
|
900
|
-
for (const t of themes) custom[t.name] = {
|
|
901
|
-
base: t.base,
|
|
902
|
-
colors: t.colors
|
|
903
|
-
};
|
|
904
|
-
return {
|
|
905
|
-
default: themes[0].name,
|
|
906
|
-
custom
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
//#endregion
|
|
911
|
-
//#region src/preview.ts
|
|
912
607
|
const MUSEA_ADDONS_INIT_CODE = `
|
|
913
608
|
function __museaInitAddons(container, variantName) {
|
|
914
609
|
// === DOM event capture ===
|
|
@@ -1196,7 +891,111 @@ function __museaInitAddons(container, variantName) {
|
|
|
1196
891
|
// Notify parent that iframe is ready
|
|
1197
892
|
window.parent.postMessage({ type: 'musea:ready', payload: {} }, '*');
|
|
1198
893
|
}
|
|
1199
|
-
`;
|
|
894
|
+
`;
|
|
895
|
+
|
|
896
|
+
//#endregion
|
|
897
|
+
//#region src/preview/html.ts
|
|
898
|
+
function generatePreviewHtml(art, variant, _basePath, viteBase) {
|
|
899
|
+
const previewModuleUrl = `${_basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;
|
|
900
|
+
const base = (viteBase || "/").replace(/\/$/, "");
|
|
901
|
+
return `<!DOCTYPE html>
|
|
902
|
+
<html lang="en">
|
|
903
|
+
<head>
|
|
904
|
+
<meta charset="UTF-8">
|
|
905
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
906
|
+
<title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>
|
|
907
|
+
<script type="module" src="${base}/@vite/client"></script>
|
|
908
|
+
<style>
|
|
909
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
910
|
+
html, body {
|
|
911
|
+
width: 100%;
|
|
912
|
+
height: 100%;
|
|
913
|
+
}
|
|
914
|
+
body {
|
|
915
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
916
|
+
background: #ffffff;
|
|
917
|
+
}
|
|
918
|
+
.musea-variant {
|
|
919
|
+
min-height: 100vh;
|
|
920
|
+
}
|
|
921
|
+
.musea-error {
|
|
922
|
+
color: #dc2626;
|
|
923
|
+
background: #fef2f2;
|
|
924
|
+
border: 1px solid #fecaca;
|
|
925
|
+
border-radius: 8px;
|
|
926
|
+
padding: 1rem;
|
|
927
|
+
font-size: 0.875rem;
|
|
928
|
+
max-width: 400px;
|
|
929
|
+
}
|
|
930
|
+
.musea-error-title {
|
|
931
|
+
font-weight: 600;
|
|
932
|
+
margin-bottom: 0.5rem;
|
|
933
|
+
}
|
|
934
|
+
.musea-error pre {
|
|
935
|
+
font-family: monospace;
|
|
936
|
+
font-size: 0.75rem;
|
|
937
|
+
white-space: pre-wrap;
|
|
938
|
+
word-break: break-all;
|
|
939
|
+
margin-top: 0.5rem;
|
|
940
|
+
padding: 0.5rem;
|
|
941
|
+
background: #fff;
|
|
942
|
+
border-radius: 4px;
|
|
943
|
+
}
|
|
944
|
+
.musea-loading {
|
|
945
|
+
display: flex;
|
|
946
|
+
align-items: center;
|
|
947
|
+
gap: 0.75rem;
|
|
948
|
+
color: #6b7280;
|
|
949
|
+
font-size: 0.875rem;
|
|
950
|
+
}
|
|
951
|
+
.musea-spinner {
|
|
952
|
+
width: 20px;
|
|
953
|
+
height: 20px;
|
|
954
|
+
border: 2px solid #e5e7eb;
|
|
955
|
+
border-top-color: #3b82f6;
|
|
956
|
+
border-radius: 50%;
|
|
957
|
+
animation: spin 0.8s linear infinite;
|
|
958
|
+
}
|
|
959
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
960
|
+
|
|
961
|
+
/* Musea Addons: Checkerboard background for transparent mode */
|
|
962
|
+
.musea-bg-checkerboard {
|
|
963
|
+
background-image:
|
|
964
|
+
linear-gradient(45deg, #ccc 25%, transparent 25%),
|
|
965
|
+
linear-gradient(-45deg, #ccc 25%, transparent 25%),
|
|
966
|
+
linear-gradient(45deg, transparent 75%, #ccc 75%),
|
|
967
|
+
linear-gradient(-45deg, transparent 75%, #ccc 75%) !important;
|
|
968
|
+
background-size: 20px 20px !important;
|
|
969
|
+
background-position: 0 0, 0 10px, 10px -10px, -10px 0 !important;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/* Musea Addons: Measure label */
|
|
973
|
+
.musea-measure-label {
|
|
974
|
+
position: fixed;
|
|
975
|
+
background: #333;
|
|
976
|
+
color: #fff;
|
|
977
|
+
font-size: 11px;
|
|
978
|
+
padding: 2px 6px;
|
|
979
|
+
border-radius: 3px;
|
|
980
|
+
pointer-events: none;
|
|
981
|
+
z-index: 100000;
|
|
982
|
+
}
|
|
983
|
+
</style>
|
|
984
|
+
</head>
|
|
985
|
+
<body>
|
|
986
|
+
<div id="app" class="musea-variant" data-art="${escapeHtml(art.path)}" data-variant="${escapeHtml(variant.name)}">
|
|
987
|
+
<div class="musea-loading">
|
|
988
|
+
<div class="musea-spinner"></div>
|
|
989
|
+
Loading component...
|
|
990
|
+
</div>
|
|
991
|
+
</div>
|
|
992
|
+
<script type="module" src="${previewModuleUrl}"></script>
|
|
993
|
+
</body>
|
|
994
|
+
</html>`;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
//#endregion
|
|
998
|
+
//#region src/preview/index.ts
|
|
1200
999
|
function generatePreviewModule(art, variantComponentName, variantName, cssImports = [], previewSetup = null) {
|
|
1201
1000
|
const artModuleId = `virtual:musea-art:${art.path}`;
|
|
1202
1001
|
const escapedVariantName = escapeTemplate(variantName);
|
|
@@ -1352,247 +1151,6 @@ async function mount() {
|
|
|
1352
1151
|
mount();
|
|
1353
1152
|
`;
|
|
1354
1153
|
}
|
|
1355
|
-
function generatePreviewHtml(art, variant, _basePath, viteBase) {
|
|
1356
|
-
const previewModuleUrl = `${_basePath}/preview-module?art=${encodeURIComponent(art.path)}&variant=${encodeURIComponent(variant.name)}`;
|
|
1357
|
-
const base = (viteBase || "/").replace(/\/$/, "");
|
|
1358
|
-
return `<!DOCTYPE html>
|
|
1359
|
-
<html lang="en">
|
|
1360
|
-
<head>
|
|
1361
|
-
<meta charset="UTF-8">
|
|
1362
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1363
|
-
<title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>
|
|
1364
|
-
<script type="module" src="${base}/@vite/client"></script>
|
|
1365
|
-
<style>
|
|
1366
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1367
|
-
html, body {
|
|
1368
|
-
width: 100%;
|
|
1369
|
-
height: 100%;
|
|
1370
|
-
}
|
|
1371
|
-
body {
|
|
1372
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
1373
|
-
background: #ffffff;
|
|
1374
|
-
}
|
|
1375
|
-
.musea-variant {
|
|
1376
|
-
min-height: 100vh;
|
|
1377
|
-
}
|
|
1378
|
-
.musea-error {
|
|
1379
|
-
color: #dc2626;
|
|
1380
|
-
background: #fef2f2;
|
|
1381
|
-
border: 1px solid #fecaca;
|
|
1382
|
-
border-radius: 8px;
|
|
1383
|
-
padding: 1rem;
|
|
1384
|
-
font-size: 0.875rem;
|
|
1385
|
-
max-width: 400px;
|
|
1386
|
-
}
|
|
1387
|
-
.musea-error-title {
|
|
1388
|
-
font-weight: 600;
|
|
1389
|
-
margin-bottom: 0.5rem;
|
|
1390
|
-
}
|
|
1391
|
-
.musea-error pre {
|
|
1392
|
-
font-family: monospace;
|
|
1393
|
-
font-size: 0.75rem;
|
|
1394
|
-
white-space: pre-wrap;
|
|
1395
|
-
word-break: break-all;
|
|
1396
|
-
margin-top: 0.5rem;
|
|
1397
|
-
padding: 0.5rem;
|
|
1398
|
-
background: #fff;
|
|
1399
|
-
border-radius: 4px;
|
|
1400
|
-
}
|
|
1401
|
-
.musea-loading {
|
|
1402
|
-
display: flex;
|
|
1403
|
-
align-items: center;
|
|
1404
|
-
gap: 0.75rem;
|
|
1405
|
-
color: #6b7280;
|
|
1406
|
-
font-size: 0.875rem;
|
|
1407
|
-
}
|
|
1408
|
-
.musea-spinner {
|
|
1409
|
-
width: 20px;
|
|
1410
|
-
height: 20px;
|
|
1411
|
-
border: 2px solid #e5e7eb;
|
|
1412
|
-
border-top-color: #3b82f6;
|
|
1413
|
-
border-radius: 50%;
|
|
1414
|
-
animation: spin 0.8s linear infinite;
|
|
1415
|
-
}
|
|
1416
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
1417
|
-
|
|
1418
|
-
/* Musea Addons: Checkerboard background for transparent mode */
|
|
1419
|
-
.musea-bg-checkerboard {
|
|
1420
|
-
background-image:
|
|
1421
|
-
linear-gradient(45deg, #ccc 25%, transparent 25%),
|
|
1422
|
-
linear-gradient(-45deg, #ccc 25%, transparent 25%),
|
|
1423
|
-
linear-gradient(45deg, transparent 75%, #ccc 75%),
|
|
1424
|
-
linear-gradient(-45deg, transparent 75%, #ccc 75%) !important;
|
|
1425
|
-
background-size: 20px 20px !important;
|
|
1426
|
-
background-position: 0 0, 0 10px, 10px -10px, -10px 0 !important;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
/* Musea Addons: Measure label */
|
|
1430
|
-
.musea-measure-label {
|
|
1431
|
-
position: fixed;
|
|
1432
|
-
background: #333;
|
|
1433
|
-
color: #fff;
|
|
1434
|
-
font-size: 11px;
|
|
1435
|
-
padding: 2px 6px;
|
|
1436
|
-
border-radius: 3px;
|
|
1437
|
-
pointer-events: none;
|
|
1438
|
-
z-index: 100000;
|
|
1439
|
-
}
|
|
1440
|
-
</style>
|
|
1441
|
-
</head>
|
|
1442
|
-
<body>
|
|
1443
|
-
<div id="app" class="musea-variant" data-art="${escapeHtml(art.path)}" data-variant="${escapeHtml(variant.name)}">
|
|
1444
|
-
<div class="musea-loading">
|
|
1445
|
-
<div class="musea-spinner"></div>
|
|
1446
|
-
Loading component...
|
|
1447
|
-
</div>
|
|
1448
|
-
</div>
|
|
1449
|
-
<script type="module" src="${previewModuleUrl}"></script>
|
|
1450
|
-
</body>
|
|
1451
|
-
</html>`;
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
//#endregion
|
|
1455
|
-
//#region src/manifest.ts
|
|
1456
|
-
/**
|
|
1457
|
-
* Generate the virtual manifest module code containing all art file metadata.
|
|
1458
|
-
*/
|
|
1459
|
-
function generateManifestModule(artFiles) {
|
|
1460
|
-
const arts = Array.from(artFiles.values());
|
|
1461
|
-
return `export const arts = ${JSON.stringify(arts, null, 2)};`;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
//#endregion
|
|
1465
|
-
//#region src/art-module.ts
|
|
1466
|
-
/**
|
|
1467
|
-
* Extract the content of the first <script setup> block from a Vue SFC source.
|
|
1468
|
-
*/
|
|
1469
|
-
function extractScriptSetupContent(source) {
|
|
1470
|
-
const match = source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/);
|
|
1471
|
-
return match?.[1]?.trim();
|
|
1472
|
-
}
|
|
1473
|
-
/**
|
|
1474
|
-
* Parse script setup content into imports and setup body.
|
|
1475
|
-
* Returns the import lines, setup body lines, and all identifiers to expose.
|
|
1476
|
-
*/
|
|
1477
|
-
function parseScriptSetupForArt(content) {
|
|
1478
|
-
const lines = content.split("\n");
|
|
1479
|
-
const imports = [];
|
|
1480
|
-
const setupBody = [];
|
|
1481
|
-
const returnNames = new Set();
|
|
1482
|
-
for (const line of lines) {
|
|
1483
|
-
const trimmed = line.trim();
|
|
1484
|
-
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
1485
|
-
if (trimmed.startsWith("import ")) {
|
|
1486
|
-
imports.push(line);
|
|
1487
|
-
const defaultMatch = trimmed.match(/^import\s+(\w+)/);
|
|
1488
|
-
if (defaultMatch && defaultMatch[1] !== "type") returnNames.add(defaultMatch[1]);
|
|
1489
|
-
const namedMatch = trimmed.match(/\{([^}]+)\}/);
|
|
1490
|
-
if (namedMatch) for (const part of namedMatch[1].split(",")) {
|
|
1491
|
-
const name = part.trim().split(/\s+as\s+/).pop()?.trim();
|
|
1492
|
-
if (name && !name.startsWith("type ")) returnNames.add(name);
|
|
1493
|
-
}
|
|
1494
|
-
} else {
|
|
1495
|
-
setupBody.push(line);
|
|
1496
|
-
const constMatch = trimmed.match(/^(?:const|let|var)\s+(\w+)/);
|
|
1497
|
-
if (constMatch) returnNames.add(constMatch[1]);
|
|
1498
|
-
const destructMatch = trimmed.match(/^(?:const|let|var)\s+\{([^}]+)\}/);
|
|
1499
|
-
if (destructMatch) for (const part of destructMatch[1].split(",")) {
|
|
1500
|
-
const name = part.trim().split(/\s*:\s*/).shift()?.trim();
|
|
1501
|
-
if (name) returnNames.add(name);
|
|
1502
|
-
}
|
|
1503
|
-
const arrayMatch = trimmed.match(/^(?:const|let|var)\s+\[([^\]]+)\]/);
|
|
1504
|
-
if (arrayMatch) for (const part of arrayMatch[1].split(",")) {
|
|
1505
|
-
const name = part.trim();
|
|
1506
|
-
if (name && name !== "...") returnNames.add(name);
|
|
1507
|
-
}
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
returnNames.delete("type");
|
|
1511
|
-
return {
|
|
1512
|
-
imports,
|
|
1513
|
-
setupBody,
|
|
1514
|
-
returnNames: [...returnNames]
|
|
1515
|
-
};
|
|
1516
|
-
}
|
|
1517
|
-
function generateArtModule(art, filePath) {
|
|
1518
|
-
let componentImportPath;
|
|
1519
|
-
let componentName;
|
|
1520
|
-
if (art.isInline && art.componentPath) {
|
|
1521
|
-
componentImportPath = art.componentPath;
|
|
1522
|
-
componentName = path.basename(art.componentPath, ".vue");
|
|
1523
|
-
} else if (art.metadata.component) {
|
|
1524
|
-
const comp = art.metadata.component;
|
|
1525
|
-
componentImportPath = path.isAbsolute(comp) ? comp : path.resolve(path.dirname(filePath), comp);
|
|
1526
|
-
componentName = path.basename(comp, ".vue");
|
|
1527
|
-
}
|
|
1528
|
-
const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
|
|
1529
|
-
let code = `
|
|
1530
|
-
// Auto-generated module for: ${path.basename(filePath)}
|
|
1531
|
-
import { defineComponent, h } from 'vue';
|
|
1532
|
-
`;
|
|
1533
|
-
if (scriptSetup) {
|
|
1534
|
-
const artDir = path.dirname(filePath);
|
|
1535
|
-
for (const imp of scriptSetup.imports) {
|
|
1536
|
-
const resolved = imp.replace(/from\s+(['"])(\.[^'"]+)\1/, (_match, quote, relPath) => {
|
|
1537
|
-
const absPath = path.resolve(artDir, relPath);
|
|
1538
|
-
return `from ${quote}${absPath}${quote}`;
|
|
1539
|
-
});
|
|
1540
|
-
code += `${resolved}\n`;
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
if (componentImportPath && componentName) {
|
|
1544
|
-
const alreadyImported = scriptSetup?.imports.some((imp) => {
|
|
1545
|
-
if (imp.includes(`from '${componentImportPath}'`) || imp.includes(`from "${componentImportPath}"`)) return true;
|
|
1546
|
-
return new RegExp(`^import\\s+${componentName}[\\s,]`).test(imp.trim());
|
|
1547
|
-
});
|
|
1548
|
-
if (!alreadyImported) code += `import ${componentName} from '${componentImportPath}';\n`;
|
|
1549
|
-
code += `export const __component__ = ${componentName};\n`;
|
|
1550
|
-
}
|
|
1551
|
-
code += `
|
|
1552
|
-
export const metadata = ${JSON.stringify(art.metadata)};
|
|
1553
|
-
export const variants = ${JSON.stringify(art.variants)};
|
|
1554
|
-
`;
|
|
1555
|
-
for (const variant of art.variants) {
|
|
1556
|
-
const variantComponentName = toPascalCase(variant.name);
|
|
1557
|
-
let template = variant.template;
|
|
1558
|
-
if (componentName) template = template.replace(/<Self/g, `<${componentName}`).replace(/<\/Self>/g, `</${componentName}>`);
|
|
1559
|
-
const escapedTemplate = template.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
|
|
1560
|
-
const fullTemplate = `<div data-variant="${variant.name}">${escapedTemplate}</div>`;
|
|
1561
|
-
const componentNames = new Set();
|
|
1562
|
-
if (componentName) componentNames.add(componentName);
|
|
1563
|
-
if (scriptSetup) {
|
|
1564
|
-
for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.add(name);
|
|
1565
|
-
}
|
|
1566
|
-
const components = componentNames.size > 0 ? ` components: { ${[...componentNames].join(", ")} },\n` : "";
|
|
1567
|
-
if (scriptSetup && scriptSetup.setupBody.length > 0) code += `
|
|
1568
|
-
export const ${variantComponentName} = defineComponent({
|
|
1569
|
-
name: '${variantComponentName}',
|
|
1570
|
-
${components} setup() {
|
|
1571
|
-
${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
|
|
1572
|
-
return { ${scriptSetup.returnNames.join(", ")} };
|
|
1573
|
-
},
|
|
1574
|
-
template: \`${fullTemplate}\`,
|
|
1575
|
-
});
|
|
1576
|
-
`;
|
|
1577
|
-
else if (componentName) code += `
|
|
1578
|
-
export const ${variantComponentName} = {
|
|
1579
|
-
name: '${variantComponentName}',
|
|
1580
|
-
${components} template: \`${fullTemplate}\`,
|
|
1581
|
-
};
|
|
1582
|
-
`;
|
|
1583
|
-
else code += `
|
|
1584
|
-
export const ${variantComponentName} = {
|
|
1585
|
-
name: '${variantComponentName}',
|
|
1586
|
-
template: \`${fullTemplate}\`,
|
|
1587
|
-
};
|
|
1588
|
-
`;
|
|
1589
|
-
}
|
|
1590
|
-
const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];
|
|
1591
|
-
if (defaultVariant) code += `
|
|
1592
|
-
export default ${toPascalCase(defaultVariant.name)};
|
|
1593
|
-
`;
|
|
1594
|
-
return code;
|
|
1595
|
-
}
|
|
1596
1154
|
|
|
1597
1155
|
//#endregion
|
|
1598
1156
|
//#region src/server-middleware.ts
|
|
@@ -1869,169 +1427,7 @@ function formatCategoryName(name) {
|
|
|
1869
1427
|
}
|
|
1870
1428
|
|
|
1871
1429
|
//#endregion
|
|
1872
|
-
//#region src/tokens/
|
|
1873
|
-
const REFERENCE_PATTERN = /^\{(.+)\}$/;
|
|
1874
|
-
const MAX_RESOLVE_DEPTH = 10;
|
|
1875
|
-
/**
|
|
1876
|
-
* Flatten nested categories into a flat map keyed by dot-path.
|
|
1877
|
-
*/
|
|
1878
|
-
function buildTokenMap(categories, prefix = []) {
|
|
1879
|
-
const map = {};
|
|
1880
|
-
for (const cat of categories) {
|
|
1881
|
-
const catKey = cat.name.toLowerCase().replace(/\s+/g, "-");
|
|
1882
|
-
const catPath = [...prefix, catKey];
|
|
1883
|
-
for (const [name, token] of Object.entries(cat.tokens)) {
|
|
1884
|
-
const dotPath = [...catPath, name].join(".");
|
|
1885
|
-
map[dotPath] = token;
|
|
1886
|
-
}
|
|
1887
|
-
if (cat.subcategories) {
|
|
1888
|
-
const subMap = buildTokenMap(cat.subcategories, catPath);
|
|
1889
|
-
Object.assign(map, subMap);
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
return map;
|
|
1893
|
-
}
|
|
1894
|
-
/**
|
|
1895
|
-
* Resolve references in categories, setting $tier, $reference, and $resolvedValue.
|
|
1896
|
-
*/
|
|
1897
|
-
function resolveReferences(categories, tokenMap) {
|
|
1898
|
-
for (const cat of categories) {
|
|
1899
|
-
for (const token of Object.values(cat.tokens)) resolveTokenReference(token, tokenMap);
|
|
1900
|
-
if (cat.subcategories) resolveReferences(cat.subcategories, tokenMap);
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
function resolveTokenReference(token, tokenMap) {
|
|
1904
|
-
if (typeof token.value === "string") {
|
|
1905
|
-
const match = token.value.match(REFERENCE_PATTERN);
|
|
1906
|
-
if (match) {
|
|
1907
|
-
token.$tier = token.$tier ?? "semantic";
|
|
1908
|
-
token.$reference = match[1];
|
|
1909
|
-
token.$resolvedValue = resolveValue(match[1], tokenMap, 0, new Set());
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
token.$tier = token.$tier ?? "primitive";
|
|
1914
|
-
}
|
|
1915
|
-
function resolveValue(ref, tokenMap, depth, visited) {
|
|
1916
|
-
if (depth >= MAX_RESOLVE_DEPTH || visited.has(ref)) return void 0;
|
|
1917
|
-
visited.add(ref);
|
|
1918
|
-
const target = tokenMap[ref];
|
|
1919
|
-
if (!target) return void 0;
|
|
1920
|
-
if (typeof target.value === "string") {
|
|
1921
|
-
const match = target.value.match(REFERENCE_PATTERN);
|
|
1922
|
-
if (match) return resolveValue(match[1], tokenMap, depth + 1, visited);
|
|
1923
|
-
}
|
|
1924
|
-
return target.value;
|
|
1925
|
-
}
|
|
1926
|
-
/**
|
|
1927
|
-
* Read raw JSON token file.
|
|
1928
|
-
*/
|
|
1929
|
-
async function readRawTokenFile(tokensPath) {
|
|
1930
|
-
const content = await fs.promises.readFile(tokensPath, "utf-8");
|
|
1931
|
-
return JSON.parse(content);
|
|
1932
|
-
}
|
|
1933
|
-
/**
|
|
1934
|
-
* Write raw JSON token file atomically (write tmp, rename).
|
|
1935
|
-
*/
|
|
1936
|
-
async function writeRawTokenFile(tokensPath, data) {
|
|
1937
|
-
const tmpPath = tokensPath + ".tmp";
|
|
1938
|
-
await fs.promises.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
1939
|
-
await fs.promises.rename(tmpPath, tokensPath);
|
|
1940
|
-
}
|
|
1941
|
-
/**
|
|
1942
|
-
* Set a token at a dot-separated path in the raw JSON structure.
|
|
1943
|
-
*/
|
|
1944
|
-
function setTokenAtPath(data, dotPath, token) {
|
|
1945
|
-
const parts = dotPath.split(".");
|
|
1946
|
-
let current = data;
|
|
1947
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1948
|
-
const key = parts[i];
|
|
1949
|
-
if (typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
1950
|
-
current = current[key];
|
|
1951
|
-
}
|
|
1952
|
-
const leafKey = parts[parts.length - 1];
|
|
1953
|
-
const raw = { value: token.value };
|
|
1954
|
-
if (token.type) raw.type = token.type;
|
|
1955
|
-
if (token.description) raw.description = token.description;
|
|
1956
|
-
if (token.$tier) raw.$tier = token.$tier;
|
|
1957
|
-
if (token.$reference) raw.$reference = token.$reference;
|
|
1958
|
-
if (token.attributes) raw.attributes = token.attributes;
|
|
1959
|
-
current[leafKey] = raw;
|
|
1960
|
-
}
|
|
1961
|
-
/**
|
|
1962
|
-
* Delete a token at a dot-separated path, cleaning empty parents.
|
|
1963
|
-
*/
|
|
1964
|
-
function deleteTokenAtPath(data, dotPath) {
|
|
1965
|
-
const parts = dotPath.split(".");
|
|
1966
|
-
const parents = [];
|
|
1967
|
-
let current = data;
|
|
1968
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1969
|
-
const key = parts[i];
|
|
1970
|
-
if (typeof current[key] !== "object" || current[key] === null) return false;
|
|
1971
|
-
parents.push({
|
|
1972
|
-
obj: current,
|
|
1973
|
-
key
|
|
1974
|
-
});
|
|
1975
|
-
current = current[key];
|
|
1976
|
-
}
|
|
1977
|
-
const leafKey = parts[parts.length - 1];
|
|
1978
|
-
if (!(leafKey in current)) return false;
|
|
1979
|
-
delete current[leafKey];
|
|
1980
|
-
for (let i = parents.length - 1; i >= 0; i--) {
|
|
1981
|
-
const { obj, key } = parents[i];
|
|
1982
|
-
const child = obj[key];
|
|
1983
|
-
if (Object.keys(child).length === 0) delete obj[key];
|
|
1984
|
-
else break;
|
|
1985
|
-
}
|
|
1986
|
-
return true;
|
|
1987
|
-
}
|
|
1988
|
-
/**
|
|
1989
|
-
* Validate that a semantic reference points to an existing token and has no cycles.
|
|
1990
|
-
*/
|
|
1991
|
-
function validateSemanticReference(tokenMap, reference, selfPath) {
|
|
1992
|
-
if (!tokenMap[reference]) return {
|
|
1993
|
-
valid: false,
|
|
1994
|
-
error: `Reference target "${reference}" does not exist`
|
|
1995
|
-
};
|
|
1996
|
-
const visited = new Set();
|
|
1997
|
-
if (selfPath) visited.add(selfPath);
|
|
1998
|
-
let current = reference;
|
|
1999
|
-
let depth = 0;
|
|
2000
|
-
while (depth < MAX_RESOLVE_DEPTH) {
|
|
2001
|
-
if (visited.has(current)) return {
|
|
2002
|
-
valid: false,
|
|
2003
|
-
error: `Circular reference detected at "${current}"`
|
|
2004
|
-
};
|
|
2005
|
-
visited.add(current);
|
|
2006
|
-
const target = tokenMap[current];
|
|
2007
|
-
if (!target) break;
|
|
2008
|
-
if (typeof target.value === "string") {
|
|
2009
|
-
const match = target.value.match(REFERENCE_PATTERN);
|
|
2010
|
-
if (match) {
|
|
2011
|
-
current = match[1];
|
|
2012
|
-
depth++;
|
|
2013
|
-
continue;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
break;
|
|
2017
|
-
}
|
|
2018
|
-
if (depth >= MAX_RESOLVE_DEPTH) return {
|
|
2019
|
-
valid: false,
|
|
2020
|
-
error: "Reference chain too deep (max 10)"
|
|
2021
|
-
};
|
|
2022
|
-
return { valid: true };
|
|
2023
|
-
}
|
|
2024
|
-
/**
|
|
2025
|
-
* Find all tokens that reference the given path.
|
|
2026
|
-
*/
|
|
2027
|
-
function findDependentTokens(tokenMap, targetPath) {
|
|
2028
|
-
const dependents = [];
|
|
2029
|
-
for (const [path$1, token] of Object.entries(tokenMap)) if (typeof token.value === "string") {
|
|
2030
|
-
const match = token.value.match(REFERENCE_PATTERN);
|
|
2031
|
-
if (match && match[1] === targetPath) dependents.push(path$1);
|
|
2032
|
-
}
|
|
2033
|
-
return dependents;
|
|
2034
|
-
}
|
|
1430
|
+
//#region src/tokens/usage.ts
|
|
2035
1431
|
/**
|
|
2036
1432
|
* Normalize a token value for comparison.
|
|
2037
1433
|
* - Lowercase, trim
|
|
@@ -2121,7 +1517,172 @@ function scanTokenUsage(artFiles, tokenMap) {
|
|
|
2121
1517
|
}
|
|
2122
1518
|
}
|
|
2123
1519
|
}
|
|
2124
|
-
return usageMap;
|
|
1520
|
+
return usageMap;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
//#endregion
|
|
1524
|
+
//#region src/tokens/resolver.ts
|
|
1525
|
+
const REFERENCE_PATTERN = /^\{(.+)\}$/;
|
|
1526
|
+
const MAX_RESOLVE_DEPTH = 10;
|
|
1527
|
+
/**
|
|
1528
|
+
* Flatten nested categories into a flat map keyed by dot-path.
|
|
1529
|
+
*/
|
|
1530
|
+
function buildTokenMap(categories, prefix = []) {
|
|
1531
|
+
const map = {};
|
|
1532
|
+
for (const cat of categories) {
|
|
1533
|
+
const catKey = cat.name.toLowerCase().replace(/\s+/g, "-");
|
|
1534
|
+
const catPath = [...prefix, catKey];
|
|
1535
|
+
for (const [name, token] of Object.entries(cat.tokens)) {
|
|
1536
|
+
const dotPath = [...catPath, name].join(".");
|
|
1537
|
+
map[dotPath] = token;
|
|
1538
|
+
}
|
|
1539
|
+
if (cat.subcategories) {
|
|
1540
|
+
const subMap = buildTokenMap(cat.subcategories, catPath);
|
|
1541
|
+
Object.assign(map, subMap);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return map;
|
|
1545
|
+
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Resolve references in categories, setting $tier, $reference, and $resolvedValue.
|
|
1548
|
+
*/
|
|
1549
|
+
function resolveReferences(categories, tokenMap) {
|
|
1550
|
+
for (const cat of categories) {
|
|
1551
|
+
for (const token of Object.values(cat.tokens)) resolveTokenReference(token, tokenMap);
|
|
1552
|
+
if (cat.subcategories) resolveReferences(cat.subcategories, tokenMap);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function resolveTokenReference(token, tokenMap) {
|
|
1556
|
+
if (typeof token.value === "string") {
|
|
1557
|
+
const match = token.value.match(REFERENCE_PATTERN);
|
|
1558
|
+
if (match) {
|
|
1559
|
+
token.$tier = token.$tier ?? "semantic";
|
|
1560
|
+
token.$reference = match[1];
|
|
1561
|
+
token.$resolvedValue = resolveValue(match[1], tokenMap, 0, new Set());
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
token.$tier = token.$tier ?? "primitive";
|
|
1566
|
+
}
|
|
1567
|
+
function resolveValue(ref, tokenMap, depth, visited) {
|
|
1568
|
+
if (depth >= MAX_RESOLVE_DEPTH || visited.has(ref)) return void 0;
|
|
1569
|
+
visited.add(ref);
|
|
1570
|
+
const target = tokenMap[ref];
|
|
1571
|
+
if (!target) return void 0;
|
|
1572
|
+
if (typeof target.value === "string") {
|
|
1573
|
+
const match = target.value.match(REFERENCE_PATTERN);
|
|
1574
|
+
if (match) return resolveValue(match[1], tokenMap, depth + 1, visited);
|
|
1575
|
+
}
|
|
1576
|
+
return target.value;
|
|
1577
|
+
}
|
|
1578
|
+
/**
|
|
1579
|
+
* Read raw JSON token file.
|
|
1580
|
+
*/
|
|
1581
|
+
async function readRawTokenFile(tokensPath) {
|
|
1582
|
+
const content = await fs.promises.readFile(tokensPath, "utf-8");
|
|
1583
|
+
return JSON.parse(content);
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Write raw JSON token file atomically (write tmp, rename).
|
|
1587
|
+
*/
|
|
1588
|
+
async function writeRawTokenFile(tokensPath, data) {
|
|
1589
|
+
const tmpPath = tokensPath + ".tmp";
|
|
1590
|
+
await fs.promises.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
1591
|
+
await fs.promises.rename(tmpPath, tokensPath);
|
|
1592
|
+
}
|
|
1593
|
+
/**
|
|
1594
|
+
* Set a token at a dot-separated path in the raw JSON structure.
|
|
1595
|
+
*/
|
|
1596
|
+
function setTokenAtPath(data, dotPath, token) {
|
|
1597
|
+
const parts = dotPath.split(".");
|
|
1598
|
+
let current = data;
|
|
1599
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1600
|
+
const key = parts[i];
|
|
1601
|
+
if (typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
1602
|
+
current = current[key];
|
|
1603
|
+
}
|
|
1604
|
+
const leafKey = parts[parts.length - 1];
|
|
1605
|
+
const raw = { value: token.value };
|
|
1606
|
+
if (token.type) raw.type = token.type;
|
|
1607
|
+
if (token.description) raw.description = token.description;
|
|
1608
|
+
if (token.$tier) raw.$tier = token.$tier;
|
|
1609
|
+
if (token.$reference) raw.$reference = token.$reference;
|
|
1610
|
+
if (token.attributes) raw.attributes = token.attributes;
|
|
1611
|
+
current[leafKey] = raw;
|
|
1612
|
+
}
|
|
1613
|
+
/**
|
|
1614
|
+
* Delete a token at a dot-separated path, cleaning empty parents.
|
|
1615
|
+
*/
|
|
1616
|
+
function deleteTokenAtPath(data, dotPath) {
|
|
1617
|
+
const parts = dotPath.split(".");
|
|
1618
|
+
const parents = [];
|
|
1619
|
+
let current = data;
|
|
1620
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
1621
|
+
const key = parts[i];
|
|
1622
|
+
if (typeof current[key] !== "object" || current[key] === null) return false;
|
|
1623
|
+
parents.push({
|
|
1624
|
+
obj: current,
|
|
1625
|
+
key
|
|
1626
|
+
});
|
|
1627
|
+
current = current[key];
|
|
1628
|
+
}
|
|
1629
|
+
const leafKey = parts[parts.length - 1];
|
|
1630
|
+
if (!(leafKey in current)) return false;
|
|
1631
|
+
delete current[leafKey];
|
|
1632
|
+
for (let i = parents.length - 1; i >= 0; i--) {
|
|
1633
|
+
const { obj, key } = parents[i];
|
|
1634
|
+
const child = obj[key];
|
|
1635
|
+
if (Object.keys(child).length === 0) delete obj[key];
|
|
1636
|
+
else break;
|
|
1637
|
+
}
|
|
1638
|
+
return true;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Validate that a semantic reference points to an existing token and has no cycles.
|
|
1642
|
+
*/
|
|
1643
|
+
function validateSemanticReference(tokenMap, reference, selfPath) {
|
|
1644
|
+
if (!tokenMap[reference]) return {
|
|
1645
|
+
valid: false,
|
|
1646
|
+
error: `Reference target "${reference}" does not exist`
|
|
1647
|
+
};
|
|
1648
|
+
const visited = new Set();
|
|
1649
|
+
if (selfPath) visited.add(selfPath);
|
|
1650
|
+
let current = reference;
|
|
1651
|
+
let depth = 0;
|
|
1652
|
+
while (depth < MAX_RESOLVE_DEPTH) {
|
|
1653
|
+
if (visited.has(current)) return {
|
|
1654
|
+
valid: false,
|
|
1655
|
+
error: `Circular reference detected at "${current}"`
|
|
1656
|
+
};
|
|
1657
|
+
visited.add(current);
|
|
1658
|
+
const target = tokenMap[current];
|
|
1659
|
+
if (!target) break;
|
|
1660
|
+
if (typeof target.value === "string") {
|
|
1661
|
+
const match = target.value.match(REFERENCE_PATTERN);
|
|
1662
|
+
if (match) {
|
|
1663
|
+
current = match[1];
|
|
1664
|
+
depth++;
|
|
1665
|
+
continue;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
break;
|
|
1669
|
+
}
|
|
1670
|
+
if (depth >= MAX_RESOLVE_DEPTH) return {
|
|
1671
|
+
valid: false,
|
|
1672
|
+
error: "Reference chain too deep (max 10)"
|
|
1673
|
+
};
|
|
1674
|
+
return { valid: true };
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* Find all tokens that reference the given path.
|
|
1678
|
+
*/
|
|
1679
|
+
function findDependentTokens(tokenMap, targetPath) {
|
|
1680
|
+
const dependents = [];
|
|
1681
|
+
for (const [path$1, token] of Object.entries(tokenMap)) if (typeof token.value === "string") {
|
|
1682
|
+
const match = token.value.match(REFERENCE_PATTERN);
|
|
1683
|
+
if (match && match[1] === targetPath) dependents.push(path$1);
|
|
1684
|
+
}
|
|
1685
|
+
return dependents;
|
|
2125
1686
|
}
|
|
2126
1687
|
|
|
2127
1688
|
//#endregion
|
|
@@ -2441,278 +2002,52 @@ async function handleTokensUpdate(ctx, readBody, sendJson, sendError) {
|
|
|
2441
2002
|
const resolvedTokenMap = buildTokenMap(categories);
|
|
2442
2003
|
sendJson({
|
|
2443
2004
|
categories,
|
|
2444
|
-
tokenMap: resolvedTokenMap
|
|
2445
|
-
});
|
|
2446
|
-
} catch (e) {
|
|
2447
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2448
|
-
}
|
|
2449
|
-
}
|
|
2450
|
-
/** DELETE /api/tokens */
|
|
2451
|
-
async function handleTokensDelete(ctx, readBody, sendJson, sendError) {
|
|
2452
|
-
if (!ctx.tokensPath) {
|
|
2453
|
-
sendError("No tokens path configured", 400);
|
|
2454
|
-
return;
|
|
2455
|
-
}
|
|
2456
|
-
const body = await readBody();
|
|
2457
|
-
try {
|
|
2458
|
-
const { path: dotPath } = JSON.parse(body);
|
|
2459
|
-
if (!dotPath) {
|
|
2460
|
-
sendError("Missing required field: path", 400);
|
|
2461
|
-
return;
|
|
2462
|
-
}
|
|
2463
|
-
const absoluteTokensPath = path.resolve(ctx.config.root, ctx.tokensPath);
|
|
2464
|
-
const currentCategories = await parseTokens(absoluteTokensPath);
|
|
2465
|
-
const currentMap = buildTokenMap(currentCategories);
|
|
2466
|
-
const dependents = findDependentTokens(currentMap, dotPath);
|
|
2467
|
-
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2468
|
-
const deleted = deleteTokenAtPath(rawData, dotPath);
|
|
2469
|
-
if (!deleted) {
|
|
2470
|
-
sendError(`Token not found at path "${dotPath}"`, 404);
|
|
2471
|
-
return;
|
|
2472
|
-
}
|
|
2473
|
-
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
2474
|
-
const categories = await parseTokens(absoluteTokensPath);
|
|
2475
|
-
const tokenMap = buildTokenMap(categories);
|
|
2476
|
-
resolveReferences(categories, tokenMap);
|
|
2477
|
-
const resolvedTokenMap = buildTokenMap(categories);
|
|
2478
|
-
sendJson({
|
|
2479
|
-
categories,
|
|
2480
|
-
tokenMap: resolvedTokenMap,
|
|
2481
|
-
dependentsWarning: dependents.length > 0 ? dependents : void 0
|
|
2482
|
-
});
|
|
2483
|
-
} catch (e) {
|
|
2484
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
//#endregion
|
|
2489
|
-
//#region src/api-routes.ts
|
|
2490
|
-
/** Helper to read the full request body as a string. */
|
|
2491
|
-
function collectBody(req) {
|
|
2492
|
-
return new Promise((resolve) => {
|
|
2493
|
-
let body = "";
|
|
2494
|
-
req.on("data", (chunk) => {
|
|
2495
|
-
body += chunk;
|
|
2496
|
-
});
|
|
2497
|
-
req.on("end", () => resolve(body));
|
|
2498
|
-
});
|
|
2499
|
-
}
|
|
2500
|
-
/**
|
|
2501
|
-
* Create the API middleware handler for the Musea gallery.
|
|
2502
|
-
*
|
|
2503
|
-
* Returns a Connect-compatible middleware function that handles all
|
|
2504
|
-
* `/api/...` sub-routes under the configured basePath.
|
|
2505
|
-
*/
|
|
2506
|
-
function createApiMiddleware(ctx) {
|
|
2507
|
-
return async (req, res, next) => {
|
|
2508
|
-
const sendJson = (data, status = 200) => {
|
|
2509
|
-
res.statusCode = status;
|
|
2510
|
-
res.setHeader("Content-Type", "application/json");
|
|
2511
|
-
res.end(JSON.stringify(data));
|
|
2512
|
-
};
|
|
2513
|
-
const sendError = (message, status = 500) => {
|
|
2514
|
-
sendJson({ error: message }, status);
|
|
2515
|
-
};
|
|
2516
|
-
const readBody = () => collectBody(req);
|
|
2517
|
-
const url = req.url || "/";
|
|
2518
|
-
if (url === "/arts" && req.method === "GET") {
|
|
2519
|
-
sendJson(Array.from(ctx.artFiles.values()));
|
|
2520
|
-
return;
|
|
2521
|
-
}
|
|
2522
|
-
if (url === "/tokens/usage" && req.method === "GET") {
|
|
2523
|
-
await handleTokensUsage(ctx, sendJson);
|
|
2524
|
-
return;
|
|
2525
|
-
}
|
|
2526
|
-
if (url === "/tokens" && req.method === "GET") {
|
|
2527
|
-
await handleTokensGet(ctx, sendJson);
|
|
2528
|
-
return;
|
|
2529
|
-
}
|
|
2530
|
-
if (url === "/tokens" && req.method === "POST") {
|
|
2531
|
-
await handleTokensCreate(ctx, readBody, sendJson, sendError);
|
|
2532
|
-
return;
|
|
2533
|
-
}
|
|
2534
|
-
if (url === "/tokens" && req.method === "PUT") {
|
|
2535
|
-
await handleTokensUpdate(ctx, readBody, sendJson, sendError);
|
|
2536
|
-
return;
|
|
2537
|
-
}
|
|
2538
|
-
if (url === "/tokens" && req.method === "DELETE") {
|
|
2539
|
-
await handleTokensDelete(ctx, readBody, sendJson, sendError);
|
|
2540
|
-
return;
|
|
2541
|
-
}
|
|
2542
|
-
if (url?.startsWith("/arts/") && req.method === "PUT") {
|
|
2543
|
-
const rest = url.slice(6);
|
|
2544
|
-
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2545
|
-
if (sourceMatch) {
|
|
2546
|
-
const artPath = decodeURIComponent(sourceMatch[1]);
|
|
2547
|
-
const art = ctx.artFiles.get(artPath);
|
|
2548
|
-
if (!art) {
|
|
2549
|
-
sendError("Art not found", 404);
|
|
2550
|
-
return;
|
|
2551
|
-
}
|
|
2552
|
-
let body = "";
|
|
2553
|
-
req.on("data", (chunk) => {
|
|
2554
|
-
body += chunk;
|
|
2555
|
-
});
|
|
2556
|
-
req.on("end", async () => {
|
|
2557
|
-
try {
|
|
2558
|
-
const { source } = JSON.parse(body);
|
|
2559
|
-
if (typeof source !== "string") {
|
|
2560
|
-
sendError("Missing required field: source", 400);
|
|
2561
|
-
return;
|
|
2562
|
-
}
|
|
2563
|
-
await fs.promises.writeFile(artPath, source, "utf-8");
|
|
2564
|
-
await ctx.processArtFile(artPath);
|
|
2565
|
-
sendJson({ success: true });
|
|
2566
|
-
} catch (e) {
|
|
2567
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2568
|
-
}
|
|
2569
|
-
});
|
|
2570
|
-
return;
|
|
2571
|
-
}
|
|
2572
|
-
next();
|
|
2573
|
-
return;
|
|
2574
|
-
}
|
|
2575
|
-
if (url?.startsWith("/arts/") && req.method === "GET") {
|
|
2576
|
-
const rest = url.slice(6);
|
|
2577
|
-
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2578
|
-
const paletteMatch = rest.match(/^(.+)\/palette$/);
|
|
2579
|
-
const analysisMatch = rest.match(/^(.+)\/analysis$/);
|
|
2580
|
-
const docsMatch = rest.match(/^(.+)\/docs$/);
|
|
2581
|
-
const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
|
|
2582
|
-
if (sourceMatch) {
|
|
2583
|
-
await handleArtSource(ctx, sourceMatch, sendJson, sendError);
|
|
2584
|
-
return;
|
|
2585
|
-
}
|
|
2586
|
-
if (paletteMatch) {
|
|
2587
|
-
await handleArtPalette(ctx, paletteMatch, sendJson, sendError);
|
|
2588
|
-
return;
|
|
2589
|
-
}
|
|
2590
|
-
if (analysisMatch) {
|
|
2591
|
-
await handleArtAnalysis(ctx, analysisMatch, sendJson, sendError);
|
|
2592
|
-
return;
|
|
2593
|
-
}
|
|
2594
|
-
if (docsMatch) {
|
|
2595
|
-
await handleArtDocs(ctx, docsMatch, sendJson, sendError);
|
|
2596
|
-
return;
|
|
2597
|
-
}
|
|
2598
|
-
if (a11yMatch) {
|
|
2599
|
-
handleArtA11y(ctx, a11yMatch, sendJson, sendError);
|
|
2600
|
-
return;
|
|
2601
|
-
}
|
|
2602
|
-
const artPath = decodeURIComponent(rest);
|
|
2603
|
-
const art = ctx.artFiles.get(artPath);
|
|
2604
|
-
if (art) sendJson(art);
|
|
2605
|
-
else sendError("Art not found", 404);
|
|
2606
|
-
return;
|
|
2607
|
-
}
|
|
2608
|
-
if (url === "/preview-with-props" && req.method === "POST") {
|
|
2609
|
-
let body = "";
|
|
2610
|
-
req.on("data", (chunk) => {
|
|
2611
|
-
body += chunk;
|
|
2612
|
-
});
|
|
2613
|
-
req.on("end", () => {
|
|
2614
|
-
try {
|
|
2615
|
-
const { artPath: reqArtPath, variantName, props: propsOverride } = JSON.parse(body);
|
|
2616
|
-
const art = ctx.artFiles.get(reqArtPath);
|
|
2617
|
-
if (!art) {
|
|
2618
|
-
sendError("Art not found", 404);
|
|
2619
|
-
return;
|
|
2620
|
-
}
|
|
2621
|
-
const variant = art.variants.find((v) => v.name === variantName);
|
|
2622
|
-
if (!variant) {
|
|
2623
|
-
sendError("Variant not found", 404);
|
|
2624
|
-
return;
|
|
2625
|
-
}
|
|
2626
|
-
const variantComponentName = toPascalCase(variant.name);
|
|
2627
|
-
const moduleCode = generatePreviewModuleWithProps(art, variantComponentName, variant.name, propsOverride, ctx.resolvedPreviewCss, ctx.resolvedPreviewSetup);
|
|
2628
|
-
res.setHeader("Content-Type", "application/javascript");
|
|
2629
|
-
res.end(moduleCode);
|
|
2630
|
-
} catch (e) {
|
|
2631
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2632
|
-
}
|
|
2633
|
-
});
|
|
2634
|
-
return;
|
|
2635
|
-
}
|
|
2636
|
-
if (url === "/generate" && req.method === "POST") {
|
|
2637
|
-
let body = "";
|
|
2638
|
-
req.on("data", (chunk) => {
|
|
2639
|
-
body += chunk;
|
|
2640
|
-
});
|
|
2641
|
-
req.on("end", async () => {
|
|
2642
|
-
try {
|
|
2643
|
-
const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);
|
|
2644
|
-
const { generateArtFile: genArt } = await import("./autogen.js");
|
|
2645
|
-
const result = await genArt(reqComponentPath, autogenOptions);
|
|
2646
|
-
sendJson({
|
|
2647
|
-
generated: true,
|
|
2648
|
-
componentName: result.componentName,
|
|
2649
|
-
variants: result.variants,
|
|
2650
|
-
artFileContent: result.artFileContent
|
|
2651
|
-
});
|
|
2652
|
-
} catch (e) {
|
|
2653
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2654
|
-
}
|
|
2655
|
-
});
|
|
2656
|
-
return;
|
|
2657
|
-
}
|
|
2658
|
-
if (url === "/run-vrt" && req.method === "POST") {
|
|
2659
|
-
let body = "";
|
|
2660
|
-
req.on("data", (chunk) => {
|
|
2661
|
-
body += chunk;
|
|
2662
|
-
});
|
|
2663
|
-
req.on("end", async () => {
|
|
2664
|
-
try {
|
|
2665
|
-
const { artPath, updateSnapshots } = JSON.parse(body);
|
|
2666
|
-
const { MuseaVrtRunner: MuseaVrtRunner$1 } = await import("./vrt.js");
|
|
2667
|
-
const runner = new MuseaVrtRunner$1({ snapshotDir: path.resolve(ctx.config.root, ".vize/snapshots") });
|
|
2668
|
-
const port = ctx.getDevServerPort();
|
|
2669
|
-
const baseUrl = `http://localhost:${port}`;
|
|
2670
|
-
let artsToTest = Array.from(ctx.artFiles.values());
|
|
2671
|
-
if (artPath) artsToTest = artsToTest.filter((a) => a.path === artPath);
|
|
2672
|
-
await runner.start();
|
|
2673
|
-
const results = await runner.runTests(artsToTest, baseUrl, { updateSnapshots });
|
|
2674
|
-
const summary = runner.getSummary(results);
|
|
2675
|
-
await runner.stop();
|
|
2676
|
-
sendJson({
|
|
2677
|
-
success: true,
|
|
2678
|
-
summary,
|
|
2679
|
-
results: results.map((r) => ({
|
|
2680
|
-
artPath: r.artPath,
|
|
2681
|
-
variantName: r.variantName,
|
|
2682
|
-
viewport: r.viewport.name,
|
|
2683
|
-
passed: r.passed,
|
|
2684
|
-
isNew: r.isNew,
|
|
2685
|
-
diffPercentage: r.diffPercentage,
|
|
2686
|
-
error: r.error
|
|
2687
|
-
}))
|
|
2688
|
-
});
|
|
2689
|
-
} catch (e) {
|
|
2690
|
-
sendError(e instanceof Error ? e.message : String(e));
|
|
2691
|
-
}
|
|
2692
|
-
});
|
|
2693
|
-
return;
|
|
2694
|
-
}
|
|
2695
|
-
next();
|
|
2696
|
-
};
|
|
2005
|
+
tokenMap: resolvedTokenMap
|
|
2006
|
+
});
|
|
2007
|
+
} catch (e) {
|
|
2008
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2009
|
+
}
|
|
2697
2010
|
}
|
|
2698
|
-
/**
|
|
2699
|
-
async function
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
if (!art) {
|
|
2703
|
-
sendError("Art not found", 404);
|
|
2011
|
+
/** DELETE /api/tokens */
|
|
2012
|
+
async function handleTokensDelete(ctx, readBody, sendJson, sendError) {
|
|
2013
|
+
if (!ctx.tokensPath) {
|
|
2014
|
+
sendError("No tokens path configured", 400);
|
|
2704
2015
|
return;
|
|
2705
2016
|
}
|
|
2017
|
+
const body = await readBody();
|
|
2706
2018
|
try {
|
|
2707
|
-
const
|
|
2019
|
+
const { path: dotPath } = JSON.parse(body);
|
|
2020
|
+
if (!dotPath) {
|
|
2021
|
+
sendError("Missing required field: path", 400);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
const absoluteTokensPath = path.resolve(ctx.config.root, ctx.tokensPath);
|
|
2025
|
+
const currentCategories = await parseTokens(absoluteTokensPath);
|
|
2026
|
+
const currentMap = buildTokenMap(currentCategories);
|
|
2027
|
+
const dependents = findDependentTokens(currentMap, dotPath);
|
|
2028
|
+
const rawData = await readRawTokenFile(absoluteTokensPath);
|
|
2029
|
+
const deleted = deleteTokenAtPath(rawData, dotPath);
|
|
2030
|
+
if (!deleted) {
|
|
2031
|
+
sendError(`Token not found at path "${dotPath}"`, 404);
|
|
2032
|
+
return;
|
|
2033
|
+
}
|
|
2034
|
+
await writeRawTokenFile(absoluteTokensPath, rawData);
|
|
2035
|
+
const categories = await parseTokens(absoluteTokensPath);
|
|
2036
|
+
const tokenMap = buildTokenMap(categories);
|
|
2037
|
+
resolveReferences(categories, tokenMap);
|
|
2038
|
+
const resolvedTokenMap = buildTokenMap(categories);
|
|
2708
2039
|
sendJson({
|
|
2709
|
-
|
|
2710
|
-
|
|
2040
|
+
categories,
|
|
2041
|
+
tokenMap: resolvedTokenMap,
|
|
2042
|
+
dependentsWarning: dependents.length > 0 ? dependents : void 0
|
|
2711
2043
|
});
|
|
2712
2044
|
} catch (e) {
|
|
2713
2045
|
sendError(e instanceof Error ? e.message : String(e));
|
|
2714
2046
|
}
|
|
2715
2047
|
}
|
|
2048
|
+
|
|
2049
|
+
//#endregion
|
|
2050
|
+
//#region src/api-routes/handler-palette.ts
|
|
2716
2051
|
/** GET /api/arts/:path/palette */
|
|
2717
2052
|
async function handleArtPalette(ctx, match, sendJson, sendError) {
|
|
2718
2053
|
const artPath = decodeURIComponent(match[1]);
|
|
@@ -2779,6 +2114,27 @@ async function handleArtPalette(ctx, match, sendJson, sendError) {
|
|
|
2779
2114
|
sendError(e instanceof Error ? e.message : String(e));
|
|
2780
2115
|
}
|
|
2781
2116
|
}
|
|
2117
|
+
|
|
2118
|
+
//#endregion
|
|
2119
|
+
//#region src/api-routes/handlers.ts
|
|
2120
|
+
/** GET /api/arts/:path/source */
|
|
2121
|
+
async function handleArtSource(ctx, match, sendJson, sendError) {
|
|
2122
|
+
const artPath = decodeURIComponent(match[1]);
|
|
2123
|
+
const art = ctx.artFiles.get(artPath);
|
|
2124
|
+
if (!art) {
|
|
2125
|
+
sendError("Art not found", 404);
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
try {
|
|
2129
|
+
const source = await fs.promises.readFile(artPath, "utf-8");
|
|
2130
|
+
sendJson({
|
|
2131
|
+
source,
|
|
2132
|
+
path: artPath
|
|
2133
|
+
});
|
|
2134
|
+
} catch (e) {
|
|
2135
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2782
2138
|
/** GET /api/arts/:path/analysis */
|
|
2783
2139
|
async function handleArtAnalysis(ctx, match, sendJson, sendError) {
|
|
2784
2140
|
const artPath = decodeURIComponent(match[1]);
|
|
@@ -2874,10 +2230,300 @@ function handleArtA11y(ctx, match, sendJson, sendError) {
|
|
|
2874
2230
|
}
|
|
2875
2231
|
|
|
2876
2232
|
//#endregion
|
|
2877
|
-
//#region src/
|
|
2233
|
+
//#region src/api-routes/post-handlers.ts
|
|
2234
|
+
/** POST /api/preview-with-props */
|
|
2235
|
+
function handlePreviewWithProps(ctx, body, res, sendJson, sendError) {
|
|
2236
|
+
try {
|
|
2237
|
+
const { artPath: reqArtPath, variantName, props: propsOverride } = JSON.parse(body);
|
|
2238
|
+
const art = ctx.artFiles.get(reqArtPath);
|
|
2239
|
+
if (!art) {
|
|
2240
|
+
sendError("Art not found", 404);
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
const variant = art.variants.find((v) => v.name === variantName);
|
|
2244
|
+
if (!variant) {
|
|
2245
|
+
sendError("Variant not found", 404);
|
|
2246
|
+
return;
|
|
2247
|
+
}
|
|
2248
|
+
const variantComponentName = toPascalCase(variant.name);
|
|
2249
|
+
const moduleCode = generatePreviewModuleWithProps(art, variantComponentName, variant.name, propsOverride, ctx.resolvedPreviewCss, ctx.resolvedPreviewSetup);
|
|
2250
|
+
res.setHeader("Content-Type", "application/javascript");
|
|
2251
|
+
res.end(moduleCode);
|
|
2252
|
+
} catch (e) {
|
|
2253
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
/** POST /api/generate */
|
|
2257
|
+
async function handleGenerate(body, sendJson, sendError) {
|
|
2258
|
+
try {
|
|
2259
|
+
const { componentPath: reqComponentPath, options: autogenOptions } = JSON.parse(body);
|
|
2260
|
+
const { generateArtFile: genArt } = await import("./autogen-Dx-SIBf_.js");
|
|
2261
|
+
const result = await genArt(reqComponentPath, autogenOptions);
|
|
2262
|
+
sendJson({
|
|
2263
|
+
generated: true,
|
|
2264
|
+
componentName: result.componentName,
|
|
2265
|
+
variants: result.variants,
|
|
2266
|
+
artFileContent: result.artFileContent
|
|
2267
|
+
});
|
|
2268
|
+
} catch (e) {
|
|
2269
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
/** POST /api/run-vrt */
|
|
2273
|
+
async function handleRunVrt(ctx, body, sendJson, sendError) {
|
|
2274
|
+
try {
|
|
2275
|
+
const { artPath, updateSnapshots } = JSON.parse(body);
|
|
2276
|
+
const { MuseaVrtRunner: MuseaVrtRunner$1 } = await import("./vrt.js");
|
|
2277
|
+
const runner = new MuseaVrtRunner$1({ snapshotDir: path.resolve(ctx.config.root, ".vize/snapshots") });
|
|
2278
|
+
const port = ctx.getDevServerPort();
|
|
2279
|
+
const baseUrl = `http://localhost:${port}`;
|
|
2280
|
+
let artsToTest = Array.from(ctx.artFiles.values());
|
|
2281
|
+
if (artPath) artsToTest = artsToTest.filter((a) => a.path === artPath);
|
|
2282
|
+
await runner.start();
|
|
2283
|
+
const results = await runner.runTests(artsToTest, baseUrl, { updateSnapshots });
|
|
2284
|
+
const summary = runner.getSummary(results);
|
|
2285
|
+
await runner.stop();
|
|
2286
|
+
sendJson({
|
|
2287
|
+
success: true,
|
|
2288
|
+
summary,
|
|
2289
|
+
results: results.map((r) => ({
|
|
2290
|
+
artPath: r.artPath,
|
|
2291
|
+
variantName: r.variantName,
|
|
2292
|
+
viewport: r.viewport.name,
|
|
2293
|
+
passed: r.passed,
|
|
2294
|
+
isNew: r.isNew,
|
|
2295
|
+
diffPercentage: r.diffPercentage,
|
|
2296
|
+
error: r.error
|
|
2297
|
+
}))
|
|
2298
|
+
});
|
|
2299
|
+
} catch (e) {
|
|
2300
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
//#endregion
|
|
2305
|
+
//#region src/api-routes/index.ts
|
|
2306
|
+
/** Helper to read the full request body as a string. */
|
|
2307
|
+
function collectBody(req) {
|
|
2308
|
+
return new Promise((resolve) => {
|
|
2309
|
+
let body = "";
|
|
2310
|
+
req.on("data", (chunk) => {
|
|
2311
|
+
body += chunk;
|
|
2312
|
+
});
|
|
2313
|
+
req.on("end", () => resolve(body));
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
/**
|
|
2317
|
+
* Create the API middleware handler for the Musea gallery.
|
|
2318
|
+
*
|
|
2319
|
+
* Returns a Connect-compatible middleware function that handles all
|
|
2320
|
+
* `/api/...` sub-routes under the configured basePath.
|
|
2321
|
+
*/
|
|
2322
|
+
function createApiMiddleware(ctx) {
|
|
2323
|
+
return async (req, res, next) => {
|
|
2324
|
+
const sendJson = (data, status = 200) => {
|
|
2325
|
+
res.statusCode = status;
|
|
2326
|
+
res.setHeader("Content-Type", "application/json");
|
|
2327
|
+
res.end(JSON.stringify(data));
|
|
2328
|
+
};
|
|
2329
|
+
const sendError = (message, status = 500) => {
|
|
2330
|
+
sendJson({ error: message }, status);
|
|
2331
|
+
};
|
|
2332
|
+
const readBody = () => collectBody(req);
|
|
2333
|
+
const url = req.url || "/";
|
|
2334
|
+
if (url === "/arts" && req.method === "GET") {
|
|
2335
|
+
sendJson(Array.from(ctx.artFiles.values()));
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
if (url === "/tokens/usage" && req.method === "GET") {
|
|
2339
|
+
await handleTokensUsage(ctx, sendJson);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
if (url === "/tokens" && req.method === "GET") {
|
|
2343
|
+
await handleTokensGet(ctx, sendJson);
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
if (url === "/tokens" && req.method === "POST") {
|
|
2347
|
+
await handleTokensCreate(ctx, readBody, sendJson, sendError);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
if (url === "/tokens" && req.method === "PUT") {
|
|
2351
|
+
await handleTokensUpdate(ctx, readBody, sendJson, sendError);
|
|
2352
|
+
return;
|
|
2353
|
+
}
|
|
2354
|
+
if (url === "/tokens" && req.method === "DELETE") {
|
|
2355
|
+
await handleTokensDelete(ctx, readBody, sendJson, sendError);
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
if (url?.startsWith("/arts/") && req.method === "PUT") {
|
|
2359
|
+
const rest = url.slice(6);
|
|
2360
|
+
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2361
|
+
if (sourceMatch) {
|
|
2362
|
+
const artPath = decodeURIComponent(sourceMatch[1]);
|
|
2363
|
+
const art = ctx.artFiles.get(artPath);
|
|
2364
|
+
if (!art) {
|
|
2365
|
+
sendError("Art not found", 404);
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
const body = await collectBody(req);
|
|
2369
|
+
try {
|
|
2370
|
+
const { source } = JSON.parse(body);
|
|
2371
|
+
if (typeof source !== "string") {
|
|
2372
|
+
sendError("Missing required field: source", 400);
|
|
2373
|
+
return;
|
|
2374
|
+
}
|
|
2375
|
+
await fs.promises.writeFile(artPath, source, "utf-8");
|
|
2376
|
+
await ctx.processArtFile(artPath);
|
|
2377
|
+
sendJson({ success: true });
|
|
2378
|
+
} catch (e) {
|
|
2379
|
+
sendError(e instanceof Error ? e.message : String(e));
|
|
2380
|
+
}
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
next();
|
|
2384
|
+
return;
|
|
2385
|
+
}
|
|
2386
|
+
if (url?.startsWith("/arts/") && req.method === "GET") {
|
|
2387
|
+
const rest = url.slice(6);
|
|
2388
|
+
const sourceMatch = rest.match(/^(.+)\/source$/);
|
|
2389
|
+
const paletteMatch = rest.match(/^(.+)\/palette$/);
|
|
2390
|
+
const analysisMatch = rest.match(/^(.+)\/analysis$/);
|
|
2391
|
+
const docsMatch = rest.match(/^(.+)\/docs$/);
|
|
2392
|
+
const a11yMatch = rest.match(/^(.+)\/variants\/([^/]+)\/a11y$/);
|
|
2393
|
+
if (sourceMatch) {
|
|
2394
|
+
await handleArtSource(ctx, sourceMatch, sendJson, sendError);
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
if (paletteMatch) {
|
|
2398
|
+
await handleArtPalette(ctx, paletteMatch, sendJson, sendError);
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
if (analysisMatch) {
|
|
2402
|
+
await handleArtAnalysis(ctx, analysisMatch, sendJson, sendError);
|
|
2403
|
+
return;
|
|
2404
|
+
}
|
|
2405
|
+
if (docsMatch) {
|
|
2406
|
+
await handleArtDocs(ctx, docsMatch, sendJson, sendError);
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
if (a11yMatch) {
|
|
2410
|
+
handleArtA11y(ctx, a11yMatch, sendJson, sendError);
|
|
2411
|
+
return;
|
|
2412
|
+
}
|
|
2413
|
+
const artPath = decodeURIComponent(rest);
|
|
2414
|
+
const art = ctx.artFiles.get(artPath);
|
|
2415
|
+
if (art) sendJson(art);
|
|
2416
|
+
else sendError("Art not found", 404);
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
if (req.method === "POST") {
|
|
2420
|
+
const body = await collectBody(req);
|
|
2421
|
+
if (url === "/preview-with-props") {
|
|
2422
|
+
handlePreviewWithProps(ctx, body, res, sendJson, sendError);
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
if (url === "/generate") {
|
|
2426
|
+
await handleGenerate(body, sendJson, sendError);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (url === "/run-vrt") {
|
|
2430
|
+
await handleRunVrt(ctx, body, sendJson, sendError);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
next();
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
//#endregion
|
|
2439
|
+
//#region src/manifest.ts
|
|
2440
|
+
/**
|
|
2441
|
+
* Generate the virtual manifest module code containing all art file metadata.
|
|
2442
|
+
*/
|
|
2443
|
+
function generateManifestModule(artFiles) {
|
|
2444
|
+
const arts = Array.from(artFiles.values());
|
|
2445
|
+
return `export const arts = ${JSON.stringify(arts, null, 2)};`;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
//#endregion
|
|
2449
|
+
//#region src/plugin/virtual.ts
|
|
2878
2450
|
const VIRTUAL_MUSEA_PREFIX = "\0musea:";
|
|
2879
2451
|
const VIRTUAL_GALLERY = "\0musea-gallery";
|
|
2880
2452
|
const VIRTUAL_MANIFEST = "\0musea-manifest";
|
|
2453
|
+
function createResolveId(state) {
|
|
2454
|
+
return function resolveId(id) {
|
|
2455
|
+
const root = state.getConfigRoot();
|
|
2456
|
+
if (id === VIRTUAL_GALLERY) return VIRTUAL_GALLERY;
|
|
2457
|
+
if (id === VIRTUAL_MANIFEST) return VIRTUAL_MANIFEST;
|
|
2458
|
+
if (id.startsWith("virtual:musea-preview:")) return "\0musea-preview:" + id.slice(22);
|
|
2459
|
+
if (id.startsWith("virtual:musea-art:")) {
|
|
2460
|
+
const artPath = id.slice(18);
|
|
2461
|
+
if (state.artFiles.has(artPath)) return "\0musea-art:" + artPath + "?musea-virtual";
|
|
2462
|
+
}
|
|
2463
|
+
if (id.endsWith(".art.vue")) {
|
|
2464
|
+
const resolved = path.resolve(root, id);
|
|
2465
|
+
if (state.artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
2466
|
+
}
|
|
2467
|
+
if (state.inlineArt && id.endsWith(".vue") && !id.endsWith(".art.vue")) {
|
|
2468
|
+
const resolved = path.resolve(root, id);
|
|
2469
|
+
if (state.artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
2470
|
+
}
|
|
2471
|
+
return null;
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
function createLoad(state) {
|
|
2475
|
+
return function load(id) {
|
|
2476
|
+
if (id === VIRTUAL_GALLERY) return generateGalleryModule(state.basePath);
|
|
2477
|
+
if (id === VIRTUAL_MANIFEST) return generateManifestModule(state.artFiles);
|
|
2478
|
+
if (id.startsWith("\0musea-preview:")) {
|
|
2479
|
+
const rest = id.slice(15);
|
|
2480
|
+
const lastColonIndex = rest.lastIndexOf(":");
|
|
2481
|
+
if (lastColonIndex !== -1) {
|
|
2482
|
+
const artPath = rest.slice(0, lastColonIndex);
|
|
2483
|
+
const variantName = rest.slice(lastColonIndex + 1);
|
|
2484
|
+
const art = state.artFiles.get(artPath);
|
|
2485
|
+
if (art) {
|
|
2486
|
+
const variantComponentName = toPascalCase(variantName);
|
|
2487
|
+
return generatePreviewModule(art, variantComponentName, variantName, state.resolvedPreviewCss, state.resolvedPreviewSetup);
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
if (id.startsWith("\0musea-art:")) {
|
|
2492
|
+
const artPath = id.slice(11).replace(/\?musea-virtual$/, "");
|
|
2493
|
+
const art = state.artFiles.get(artPath);
|
|
2494
|
+
if (art) return generateArtModule(art, artPath);
|
|
2495
|
+
}
|
|
2496
|
+
if (id.startsWith(VIRTUAL_MUSEA_PREFIX)) {
|
|
2497
|
+
const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length).replace(/\?musea-virtual$/, "");
|
|
2498
|
+
const art = state.artFiles.get(realPath);
|
|
2499
|
+
if (art) return generateArtModule(art, realPath);
|
|
2500
|
+
}
|
|
2501
|
+
return null;
|
|
2502
|
+
};
|
|
2503
|
+
}
|
|
2504
|
+
function createHandleHotUpdate(state) {
|
|
2505
|
+
return async function handleHotUpdate(ctx) {
|
|
2506
|
+
const { file } = ctx;
|
|
2507
|
+
if (file.endsWith(".art.vue") && state.artFiles.has(file)) {
|
|
2508
|
+
await state.processArtFile(file);
|
|
2509
|
+
const virtualId = VIRTUAL_MUSEA_PREFIX + file + "?musea-virtual";
|
|
2510
|
+
const server = state.getServer();
|
|
2511
|
+
const modules = server?.moduleGraph.getModulesByFile(virtualId);
|
|
2512
|
+
if (modules) return [...modules];
|
|
2513
|
+
}
|
|
2514
|
+
if (state.inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue") && state.artFiles.has(file)) {
|
|
2515
|
+
await state.processArtFile(file);
|
|
2516
|
+
const virtualId = VIRTUAL_MUSEA_PREFIX + file;
|
|
2517
|
+
const server = state.getServer();
|
|
2518
|
+
const modules = server?.moduleGraph.getModulesByFile(virtualId);
|
|
2519
|
+
if (modules) return [...modules];
|
|
2520
|
+
}
|
|
2521
|
+
return void 0;
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
//#endregion
|
|
2526
|
+
//#region src/plugin/index.ts
|
|
2881
2527
|
/**
|
|
2882
2528
|
* Create Musea Vite plugin.
|
|
2883
2529
|
*/
|
|
@@ -2897,6 +2543,21 @@ function musea(options = {}) {
|
|
|
2897
2543
|
const artFiles = new Map();
|
|
2898
2544
|
let resolvedPreviewCss = [];
|
|
2899
2545
|
let resolvedPreviewSetup = null;
|
|
2546
|
+
const virtualState = {
|
|
2547
|
+
basePath,
|
|
2548
|
+
get inlineArt() {
|
|
2549
|
+
return inlineArt;
|
|
2550
|
+
},
|
|
2551
|
+
artFiles,
|
|
2552
|
+
resolvedPreviewCss,
|
|
2553
|
+
resolvedPreviewSetup,
|
|
2554
|
+
getConfigRoot: () => config.root,
|
|
2555
|
+
getServer: () => server,
|
|
2556
|
+
processArtFile
|
|
2557
|
+
};
|
|
2558
|
+
const resolveId = createResolveId(virtualState);
|
|
2559
|
+
const load = createLoad(virtualState);
|
|
2560
|
+
const handleHotUpdate = createHandleHotUpdate(virtualState);
|
|
2900
2561
|
const mainPlugin = {
|
|
2901
2562
|
name: "vite-plugin-musea",
|
|
2902
2563
|
enforce: "pre",
|
|
@@ -2914,8 +2575,11 @@ function musea(options = {}) {
|
|
|
2914
2575
|
if (options.storybookCompat === void 0 && mc.storybookCompat !== void 0) storybookCompat = mc.storybookCompat;
|
|
2915
2576
|
if (options.inlineArt === void 0 && mc.inlineArt !== void 0) inlineArt = mc.inlineArt;
|
|
2916
2577
|
}
|
|
2578
|
+
virtualState.basePath = basePath;
|
|
2917
2579
|
resolvedPreviewCss = previewCss.map((cssPath) => path.isAbsolute(cssPath) ? cssPath : path.resolve(resolvedConfig.root, cssPath));
|
|
2918
2580
|
if (previewSetup) resolvedPreviewSetup = path.isAbsolute(previewSetup) ? previewSetup : path.resolve(resolvedConfig.root, previewSetup);
|
|
2581
|
+
virtualState.resolvedPreviewCss = resolvedPreviewCss;
|
|
2582
|
+
virtualState.resolvedPreviewSetup = resolvedPreviewSetup;
|
|
2919
2583
|
},
|
|
2920
2584
|
configureServer(devServer) {
|
|
2921
2585
|
server = devServer;
|
|
@@ -2994,68 +2658,9 @@ function musea(options = {}) {
|
|
|
2994
2658
|
for (const file of files) await processArtFile(file);
|
|
2995
2659
|
if (storybookCompat) await generateStorybookFiles(artFiles, config.root, storybookOutDir);
|
|
2996
2660
|
},
|
|
2997
|
-
resolveId
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
if (id.startsWith("virtual:musea-preview:")) return "\0musea-preview:" + id.slice(22);
|
|
3001
|
-
if (id.startsWith("virtual:musea-art:")) {
|
|
3002
|
-
const artPath = id.slice(18);
|
|
3003
|
-
if (artFiles.has(artPath)) return "\0musea-art:" + artPath + "?musea-virtual";
|
|
3004
|
-
}
|
|
3005
|
-
if (id.endsWith(".art.vue")) {
|
|
3006
|
-
const resolved = path.resolve(config.root, id);
|
|
3007
|
-
if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
3008
|
-
}
|
|
3009
|
-
if (inlineArt && id.endsWith(".vue") && !id.endsWith(".art.vue")) {
|
|
3010
|
-
const resolved = path.resolve(config.root, id);
|
|
3011
|
-
if (artFiles.has(resolved)) return VIRTUAL_MUSEA_PREFIX + resolved + "?musea-virtual";
|
|
3012
|
-
}
|
|
3013
|
-
return null;
|
|
3014
|
-
},
|
|
3015
|
-
load(id) {
|
|
3016
|
-
if (id === VIRTUAL_GALLERY) return generateGalleryModule(basePath);
|
|
3017
|
-
if (id === VIRTUAL_MANIFEST) return generateManifestModule(artFiles);
|
|
3018
|
-
if (id.startsWith("\0musea-preview:")) {
|
|
3019
|
-
const rest = id.slice(15);
|
|
3020
|
-
const lastColonIndex = rest.lastIndexOf(":");
|
|
3021
|
-
if (lastColonIndex !== -1) {
|
|
3022
|
-
const artPath = rest.slice(0, lastColonIndex);
|
|
3023
|
-
const variantName = rest.slice(lastColonIndex + 1);
|
|
3024
|
-
const art = artFiles.get(artPath);
|
|
3025
|
-
if (art) {
|
|
3026
|
-
const variantComponentName = toPascalCase(variantName);
|
|
3027
|
-
return generatePreviewModule(art, variantComponentName, variantName, resolvedPreviewCss, resolvedPreviewSetup);
|
|
3028
|
-
}
|
|
3029
|
-
}
|
|
3030
|
-
}
|
|
3031
|
-
if (id.startsWith("\0musea-art:")) {
|
|
3032
|
-
const artPath = id.slice(11).replace(/\?musea-virtual$/, "");
|
|
3033
|
-
const art = artFiles.get(artPath);
|
|
3034
|
-
if (art) return generateArtModule(art, artPath);
|
|
3035
|
-
}
|
|
3036
|
-
if (id.startsWith(VIRTUAL_MUSEA_PREFIX)) {
|
|
3037
|
-
const realPath = id.slice(VIRTUAL_MUSEA_PREFIX.length).replace(/\?musea-virtual$/, "");
|
|
3038
|
-
const art = artFiles.get(realPath);
|
|
3039
|
-
if (art) return generateArtModule(art, realPath);
|
|
3040
|
-
}
|
|
3041
|
-
return null;
|
|
3042
|
-
},
|
|
3043
|
-
async handleHotUpdate(ctx) {
|
|
3044
|
-
const { file } = ctx;
|
|
3045
|
-
if (file.endsWith(".art.vue") && artFiles.has(file)) {
|
|
3046
|
-
await processArtFile(file);
|
|
3047
|
-
const virtualId = VIRTUAL_MUSEA_PREFIX + file + "?musea-virtual";
|
|
3048
|
-
const modules = server?.moduleGraph.getModulesByFile(virtualId);
|
|
3049
|
-
if (modules) return [...modules];
|
|
3050
|
-
}
|
|
3051
|
-
if (inlineArt && file.endsWith(".vue") && !file.endsWith(".art.vue") && artFiles.has(file)) {
|
|
3052
|
-
await processArtFile(file);
|
|
3053
|
-
const virtualId = VIRTUAL_MUSEA_PREFIX + file;
|
|
3054
|
-
const modules = server?.moduleGraph.getModulesByFile(virtualId);
|
|
3055
|
-
if (modules) return [...modules];
|
|
3056
|
-
}
|
|
3057
|
-
return void 0;
|
|
3058
|
-
}
|
|
2661
|
+
resolveId,
|
|
2662
|
+
load,
|
|
2663
|
+
handleHotUpdate
|
|
3059
2664
|
};
|
|
3060
2665
|
async function processArtFile(filePath) {
|
|
3061
2666
|
try {
|