@vizejs/vite-plugin-musea 0.243.0 → 0.263.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/index.mjs CHANGED
@@ -1,14 +1,14 @@
1
1
  import { c as createDevSessionToken, d as resolveInside, f as resolveInsideAny, h as validateDevApiRequest, i as generateGalleryScript, l as decodeUrlComponent, m as serializeScriptValue, n as generateGalleryModule, o as HttpError, p as resolveUrlPathInside, r as generateGalleryBody, s as collectRequestBody, u as parseJsonBody } from "./gallery-DIBlUrB6.mjs";
2
2
  import { i as MuseaVrtRunner, n as generateVrtJsonReport, r as generateVrtReport } from "./vrt-Cv1PK1EF.mjs";
3
3
  import { t as MuseaA11yRunner } from "./a11y-62l8G1tr.mjs";
4
- import { n as writeArtFile, t as generateArtFile } from "./autogen-Blm3Qy_O.mjs";
4
+ import { n as writeArtFile, t as generateArtFile } from "./autogen-B7_TO0n5.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { transformWithEsbuild } from "vite";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
- import { vizeConfigStore } from "@vizejs/vite-plugin";
10
- import crypto from "node:crypto";
11
9
  import { fileURLToPath } from "node:url";
10
+ import crypto from "node:crypto";
11
+ import { VIZE_CONFIG_FILE_ENV, loadConfig, vizeConfigStore } from "@vizejs/vite-plugin";
12
12
  //#region src/native-loader.ts
