@vizejs/vite-plugin-musea 0.212.0 → 0.214.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.
Files changed (32) hide show
  1. package/README.md +45 -0
  2. package/client.d.ts +19 -0
  3. package/dist/gallery/assets/{MonacoEditor-B2IJhz0Y.js → MonacoEditor-CFvlW1hH.js} +2 -2
  4. package/dist/gallery/assets/{cssMode-Wf2sPjo8.js → cssMode-CakihE-X.js} +1 -1
  5. package/dist/gallery/assets/{editor.api2-BqfVxaLn.js → editor.api2-Cy8xN_Vy.js} +1 -1
  6. package/dist/gallery/assets/{editor.main-D0q_EY2R.js → editor.main-BLvJY5pY.js} +2 -2
  7. package/dist/gallery/assets/{freemarker2-B9IYORwa.js → freemarker2-8JKQlxkN.js} +1 -1
  8. package/dist/gallery/assets/{handlebars-CtmQAyr9.js → handlebars-DswXPT5M.js} +1 -1
  9. package/dist/gallery/assets/{html-DjlePv7f.js → html-CHz-ZCgY.js} +1 -1
  10. package/dist/gallery/assets/{htmlMode-CKqPNIWJ.js → htmlMode-DSb6gosw.js} +1 -1
  11. package/dist/gallery/assets/{index-DGDOzWKX.js → index-CfyRpnTg.js} +21 -21
  12. package/dist/gallery/assets/index-DrcH3Amz.css +1 -0
  13. package/dist/gallery/assets/{javascript-t8XpHDSo.js → javascript-93Vdo_Zi.js} +1 -1
  14. package/dist/gallery/assets/{jsonMode-C9VGEvKg.js → jsonMode-C41SvLGl.js} +1 -1
  15. package/dist/gallery/assets/{liquid-B1SQ490s.js → liquid-Ba4G1nCV.js} +1 -1
  16. package/dist/gallery/assets/{lspLanguageFeatures-BtMEaqrF.js → lspLanguageFeatures-DQpvFrJR.js} +1 -1
  17. package/dist/gallery/assets/{mdx-BcRzkdPF.js → mdx-kar2jls9.js} +1 -1
  18. package/dist/gallery/assets/{monaco.contribution-JsWDXWUd.js → monaco.contribution-D9SaY72O.js} +2 -2
  19. package/dist/gallery/assets/{python-DrL7J2F1.js → python-Cl3SeCHc.js} +1 -1
  20. package/dist/gallery/assets/{razor-DnIM76SH.js → razor-BddYKQ4w.js} +1 -1
  21. package/dist/gallery/assets/{tsMode-D0BSp2Hl.js → tsMode-CHpNkjIG.js} +1 -1
  22. package/dist/gallery/assets/{typescript-CIlbt26G.js → typescript-CiUtldbq.js} +1 -1
  23. package/dist/gallery/assets/{workers-BOfCFwI1.js → workers-BsJar7qW.js} +1 -1
  24. package/dist/gallery/assets/{xml-ChCK-qRB.js → xml-B2FG-0eD.js} +1 -1
  25. package/dist/gallery/assets/{yaml-j7nYrnzD.js → yaml-Dt-gwFST.js} +1 -1
  26. package/dist/gallery/index.html +2 -2
  27. package/dist/index.d.mts +5 -5
  28. package/dist/index.d.mts.map +1 -1
  29. package/dist/index.mjs +405 -103
  30. package/dist/index.mjs.map +1 -1
  31. package/package.json +9 -5
  32. package/dist/gallery/assets/index-BiMZ3Oo8.css +0 -1