13
13
  /**
14
14
  * Native binding loader for @vizejs/native.
@@ -92,15 +92,6 @@ function analyzeSfcFallback(source, _options) {
92
92
  }
93
93
  }
94
94
  //#endregion
95
- //#region src/component-source.ts
96
- function allowedSourceRoots(root, scanRoots = []) {
97
- return [...new Set([root, ...scanRoots].map((sourceRoot) => path.resolve(sourceRoot)))];
98
- }
99
- function resolveComponentSourcePath(art, artPath, sourceRoots) {
100
- const componentPath = art.isInline && art.componentPath ? art.componentPath : art.metadata.component ? path.isAbsolute(art.metadata.component) ? art.metadata.component : path.resolve(path.dirname(artPath), art.metadata.component) : null;
101
- return componentPath ? resolveInsideAny(sourceRoots, componentPath, "component path") : null;
102
- }
103
- //#endregion
104
95
  //#region src/utils.ts
105
96
  /**
106
97
  * Shared utility functions for the Musea Vite plugin.
@@ -178,16 +169,48 @@ async function generateStorybookFiles(artFiles, root, outDir) {
178
169
  const binding = loadNative();
179
170
  const outputDir = path.resolve(root, outDir);
180
171
  await fs.promises.mkdir(outputDir, { recursive: true });
181
- for (const [filePath, _art] of artFiles) try {
172
+ for (const [filePath, art] of artFiles) try {
182
173
  const source = await fs.promises.readFile(filePath, "utf-8");
183
174
  const csf = binding.artToCsf(source, { filename: filePath });
184
175
  const outputPath = path.join(outputDir, csf.filename);
185
- await fs.promises.writeFile(outputPath, csf.code, "utf-8");
176
+ const code = rewriteStorybookScriptImports(rewriteStorybookComponentImport(csf.code, art, filePath, outputPath), filePath, outputPath);
177
+ await fs.promises.writeFile(outputPath, code, "utf-8");
186
178
  console.log(`[musea] Generated: ${path.relative(root, outputPath)}`);
187
179
  } catch (e) {
188
180
  console.error(`[musea] Failed to generate CSF for ${filePath}:`, e);
189
181
  }
190
182
  }
183
+ function rewriteStorybookComponentImport(code, art, artPath, outputPath) {
184
+ const component = art.isInline && art.componentPath ? art.componentPath : art.metadata.component;
185
+ if (!component || isBareImport(component)) return code;
186
+ const resolvedComponent = path.isAbsolute(component) ? component : path.resolve(path.dirname(artPath), component);
187
+ let relativeComponent = normalizeGlobPath(path.relative(path.dirname(outputPath), resolvedComponent));
188
+ if (!relativeComponent.startsWith(".")) relativeComponent = `./${relativeComponent}`;
189
+ return code.replace(/import\s+(__museaComponent)\s+from\s+(['"])([^'"]+)\2;/, `import $1 from $2${relativeComponent}$2;`);
190
+ }
191
+ function isBareImport(source) {
192
+ return !source.startsWith(".") && !path.isAbsolute(source);
193
+ }
194
+ /**
195
+ * Rewrite relative import specifiers in the generated Storybook CSF file so that
196
+ * imports lifted verbatim from the art file's `<script setup>` resolve against
197
+ * the generated file's location instead of the original art file's directory.
198
+ *
199
+ * The `__museaComponent` import is already rebased by
200
+ * `rewriteStorybookComponentImport` and is skipped here.
201
+ */
202
+ function rewriteStorybookScriptImports(code, artPath, outputPath) {
203
+ const artDir = path.dirname(artPath);
204
+ const outDir = path.dirname(outputPath);
205
+ return code.replace(/(import\s+(?:[\s\S]*?)\s+from\s+)(['"])([^'"]+)\2(\s*;?)/g, (match, head, quote, specifier, tail) => {
206
+ if (!specifier.startsWith(".")) return match;
207
+ if (/\b__museaComponent\b/.test(head)) return match;
208
+ const resolved = path.resolve(artDir, specifier);
209
+ let rebased = normalizeGlobPath(path.relative(outDir, resolved));
210
+ if (!rebased.startsWith(".")) rebased = `./${rebased}`;
211
+ return `${head}${quote}${rebased}${quote}${tail}`;
212
+ });
213
+ }
191
214
  function toPascalCase(str) {
192
215
  const pascal = str.normalize("NFKD").replace(/[^\p{L}\p{N}]+/gu, " ").trim().split(/\s+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
193
216
  if (!pascal) return "Variant";
@@ -211,346 +234,7 @@ function buildThemeConfig(theme) {
211
234
  return {
212
235
  default: themes[0].name,
213
236
  custom
214
- };
215
- }
216
- //#endregion
217
- //#region src/art-module.ts
218
- /**
219
- * Art module generation for Musea.
220
- *
221
- * Generates the virtual ES modules that represent parsed `.art.vue` files,
222
- * including variant component definitions and script setup handling.
223
- */
224
- /**
225
- * Extract the content of the first <script setup> block from a Vue SFC source.
226
- */
227
- function extractScriptSetupContent(source) {
228
- return source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
229
- }
230
- function extractScriptSetupIsolated(source) {
231
- const match = source.match(/<script\s+([^>]*)\bsetup\b([^>]*)>/);
232
- if (!match) return true;
233
- const isolate = `${match[1] ?? ""} ${match[2] ?? ""}`.match(/\bisolate\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/);
234
- return (isolate?.[1] ?? isolate?.[2] ?? isolate?.[3]) !== "false";
235
- }
236
- function resolveRelativeSpecifier(specifier, artDir) {
237
- if (!specifier.startsWith(".")) return specifier;
238
- return path.resolve(artDir, specifier);
239
- }
240
- function rewriteRelativeImportStatement(statement, artDir) {
241
- return statement.replace(/\bfrom\s+(['"])([^'"]+)\1/g, (_match, quote, specifier) => `from ${quote}${resolveRelativeSpecifier(specifier, artDir)}${quote}`).replace(/^(\s*import\s+)(['"])([^'"]+)\2(\s*;?\s*)$/s, (_match, prefix, quote, specifier, suffix) => `${prefix}${quote}${resolveRelativeSpecifier(specifier, artDir)}${quote}${suffix}`);
242
- }
243
- function escapeTemplateLiteral(str) {
244
- return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
245
- }
246
- function countCharBalance(source, openChar, closeChar) {
247
- let balance = 0;
248
- for (const char of source) if (char === openChar) balance++;
249
- else if (char === closeChar) balance--;
250
- return balance;
251
- }
252
- function isCompleteImportStatement(statement) {
253
- const trimmed = statement.trim();
254
- if (!trimmed.startsWith("import ")) return false;
255
- if (countCharBalance(statement, "{", "}") > 0) return false;
256
- return /^import\s+[\s\S]+?\s+from\s+['"][^'"]+['"]\s*;?$/s.test(trimmed) || /^import\s+['"][^'"]+['"]\s*;?$/s.test(trimmed);
257
- }
258
- function splitTopLevelCommaList(source) {
259
- const parts = [];
260
- let current = "";
261
- let braceDepth = 0;
262
- let bracketDepth = 0;
263
- let parenDepth = 0;
264
- for (const char of source) {
265
- if (char === "," && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
266
- const trimmed = current.trim();
267
- if (trimmed) parts.push(trimmed);
268
- current = "";
269
- continue;
270
- }
271
- current += char;
272
- if (char === "{") braceDepth++;
273
- else if (char === "}") braceDepth--;
274
- else if (char === "[") bracketDepth++;
275
- else if (char === "]") bracketDepth--;
276
- else if (char === "(") parenDepth++;
277
- else if (char === ")") parenDepth--;
278
- }
279
- const trimmed = current.trim();
280
- if (trimmed) parts.push(trimmed);
281
- return parts;
282
- }
283
- function collectImportedNames(statement, returnNames) {
284
- const fromMatch = statement.replace(/\s+/g, " ").trim().replace(/;$/, "").match(/^import\s+(type\s+)?(.+?)\s+from\s+['"][^'"]+['"]$/);
285
- if (!fromMatch) return;
286
- if (fromMatch[1]) return;
287
- const specifierParts = splitTopLevelCommaList(fromMatch[2].trim());
288
- const defaultOrNamespace = specifierParts[0]?.trim() ?? "";
289
- const trailing = specifierParts.slice(1).join(", ").trim();
290
- if (defaultOrNamespace && !defaultOrNamespace.startsWith("{")) {
291
- const namespaceMatch = defaultOrNamespace.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
292
- if (namespaceMatch) returnNames.add(namespaceMatch[1]);
293
- else if (!defaultOrNamespace.startsWith("type ")) returnNames.add(defaultOrNamespace);
294
- }
295
- const namedBlock = defaultOrNamespace.startsWith("{") ? defaultOrNamespace : trailing.startsWith("{") ? trailing : "";
296
- if (!namedBlock) return;
297
- const namedContent = namedBlock.slice(1, -1);
298
- for (const part of splitTopLevelCommaList(namedContent)) {
299
- const trimmed = part.trim();
300
- if (!trimmed || trimmed.startsWith("type ")) continue;
301
- const alias = trimmed.split(/\s+as\s+/).pop()?.trim();
302
- if (alias) returnNames.add(alias);
303
- }
304
- }
305
- function importDeclaresName(statement, name) {
306
- const names = /* @__PURE__ */ new Set();
307
- collectImportedNames(statement, names);
308
- return names.has(name);
309
- }
310
- function collectObjectDestructuredNames(statement, returnNames) {
311
- const match = statement.match(/^(?:export\s+)?(?:const|let|var)\s+\{([\s\S]*?)\}\s*=/);
312
- if (!match) return;
313
- for (const part of splitTopLevelCommaList(match[1])) {
314
- let name = part.trim();
315
- if (!name) continue;
316
- if (name.startsWith("...")) name = name.slice(3).trim();
317
- else if (name.includes(":")) name = name.split(":").pop().trim();
318
- else if (name.includes("=")) name = name.split("=")[0].trim();
319
- if (name.includes("=")) name = name.split("=")[0].trim();
320
- if (/^[A-Za-z_$][\w$]*$/.test(name)) returnNames.add(name);
321
- }
322
- }
323
- function collectArrayDestructuredNames(statement, returnNames) {
324
- const match = statement.match(/^(?:export\s+)?(?:const|let|var)\s+\[([\s\S]*?)\]\s*=/);
325
- if (!match) return;
326
- for (const part of splitTopLevelCommaList(match[1])) {
327
- let name = part.trim();
328
- if (!name) continue;
329
- if (name.startsWith("...")) name = name.slice(3).trim();
330
- if (name.includes("=")) name = name.split("=")[0].trim();
331
- if (/^[A-Za-z_$][\w$]*$/.test(name)) returnNames.add(name);
332
- }
333
- }
334
- function collectTopLevelReturnNames(setupBody, returnNames) {
335
- let braceDepth = 0;
336
- for (let i = 0; i < setupBody.length; i++) {
337
- const line = setupBody[i];
338
- const trimmed = line.trim();
339
- if (braceDepth === 0) {
340
- if (/^(?:export\s+)?(?:const|let|var)\s+\{/.test(trimmed)) {
341
- const statementLines = [line];
342
- let balance = countCharBalance(line, "{", "}") + countCharBalance(line, "[", "]") + countCharBalance(line, "(", ")");
343
- while (balance > 0 && i + 1 < setupBody.length) {
344
- i++;
345
- statementLines.push(setupBody[i]);
346
- balance += countCharBalance(setupBody[i], "{", "}") + countCharBalance(setupBody[i], "[", "]") + countCharBalance(setupBody[i], "(", ")");
347
- }
348
- collectObjectDestructuredNames(statementLines.join("\n"), returnNames);
349
- continue;
350
- }
351
- if (/^(?:export\s+)?(?:const|let|var)\s+\[/.test(trimmed)) {
352
- const statementLines = [line];
353
- let balance = countCharBalance(line, "{", "}") + countCharBalance(line, "[", "]") + countCharBalance(line, "(", ")");
354
- while (balance > 0 && i + 1 < setupBody.length) {
355
- i++;
356
- statementLines.push(setupBody[i]);
357
- balance += countCharBalance(setupBody[i], "{", "}") + countCharBalance(setupBody[i], "[", "]") + countCharBalance(setupBody[i], "(", ")");
358
- }
359
- collectArrayDestructuredNames(statementLines.join("\n"), returnNames);
360
- continue;
361
- }
362
- const constMatch = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/);
363
- if (constMatch) returnNames.add(constMatch[1]);
364
- const functionMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/);
365
- if (functionMatch) returnNames.add(functionMatch[1]);
366
- const classMatch = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_$][\w$]*)\b/);
367
- if (classMatch) returnNames.add(classMatch[1]);
368
- }
369
- braceDepth += countCharBalance(line, "{", "}");
370
- }
371
- }
372
- /**
373
- * Parse script setup content into imports and setup body.
374
- * Returns the import lines, setup body lines, and all identifiers to expose.
375
- */
376
- function parseScriptSetupForArt(content) {
377
- const lines = content.split("\n");
378
- const imports = [];
379
- const setupBody = [];
380
- const returnNames = /* @__PURE__ */ new Set();
381
- let currentImport = null;
382
- const defineArtComponent = extractDefineArtComponent(content);
383
- let defineArtBalance = 0;
384
- for (const line of lines) {
385
- const trimmed = line.trim();
386
- if (defineArtBalance > 0) {
387
- defineArtBalance += countCharBalance(line, "(", ")");
388
- continue;
389
- }
390
- if (currentImport) {
391
- currentImport.push(line);
392
- const statement = currentImport.join("\n");
393
- if (isCompleteImportStatement(statement)) {
394
- imports.push(statement);
395
- collectImportedNames(statement, returnNames);
396
- currentImport = null;
397
- }
398
- continue;
399
- }
400
- if (trimmed.startsWith("import ")) {
401
- currentImport = [line];
402
- const statement = currentImport.join("\n");
403
- if (isCompleteImportStatement(statement)) {
404
- imports.push(statement);
405
- collectImportedNames(statement, returnNames);
406
- currentImport = null;
407
- }
408
- continue;
409
- }
410
- if (isDefineArtLine(trimmed)) {
411
- defineArtBalance = Math.max(0, countCharBalance(line, "(", ")"));
412
- continue;
413
- }
414
- setupBody.push(line);
415
- }
416
- if (currentImport) {
417
- const statement = currentImport.join("\n");
418
- imports.push(statement);
419
- collectImportedNames(statement, returnNames);
420
- }
421
- collectTopLevelReturnNames(setupBody, returnNames);
422
- returnNames.delete("type");
423
- if (defineArtComponent.name) returnNames.delete(defineArtComponent.name);
424
- return {
425
- imports,
426
- setupBody,
427
- returnNames: [...returnNames],
428
- defineArtComponentName: defineArtComponent.name,
429
- defineArtComponentSource: defineArtComponent.source
430
- };
431
- }
432
- function extractDefineArtComponent(content) {
433
- const sourceMatch = content.match(/\bdefineArt\s*\(\s*(['"])([^'"]+)\1/);
434
- if (sourceMatch) return {
435
- name: componentNameFromSource(sourceMatch[2]),
436
- source: sourceMatch[2]
437
- };
438
- return { name: content.match(/\bdefineArt\s*\(\s*([A-Za-z_$][\w$]*)/)?.[1] };
439
- }
440
- function componentNameFromSource(source) {
441
- const withoutQuery = source.split(/[?#]/, 1)[0] || source;
442
- const filename = path.basename(withoutQuery);
443
- const extension = path.extname(filename);
444
- const name = toPascalCase(extension ? filename.slice(0, -extension.length) : filename);
445
- return name === "Variant" ? "MuseaComponent" : name;
446
- }
447
- function isDefineArtLine(trimmed) {
448
- return /\bdefineArt\s*\(/.test(trimmed);
449
- }
450
- function generateArtModule(art, filePath, options = {}) {
451
- let componentImportPath;
452
- let componentTagName;
453
- let componentBindingName = "__MuseaComponent";
454
- const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
455
- const defineArtComponentName = scriptSetup?.defineArtComponentName;
456
- const defineArtComponentSource = scriptSetup?.defineArtComponentSource;
457
- if (art.isInline && art.componentPath) {
458
- componentImportPath = options.root ? resolveComponentSourcePath(art, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : art.componentPath;
459
- componentTagName = "MuseaComponent";
460
- } else if (defineArtComponentSource || art.metadata.component) {
461
- const componentSource = defineArtComponentSource ?? art.metadata.component;
462
- if (componentSource) {
463
- const sourceArt = componentSource === art.metadata.component ? art : {
464
- ...art,
465
- metadata: {
466
- ...art.metadata,
467
- component: componentSource
468
- }
469
- };
470
- componentImportPath = options.root ? resolveComponentSourcePath(sourceArt, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : path.isAbsolute(componentSource) ? componentSource : path.resolve(path.dirname(filePath), componentSource);
471
- }
472
- componentTagName = defineArtComponentName ?? (art.metadata.component ? componentNameFromSource(art.metadata.component) : "MuseaComponent");
473
- componentBindingName = componentTagName;
474
- }
475
- let code = `
476
- // Auto-generated module for: ${path.basename(filePath)}
477
- import { defineComponent, h } from 'vue';
478
- `;
479
- if (scriptSetup) {
480
- const artDir = path.dirname(filePath);
481
- for (const imp of scriptSetup.imports) {
482
- const resolved = rewriteRelativeImportStatement(imp, artDir);
483
- code += `${resolved}\n`;
484
- }
485
- }
486
- if (componentImportPath && componentTagName) {
487
- if (!scriptSetup?.imports.some((imp) => importDeclaresName(imp, componentBindingName))) code += `import ${componentBindingName} from ${JSON.stringify(componentImportPath)};\n`;
488
- code += `export const __component__ = ${componentBindingName};\n`;
489
- }
490
- code += `
491
- export const metadata = ${JSON.stringify(art.metadata)};
492
- export const variants = ${JSON.stringify(art.variants)};
493
- export const __styles__ = ${JSON.stringify(art.styleBlocks ?? [])};
494
- `;
495
- const hasSetupBody = scriptSetup?.setupBody.some((line) => line.trim().length > 0) ?? false;
496
- const hasSetup = !!scriptSetup && (hasSetupBody || scriptSetup.returnNames.length > 0);
497
- const setupReturn = `{ ${scriptSetup?.returnNames.join(", ") ?? ""} }`;
498
- const isolatedSetup = art.scriptSetupIsolated !== false;
499
- if (scriptSetup && hasSetup && !isolatedSetup) code += `
500
- const __museaSharedSetup = (() => {
501
- ${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
502
- return ${setupReturn};
503
- })();
504
- `;
505
- for (const variant of art.variants) {
506
- const variantComponentName = toPascalCase(variant.name);
507
- let template = variant.template;
508
- if (componentTagName) template = template.replace(/<Self/g, `<${componentTagName}`).replace(/<\/Self>/g, `</${componentTagName}>`);
509
- const escapedTemplate = escapeTemplateLiteral(template);
510
- const fullTemplate = `<div data-variant="${escapeTemplateLiteral(escapeHtml(variant.name))}">${escapedTemplate}</div>`;
511
- const componentNames = /* @__PURE__ */ new Map();
512
- if (componentTagName) componentNames.set(componentTagName, componentBindingName);
513
- if (scriptSetup) {
514
- for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.set(name, name);
515
- }
516
- const components = componentNames.size > 0 ? ` components: { ${[...componentNames].map(([name, value]) => `${JSON.stringify(name)}: ${value}`).join(", ")} },\n` : "";
517
- if (scriptSetup && hasSetup && isolatedSetup) code += `
518
- export const ${variantComponentName} = defineComponent({
519
- name: '${variantComponentName}',
520
- ${components} setup() {
521
- ${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
522
- return ${setupReturn};
523
- },
524
- template: \`${fullTemplate}\`,
525
- });
526
- `;
527
- else if (scriptSetup && hasSetup) code += `
528
- export const ${variantComponentName} = defineComponent({
529
- name: '${variantComponentName}',
530
- ${components} setup() {
531
- return __museaSharedSetup;
532
- },
533
- template: \`${fullTemplate}\`,
534
- });
535
- `;
536
- else if (componentTagName) code += `
537
- export const ${variantComponentName} = {
538
- name: '${variantComponentName}',
539
- ${components} template: \`${fullTemplate}\`,
540
- };
541
- `;
542
- else code += `
543
- export const ${variantComponentName} = {
544
- name: '${variantComponentName}',
545
- template: \`${fullTemplate}\`,
546
- };
547
- `;
548
- }
549
- const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];
550
- if (defaultVariant) code += `
551
- export default ${toPascalCase(defaultVariant.name)};
552
- `;
553
- return code;
237
+ };
554
238
  }
555
239
  //#endregion
556
240
  //#region src/preview/addons.ts
@@ -1169,53 +853,401 @@ function ensureArtStyles(styles) {
1169
853
  document.head.appendChild(tag);
1170
854
  }
1171
855
  }
1172
-
1173
- function renderError(title, error) {
1174
- container.textContent = '';
1175
- const root = document.createElement('div');
1176
- root.className = 'musea-error';
1177
-
1178
- const titleEl = document.createElement('div');
1179
- titleEl.className = 'musea-error-title';
1180
- titleEl.textContent = title;
1181
- root.appendChild(titleEl);
1182
-
1183
- const messageEl = document.createElement('div');
1184
- messageEl.textContent = error instanceof Error ? error.message : String(error);
1185
- root.appendChild(messageEl);
1186
-
1187
- container.appendChild(root);
856
+
857
+ function renderError(title, error) {
858
+ container.textContent = '';
859
+ const root = document.createElement('div');
860
+ root.className = 'musea-error';
861
+
862
+ const titleEl = document.createElement('div');
863
+ titleEl.className = 'musea-error-title';
864
+ titleEl.textContent = title;
865
+ root.appendChild(titleEl);
866
+
867
+ const messageEl = document.createElement('div');
868
+ messageEl.textContent = error instanceof Error ? error.message : String(error);
869
+ root.appendChild(messageEl);
870
+
871
+ container.appendChild(root);
872
+ }
873
+
874
+ async function mount() {
875
+ try {
876
+ const VariantComponent = artModule[${variantComponentNameLiteral}];
877
+ if (!VariantComponent) {
878
+ throw new Error('Variant component ' + ${variantComponentNameLiteral} + ' not found');
879
+ }
880
+
881
+ const WrappedComponent = {
882
+ render() {
883
+ return h(VariantComponent, propsOverride);
884
+ }
885
+ };
886
+
887
+ const app = createApp(WrappedComponent);
888
+ ensureArtStyles(artModule.__styles__);
889
+ ${setupCall}
890
+ container.innerHTML = '';
891
+ container.className = 'musea-variant';
892
+ app.mount(container);
893
+ console.log('[musea-preview] Mounted variant with props override:', ${variantNameLiteral});
894
+ __museaInitAddons(container, ${variantNameLiteral}, ${actionEvents});
895
+ } catch (error) {
896
+ console.error('[musea-preview] Failed to mount:', error);
897
+ renderError('Failed to render', error);
898
+ }
899
+ }
900
+
901
+ mount();
902
+ `;
903
+ }
904
+ //#endregion
905
+ //#region src/component-source.ts
906
+ function allowedSourceRoots(root, scanRoots = []) {
907
+ return [...new Set([root, ...scanRoots].map((sourceRoot) => path.resolve(sourceRoot)))];
908
+ }
909
+ function resolveComponentSourcePath(art, artPath, sourceRoots) {
910
+ const componentPath = art.isInline && art.componentPath ? art.componentPath : art.metadata.component ? path.isAbsolute(art.metadata.component) ? art.metadata.component : path.resolve(path.dirname(artPath), art.metadata.component) : null;
911
+ return componentPath ? resolveInsideAny(sourceRoots, componentPath, "component path") : null;
912
+ }
913
+ //#endregion
914
+ //#region src/art-module.ts
915
+ /**
916
+ * Art module generation for Musea.
917
+ *
918
+ * Generates the virtual ES modules that represent parsed `.art.vue` files,
919
+ * including variant component definitions and script setup handling.
920
+ */
921
+ /**
922
+ * Extract the content of the first <script setup> block from a Vue SFC source.
923
+ */
924
+ function extractScriptSetupContent(source) {
925
+ return source.match(/<script\s+[^>]*setup[^>]*>([\s\S]*?)<\/script>/)?.[1]?.trim();
926
+ }
927
+ function extractScriptSetupIsolated(source) {
928
+ const match = source.match(/<script\s+([^>]*)\bsetup\b([^>]*)>/);
929
+ if (!match) return true;
930
+ const isolate = `${match[1] ?? ""} ${match[2] ?? ""}`.match(/\bisolate\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/);
931
+ return (isolate?.[1] ?? isolate?.[2] ?? isolate?.[3]) !== "false";
932
+ }
933
+ function resolveRelativeSpecifier(specifier, artDir) {
934
+ if (!specifier.startsWith(".")) return specifier;
935
+ return path.resolve(artDir, specifier);
936
+ }
937
+ function rewriteRelativeImportStatement(statement, artDir) {
938
+ return statement.replace(/\bfrom\s+(['"])([^'"]+)\1/g, (_match, quote, specifier) => `from ${quote}${resolveRelativeSpecifier(specifier, artDir)}${quote}`).replace(/^(\s*import\s+)(['"])([^'"]+)\2(\s*;?\s*)$/s, (_match, prefix, quote, specifier, suffix) => `${prefix}${quote}${resolveRelativeSpecifier(specifier, artDir)}${quote}${suffix}`);
939
+ }
940
+ function escapeTemplateLiteral(str) {
941
+ return str.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$/g, "\\$");
942
+ }
943
+ function countCharBalance(source, openChar, closeChar) {
944
+ let balance = 0;
945
+ for (const char of source) if (char === openChar) balance++;
946
+ else if (char === closeChar) balance--;
947
+ return balance;
948
+ }
949
+ function isCompleteImportStatement(statement) {
950
+ const trimmed = statement.trim();
951
+ if (!trimmed.startsWith("import ")) return false;
952
+ if (countCharBalance(statement, "{", "}") > 0) return false;
953
+ return /^import\s+[\s\S]+?\s+from\s+['"][^'"]+['"]\s*;?$/s.test(trimmed) || /^import\s+['"][^'"]+['"]\s*;?$/s.test(trimmed);
954
+ }
955
+ function splitTopLevelCommaList(source) {
956
+ const parts = [];
957
+ let current = "";
958
+ let braceDepth = 0;
959
+ let bracketDepth = 0;
960
+ let parenDepth = 0;
961
+ for (const char of source) {
962
+ if (char === "," && braceDepth === 0 && bracketDepth === 0 && parenDepth === 0) {
963
+ const trimmed = current.trim();
964
+ if (trimmed) parts.push(trimmed);
965
+ current = "";
966
+ continue;
967
+ }
968
+ current += char;
969
+ if (char === "{") braceDepth++;
970
+ else if (char === "}") braceDepth--;
971
+ else if (char === "[") bracketDepth++;
972
+ else if (char === "]") bracketDepth--;
973
+ else if (char === "(") parenDepth++;
974
+ else if (char === ")") parenDepth--;
975
+ }
976
+ const trimmed = current.trim();
977
+ if (trimmed) parts.push(trimmed);
978
+ return parts;
979
+ }
980
+ function collectImportedNames(statement, returnNames) {
981
+ const fromMatch = statement.replace(/\s+/g, " ").trim().replace(/;$/, "").match(/^import\s+(type\s+)?(.+?)\s+from\s+['"][^'"]+['"]$/);
982
+ if (!fromMatch) return;
983
+ if (fromMatch[1]) return;
984
+ const specifierParts = splitTopLevelCommaList(fromMatch[2].trim());
985
+ const defaultOrNamespace = specifierParts[0]?.trim() ?? "";
986
+ const trailing = specifierParts.slice(1).join(", ").trim();
987
+ if (defaultOrNamespace && !defaultOrNamespace.startsWith("{")) {
988
+ const namespaceMatch = defaultOrNamespace.match(/^\*\s+as\s+([A-Za-z_$][\w$]*)$/);
989
+ if (namespaceMatch) returnNames.add(namespaceMatch[1]);
990
+ else if (!defaultOrNamespace.startsWith("type ")) returnNames.add(defaultOrNamespace);
991
+ }
992
+ const namedBlock = defaultOrNamespace.startsWith("{") ? defaultOrNamespace : trailing.startsWith("{") ? trailing : "";
993
+ if (!namedBlock) return;
994
+ const namedContent = namedBlock.slice(1, -1);
995
+ for (const part of splitTopLevelCommaList(namedContent)) {
996
+ const trimmed = part.trim();
997
+ if (!trimmed || trimmed.startsWith("type ")) continue;
998
+ const alias = trimmed.split(/\s+as\s+/).pop()?.trim();
999
+ if (alias) returnNames.add(alias);
1000
+ }
1001
+ }
1002
+ function importDeclaresName(statement, name) {
1003
+ const names = /* @__PURE__ */ new Set();
1004
+ collectImportedNames(statement, names);
1005
+ return names.has(name);
1006
+ }
1007
+ function collectObjectDestructuredNames(statement, returnNames) {
1008
+ const match = statement.match(/^(?:export\s+)?(?:const|let|var)\s+\{([\s\S]*?)\}\s*=/);
1009
+ if (!match) return;
1010
+ for (const part of splitTopLevelCommaList(match[1])) {
1011
+ let name = part.trim();
1012
+ if (!name) continue;
1013
+ if (name.startsWith("...")) name = name.slice(3).trim();
1014
+ else if (name.includes(":")) name = name.split(":").pop().trim();
1015
+ else if (name.includes("=")) name = name.split("=")[0].trim();
1016
+ if (name.includes("=")) name = name.split("=")[0].trim();
1017
+ if (/^[A-Za-z_$][\w$]*$/.test(name)) returnNames.add(name);
1018
+ }
1019
+ }
1020
+ function collectArrayDestructuredNames(statement, returnNames) {
1021
+ const match = statement.match(/^(?:export\s+)?(?:const|let|var)\s+\[([\s\S]*?)\]\s*=/);
1022
+ if (!match) return;
1023
+ for (const part of splitTopLevelCommaList(match[1])) {
1024
+ let name = part.trim();
1025
+ if (!name) continue;
1026
+ if (name.startsWith("...")) name = name.slice(3).trim();
1027
+ if (name.includes("=")) name = name.split("=")[0].trim();
1028
+ if (/^[A-Za-z_$][\w$]*$/.test(name)) returnNames.add(name);
1029
+ }
1030
+ }
1031
+ function collectTopLevelReturnNames(setupBody, returnNames) {
1032
+ let braceDepth = 0;
1033
+ for (let i = 0; i < setupBody.length; i++) {
1034
+ const line = setupBody[i];
1035
+ const trimmed = line.trim();
1036
+ if (braceDepth === 0) {
1037
+ if (/^(?:export\s+)?(?:const|let|var)\s+\{/.test(trimmed)) {
1038
+ const statementLines = [line];
1039
+ let balance = countCharBalance(line, "{", "}") + countCharBalance(line, "[", "]") + countCharBalance(line, "(", ")");
1040
+ while (balance > 0 && i + 1 < setupBody.length) {
1041
+ i++;
1042
+ statementLines.push(setupBody[i]);
1043
+ balance += countCharBalance(setupBody[i], "{", "}") + countCharBalance(setupBody[i], "[", "]") + countCharBalance(setupBody[i], "(", ")");
1044
+ }
1045
+ collectObjectDestructuredNames(statementLines.join("\n"), returnNames);
1046
+ continue;
1047
+ }
1048
+ if (/^(?:export\s+)?(?:const|let|var)\s+\[/.test(trimmed)) {
1049
+ const statementLines = [line];
1050
+ let balance = countCharBalance(line, "{", "}") + countCharBalance(line, "[", "]") + countCharBalance(line, "(", ")");
1051
+ while (balance > 0 && i + 1 < setupBody.length) {
1052
+ i++;
1053
+ statementLines.push(setupBody[i]);
1054
+ balance += countCharBalance(setupBody[i], "{", "}") + countCharBalance(setupBody[i], "[", "]") + countCharBalance(setupBody[i], "(", ")");
1055
+ }
1056
+ collectArrayDestructuredNames(statementLines.join("\n"), returnNames);
1057
+ continue;
1058
+ }
1059
+ const constMatch = trimmed.match(/^(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)/);
1060
+ if (constMatch) returnNames.add(constMatch[1]);
1061
+ const functionMatch = trimmed.match(/^(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/);
1062
+ if (functionMatch) returnNames.add(functionMatch[1]);
1063
+ const classMatch = trimmed.match(/^(?:export\s+)?class\s+([A-Za-z_$][\w$]*)\b/);
1064
+ if (classMatch) returnNames.add(classMatch[1]);
1065
+ }
1066
+ braceDepth += countCharBalance(line, "{", "}");
1067
+ }
1068
+ }
1069
+ /**
1070
+ * Parse script setup content into imports and setup body.
1071
+ * Returns the import lines, setup body lines, and all identifiers to expose.
1072
+ */
1073
+ function parseScriptSetupForArt(content) {
1074
+ const lines = content.split("\n");
1075
+ const imports = [];
1076
+ const setupBody = [];
1077
+ const returnNames = /* @__PURE__ */ new Set();
1078
+ let currentImport = null;
1079
+ const defineArtComponent = extractDefineArtComponent(content);
1080
+ let defineArtBalance = 0;
1081
+ for (const line of lines) {
1082
+ const trimmed = line.trim();
1083
+ if (defineArtBalance > 0) {
1084
+ defineArtBalance += countCharBalance(line, "(", ")");
1085
+ continue;
1086
+ }
1087
+ if (currentImport) {
1088
+ currentImport.push(line);
1089
+ const statement = currentImport.join("\n");
1090
+ if (isCompleteImportStatement(statement)) {
1091
+ imports.push(statement);
1092
+ collectImportedNames(statement, returnNames);
1093
+ currentImport = null;
1094
+ }
1095
+ continue;
1096
+ }
1097
+ if (trimmed.startsWith("import ")) {
1098
+ currentImport = [line];
1099
+ const statement = currentImport.join("\n");
1100
+ if (isCompleteImportStatement(statement)) {
1101
+ imports.push(statement);
1102
+ collectImportedNames(statement, returnNames);
1103
+ currentImport = null;
1104
+ }
1105
+ continue;
1106
+ }
1107
+ if (isDefineArtLine(trimmed)) {
1108
+ defineArtBalance = Math.max(0, countCharBalance(line, "(", ")"));
1109
+ continue;
1110
+ }
1111
+ setupBody.push(line);
1112
+ }
1113
+ if (currentImport) {
1114
+ const statement = currentImport.join("\n");
1115
+ imports.push(statement);
1116
+ collectImportedNames(statement, returnNames);
1117
+ }
1118
+ collectTopLevelReturnNames(setupBody, returnNames);
1119
+ returnNames.delete("type");
1120
+ if (defineArtComponent.name) returnNames.delete(defineArtComponent.name);
1121
+ return {
1122
+ imports,
1123
+ setupBody,
1124
+ returnNames: [...returnNames],
1125
+ defineArtComponentName: defineArtComponent.name,
1126
+ defineArtComponentSource: defineArtComponent.source
1127
+ };
1128
+ }
1129
+ function extractDefineArtComponent(content) {
1130
+ const sourceMatch = content.match(/\bdefineArt\s*\(\s*(['"])([^'"]+)\1/);
1131
+ if (sourceMatch) return {
1132
+ name: componentNameFromSource(sourceMatch[2]),
1133
+ source: sourceMatch[2]
1134
+ };
1135
+ return { name: content.match(/\bdefineArt\s*\(\s*([A-Za-z_$][\w$]*)/)?.[1] };
1188
1136
  }
1189
-
1190
- async function mount() {
1191
- try {
1192
- const VariantComponent = artModule[${variantComponentNameLiteral}];
1193
- if (!VariantComponent) {
1194
- throw new Error('Variant component ' + ${variantComponentNameLiteral} + ' not found');
1195
- }
1196
-
1197
- const WrappedComponent = {
1198
- render() {
1199
- return h(VariantComponent, propsOverride);
1200
- }
1201
- };
1202
-
1203
- const app = createApp(WrappedComponent);
1204
- ensureArtStyles(artModule.__styles__);
1205
- ${setupCall}
1206
- container.innerHTML = '';
1207
- container.className = 'musea-variant';
1208
- app.mount(container);
1209
- console.log('[musea-preview] Mounted variant with props override:', ${variantNameLiteral});
1210
- __museaInitAddons(container, ${variantNameLiteral}, ${actionEvents});
1211
- } catch (error) {
1212
- console.error('[musea-preview] Failed to mount:', error);
1213
- renderError('Failed to render', error);
1214
- }
1137
+ function componentNameFromSource(source) {
1138
+ const withoutQuery = source.split(/[?#]/, 1)[0] || source;
1139
+ const filename = path.basename(withoutQuery);
1140
+ const extension = path.extname(filename);
1141
+ const name = toPascalCase(extension ? filename.slice(0, -extension.length) : filename);
1142
+ return name === "Variant" ? "MuseaComponent" : name;
1215
1143
  }
1216
-
1217
- mount();
1144
+ function isDefineArtLine(trimmed) {
1145
+ return /\bdefineArt\s*\(/.test(trimmed);
1146
+ }
1147
+ function generateArtModule(art, filePath, options = {}) {
1148
+ let componentImportPath;
1149
+ let componentTagName;
1150
+ let componentBindingName = "__MuseaComponent";
1151
+ const scriptSetup = art.scriptSetupContent ? parseScriptSetupForArt(art.scriptSetupContent) : null;
1152
+ const defineArtComponentName = scriptSetup?.defineArtComponentName;
1153
+ const defineArtComponentSource = scriptSetup?.defineArtComponentSource;
1154
+ if (art.isInline && art.componentPath) {
1155
+ componentImportPath = options.root ? resolveComponentSourcePath(art, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : art.componentPath;
1156
+ componentTagName = "MuseaComponent";
1157
+ } else if (defineArtComponentSource || art.metadata.component) {
1158
+ const componentSource = defineArtComponentSource ?? art.metadata.component;
1159
+ if (componentSource) {
1160
+ const sourceArt = componentSource === art.metadata.component ? art : {
1161
+ ...art,
1162
+ metadata: {
1163
+ ...art.metadata,
1164
+ component: componentSource
1165
+ }
1166
+ };
1167
+ componentImportPath = options.root ? resolveComponentSourcePath(sourceArt, filePath, allowedSourceRoots(options.root, options.scanRoots ?? [])) ?? void 0 : path.isAbsolute(componentSource) ? componentSource : path.resolve(path.dirname(filePath), componentSource);
1168
+ }
1169
+ componentTagName = defineArtComponentName ?? (art.metadata.component ? componentNameFromSource(art.metadata.component) : "MuseaComponent");
1170
+ componentBindingName = componentTagName;
1171
+ }
1172
+ let code = `
1173
+ // Auto-generated module for: ${path.basename(filePath)}
1174
+ import { defineComponent, h } from 'vue';
1175
+ `;
1176
+ if (scriptSetup) {
1177
+ const artDir = path.dirname(filePath);
1178
+ for (const imp of scriptSetup.imports) {
1179
+ const resolved = rewriteRelativeImportStatement(imp, artDir);
1180
+ code += `${resolved}\n`;
1181
+ }
1182
+ }
1183
+ if (componentImportPath && componentTagName) {
1184
+ if (!scriptSetup?.imports.some((imp) => importDeclaresName(imp, componentBindingName))) code += `import ${componentBindingName} from ${JSON.stringify(componentImportPath)};\n`;
1185
+ code += `export const __component__ = ${componentBindingName};\n`;
1186
+ }
1187
+ code += `
1188
+ export const metadata = ${JSON.stringify(art.metadata)};
1189
+ export const variants = ${JSON.stringify(art.variants)};
1190
+ export const __styles__ = ${JSON.stringify(art.styleBlocks ?? [])};
1191
+ `;
1192
+ const hasSetupBody = scriptSetup?.setupBody.some((line) => line.trim().length > 0) ?? false;
1193
+ const hasSetup = !!scriptSetup && (hasSetupBody || scriptSetup.returnNames.length > 0);
1194
+ const setupReturn = `{ ${scriptSetup?.returnNames.join(", ") ?? ""} }`;
1195
+ const isolatedSetup = art.scriptSetupIsolated !== false;
1196
+ if (scriptSetup && hasSetup && !isolatedSetup) code += `
1197
+ const __museaSharedSetup = (() => {
1198
+ ${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
1199
+ return ${setupReturn};
1200
+ })();
1201
+ `;
1202
+ for (const variant of art.variants) {
1203
+ const variantComponentName = toPascalCase(variant.name);
1204
+ let template = variant.template;
1205
+ if (componentTagName) template = template.replace(/<Self/g, `<${componentTagName}`).replace(/<\/Self>/g, `</${componentTagName}>`);
1206
+ const escapedTemplate = escapeTemplateLiteral(template);
1207
+ const fullTemplate = `<div data-variant="${escapeTemplateLiteral(escapeHtml(variant.name))}">${escapedTemplate}</div>`;
1208
+ const componentNames = /* @__PURE__ */ new Map();
1209
+ if (componentTagName) componentNames.set(componentTagName, componentBindingName);
1210
+ if (scriptSetup) {
1211
+ for (const name of scriptSetup.returnNames) if (/^[A-Z]/.test(name)) componentNames.set(name, name);
1212
+ }
1213
+ const components = componentNames.size > 0 ? ` components: { ${[...componentNames].map(([name, value]) => `${JSON.stringify(name)}: ${value}`).join(", ")} },\n` : "";
1214
+ if (scriptSetup && hasSetup && isolatedSetup) code += `
1215
+ export const ${variantComponentName} = defineComponent({
1216
+ name: '${variantComponentName}',
1217
+ ${components} setup() {
1218
+ ${scriptSetup.setupBody.map((l) => ` ${l}`).join("\n")}
1219
+ return ${setupReturn};
1220
+ },
1221
+ template: \`${fullTemplate}\`,
1222
+ });
1223
+ `;
1224
+ else if (scriptSetup && hasSetup) code += `
1225
+ export const ${variantComponentName} = defineComponent({
1226
+ name: '${variantComponentName}',
1227
+ ${components} setup() {
1228
+ return __museaSharedSetup;
1229
+ },
1230
+ template: \`${fullTemplate}\`,
1231
+ });
1232
+ `;
1233
+ else if (componentTagName) code += `
1234
+ export const ${variantComponentName} = {
1235
+ name: '${variantComponentName}',
1236
+ ${components} template: \`${fullTemplate}\`,
1237
+ };
1238
+ `;
1239
+ else code += `
1240
+ export const ${variantComponentName} = {
1241
+ name: '${variantComponentName}',
1242
+ template: \`${fullTemplate}\`,
1243
+ };
1244
+ `;
1245
+ }
1246
+ const defaultVariant = art.variants.find((v) => v.isDefault) || art.variants[0];
1247
+ if (defaultVariant) code += `
1248
+ export default ${toPascalCase(defaultVariant.name)};
1218
1249
  `;
1250
+ return code;
1219
1251
  }
1220
1252
  //#endregion
1221
1253
  //#region src/server-middleware.ts
@@ -2109,7 +2141,9 @@ async function processStyleDictionary(config) {
2109
2141
  //#endregion
2110
2142
  //#region src/api-tokens.ts
2111
2143
  function resolveTokensPath(ctx) {
2112
- return resolveInside(ctx.config.root, ctx.tokensPath, "tokensPath");
2144
+ const allowedRoots = [ctx.config.root, ...ctx.scanRoots];
2145
+ if (ctx.projectRoot) allowedRoots.push(ctx.projectRoot);
2146
+ return resolveInsideAny(allowedRoots, ctx.tokensPath, "tokensPath");
2113
2147
  }
2114
2148
  function sendTokenMutationError(e, sendError) {
2115
2149
  if (e instanceof HttpError) {
@@ -2894,6 +2928,71 @@ function watchMuseaArtFiles(watcher, files) {
2894
2928
  watcher.add(targets);
2895
2929
  }
2896
2930
  //#endregion
2931
+ //#region src/plugin/vue-alias.ts
2932
+ const require = createRequire(import.meta.url);
2933
+ function createVueRuntimeCompilerAlias() {
2934
+ return {
2935
+ find: /^vue$/,
2936
+ replacement: resolveVueRuntimeCompiler()
2937
+ };
2938
+ }
2939
+ function resolveVueRuntimeCompiler() {
2940
+ try {
2941
+ return require.resolve("vue/dist/vue.esm-bundler.js");
2942
+ } catch {
2943
+ return "vue/dist/vue.esm-bundler.js";
2944
+ }
2945
+ }
2946
+ //#endregion
2947
+ //#region src/plugin/storybook-inputs.ts
2948
+ function isStorybookTsxInput(filePath) {
2949
+ return /\.(?:stories|story)\.tsx$/i.test(filePath);
2950
+ }
2951
+ async function scanStorybookTsxInputs(root, include, exclude) {
2952
+ const files = /* @__PURE__ */ new Set();
2953
+ const visitedDirs = /* @__PURE__ */ new Set();
2954
+ async function scan(dir) {
2955
+ const resolvedDir = path.resolve(dir);
2956
+ if (visitedDirs.has(resolvedDir)) return;
2957
+ visitedDirs.add(resolvedDir);
2958
+ let entries;
2959
+ try {
2960
+ entries = await fs.promises.readdir(resolvedDir, { withFileTypes: true });
2961
+ } catch {
2962
+ return;
2963
+ }
2964
+ for (const entry of entries) {
2965
+ const fullPath = path.join(resolvedDir, entry.name);
2966
+ if (isExcluded(fullPath, entry.name, exclude, root)) continue;
2967
+ if (entry.isDirectory()) await scan(fullPath);
2968
+ else if (entry.isFile() && isStorybookTsxInput(entry.name) && shouldProcess(fullPath, include, exclude, root)) files.add(fullPath);
2969
+ }
2970
+ }
2971
+ for (const scanRoot of resolveScanRoots(root, include)) await scan(scanRoot);
2972
+ return [...files].sort();
2973
+ }
2974
+ async function assertNoUnsupportedStorybookTsxInputs(root, include, exclude) {
2975
+ const files = await scanStorybookTsxInputs(root, include, exclude);
2976
+ if (files.length > 0) throw new Error(formatUnsupportedStorybookTsxInputError(root, files));
2977
+ }
2978
+ function formatUnsupportedStorybookTsxInputError(root, files) {
2979
+ return [
2980
+ "[musea] Storybook TSX files matched by include are not supported as Musea art inputs yet.",
2981
+ "Remove *.stories.tsx from musea.include or migrate those stories to .art.vue files.",
2982
+ "Matched files:",
2983
+ `${files.slice(0, 5).map((file) => ` - ${relativePath(root, file)}`).join("\n")}${files.length > 5 ? `\n ... and ${files.length - 5} more` : ""}`
2984
+ ].join("\n");
2985
+ }
2986
+ function isExcluded(filePath, name, exclude, root) {
2987
+ return exclude.some((pattern) => matchesPathPattern(filePath, pattern, root) || matchGlob(name, pattern));
2988
+ }
2989
+ function matchesPathPattern(filePath, pattern, root) {
2990
+ return matchGlob(path.isAbsolute(pattern) ? path.resolve(filePath) : path.relative(root, filePath), pattern);
2991
+ }
2992
+ function relativePath(root, filePath) {
2993
+ return path.relative(root, filePath).split(path.sep).join("/");
2994
+ }
2995
+ //#endregion
2897
2996
  //#region src/static-data.ts
2898
2997
  function staticPreviewId(artPath, variantName) {
2899
2998
  return crypto.createHash("sha256").update(artPath).update("\0").update(variantName).digest("hex").slice(0, 20);
@@ -2952,6 +3051,7 @@ function createApiContext(ctx) {
2952
3051
  artFiles: ctx.artFiles,
2953
3052
  scanRoots: ctx.scanRoots,
2954
3053
  tokensPath: ctx.tokensPath,
3054
+ projectRoot: ctx.projectRoot,
2955
3055
  basePath: ctx.basePath,
2956
3056
  resolvedPreviewCss: ctx.resolvedPreviewCss,
2957
3057
  resolvedPreviewSetup: ctx.resolvedPreviewSetup,
@@ -3032,6 +3132,9 @@ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
3032
3132
  function isMuseaStaticBuild() {
3033
3133
  return process.env[MUSEA_STATIC_BUILD_ENV] === "1";
3034
3134
  }
3135
+ function shouldEnableMuseaStaticBuild(command) {
3136
+ return isMuseaStaticBuild() || command === "build";
3137
+ }
3035
3138
  function museaStaticBuildInput(input) {
3036
3139
  const entries = {};
3037
3140
  if (typeof input === "string") entries[STATIC_USER_INPUT_NAME] = input;
@@ -3047,6 +3150,9 @@ function applyMuseaStaticBuildInput(options) {
3047
3150
  options.input = museaStaticBuildInput(options.input);
3048
3151
  return null;
3049
3152
  }
3153
+ function museaStaticBuildConfig(input) {
3154
+ return { build: { rollupOptions: { input: museaStaticBuildInput(input) } } };
3155
+ }
3050
3156
  function resolveStaticRuntimeId(id) {
3051
3157
  return id === "virtual:musea-static-runtime" ? RESOLVED_STATIC_RUNTIME : null;
3052
3158
  }
@@ -3233,15 +3339,27 @@ function relativeUrl(fromDir, toFile) {
3233
3339
  return relative.startsWith(".") ? relative : `./${relative}`;
3234
3340
  }
3235
3341
  //#endregion
3236
- //#region src/plugin/index.ts
3237
- const require = createRequire(import.meta.url);
3238
- function resolveVueRuntimeCompiler() {
3342
+ //#region src/plugin/config.ts
3343
+ async function resolveMuseaSharedConfig(resolvedConfig) {
3344
+ const sharedConfig = vizeConfigStore.get(resolvedConfig.root);
3345
+ if (sharedConfig) return sharedConfig;
3346
+ const configFile = process.env[VIZE_CONFIG_FILE_ENV];
3347
+ if (!configFile) return null;
3239
3348
  try {
3240
- return require.resolve("vue/dist/vue.esm-bundler.js");
3241
- } catch {
3242
- return "vue/dist/vue.esm-bundler.js";
3349
+ return await loadConfig(resolvedConfig.root, {
3350
+ configFile,
3351
+ env: {
3352
+ mode: resolvedConfig.mode,
3353
+ command: resolvedConfig.command === "build" ? "build" : "serve",
3354
+ isSsrBuild: !!resolvedConfig.build?.ssr
3355
+ }
3356
+ });
3357
+ } catch (error) {
3358
+ throw new Error(`[musea] Failed to load Vize config from ${configFile}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
3243
3359
  }
3244
3360
  }
3361
+ //#endregion
3362
+ //#region src/plugin/art-processing.ts
3245
3363
  function extractArtTagAttributes(source) {
3246
3364
  const artTagMatch = source.match(/<art\b([\s\S]*?)>/i);
3247
3365
  if (!artTagMatch) return {};
@@ -3277,6 +3395,62 @@ function extractStyleBlocks(source) {
3277
3395
  }
3278
3396
  return styles;
3279
3397
  }
3398
+ function formatArtProcessingError(root, filePath, error) {
3399
+ const detail = error instanceof Error ? error.message || error.name : error == null ? "unknown error" : stringifyUnknown(error);
3400
+ return `[musea] Failed to process ${path.relative(root, filePath) || filePath}: ${detail}`;
3401
+ }
3402
+ function stringifyUnknown(value) {
3403
+ if (typeof value === "string") return value;
3404
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return value.toString();
3405
+ try {
3406
+ return JSON.stringify(value) ?? "unknown error";
3407
+ } catch {
3408
+ return "unknown error";
3409
+ }
3410
+ }
3411
+ async function processMuseaArtFile(filePath, ctx) {
3412
+ try {
3413
+ const source = await fs.promises.readFile(filePath, "utf-8");
3414
+ const parsed = loadNative().parseArt(source, { filename: filePath });
3415
+ const customMetadata = extractCustomArtMetadata(source);
3416
+ if (!parsed.variants || parsed.variants.length === 0) return null;
3417
+ const isInline = !filePath.endsWith(".art.vue");
3418
+ return {
3419
+ path: filePath,
3420
+ metadata: {
3421
+ title: parsed.metadata.title || (isInline ? path.basename(filePath, ".vue") : ""),
3422
+ description: parsed.metadata.description,
3423
+ component: isInline ? void 0 : parsed.metadata.component,
3424
+ category: parsed.metadata.category,
3425
+ tags: parsed.metadata.tags,
3426
+ status: parsed.metadata.status,
3427
+ order: parsed.metadata.order,
3428
+ actionEvents: customMetadata.actionEvents ?? parsed.metadata.actionEvents
3429
+ },
3430
+ variants: parsed.variants.map((v) => ({
3431
+ name: v.name,
3432
+ template: v.template,
3433
+ isDefault: v.isDefault,
3434
+ skipVrt: v.skipVrt
3435
+ })),
3436
+ hasScriptSetup: isInline ? false : parsed.hasScriptSetup,
3437
+ scriptSetupContent: !isInline && parsed.hasScriptSetup ? extractScriptSetupContent(source) : void 0,
3438
+ scriptSetupIsolated: !isInline && parsed.hasScriptSetup ? extractScriptSetupIsolated(source) : true,
3439
+ hasScript: parsed.hasScript,
3440
+ styleCount: parsed.styleCount,
3441
+ styleBlocks: isInline ? [] : extractStyleBlocks(source),
3442
+ isInline,
3443
+ componentPath: isInline ? filePath : void 0
3444
+ };
3445
+ } catch (error) {
3446
+ const message = formatArtProcessingError(ctx.root, filePath, error);
3447
+ if (ctx.command === "build") throw new Error(message);
3448
+ (ctx.onError ?? console.error)(message);
3449
+ return null;
3450
+ }
3451
+ }
3452
+ //#endregion
3453
+ //#region src/plugin/index.ts
3280
3454
  function musea(options = {}) {
3281
3455
  let include = options.include ?? ["**/*.art.vue"];
3282
3456
  let exclude = options.exclude ?? ["node_modules/**", "dist/**"];
@@ -3285,6 +3459,7 @@ function musea(options = {}) {
3285
3459
  const storybookOutDir = options.storybookOutDir ?? ".storybook/stories";
3286
3460
  let inlineArt = options.inlineArt ?? false;
3287
3461
  const tokensPath = options.tokensPath;
3462
+ const projectRootOption = options.projectRoot;
3288
3463
  const themeConfig = buildThemeConfig(options.theme);
3289
3464
  const previewCss = options.previewCss ?? [];
3290
3465
  const previewSetup = options.previewSetup;
@@ -3295,6 +3470,7 @@ function musea(options = {}) {
3295
3470
  let resolvedPreviewCss = [];
3296
3471
  let resolvedPreviewSetup = null;
3297
3472
  let scanRoots = [];
3473
+ let staticBuildEnabled = isMuseaStaticBuild();
3298
3474
  const virtualState = {
3299
3475
  basePath,
3300
3476
  get inlineArt() {
@@ -3316,13 +3492,18 @@ function musea(options = {}) {
3316
3492
  apply(_config, env) {
3317
3493
  return shouldApplyMuseaPlugin(env);
3318
3494
  },
3319
- config() {
3320
- return { resolve: { alias: { vue: resolveVueRuntimeCompiler() } } };
3495
+ config(userConfig, env) {
3496
+ staticBuildEnabled = shouldEnableMuseaStaticBuild(env.command);
3497
+ const staticBuildConfig = staticBuildEnabled ? museaStaticBuildConfig(userConfig.build?.rollupOptions?.input) : {};
3498
+ return {
3499
+ resolve: { alias: [createVueRuntimeCompilerAlias()] },
3500
+ ...staticBuildConfig
3501
+ };
3321
3502
  },
3322
3503
  options: applyMuseaStaticBuildInput,
3323
- configResolved(resolvedConfig) {
3504
+ async configResolved(resolvedConfig) {
3324
3505
  config = resolvedConfig;
3325
- const vizeConfig = vizeConfigStore.get(resolvedConfig.root);
3506
+ const vizeConfig = await resolveMuseaSharedConfig(resolvedConfig);
3326
3507
  if (vizeConfig?.musea) {
3327
3508
  const mc = vizeConfig.musea;
3328
3509
  if (!options.include && mc.include) include = mc.include;
@@ -3354,6 +3535,7 @@ function musea(options = {}) {
3354
3535
  artFiles,
3355
3536
  scanRoots,
3356
3537
  tokensPath,
3538
+ projectRoot: resolveProjectRoot(projectRootOption, config.root),
3357
3539
  basePath,
3358
3540
  resolvedPreviewCss,
3359
3541
  resolvedPreviewSetup,
@@ -3401,7 +3583,12 @@ function musea(options = {}) {
3401
3583
  if (address && typeof address === "object") {
3402
3584
  const protocol = devServer.config.server.https ? "https" : "http";
3403
3585
  const rawHost = address.address;
3404
- const url = `${protocol}://${rawHost === "::" || rawHost === "::1" || rawHost === "0.0.0.0" || rawHost === "127.0.0.1" ? "localhost" : rawHost}:${address.port}${basePath}`;
3586
+ const url = `${protocol}://${[
3587
+ "::",
3588
+ "::1",
3589
+ "0.0.0.0",
3590
+ "127.0.0.1"
3591
+ ].includes(rawHost) ? "localhost" : rawHost}:${address.port}${basePath}`;
3405
3592
  console.log();
3406
3593
  console.log(` \x1b[36m➜\x1b[0m \x1b[1mMusea Gallery:\x1b[0m \x1b[36m${url}\x1b[0m`);
3407
3594
  }
@@ -3410,6 +3597,7 @@ function musea(options = {}) {
3410
3597
  },
3411
3598
  async buildStart() {
3412
3599
  console.log(`[musea] config.root: ${config.root}, include: ${JSON.stringify(include)}`);
3600
+ if (storybookCompat && staticBuildEnabled) await assertNoUnsupportedStorybookTsxInputs(config.root, include, exclude);
3413
3601
  const files = await scanArtFiles(config.root, include, exclude, inlineArt);
3414
3602
  console.log(`[musea] Found ${files.length} art files`);
3415
3603
  if (server) watchMuseaArtFiles(server.watcher, files);
@@ -3423,12 +3611,13 @@ function musea(options = {}) {
3423
3611
  return loadStaticRuntimeModule(id, artFiles) ?? virtualLoad(id);
3424
3612
  },
3425
3613
  async generateBundle(_options, bundle) {
3426
- if (!isMuseaStaticBuild()) return;
3614
+ if (!staticBuildEnabled) return;
3427
3615
  await emitStaticGallery((asset) => void this.emitFile(asset), bundle, {
3428
3616
  config,
3429
3617
  artFiles,
3430
3618
  scanRoots,
3431
3619
  tokensPath,
3620
+ projectRoot: resolveProjectRoot(projectRootOption, config.root),
3432
3621
  basePath,
3433
3622
  resolvedPreviewCss,
3434
3623
  resolvedPreviewSetup,
@@ -3450,46 +3639,19 @@ function musea(options = {}) {
3450
3639
  handleHotUpdate: createHandleHotUpdate(virtualState)
3451
3640
  };
3452
3641
  async function processArtFile(filePath) {
3453
- try {
3454
- const source = await fs.promises.readFile(filePath, "utf-8");
3455
- const parsed = loadNative().parseArt(source, { filename: filePath });
3456
- const customMetadata = extractCustomArtMetadata(source);
3457
- if (!parsed.variants || parsed.variants.length === 0) return;
3458
- const isInline = !filePath.endsWith(".art.vue");
3459
- const info = {
3460
- path: filePath,
3461
- metadata: {
3462
- title: parsed.metadata.title || (isInline ? path.basename(filePath, ".vue") : ""),
3463
- description: parsed.metadata.description,
3464
- component: isInline ? void 0 : parsed.metadata.component,
3465
- category: parsed.metadata.category,
3466
- tags: parsed.metadata.tags,
3467
- status: parsed.metadata.status,
3468
- order: parsed.metadata.order,
3469
- actionEvents: customMetadata.actionEvents ?? parsed.metadata.actionEvents
3470
- },
3471
- variants: parsed.variants.map((v) => ({
3472
- name: v.name,
3473
- template: v.template,
3474
- isDefault: v.isDefault,
3475
- skipVrt: v.skipVrt
3476
- })),
3477
- hasScriptSetup: isInline ? false : parsed.hasScriptSetup,
3478
- scriptSetupContent: !isInline && parsed.hasScriptSetup ? extractScriptSetupContent(source) : void 0,
3479
- scriptSetupIsolated: !isInline && parsed.hasScriptSetup ? extractScriptSetupIsolated(source) : true,
3480
- hasScript: parsed.hasScript,
3481
- styleCount: parsed.styleCount,
3482
- styleBlocks: isInline ? [] : extractStyleBlocks(source),
3483
- isInline,
3484
- componentPath: isInline ? filePath : void 0
3485
- };
3486
- artFiles.set(filePath, info);
3487
- } catch (e) {
3488
- console.error(`[musea] Failed to process ${filePath}:`, e);
3489
- }
3642
+ const info = await processMuseaArtFile(filePath, {
3643
+ root: config.root,
3644
+ command: config.command
3645
+ });
3646
+ if (info) artFiles.set(filePath, info);
3490
3647
  }
3491
3648
  return [mainPlugin];
3492
3649
  }
3650
+ function resolveProjectRoot(projectRoot, viteRoot) {
3651
+ if (!projectRoot) return void 0;
3652
+ const absolute = path.isAbsolute(projectRoot) ? projectRoot : path.resolve(viteRoot, projectRoot);
3653
+ return absolute === path.resolve(viteRoot) ? void 0 : absolute;
3654
+ }
3493
3655
  //#endregion
3494
3656
  //#region src/index.ts
3495
3657
  var src_default = musea;