package/dist/index.mjs CHANGED
@@ -876,63 +876,70 @@ function generatePreviewHtml(art, variant, _basePath, viteBase) {
876
876
  <title>${escapeHtml(art.metadata.title)} - ${escapeHtml(variant.name)}</title>
877
877
  <script type="module" src="${base}/@vite/client"><\/script>
878
878
  <style>
879
- * { box-sizing: border-box; margin: 0; padding: 0; }
880
- :root {
881
- --musea-preview-padding: clamp(1rem, 2vw, 1.5rem);
882
- }
883
- html, body {
884
- width: 100%;
885
- height: 100%;
886
- }
887
- body {
888
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
889
- background: #ffffff;
890
- padding: var(--musea-preview-padding);
891
- overflow: auto;
892
- }
893
- .musea-variant {
894
- width: 100%;
895
- min-height: calc(100vh - (var(--musea-preview-padding) * 2));
896
- }
897
- .musea-error {
898
- color: #dc2626;
899
- background: #fef2f2;
900
- border: 1px solid #fecaca;
901
- border-radius: 8px;
902
- padding: 1rem;
903
- font-size: 0.875rem;
904
- max-width: 400px;
905
- }
906
- .musea-error-title {
907
- font-weight: 600;
908
- margin-bottom: 0.5rem;
909
- }
910
- .musea-error pre {
911
- font-family: monospace;
912
- font-size: 0.75rem;
913
- white-space: pre-wrap;
914
- word-break: break-all;
915
- margin-top: 0.5rem;
916
- padding: 0.5rem;
917
- background: #fff;
918
- border-radius: 4px;
919
- }
920
- .musea-loading {
921
- display: flex;
922
- align-items: center;
923
- gap: 0.75rem;
924
- color: #6b7280;
925
- font-size: 0.875rem;
926
- }
927
- .musea-spinner {
928
- width: 20px;
929
- height: 20px;
930
- border: 2px solid #e5e7eb;
931
- border-top-color: #3b82f6;
932
- border-radius: 50%;
933
- animation: spin 0.8s linear infinite;
879
+ @layer musea-preview {
880
+ *,
881
+ *::before,
882
+ *::after {
883
+ box-sizing: border-box;
884
+ }
885
+ :root {
886
+ --musea-preview-padding: clamp(1rem, 2vw, 1.5rem);
887
+ }
888
+ html, body {
889
+ width: 100%;
890
+ height: 100%;
891
+ margin: 0;
892
+ }
893
+ body {
894
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
895
+ background: #ffffff;
896
+ padding: var(--musea-preview-padding);
897
+ overflow: auto;
898
+ }
899
+ .musea-variant {
900
+ width: 100%;
901
+ min-height: calc(100vh - (var(--musea-preview-padding) * 2));
902
+ }
903
+ .musea-error {
904
+ color: #dc2626;
905
+ background: #fef2f2;
906
+ border: 1px solid #fecaca;
907
+ border-radius: 8px;
908
+ padding: 1rem;
909
+ font-size: 0.875rem;
910
+ max-width: 400px;
911
+ }
912
+ .musea-error-title {
913
+ font-weight: 600;
914
+ margin-bottom: 0.5rem;
915
+ }
916
+ .musea-error pre {
917
+ font-family: monospace;
918
+ font-size: 0.75rem;
919
+ white-space: pre-wrap;
920
+ word-break: break-all;
921
+ margin-top: 0.5rem;
922
+ padding: 0.5rem;
923
+ background: #fff;
924
+ border-radius: 4px;
925
+ }
926
+ .musea-loading {
927
+ display: flex;
928
+ align-items: center;
929
+ gap: 0.75rem;
930
+ color: #6b7280;
931
+ font-size: 0.875rem;
932
+ }
933
+ .musea-spinner {
934
+ width: 20px;
935
+ height: 20px;
936
+ border: 2px solid #e5e7eb;
937
+ border-top-color: #3b82f6;
938
+ border-radius: 50%;
939
+ animation: musea-preview-spin 0.8s linear infinite;
940
+ }
934
941
  }
935
- @keyframes spin { to { transform: rotate(360deg); } }
942
+ @keyframes musea-preview-spin { to { transform: rotate(360deg); } }
936
943
 
937
944
  /* Musea Addons: Checkerboard background for transparent mode */
938
945
  .musea-bg-checkerboard {
@@ -1483,6 +1490,220 @@ function nullRecord(record) {
1483
1490
  return Object.assign(Object.create(null), record);
1484
1491
  }
1485
1492
  //#endregion
1493
+ //#region src/tokens/tailwind.ts
1494
+ const TAILWIND_TOKEN_EXTENSIONS = new Set([
1495
+ ".css",
1496
+ ".pcss",
1497
+ ".postcss"
1498
+ ]);
1499
+ const VARIABLE_DECLARATION_RE = /(--[A-Za-z0-9_-]+)\s*:\s*([^;{}]+);/g;
1500
+ const CSS_COMMENT_RE = /\/\*[\s\S]*?\*\//g;
1501
+ const NAMESPACE_MAPPINGS = [
1502
+ {
1503
+ prefix: "inset-shadow",
1504
+ category: "shadow",
1505
+ pathPrefix: ["inset"],
1506
+ type: "shadow"
1507
+ },
1508
+ {
1509
+ prefix: "drop-shadow",
1510
+ category: "shadow",
1511
+ pathPrefix: ["drop"],
1512
+ type: "shadow"
1513
+ },
1514
+ {
1515
+ prefix: "font-weight",
1516
+ category: "typography",
1517
+ pathPrefix: ["fontWeight"],
1518
+ type: "fontWeight"
1519
+ },
1520
+ {
1521
+ prefix: "color",
1522
+ category: "color",
1523
+ type: "color"
1524
+ },
1525
+ {
1526
+ prefix: "spacing",
1527
+ category: "spacing",
1528
+ type: "dimension"
1529
+ },
1530
+ {
1531
+ prefix: "font",
1532
+ category: "typography",
1533
+ pathPrefix: ["font"],
1534
+ type: "fontFamily"
1535
+ },
1536
+ {
1537
+ prefix: "text",
1538
+ category: "typography",
1539
+ pathPrefix: ["fontSize"],
1540
+ type: "dimension"
1541
+ },
1542
+ {
1543
+ prefix: "leading",
1544
+ category: "typography",
1545
+ pathPrefix: ["lineHeight"],
1546
+ type: "number"
1547
+ },
1548
+ {
1549
+ prefix: "tracking",
1550
+ category: "typography",
1551
+ pathPrefix: ["letterSpacing"],
1552
+ type: "dimension"
1553
+ },
1554
+ {
1555
+ prefix: "radius",
1556
+ category: "radius",
1557
+ type: "dimension"
1558
+ },
1559
+ {
1560
+ prefix: "shadow",
1561
+ category: "shadow",
1562
+ type: "shadow"
1563
+ },
1564
+ {
1565
+ prefix: "breakpoint",
1566
+ category: "breakpoint",
1567
+ type: "dimension"
1568
+ },
1569
+ {
1570
+ prefix: "container",
1571
+ category: "container",
1572
+ type: "dimension"
1573
+ },
1574
+ {
1575
+ prefix: "ease",
1576
+ category: "easing",
1577
+ type: "cubicBezier"
1578
+ },
1579
+ {
1580
+ prefix: "animate",
1581
+ category: "animation",
1582
+ type: "transition"
1583
+ },
1584
+ {
1585
+ prefix: "blur",
1586
+ category: "blur",
1587
+ type: "dimension"
1588
+ },
1589
+ {
1590
+ prefix: "perspective",
1591
+ category: "perspective",
1592
+ type: "dimension"
1593
+ },
1594
+ {
1595
+ prefix: "aspect",
1596
+ category: "aspectRatio",
1597
+ type: "number"
1598
+ }
1599
+ ];
1600
+ async function isTailwindTokenPath(tokensPath) {
1601
+ const stat = await fs.promises.stat(tokensPath).catch(() => null);
1602
+ if (!stat) return false;
1603
+ if (stat.isFile()) return TAILWIND_TOKEN_EXTENSIONS.has(path.extname(tokensPath));
1604
+ if (!stat.isDirectory()) return false;
1605
+ return (await fs.promises.readdir(tokensPath, { withFileTypes: true })).some((entry) => entry.isFile() && TAILWIND_TOKEN_EXTENSIONS.has(path.extname(entry.name)));
1606
+ }
1607
+ async function parseTailwindTokens(tokensPath) {
1608
+ const files = await collectTailwindTokenFiles(tokensPath);
1609
+ const candidates = [];
1610
+ for (const file of files) {
1611
+ const css = await fs.promises.readFile(file, "utf-8");
1612
+ candidates.push(...extractTailwindTokenCandidates(css));
1613
+ }
1614
+ return normalizeCategories(buildCategories(candidates));
1615
+ }
1616
+ async function collectTailwindTokenFiles(tokensPath) {
1617
+ if ((await fs.promises.stat(tokensPath)).isFile()) return [tokensPath];
1618
+ return (await fs.promises.readdir(tokensPath, { withFileTypes: true })).filter((entry) => entry.isFile() && TAILWIND_TOKEN_EXTENSIONS.has(path.extname(entry.name))).map((entry) => path.join(tokensPath, entry.name)).sort();
1619
+ }
1620
+ function extractTailwindTokenCandidates(css) {
1621
+ const source = css.replace(CSS_COMMENT_RE, "");
1622
+ const candidates = [];
1623
+ let match;
1624
+ VARIABLE_DECLARATION_RE.lastIndex = 0;
1625
+ while ((match = VARIABLE_DECLARATION_RE.exec(source)) !== null) {
1626
+ const variable = match[1];
1627
+ const value = match[2].trim();
1628
+ const mapped = mapTailwindVariable(variable);
1629
+ if (!mapped || !value) continue;
1630
+ candidates.push({
1631
+ variable,
1632
+ value,
1633
+ ...mapped
1634
+ });
1635
+ }
1636
+ return candidates;
1637
+ }
1638
+ function mapTailwindVariable(variable) {
1639
+ const name = variable.replace(/^--/, "");
1640
+ const mapping = NAMESPACE_MAPPINGS.find((candidate) => name === candidate.prefix || name.startsWith(`${candidate.prefix}-`));
1641
+ if (!mapping) return null;
1642
+ const rest = name === mapping.prefix ? "DEFAULT" : name.slice(mapping.prefix.length + 1);
1643
+ const pathParts = [...mapping.pathPrefix ?? [], ...tailwindNameToPath(rest)];
1644
+ if (pathParts.length === 0) return null;
1645
+ return {
1646
+ category: mapping.category,
1647
+ path: pathParts,
1648
+ type: mapping.type
1649
+ };
1650
+ }
1651
+ function tailwindNameToPath(name) {
1652
+ if (!name || name === "DEFAULT") return ["DEFAULT"];
1653
+ const parts = name.split("-").filter(Boolean);
1654
+ if (parts.length <= 1) return parts;
1655
+ const last = parts[parts.length - 1];
1656
+ if (/^\d+$/.test(last)) return [...parts.slice(0, -1), last];
1657
+ return [parts.join("-")];
1658
+ }
1659
+ function buildCategories(candidates) {
1660
+ const variableToPath = /* @__PURE__ */ new Map();
1661
+ for (const candidate of candidates) variableToPath.set(candidate.variable, [candidate.category, ...candidate.path].join("."));
1662
+ const categoryMap = /* @__PURE__ */ new Map();
1663
+ for (const candidate of candidates) {
1664
+ const category = categoryMap.get(candidate.category) ?? {
1665
+ name: candidate.category,
1666
+ tokens: nullRecord({})
1667
+ };
1668
+ categoryMap.set(candidate.category, category);
1669
+ const valueReference = parseTailwindVariableReference(candidate.value);
1670
+ const referencedPath = valueReference ? variableToPath.get(valueReference) : void 0;
1671
+ const token = {
1672
+ value: referencedPath ? `{${referencedPath}}` : candidate.value,
1673
+ type: candidate.type,
1674
+ description: `Tailwind theme variable ${candidate.variable}`,
1675
+ attributes: { tailwindVariable: candidate.variable },
1676
+ $tier: referencedPath ? "semantic" : "primitive",
1677
+ ...referencedPath ? { $reference: referencedPath } : {}
1678
+ };
1679
+ setCategoryToken(category, candidate.path, token);
1680
+ }
1681
+ return [...categoryMap.values()];
1682
+ }
1683
+ function parseTailwindVariableReference(value) {
1684
+ return value.match(/^var\(\s*(--[A-Za-z0-9_-]+)\s*(?:,[^)]+)?\)$/)?.[1];
1685
+ }
1686
+ function setCategoryToken(category, pathParts, token) {
1687
+ if (pathParts.length === 1) {
1688
+ category.tokens[pathParts[0]] = token;
1689
+ return;
1690
+ }
1691
+ let current = category;
1692
+ for (const part of pathParts.slice(0, -1)) {
1693
+ current.subcategories ??= [];
1694
+ let next = current.subcategories.find((subcategory) => subcategory.name === part);
1695
+ if (!next) {
1696
+ next = {
1697
+ name: part,
1698
+ tokens: nullRecord({})
1699
+ };
1700
+ current.subcategories.push(next);
1701
+ }
1702
+ current = next;
1703
+ }
1704
+ current.tokens[pathParts[pathParts.length - 1]] = token;
1705
+ }
1706
+ //#endregion
1486
1707
  //#region src/tokens/parser.ts
1487
1708
  /**
1488
1709
  * Token parsing utilities for Style Dictionary integration.
@@ -1490,9 +1711,10 @@ function nullRecord(record) {
1490
1711
  * Thin native binding for design token files (JSON) and directories.
1491
1712
  */
1492
1713
  /**
1493
- * Parse Style Dictionary tokens file or directory.
1714
+ * Parse Style Dictionary tokens file/directory or Tailwind CSS theme variables.
1494
1715
  */
1495
1716
  async function parseTokens(tokensPath) {
1717
+ if (await isTailwindTokenPath(tokensPath)) return parseTailwindTokens(tokensPath);
1496
1718
  return normalizeCategories(tokenNative().parseDesignTokensFromPath(tokensPath));
1497
1719
  }
1498
1720
  //#endregion
@@ -1532,6 +1754,13 @@ function scanTokenUsage(artFiles, tokenMap) {
1532
1754
  const existing = valueLookup.get(normalized);
1533
1755
  if (existing) existing.push(tokenPath);
1534
1756
  else valueLookup.set(normalized, [tokenPath]);
1757
+ const tailwindVariable = token.attributes?.tailwindVariable;
1758
+ if (typeof tailwindVariable === "string") {
1759
+ const normalizedVariable = normalizeTokenValue(`var(${tailwindVariable})`);
1760
+ const variableMatches = valueLookup.get(normalizedVariable);
1761
+ if (variableMatches) variableMatches.push(tokenPath);
1762
+ else valueLookup.set(normalizedVariable, [tokenPath]);
1763
+ }
1535
1764
  }
1536
1765
  const usageMap = {};
1537
1766
  for (const [artPath, artInfo] of artFiles) {
@@ -1600,6 +1829,7 @@ function scanTokenUsage(artFiles, tokenMap) {
1600
1829
  * Handles building flat token maps from categories, resolving reference chains,
1601
1830
  * reading/writing raw token files, and validating semantic references.
1602
1831
  */
1832
+ const JSON_TOKEN_EXTENSIONS = new Set([".json", ".tokens.json"]);
1603
1833
  const UNSAFE_TOKEN_PATH_SEGMENTS = new Set([
1604
1834
  "__proto__",
1605
1835
  "prototype",
@@ -1629,6 +1859,7 @@ function resolveReferences(categories, _tokenMap) {
1629
1859
  * Read raw JSON token file.
1630
1860
  */
1631
1861
  async function readRawTokenFile(tokensPath) {
1862
+ if (!isJsonTokenPath(tokensPath)) throw new Error("Token editing is only supported for JSON token files");
1632
1863
  const content = await fs.promises.readFile(tokensPath, "utf-8");
1633
1864
  return JSON.parse(content);
1634
1865
  }
@@ -1636,10 +1867,14 @@ async function readRawTokenFile(tokensPath) {
1636
1867
  * Write raw JSON token file atomically (write tmp, rename).
1637
1868
  */
1638
1869
  async function writeRawTokenFile(tokensPath, data) {
1870
+ if (!isJsonTokenPath(tokensPath)) throw new Error("Token editing is only supported for JSON token files");
1639
1871
  const tmpPath = tokensPath + ".tmp";
1640
1872
  await fs.promises.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
1641
1873
  await fs.promises.rename(tmpPath, tokensPath);
1642
1874
  }
1875
+ function isJsonTokenPath(tokensPath) {
1876
+ return JSON_TOKEN_EXTENSIONS.has(path.extname(tokensPath)) || tokensPath.endsWith(".tokens.json");
1877
+ }
1643
1878
  /**
1644
1879
  * Set a token at a dot-separated path in the raw JSON structure.
1645
1880
  */
@@ -1707,7 +1942,7 @@ function findDependentTokens(tokenMap, targetPath) {
1707
1942
  * Generates HTML, Markdown, and JSON documentation from parsed token categories,
1708
1943
  * and provides the main processStyleDictionary orchestrator function.
1709
1944
  */
1710
- const SAFE_CSS_COLOR_PATTERN = /^(?:#[0-9a-fA-F]{3,8}|(?:rgb|hsl)a?\(\s*[-+.\d,%\s]+\))$/;
1945
+ const SAFE_CSS_COLOR_PATTERN = /^(?:#[0-9a-fA-F]{3,8}|(?:rgb|hsl)a?\(\s*[-+.\d,%\s/]+\)|(?:oklch|oklab|lab|lch)\(\s*[-+.\d,%\s/]+\))$/;
1711
1946
  const DANGEROUS_PROTOCOL_PATTERN = /\b(javascript|vbscript):/gi;
1712
1947
  function escapeTokenText(value) {
1713
1948
  return escapeHtml(value).replace(DANGEROUS_PROTOCOL_PATTERN, "$1&#58;");
@@ -1879,7 +2114,7 @@ function sendTokenMutationError(e, sendError) {
1879
2114
  sendError(e.message, e.status);
1880
2115
  return;
1881
2116
  }
1882
- if (e instanceof Error && /token path/i.test(e.message)) {
2117
+ if (e instanceof Error && /(token path|token editing)/i.test(e.message)) {
1883
2118
  sendError(e.message, 400);
1884
2119
  return;
1885
2120
  }
@@ -2080,56 +2315,123 @@ async function handleArtPalette(ctx, match, sendJson, sendError) {
2080
2315
  json: "{}",
2081
2316
  typescript: ""
2082
2317
  };
2083
- if (palette.controls.length === 0) {
2084
- const resolvedComponentPath = resolveComponentSourcePath(art, artPath, allowedSourceRoots(ctx.config.root, ctx.scanRoots));
2085
- if (!resolvedComponentPath) {
2086
- sendJson(palette);
2087
- return;
2088
- }
2089
- try {
2090
- const componentSource = await fs.promises.readFile(resolvedComponentPath, "utf-8");
2091
- const analysis = binding.analyzeSfc ? binding.analyzeSfc(componentSource, { filename: resolvedComponentPath }) : analyzeSfcFallback(componentSource, { filename: resolvedComponentPath });
2092
- if (analysis.props.length > 0) {
2093
- palette.controls = analysis.props.map((prop) => {
2094
- let control = "text";
2095
- if (prop.type === "boolean") control = "boolean";
2096
- else if (prop.type === "number") control = "number";
2097
- else if (prop.type.includes("|") && !prop.type.includes("=>")) control = "select";
2098
- const options = [];
2099
- if (control === "select") {
2100
- const optionMatches = prop.type.match(/"([^"]+)"/g);
2101
- if (optionMatches) for (const opt of optionMatches) {
2102
- const val = opt.replace(/"/g, "");
2103
- options.push({
2104
- label: val,
2105
- value: val
2106
- });
2107
- }
2108
- }
2109
- return {
2110
- name: prop.name,
2111
- control,
2112
- default_value: prop.default_value !== void 0 ? prop.default_value === "true" ? true : prop.default_value === "false" ? false : typeof prop.default_value === "string" && prop.default_value.startsWith("\"") ? prop.default_value.replace(/^"|"$/g, "") : prop.default_value : void 0,
2113
- description: void 0,
2114
- required: prop.required,
2115
- options,
2116
- range: void 0,
2117
- group: void 0
2118
- };
2119
- });
2120
- palette.json = JSON.stringify({
2121
- title: palette.title,
2122
- controls: palette.controls
2123
- }, null, 2);
2124
- palette.typescript = `export interface ${palette.title}Props {\n${palette.controls.map((c) => ` ${c.name}${c.required ? "" : "?"}: ${c.control === "boolean" ? "boolean" : c.control === "number" ? "number" : c.control === "select" ? c.options.map((o) => `"${String(o.value)}"`).join(" | ") : "string"};`).join("\n")}\n}\n`;
2125
- }
2126
- } catch {}
2318
+ const resolvedComponentPath = resolveComponentSourcePath(art, artPath, allowedSourceRoots(ctx.config.root, ctx.scanRoots));
2319
+ if (!resolvedComponentPath) {
2320
+ sendJson(palette);
2321
+ return;
2127
2322
  }
2323
+ try {
2324
+ const componentSource = await fs.promises.readFile(resolvedComponentPath, "utf-8");
2325
+ const analysis = binding.analyzeSfc ? binding.analyzeSfc(componentSource, { filename: resolvedComponentPath }) : analyzeSfcFallback(componentSource, { filename: resolvedComponentPath });
2326
+ if (analysis.props.length > 0) mergeSfcPropsIntoPalette(palette, analysis.props);
2327
+ } catch {}
2128
2328
  sendJson(palette);
2129
2329
  } catch (e) {
2130
2330
  sendError(e instanceof Error ? e.message : String(e));
2131
2331
  }
2132
2332
  }
2333
+ function mergeSfcPropsIntoPalette(palette, props) {
2334
+ const controlsByName = /* @__PURE__ */ new Map();
2335
+ for (const control of palette.controls) controlsByName.set(normalizePropName(control.name), control);
2336
+ for (const prop of props) {
2337
+ const control = controlsByName.get(normalizePropName(prop.name));
2338
+ if (control) applyPropMetadata(control, prop);
2339
+ else palette.controls.push(controlFromProp(prop));
2340
+ }
2341
+ palette.json = JSON.stringify({
2342
+ title: palette.title,
2343
+ controls: palette.controls
2344
+ }, null, 2);
2345
+ palette.typescript = generateTypescript(palette);
2346
+ }
2347
+ function applyPropMetadata(control, prop) {
2348
+ const inferred = inferControl(prop.type);
2349
+ control.name = prop.name;
2350
+ control.required = prop.required;
2351
+ if (control.default_value === void 0 && prop.default_value !== void 0) control.default_value = parseDefault(prop.default_value);
2352
+ if (inferred.control !== "text" || control.control === "text") control.control = inferred.control;
2353
+ if (inferred.options.length > 0) control.options = inferred.options;
2354
+ }
2355
+ function controlFromProp(prop) {
2356
+ const inferred = inferControl(prop.type);
2357
+ return {
2358
+ name: prop.name,
2359
+ control: inferred.control,
2360
+ default_value: prop.default_value === void 0 ? void 0 : parseDefault(prop.default_value),
2361
+ description: void 0,
2362
+ required: prop.required,
2363
+ options: inferred.options,
2364
+ range: void 0,
2365
+ group: void 0
2366
+ };
2367
+ }
2368
+ function inferControl(type) {
2369
+ const normalized = type.trim();
2370
+ const options = literalOptions(normalized);
2371
+ if (options.length > 0) return {
2372
+ control: "select",
2373
+ options
2374
+ };
2375
+ if (normalized === "boolean") return {
2376
+ control: "boolean",
2377
+ options: []
2378
+ };
2379
+ if (normalized === "number") return {
2380
+ control: "number",
2381
+ options: []
2382
+ };
2383
+ if (normalized.includes("[]") || normalized.startsWith("Array<")) return {
2384
+ control: "array",
2385
+ options: []
2386
+ };
2387
+ if (normalized.startsWith("{") || normalized.startsWith("Record<")) return {
2388
+ control: "object",
2389
+ options: []
2390
+ };
2391
+ return {
2392
+ control: "text",
2393
+ options: []
2394
+ };
2395
+ }
2396
+ function literalOptions(type) {
2397
+ if (!type.includes("|") || type.includes("=>")) return [];
2398
+ return type.split("|").map((part) => part.trim()).map((part) => {
2399
+ const stringMatch = part.match(/^["']([^"']+)["']$/);
2400
+ if (stringMatch) return stringMatch[1];
2401
+ if (part === "true") return true;
2402
+ if (part === "false") return false;
2403
+ const numberValue = Number(part);
2404
+ return Number.isFinite(numberValue) ? numberValue : void 0;
2405
+ }).filter((value) => value !== void 0).map((value) => ({
2406
+ label: String(value),
2407
+ value
2408
+ }));
2409
+ }
2410
+ function parseDefault(value) {
2411
+ if (typeof value !== "string") return value;
2412
+ if (value === "true") return true;
2413
+ if (value === "false") return false;
2414
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
2415
+ return value.replace(/^["']|["']$/g, "");
2416
+ }
2417
+ function normalizePropName(name) {
2418
+ return name.replace(/[-_]/g, "").toLowerCase();
2419
+ }
2420
+ function generateTypescript(palette) {
2421
+ const fields = palette.controls.map((control) => ` ${control.name}${control.required ? "" : "?"}: ${controlTsType(control)};`).join("\n");
2422
+ return `export interface ${pascalCase(palette.title)}Props {\n${fields}\n}\n`;
2423
+ }
2424
+ function controlTsType(control) {
2425
+ if (control.control === "boolean") return "boolean";
2426
+ if (control.control === "number" || control.control === "range") return "number";
2427
+ if (control.control === "array") return "unknown[]";
2428
+ if (control.control === "object") return "Record<string, unknown>";
2429
+ if (control.control === "select" && control.options.length > 0) return control.options.map((option) => JSON.stringify(option.value)).join(" | ");
2430
+ return "string";
2431
+ }
2432
+ function pascalCase(value) {
2433
+ return value.split(/[^A-Za-z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2434
+ }
2133
2435
  //#endregion
2134
2436
  //#region src/api-routes/handlers.ts
2135
2437
  /**