@motion-proto/live-tokens 0.1.0 → 0.3.1

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 (225) hide show
  1. package/README.md +160 -21
  2. package/dist-plugin/index.cjs +823 -336
  3. package/dist-plugin/index.d.cts +9 -7
  4. package/dist-plugin/index.d.ts +9 -7
  5. package/dist-plugin/index.js +822 -335
  6. package/package.json +51 -23
  7. package/src/assets/newspaper.webp +0 -0
  8. package/src/assets/offering.webp +0 -0
  9. package/src/component-editor/BadgeEditor.svelte +170 -0
  10. package/src/component-editor/CalloutEditor.svelte +103 -0
  11. package/src/component-editor/CardEditor.svelte +184 -0
  12. package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
  13. package/src/component-editor/CornerBadgeEditor.svelte +207 -0
  14. package/src/component-editor/DialogEditor.svelte +172 -0
  15. package/src/component-editor/ImageEditor.svelte +72 -0
  16. package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
  17. package/src/component-editor/NotificationEditor.svelte +160 -0
  18. package/src/component-editor/ProgressBarEditor.svelte +124 -0
  19. package/src/component-editor/RadioButtonEditor.svelte +140 -0
  20. package/src/component-editor/SectionDividerEditor.svelte +263 -0
  21. package/src/component-editor/SegmentedControlEditor.svelte +154 -0
  22. package/src/component-editor/StandardButtonsEditor.svelte +178 -0
  23. package/src/component-editor/TabBarEditor.svelte +137 -0
  24. package/src/component-editor/TableEditor.svelte +128 -0
  25. package/src/component-editor/TooltipEditor.svelte +122 -0
  26. package/src/component-editor/editorTokens.test.ts +93 -0
  27. package/src/component-editor/groupKeySlots.test.ts +67 -0
  28. package/src/component-editor/groupKeySnapshot.test.ts +52 -0
  29. package/src/component-editor/index.ts +5 -0
  30. package/src/component-editor/registry.ts +246 -0
  31. package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
  32. package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
  33. package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
  34. package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
  35. package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
  36. package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
  37. package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
  38. package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
  39. package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
  40. package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
  41. package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
  42. package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
  43. package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
  44. package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
  45. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
  46. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
  47. package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
  48. package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
  49. package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
  50. package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
  51. package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
  52. package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
  53. package/src/component-editor/scaffolding/componentSources.ts +9 -0
  54. package/src/component-editor/scaffolding/defaultSections.ts +16 -0
  55. package/src/component-editor/scaffolding/editorContext.ts +44 -0
  56. package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
  57. package/src/component-editor/scaffolding/siblings.ts +33 -0
  58. package/src/component-editor/scaffolding/types.ts +39 -0
  59. package/src/components/Badge.svelte +231 -42
  60. package/src/components/Button.svelte +324 -124
  61. package/src/components/Callout.svelte +145 -0
  62. package/src/components/Card.svelte +123 -25
  63. package/src/components/CollapsibleSection.svelte +213 -35
  64. package/src/components/CornerBadge.svelte +224 -0
  65. package/src/components/Dialog.svelte +137 -114
  66. package/src/components/Image.svelte +43 -0
  67. package/src/components/InlineEditActions.svelte +74 -14
  68. package/src/components/Notification.svelte +184 -163
  69. package/src/components/ProgressBar.svelte +216 -22
  70. package/src/components/RadioButton.svelte +110 -40
  71. package/src/components/SectionDivider.svelte +428 -74
  72. package/src/components/SegmentedControl.svelte +203 -0
  73. package/src/components/TabBar.svelte +146 -21
  74. package/src/components/Table.svelte +102 -0
  75. package/src/components/Tooltip.svelte +45 -19
  76. package/src/components/types.ts +51 -0
  77. package/src/data/google-fonts.json +75 -0
  78. package/src/lib/ColumnsOverlay.svelte +20 -7
  79. package/src/lib/LiveEditorOverlay.svelte +265 -82
  80. package/src/lib/columnsOverlay.ts +21 -17
  81. package/src/lib/componentConfig.test.ts +204 -0
  82. package/src/lib/componentConfigKeys.ts +19 -0
  83. package/src/lib/componentConfigService.ts +88 -0
  84. package/src/lib/copyPopover.ts +30 -0
  85. package/src/lib/cssVarSync.ts +59 -7
  86. package/src/lib/editorConfigStore.ts +0 -10
  87. package/src/lib/editorCore.ts +402 -0
  88. package/src/lib/editorKeybindings.ts +52 -0
  89. package/src/lib/editorPersistence.ts +106 -0
  90. package/src/lib/editorRenderer.ts +74 -0
  91. package/src/lib/editorStore.test.ts +328 -0
  92. package/src/lib/editorStore.ts +412 -0
  93. package/src/lib/editorTypes.ts +100 -0
  94. package/src/lib/editorViewStore.ts +55 -0
  95. package/src/lib/files/versionedFileResource.ts +140 -0
  96. package/src/lib/fontLoader.ts +130 -0
  97. package/src/lib/fontMigration.ts +140 -0
  98. package/src/lib/fontParse.ts +168 -0
  99. package/src/lib/index.ts +48 -31
  100. package/src/lib/lazyConfig.test.ts +54 -0
  101. package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
  102. package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
  103. package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
  104. package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
  105. package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
  106. package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
  107. package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
  108. package/src/lib/migrations/index.ts +93 -0
  109. package/src/lib/migrations/migrations.test.ts +341 -0
  110. package/src/lib/navLinkTypes.ts +1 -0
  111. package/src/lib/overlayState.ts +3 -0
  112. package/src/lib/paletteDerivation.ts +300 -0
  113. package/src/lib/parentRouteStore.ts +42 -0
  114. package/src/lib/parsers/globalRootBlock.ts +32 -0
  115. package/src/lib/presetService.ts +94 -0
  116. package/src/lib/router.ts +49 -0
  117. package/src/lib/scrollSection.ts +45 -0
  118. package/src/lib/slices/columns.ts +59 -0
  119. package/src/lib/slices/components.ts +362 -0
  120. package/src/lib/slices/domainVars.ts +15 -0
  121. package/src/lib/slices/fonts.ts +30 -0
  122. package/src/lib/slices/gradients.ts +153 -0
  123. package/src/lib/slices/overlays.ts +132 -0
  124. package/src/lib/slices/palettes.ts +26 -0
  125. package/src/lib/slices/shadows.ts +123 -0
  126. package/src/lib/storage.ts +88 -0
  127. package/src/lib/themeInit.ts +74 -0
  128. package/src/lib/themeService.ts +101 -0
  129. package/src/lib/themeTypes.ts +146 -0
  130. package/src/lib/tokenRegistry.ts +148 -0
  131. package/src/pages/ComponentEditorPage.svelte +384 -0
  132. package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
  133. package/src/pages/Editor.svelte +98 -0
  134. package/src/pages/Editor.svelte.d.ts +2 -0
  135. package/src/pages/EditorShell.svelte +348 -0
  136. package/src/styles/_padding.scss +34 -0
  137. package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  138. package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  139. package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  140. package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  141. package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  142. package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
  143. package/src/styles/fonts.css +22 -10
  144. package/src/styles/form-controls.css +14 -16
  145. package/src/styles/tokens.css +1322 -0
  146. package/src/styles/ui-editor.css +126 -0
  147. package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
  148. package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
  149. package/src/ui/EditorViewSwitcher.svelte +180 -0
  150. package/src/ui/FontStackEditor.svelte +360 -0
  151. package/src/ui/GradientEditor.svelte +461 -0
  152. package/src/ui/GradientStopPicker.svelte +74 -0
  153. package/src/ui/PaletteEditor.svelte +1590 -0
  154. package/src/ui/PaletteEditor.test.ts +108 -0
  155. package/src/ui/PresetFileManager.svelte +567 -0
  156. package/src/ui/ProjectFontsSection.svelte +645 -0
  157. package/src/{showcase → ui}/SurfacesTab.svelte +39 -41
  158. package/src/{showcase → ui}/TextTab.svelte +27 -29
  159. package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
  160. package/src/ui/Toggle.svelte +108 -0
  161. package/src/ui/UICopyPopover.svelte +78 -0
  162. package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
  163. package/src/ui/UIFontFamilySelector.svelte +309 -0
  164. package/src/ui/UIFontSizeSelector.svelte +165 -0
  165. package/src/ui/UIFontWeightSelector.svelte +52 -0
  166. package/src/ui/UILineHeightSelector.svelte +47 -0
  167. package/src/ui/UILinkToggle.svelte +60 -0
  168. package/src/ui/UIOptionItem.svelte +74 -0
  169. package/src/ui/UIOptionList.svelte +27 -0
  170. package/src/ui/UIPaddingSelector.svelte +661 -0
  171. package/src/ui/UIPaletteSelector.svelte +1084 -0
  172. package/src/ui/UIRadio.svelte +72 -0
  173. package/src/ui/UIRadioGroup.svelte +59 -0
  174. package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
  175. package/src/ui/UITokenSelector.svelte +509 -0
  176. package/src/ui/UIVariantSelector.svelte +145 -0
  177. package/src/ui/VariablesTab.svelte +252 -0
  178. package/src/ui/index.ts +31 -0
  179. package/src/ui/keepInViewport.ts +84 -0
  180. package/src/ui/palette/GradientStopEditor.svelte +482 -0
  181. package/src/ui/palette/OverridesPanel.svelte +526 -0
  182. package/src/ui/palette/PaletteBase.svelte +165 -0
  183. package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
  184. package/src/ui/palette/paletteEditorState.ts +89 -0
  185. package/src/ui/sections/ColumnsSection.svelte +273 -0
  186. package/src/ui/sections/GradientsSection.svelte +147 -0
  187. package/src/ui/sections/OverlaysSection.svelte +670 -0
  188. package/src/ui/sections/ShadowsSection.svelte +1250 -0
  189. package/src/ui/sections/TokenScaleTable.svelte +332 -0
  190. package/src/ui/sections/tokenScales.ts +81 -0
  191. package/src/ui/variantScales.ts +108 -0
  192. package/src/components/DetailNav.svelte +0 -78
  193. package/src/components/Toggle.svelte +0 -86
  194. package/src/lib/pageSource.ts +0 -6
  195. package/src/lib/tokenInit.ts +0 -29
  196. package/src/lib/tokenService.ts +0 -144
  197. package/src/lib/tokenTypes.ts +0 -45
  198. package/src/pages/Admin.svelte +0 -100
  199. package/src/pages/ShowcasePage.svelte +0 -146
  200. package/src/showcase/BackupBrowser.svelte +0 -617
  201. package/src/showcase/ComponentsTab.svelte +0 -107
  202. package/src/showcase/PaletteEditor.svelte +0 -2579
  203. package/src/showcase/PaletteSelector.svelte +0 -627
  204. package/src/showcase/TokenMap.svelte +0 -54
  205. package/src/showcase/VariablesTab.svelte +0 -2657
  206. package/src/showcase/VisualsTab.svelte +0 -233
  207. package/src/showcase/demos/BadgeDemo.svelte +0 -58
  208. package/src/showcase/demos/CardDemo.svelte +0 -52
  209. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -194
  210. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -56
  211. package/src/showcase/demos/DialogDemo.svelte +0 -42
  212. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -27
  213. package/src/showcase/demos/NotificationDemo.svelte +0 -149
  214. package/src/showcase/demos/ProgressBarDemo.svelte +0 -56
  215. package/src/showcase/demos/RadioButtonDemo.svelte +0 -58
  216. package/src/showcase/demos/SectionDividerDemo.svelte +0 -79
  217. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -457
  218. package/src/showcase/demos/TabBarDemo.svelte +0 -60
  219. package/src/showcase/demos/TooltipDemo.svelte +0 -54
  220. package/src/showcase/editor.css +0 -93
  221. package/src/showcase/index.ts +0 -17
  222. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  223. package/src/styles/fonts/Domine/OFL.txt +0 -97
  224. package/src/styles/fonts/Domine/README.txt +0 -66
  225. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -30,84 +30,153 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/vite-plugin/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- tokenFileApi: () => tokenFileApi
33
+ themeFileApi: () => themeFileApi
34
34
  });
35
35
  module.exports = __toCommonJS(index_exports);
36
36
 
37
- // src/vite-plugin/tokenFileApi.ts
37
+ // src/vite-plugin/themeFileApi.ts
38
+ var import_fs2 = __toESM(require("fs"), 1);
39
+ var import_path2 = __toESM(require("path"), 1);
40
+
41
+ // src/lib/parsers/globalRootBlock.ts
42
+ function extractGlobalRootBody(source) {
43
+ const re = /:global\(:root\)\s*\{([^}]*)\}/g;
44
+ const bodies = [];
45
+ let m;
46
+ while ((m = re.exec(source)) !== null) {
47
+ bodies.push(m[1]);
48
+ }
49
+ return bodies.join("\n");
50
+ }
51
+
52
+ // src/lib/files/versionedFileResource.ts
53
+ function sanitizeFileName(name) {
54
+ return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
55
+ }
56
+
57
+ // src/vite-plugin/files/versionedFileResource.ts
38
58
  var import_fs = __toESM(require("fs"), 1);
39
59
  var import_path = __toESM(require("path"), 1);
40
- function tokenFileApi(opts) {
41
- const TOKENS_DIR = import_path.default.resolve(opts.tokensDir);
42
- const CSS_PATH = import_path.default.resolve(opts.variablesCssPath);
43
- const TOKENS_BACKUP_DIR = opts.tokensBackupDir ? import_path.default.resolve(opts.tokensBackupDir) : import_path.default.join(TOKENS_DIR, "_backups");
44
- const CSS_BACKUP_DIR = opts.cssBackupDir ? import_path.default.resolve(opts.cssBackupDir) : import_path.default.join(import_path.default.dirname(CSS_PATH), "_backups");
45
- const API_BASE = opts.apiBase ?? "/api";
46
- const ACTIVE_FILE = import_path.default.join(TOKENS_DIR, "_active.json");
47
- const PRODUCTION_FILE = import_path.default.join(TOKENS_DIR, "_production.json");
48
- function ensureTokensDir() {
49
- if (!import_fs.default.existsSync(TOKENS_DIR)) {
50
- import_fs.default.mkdirSync(TOKENS_DIR, { recursive: true });
51
- }
52
- if (!import_fs.default.existsSync(import_path.default.join(TOKENS_DIR, "default.json"))) {
53
- const defaultTokens = {
54
- name: "Default",
55
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
56
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
57
- editorConfigs: {},
58
- cssVariables: {}
59
- };
60
- import_fs.default.writeFileSync(import_path.default.join(TOKENS_DIR, "default.json"), JSON.stringify(defaultTokens, null, 2));
61
- }
62
- if (!import_fs.default.existsSync(ACTIVE_FILE)) {
63
- import_fs.default.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: "default" }));
64
- }
65
- if (!import_fs.default.existsSync(PRODUCTION_FILE)) {
66
- const activeFile = getActiveFileName();
67
- import_fs.default.writeFileSync(PRODUCTION_FILE, JSON.stringify({ productionFile: activeFile }));
60
+ function versionedFileResourceServer(opts) {
61
+ const dir = opts.dir;
62
+ const activePath = import_path.default.join(dir, "_active.json");
63
+ const productionPath = import_path.default.join(dir, "_production.json");
64
+ const defaultName = opts.defaultName ?? "default";
65
+ function ensureDir() {
66
+ if (!import_fs.default.existsSync(dir)) {
67
+ import_fs.default.mkdirSync(dir, { recursive: true });
68
68
  }
69
69
  }
70
- function backupTokenFile(filePath, fileName) {
71
- if (!import_fs.default.existsSync(filePath)) return;
72
- if (!import_fs.default.existsSync(TOKENS_BACKUP_DIR)) {
73
- import_fs.default.mkdirSync(TOKENS_BACKUP_DIR, { recursive: true });
70
+ function ensureMeta() {
71
+ if (!import_fs.default.existsSync(activePath)) {
72
+ import_fs.default.writeFileSync(activePath, JSON.stringify({ activeFile: defaultName }));
74
73
  }
75
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
76
- const backupPath = import_path.default.join(TOKENS_BACKUP_DIR, `${fileName}_${timestamp}.json`);
77
- import_fs.default.copyFileSync(filePath, backupPath);
78
- const allBackups = import_fs.default.readdirSync(TOKENS_BACKUP_DIR).filter((f) => f.startsWith(`${fileName}_`) && f.endsWith(".json")).sort().reverse();
79
- for (const old of allBackups.slice(10)) {
80
- import_fs.default.unlinkSync(import_path.default.join(TOKENS_BACKUP_DIR, old));
74
+ if (!import_fs.default.existsSync(productionPath)) {
75
+ import_fs.default.writeFileSync(productionPath, JSON.stringify({ productionFile: defaultName }));
81
76
  }
82
77
  }
83
- function backupCssFile() {
84
- if (!import_fs.default.existsSync(CSS_PATH)) return;
85
- if (!import_fs.default.existsSync(CSS_BACKUP_DIR)) {
86
- import_fs.default.mkdirSync(CSS_BACKUP_DIR, { recursive: true });
87
- }
88
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
89
- const backupPath = import_path.default.join(CSS_BACKUP_DIR, `variables_${timestamp}.css`);
90
- import_fs.default.copyFileSync(CSS_PATH, backupPath);
91
- const allBackups = import_fs.default.readdirSync(CSS_BACKUP_DIR).filter((f) => f.startsWith("variables_") && f.endsWith(".css")).sort().reverse();
92
- for (const old of allBackups.slice(10)) {
93
- import_fs.default.unlinkSync(import_path.default.join(CSS_BACKUP_DIR, old));
94
- }
78
+ function filePath(name) {
79
+ return import_path.default.join(dir, `${name}.json`);
95
80
  }
96
- function getActiveFileName() {
81
+ function getActiveName() {
97
82
  try {
98
- const data = JSON.parse(import_fs.default.readFileSync(ACTIVE_FILE, "utf-8"));
99
- return data.activeFile || "default";
83
+ const data = JSON.parse(import_fs.default.readFileSync(activePath, "utf-8"));
84
+ return data.activeFile || defaultName;
100
85
  } catch {
101
- return "default";
86
+ return defaultName;
102
87
  }
103
88
  }
104
- function getProductionFileName() {
89
+ function getProductionName() {
105
90
  try {
106
- const data = JSON.parse(import_fs.default.readFileSync(PRODUCTION_FILE, "utf-8"));
107
- return data.productionFile || "default";
91
+ const data = JSON.parse(import_fs.default.readFileSync(productionPath, "utf-8"));
92
+ return data.productionFile || defaultName;
108
93
  } catch {
109
- return "default";
94
+ return defaultName;
95
+ }
96
+ }
97
+ function setActiveName(name) {
98
+ import_fs.default.writeFileSync(activePath, JSON.stringify({ activeFile: name }));
99
+ }
100
+ function setProductionName(name) {
101
+ import_fs.default.writeFileSync(productionPath, JSON.stringify({ productionFile: name }));
102
+ }
103
+ return {
104
+ dir,
105
+ activePath,
106
+ productionPath,
107
+ ensureDir,
108
+ ensureMeta,
109
+ filePath,
110
+ getActiveName,
111
+ getProductionName,
112
+ setActiveName,
113
+ setProductionName
114
+ };
115
+ }
116
+
117
+ // src/vite-plugin/files/routeTable.ts
118
+ async function dispatch(req, res, routes) {
119
+ const url = req.url || "";
120
+ const method = req.method || "GET";
121
+ for (const route of routes) {
122
+ if (route.method !== method) continue;
123
+ let params = [];
124
+ if (typeof route.pattern === "string") {
125
+ if (url !== route.pattern) continue;
126
+ } else {
127
+ const m = url.match(route.pattern);
128
+ if (!m) continue;
129
+ params = m.slice(1);
130
+ }
131
+ try {
132
+ await route.handler({ url, params, req, res });
133
+ } catch (err) {
134
+ if (!res.writableEnded) {
135
+ res.statusCode = 500;
136
+ res.setHeader("Content-Type", "application/json");
137
+ res.end(JSON.stringify({ error: err?.message ?? "Internal error" }));
138
+ }
139
+ }
140
+ return true;
141
+ }
142
+ return false;
143
+ }
144
+
145
+ // src/vite-plugin/themeFileApi.ts
146
+ function themeFileApi(opts) {
147
+ const THEMES_DIR = import_path2.default.resolve(opts.themesDir);
148
+ const CSS_PATH = import_path2.default.resolve(opts.tokensCssPath);
149
+ const FONTS_CSS_PATH = opts.fontsCssPath ? import_path2.default.resolve(opts.fontsCssPath) : import_path2.default.join(import_path2.default.dirname(CSS_PATH), "fonts.css");
150
+ const API_BASE = opts.apiBase ?? "/api";
151
+ const COMPONENT_CONFIGS_DIR = opts.componentConfigsDir ? import_path2.default.resolve(opts.componentConfigsDir) : import_path2.default.resolve("component-configs");
152
+ const COMPONENTS_SRC_DIR = opts.componentsSrcDir ? import_path2.default.resolve(opts.componentsSrcDir) : import_path2.default.resolve("src/components");
153
+ const PRESETS_DIR = opts.presetsDir ? import_path2.default.resolve(opts.presetsDir) : import_path2.default.resolve("presets");
154
+ const themesResource = versionedFileResourceServer({
155
+ dir: THEMES_DIR
156
+ });
157
+ const componentResourceCache = /* @__PURE__ */ new Map();
158
+ function componentResource(comp) {
159
+ let r = componentResourceCache.get(comp);
160
+ if (!r) {
161
+ r = versionedFileResourceServer({ dir: import_path2.default.join(COMPONENT_CONFIGS_DIR, comp) });
162
+ componentResourceCache.set(comp, r);
163
+ }
164
+ return r;
165
+ }
166
+ const presetsResource = versionedFileResourceServer({ dir: PRESETS_DIR });
167
+ function ensureThemesDir() {
168
+ themesResource.ensureDir();
169
+ if (!import_fs2.default.existsSync(import_path2.default.join(THEMES_DIR, "default.json"))) {
170
+ const defaultTheme = {
171
+ name: "Default",
172
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
173
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
174
+ editorConfigs: {},
175
+ cssVariables: {}
176
+ };
177
+ import_fs2.default.writeFileSync(import_path2.default.join(THEMES_DIR, "default.json"), JSON.stringify(defaultTheme, null, 2));
110
178
  }
179
+ themesResource.ensureMeta();
111
180
  }
112
181
  function readBody(req) {
113
182
  return new Promise((resolve, reject) => {
@@ -122,16 +191,48 @@ function tokenFileApi(opts) {
122
191
  res.setHeader("Content-Type", "application/json");
123
192
  res.end(JSON.stringify(data));
124
193
  }
125
- function sanitize(name) {
126
- return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
194
+ const SYSTEM_CASCADES_SSR = {
195
+ "system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
196
+ "system-ui-serif": 'Georgia, "Times New Roman", serif',
197
+ "system-ui-mono": 'ui-monospace, "SF Mono", Menlo, Consolas, monospace'
198
+ };
199
+ function resolveFontStacks(themeData) {
200
+ const stacks = themeData.fontStacks;
201
+ const sources = themeData.fontSources;
202
+ if (!stacks || stacks.length === 0) return {};
203
+ const familyById = /* @__PURE__ */ new Map();
204
+ for (const src of sources ?? []) {
205
+ for (const f of src.families) familyById.set(f.id, f.cssName);
206
+ }
207
+ const out = {};
208
+ for (const stack of stacks) {
209
+ const parts = [];
210
+ for (const slot of stack.slots) {
211
+ if (slot.kind === "project") {
212
+ const css = familyById.get(slot.familyId);
213
+ if (css) parts.push(css);
214
+ } else if (slot.kind === "system") {
215
+ const cascade = SYSTEM_CASCADES_SSR[slot.preset];
216
+ if (cascade) parts.push(cascade);
217
+ } else if (slot.kind === "generic") {
218
+ parts.push(slot.value);
219
+ }
220
+ }
221
+ if (parts.length > 0) out[stack.variable] = parts.join(", ");
222
+ }
223
+ return out;
127
224
  }
128
225
  function syncTokensToCss(fileName) {
129
- const tokenPath = import_path.default.join(TOKENS_DIR, `${fileName}.json`);
130
- if (!import_fs.default.existsSync(tokenPath)) return;
131
- const tokenData = JSON.parse(import_fs.default.readFileSync(tokenPath, "utf-8"));
132
- const cssVars = tokenData.cssVariables || {};
226
+ const themePath = import_path2.default.join(THEMES_DIR, `${fileName}.json`);
227
+ if (!import_fs2.default.existsSync(themePath)) return;
228
+ const themeData = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
229
+ const cssVars = { ...themeData.cssVariables || {} };
230
+ const resolvedFontVars = resolveFontStacks(themeData);
231
+ for (const [name, value] of Object.entries(resolvedFontVars)) {
232
+ cssVars[name] = value;
233
+ }
133
234
  if (Object.keys(cssVars).length === 0) return;
134
- const cssContent = import_fs.default.readFileSync(CSS_PATH, "utf-8");
235
+ const cssContent = import_fs2.default.readFileSync(CSS_PATH, "utf-8");
135
236
  const remaining = new Set(Object.keys(cssVars));
136
237
  const updatedContent = cssContent.replace(
137
238
  /^(\s*)(--[\w-]+):\s*(.+);/gm,
@@ -155,290 +256,676 @@ ${newVars}
155
256
  }$1`
156
257
  );
157
258
  }
158
- backupCssFile();
159
- import_fs.default.writeFileSync(CSS_PATH, finalContent);
160
- console.log(`[syncTokensToCss] Wrote ${Object.keys(cssVars).length} variables from "${fileName}" into variables.css`);
259
+ import_fs2.default.writeFileSync(CSS_PATH, finalContent);
260
+ console.log(`[syncTokensToCss] Wrote ${Object.keys(cssVars).length} variables from "${fileName}" into tokens.css`);
161
261
  }
162
- const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
163
- const TOKENS_ROUTE = `${API_BASE}/tokens`;
164
- const TOKENS_ACTIVE_ROUTE = `${API_BASE}/tokens/active`;
165
- const TOKENS_PRODUCTION_ROUTE = `${API_BASE}/tokens/production`;
166
- const BACKUPS_ROUTE = `${API_BASE}/backups`;
167
- const CURRENT_CSS_ROUTE = `${API_BASE}/current-css`;
168
- const TOKEN_BY_NAME_REGEX = new RegExp(`^${escapedBase}/tokens/([a-z0-9\\-_]+)$`);
169
- const BACKUP_GET_REGEX = new RegExp(`^${escapedBase}/backups/(tokens|css)/(.+)$`);
170
- const BACKUP_RESTORE_REGEX = new RegExp(`^${escapedBase}/backups/(tokens|css)/(.+)/restore$`);
171
- return {
172
- name: "token-file-api",
173
- configureServer(server) {
174
- ensureTokensDir();
175
- server.middlewares.use(async (req, res, next) => {
176
- const url = req.url || "";
177
- if (url === TOKENS_ROUTE && req.method === "GET") {
178
- try {
179
- const activeFile = getActiveFileName();
180
- const files = import_fs.default.readdirSync(TOKENS_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
181
- const filePath = import_path.default.join(TOKENS_DIR, f);
182
- const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
183
- const fileName = f.replace(".json", "");
184
- return {
185
- name: data.name || fileName,
186
- fileName,
187
- updatedAt: data.updatedAt || "",
188
- isActive: fileName === activeFile
189
- };
190
- });
191
- jsonResponse(res, 200, { files });
192
- } catch (err) {
193
- jsonResponse(res, 500, { error: err.message });
194
- }
195
- return;
262
+ function syncFontsToCss(fileName) {
263
+ const themePath = import_path2.default.join(THEMES_DIR, `${fileName}.json`);
264
+ if (!import_fs2.default.existsSync(themePath)) return;
265
+ const themeData = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
266
+ const sources = themeData.fontSources;
267
+ if (!sources) return;
268
+ const lines = [];
269
+ lines.push("/* Generated from the production theme by syncFontsToCss. Do not edit. */");
270
+ lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
271
+ lines.push("");
272
+ for (const source of sources) {
273
+ const familyNames = source.families.map((f) => f.name).join(", ");
274
+ const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
275
+ lines.push(`/* ${label} */`);
276
+ if (source.kind === "font-face") {
277
+ if (source.cssText) lines.push(source.cssText);
278
+ } else if (source.url) {
279
+ lines.push(`@import url('${source.url}');`);
280
+ }
281
+ lines.push("");
282
+ }
283
+ const content = lines.join("\n");
284
+ if (!import_fs2.default.existsSync(import_path2.default.dirname(FONTS_CSS_PATH))) {
285
+ import_fs2.default.mkdirSync(import_path2.default.dirname(FONTS_CSS_PATH), { recursive: true });
286
+ }
287
+ import_fs2.default.writeFileSync(FONTS_CSS_PATH, content);
288
+ console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${import_path2.default.basename(FONTS_CSS_PATH)}`);
289
+ }
290
+ function extractAliasDeclarations(body) {
291
+ const aliases = {};
292
+ const re = /(--[a-z0-9-]+)\s*:\s*var\((--[a-z0-9-]+)\)\s*;/gi;
293
+ let m;
294
+ while ((m = re.exec(body)) !== null) aliases[m[1]] = m[2];
295
+ return aliases;
296
+ }
297
+ function componentNameFromFile(filePath) {
298
+ return import_path2.default.basename(filePath, ".svelte").toLowerCase();
299
+ }
300
+ function listComponentSourcePaths() {
301
+ if (!import_fs2.default.existsSync(COMPONENTS_SRC_DIR)) return [];
302
+ return import_fs2.default.readdirSync(COMPONENTS_SRC_DIR).filter((f) => f.endsWith(".svelte")).map((f) => import_path2.default.join(COMPONENTS_SRC_DIR, f));
303
+ }
304
+ function listComponentNames() {
305
+ return listComponentSourcePaths().map(componentNameFromFile);
306
+ }
307
+ function generateDefaultConfig(comp, sourcePath) {
308
+ if (!import_fs2.default.existsSync(sourcePath)) return;
309
+ const r = componentResource(comp);
310
+ r.ensureDir();
311
+ const defaultPath = r.filePath("default");
312
+ const sourceStat = import_fs2.default.statSync(sourcePath);
313
+ if (import_fs2.default.existsSync(defaultPath)) {
314
+ const defaultStat = import_fs2.default.statSync(defaultPath);
315
+ if (defaultStat.mtimeMs >= sourceStat.mtimeMs) return;
316
+ }
317
+ const source = import_fs2.default.readFileSync(sourcePath, "utf-8");
318
+ const body = extractGlobalRootBody(source);
319
+ const aliases = extractAliasDeclarations(body);
320
+ const now = (/* @__PURE__ */ new Date()).toISOString();
321
+ let createdAt = now;
322
+ let existingUpdatedAt;
323
+ let existingAliases;
324
+ if (import_fs2.default.existsSync(defaultPath)) {
325
+ try {
326
+ const existing = JSON.parse(import_fs2.default.readFileSync(defaultPath, "utf-8"));
327
+ if (existing.createdAt) createdAt = existing.createdAt;
328
+ if (typeof existing.updatedAt === "string") existingUpdatedAt = existing.updatedAt;
329
+ if (existing.aliases && typeof existing.aliases === "object") {
330
+ existingAliases = existing.aliases;
196
331
  }
197
- if (url === TOKENS_ACTIVE_ROUTE && req.method === "GET") {
198
- try {
199
- const activeFile = getActiveFileName();
200
- const filePath = import_path.default.join(TOKENS_DIR, `${activeFile}.json`);
201
- if (!import_fs.default.existsSync(filePath)) {
202
- jsonResponse(res, 404, { error: "Active token file not found" });
203
- return;
204
- }
205
- const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
206
- data._fileName = activeFile;
207
- jsonResponse(res, 200, data);
208
- } catch (err) {
209
- jsonResponse(res, 500, { error: err.message });
210
- }
211
- return;
332
+ } catch {
333
+ }
334
+ }
335
+ const aliasesUnchanged = existingAliases !== void 0 && JSON.stringify(existingAliases) === JSON.stringify(aliases);
336
+ const defaultConfig = {
337
+ name: "default",
338
+ component: comp,
339
+ createdAt,
340
+ updatedAt: aliasesUnchanged && existingUpdatedAt ? existingUpdatedAt : now,
341
+ aliases
342
+ };
343
+ if (aliasesUnchanged && existingUpdatedAt) {
344
+ const t = /* @__PURE__ */ new Date();
345
+ import_fs2.default.utimesSync(defaultPath, t, t);
346
+ return;
347
+ }
348
+ import_fs2.default.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
349
+ }
350
+ function ensureComponentConfigsDir() {
351
+ if (!import_fs2.default.existsSync(COMPONENT_CONFIGS_DIR)) {
352
+ import_fs2.default.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
353
+ }
354
+ for (const sourcePath of listComponentSourcePaths()) {
355
+ const comp = componentNameFromFile(sourcePath);
356
+ const r = componentResource(comp);
357
+ r.ensureDir();
358
+ generateDefaultConfig(comp, sourcePath);
359
+ r.ensureMeta();
360
+ }
361
+ }
362
+ function ensurePresetsDir() {
363
+ presetsResource.ensureDir();
364
+ const defaultPath = import_path2.default.join(PRESETS_DIR, "default.json");
365
+ if (!import_fs2.default.existsSync(defaultPath)) {
366
+ const componentConfigs = {};
367
+ for (const comp of listComponentNames()) {
368
+ componentConfigs[comp] = componentResource(comp).getActiveName();
369
+ }
370
+ const now = (/* @__PURE__ */ new Date()).toISOString();
371
+ const defaultPreset = {
372
+ name: "Default",
373
+ createdAt: now,
374
+ updatedAt: now,
375
+ theme: themesResource.getActiveName(),
376
+ componentConfigs
377
+ };
378
+ import_fs2.default.writeFileSync(defaultPath, JSON.stringify(defaultPreset, null, 2));
379
+ }
380
+ presetsResource.ensureMeta();
381
+ }
382
+ function readComponentConfig(comp, name) {
383
+ const filePath = componentResource(comp).filePath(name);
384
+ if (!import_fs2.default.existsSync(filePath)) return null;
385
+ try {
386
+ return JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
387
+ } catch {
388
+ return null;
389
+ }
390
+ }
391
+ const COMPONENT_OVERRIDES_START = "/* component-aliases:start */";
392
+ const COMPONENT_OVERRIDES_END = "/* component-aliases:end */";
393
+ function syncComponentsToCss() {
394
+ if (!import_fs2.default.existsSync(CSS_PATH)) return;
395
+ if (!import_fs2.default.existsSync(COMPONENT_CONFIGS_DIR)) return;
396
+ const lines = [];
397
+ const components = listComponentNames();
398
+ for (const comp of components) {
399
+ const prod = componentResource(comp).getProductionName();
400
+ if (prod === "default") continue;
401
+ const prodCfg = readComponentConfig(comp, prod);
402
+ const defaultCfg = readComponentConfig(comp, "default");
403
+ if (!prodCfg || !defaultCfg) continue;
404
+ const overrides = [];
405
+ for (const [varName, semanticName] of Object.entries(prodCfg.aliases ?? {})) {
406
+ if ((defaultCfg.aliases ?? {})[varName] !== semanticName) {
407
+ overrides.push([varName, semanticName]);
212
408
  }
213
- if (url === TOKENS_ACTIVE_ROUTE && req.method === "PUT") {
214
- try {
215
- const body = JSON.parse(await readBody(req));
216
- const fileName = sanitize(body.name || "default");
217
- if (!import_fs.default.existsSync(import_path.default.join(TOKENS_DIR, `${fileName}.json`))) {
218
- jsonResponse(res, 404, { error: "Token file not found" });
219
- return;
220
- }
221
- import_fs.default.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: fileName }));
222
- jsonResponse(res, 200, { ok: true, activeFile: fileName });
223
- } catch (err) {
224
- jsonResponse(res, 500, { error: err.message });
225
- }
226
- return;
409
+ }
410
+ if (overrides.length === 0) continue;
411
+ lines.push(` /* ${comp} (${prod}) */`);
412
+ for (const [varName, semanticName] of overrides) {
413
+ lines.push(` ${varName}: var(${semanticName});`);
414
+ }
415
+ }
416
+ const block = lines.length > 0 ? `
417
+
418
+ ${COMPONENT_OVERRIDES_START}
419
+ :root:root {
420
+ ${lines.join("\n")}
421
+ }
422
+ ${COMPONENT_OVERRIDES_END}
423
+ ` : "";
424
+ let cssContent = import_fs2.default.readFileSync(CSS_PATH, "utf-8");
425
+ const startIdx = cssContent.indexOf(COMPONENT_OVERRIDES_START);
426
+ const endIdx = cssContent.indexOf(COMPONENT_OVERRIDES_END);
427
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
428
+ let stripStart = startIdx;
429
+ while (stripStart > 0 && cssContent[stripStart - 1] === "\n") stripStart--;
430
+ const after = cssContent.slice(endIdx + COMPONENT_OVERRIDES_END.length);
431
+ cssContent = cssContent.slice(0, stripStart) + (block || "\n") + after.replace(/^\n+/, "");
432
+ } else if (block) {
433
+ cssContent = cssContent.replace(/\n*$/, "") + block;
434
+ }
435
+ import_fs2.default.writeFileSync(CSS_PATH, cssContent);
436
+ console.log(
437
+ `[syncComponentsToCss] Wrote ${lines.filter((l) => !l.trim().startsWith("/*")).length} alias override(s) to tokens.css`
438
+ );
439
+ }
440
+ const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
441
+ const THEMES_ROUTE = `${API_BASE}/themes`;
442
+ const THEMES_ACTIVE_ROUTE = `${API_BASE}/themes/active`;
443
+ const THEMES_PRODUCTION_ROUTE = `${API_BASE}/themes/production`;
444
+ const COMPONENT_CONFIGS_ROUTE = `${API_BASE}/component-configs`;
445
+ const PRESETS_ROUTE = `${API_BASE}/presets`;
446
+ const PRESETS_ACTIVE_ROUTE = `${API_BASE}/presets/active`;
447
+ const THEME_BY_NAME_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)$`);
448
+ const COMP_LIST_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)$`);
449
+ const COMP_ACTIVE_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/active$`);
450
+ const COMP_PRODUCTION_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/production$`);
451
+ const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
452
+ const PRESET_APPLY_REGEX = new RegExp(`^${escapedBase}/presets/([a-z0-9\\-_]+)/apply$`);
453
+ const PRESET_BY_NAME_REGEX = new RegExp(`^${escapedBase}/presets/([a-z0-9\\-_]+)$`);
454
+ async function handleListThemes(_ctx) {
455
+ const activeFile = themesResource.getActiveName();
456
+ const files = import_fs2.default.readdirSync(THEMES_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
457
+ const filePath = import_path2.default.join(THEMES_DIR, f);
458
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
459
+ const fileName = f.replace(".json", "");
460
+ return {
461
+ name: data.name || fileName,
462
+ fileName,
463
+ updatedAt: data.updatedAt || "",
464
+ isActive: fileName === activeFile
465
+ };
466
+ });
467
+ jsonResponse(_ctx.res, 200, { files });
468
+ }
469
+ async function handleGetActiveTheme({ res }) {
470
+ const activeFile = themesResource.getActiveName();
471
+ const filePath = themesResource.filePath(activeFile);
472
+ if (!import_fs2.default.existsSync(filePath)) {
473
+ jsonResponse(res, 404, { error: "Active theme not found" });
474
+ return;
475
+ }
476
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
477
+ data._fileName = activeFile;
478
+ jsonResponse(res, 200, data);
479
+ }
480
+ async function handleSetActiveTheme({ req, res }) {
481
+ const body = JSON.parse(await readBody(req));
482
+ const fileName = sanitizeFileName(body.name || "default");
483
+ if (!import_fs2.default.existsSync(themesResource.filePath(fileName))) {
484
+ jsonResponse(res, 404, { error: "Theme not found" });
485
+ return;
486
+ }
487
+ themesResource.setActiveName(fileName);
488
+ jsonResponse(res, 200, { ok: true, activeFile: fileName });
489
+ }
490
+ async function handleGetProductionTheme({ res }) {
491
+ const prodFile = themesResource.getProductionName();
492
+ const filePath = themesResource.filePath(prodFile);
493
+ if (!import_fs2.default.existsSync(filePath)) {
494
+ jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
495
+ return;
496
+ }
497
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
498
+ jsonResponse(res, 200, {
499
+ fileName: prodFile,
500
+ name: data.name || prodFile,
501
+ updatedAt: data.updatedAt || "",
502
+ cssVariables: data.cssVariables || {}
503
+ });
504
+ }
505
+ async function handleSetProductionTheme({ req, res }) {
506
+ const body = JSON.parse(await readBody(req));
507
+ const fileName = sanitizeFileName(body.name || "default");
508
+ if (!import_fs2.default.existsSync(themesResource.filePath(fileName))) {
509
+ jsonResponse(res, 404, { error: "Theme not found" });
510
+ return;
511
+ }
512
+ themesResource.setProductionName(fileName);
513
+ syncTokensToCss(fileName);
514
+ syncFontsToCss(fileName);
515
+ syncComponentsToCss();
516
+ const data = JSON.parse(import_fs2.default.readFileSync(themesResource.filePath(fileName), "utf-8"));
517
+ jsonResponse(res, 200, {
518
+ ok: true,
519
+ fileName,
520
+ name: data.name || fileName,
521
+ updatedAt: data.updatedAt || ""
522
+ });
523
+ }
524
+ async function handleThemeByName({ params, req, res }) {
525
+ const [fileName] = params;
526
+ const filePath = themesResource.filePath(fileName);
527
+ if (req.method === "GET") {
528
+ if (!import_fs2.default.existsSync(filePath)) {
529
+ jsonResponse(res, 404, { error: "Not found" });
530
+ return;
531
+ }
532
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
533
+ data._fileName = fileName;
534
+ jsonResponse(res, 200, data);
535
+ return;
536
+ }
537
+ if (req.method === "PUT") {
538
+ const body = JSON.parse(await readBody(req));
539
+ body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
540
+ if (import_fs2.default.existsSync(filePath)) {
541
+ try {
542
+ const existing = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
543
+ if (existing.createdAt) body.createdAt = existing.createdAt;
544
+ } catch {
227
545
  }
228
- if (url === TOKENS_PRODUCTION_ROUTE && req.method === "GET") {
229
- try {
230
- const prodFile = getProductionFileName();
231
- const filePath = import_path.default.join(TOKENS_DIR, `${prodFile}.json`);
232
- if (!import_fs.default.existsSync(filePath)) {
233
- jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
234
- return;
235
- }
236
- const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
237
- jsonResponse(res, 200, {
238
- fileName: prodFile,
239
- name: data.name || prodFile,
240
- updatedAt: data.updatedAt || "",
241
- cssVariables: data.cssVariables || {}
242
- });
243
- } catch (err) {
244
- jsonResponse(res, 500, { error: err.message });
245
- }
246
- return;
546
+ }
547
+ if (!body.createdAt) body.createdAt = body.updatedAt;
548
+ import_fs2.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
549
+ if (fileName === themesResource.getProductionName()) {
550
+ syncTokensToCss(fileName);
551
+ syncFontsToCss(fileName);
552
+ syncComponentsToCss();
553
+ }
554
+ jsonResponse(res, 200, { ok: true, fileName });
555
+ return;
556
+ }
557
+ if (req.method === "DELETE") {
558
+ if (fileName === "default") {
559
+ jsonResponse(res, 403, { error: "Cannot delete the default theme" });
560
+ return;
561
+ }
562
+ if (import_fs2.default.existsSync(filePath)) {
563
+ import_fs2.default.unlinkSync(filePath);
564
+ if (themesResource.getActiveName() === fileName) {
565
+ themesResource.setActiveName("default");
247
566
  }
248
- if (url === TOKENS_PRODUCTION_ROUTE && req.method === "PUT") {
249
- try {
250
- const body = JSON.parse(await readBody(req));
251
- const fileName = sanitize(body.name || "default");
252
- if (!import_fs.default.existsSync(import_path.default.join(TOKENS_DIR, `${fileName}.json`))) {
253
- jsonResponse(res, 404, { error: "Token file not found" });
254
- return;
255
- }
256
- import_fs.default.writeFileSync(PRODUCTION_FILE, JSON.stringify({ productionFile: fileName }));
257
- syncTokensToCss(fileName);
258
- const data = JSON.parse(import_fs.default.readFileSync(import_path.default.join(TOKENS_DIR, `${fileName}.json`), "utf-8"));
259
- jsonResponse(res, 200, {
260
- ok: true,
261
- fileName,
262
- name: data.name || fileName,
263
- updatedAt: data.updatedAt || ""
264
- });
265
- } catch (err) {
266
- jsonResponse(res, 500, { error: err.message });
267
- }
268
- return;
567
+ }
568
+ jsonResponse(res, 200, { ok: true });
569
+ return;
570
+ }
571
+ }
572
+ async function handleListComponents({ res }) {
573
+ const components = listComponentNames().map((comp) => {
574
+ const r = componentResource(comp);
575
+ return {
576
+ name: comp,
577
+ activeFile: r.getActiveName(),
578
+ productionFile: r.getProductionName()
579
+ };
580
+ });
581
+ jsonResponse(res, 200, { components });
582
+ }
583
+ async function handleGetComponentActive({ params, res }) {
584
+ const [comp] = params;
585
+ const r = componentResource(comp);
586
+ const activeFile = r.getActiveName();
587
+ const cfg = readComponentConfig(comp, activeFile);
588
+ if (!cfg) {
589
+ jsonResponse(res, 404, { error: "Active config not found" });
590
+ return;
591
+ }
592
+ jsonResponse(res, 200, { ...cfg, _fileName: activeFile });
593
+ }
594
+ async function handleSetComponentActive({ params, req, res }) {
595
+ const [comp] = params;
596
+ const body = JSON.parse(await readBody(req));
597
+ const fileName = sanitizeFileName(body.name || "default");
598
+ const r = componentResource(comp);
599
+ const configPath = r.filePath(fileName);
600
+ if (!import_fs2.default.existsSync(configPath)) {
601
+ jsonResponse(res, 404, { error: "Config not found" });
602
+ return;
603
+ }
604
+ r.ensureDir();
605
+ r.setActiveName(fileName);
606
+ jsonResponse(res, 200, { ok: true, activeFile: fileName });
607
+ }
608
+ async function handleGetComponentProduction({ params, res }) {
609
+ const [comp] = params;
610
+ const r = componentResource(comp);
611
+ const prodFile = r.getProductionName();
612
+ const cfg = readComponentConfig(comp, prodFile);
613
+ jsonResponse(res, 200, {
614
+ fileName: prodFile,
615
+ name: cfg?.name || prodFile,
616
+ aliases: cfg?.aliases || {}
617
+ });
618
+ }
619
+ async function handleSetComponentProduction({ params, req, res }) {
620
+ const [comp] = params;
621
+ const body = JSON.parse(await readBody(req));
622
+ const fileName = sanitizeFileName(body.name || "default");
623
+ const r = componentResource(comp);
624
+ const configPath = r.filePath(fileName);
625
+ if (!import_fs2.default.existsSync(configPath)) {
626
+ jsonResponse(res, 404, { error: "Config not found" });
627
+ return;
628
+ }
629
+ r.ensureDir();
630
+ r.setProductionName(fileName);
631
+ syncComponentsToCss();
632
+ const cfg = readComponentConfig(comp, fileName);
633
+ jsonResponse(res, 200, {
634
+ ok: true,
635
+ fileName,
636
+ name: cfg?.name || fileName,
637
+ aliases: cfg?.aliases || {}
638
+ });
639
+ }
640
+ async function handleComponentConfigByName({ params, req, res }) {
641
+ const [comp, name] = params;
642
+ const r = componentResource(comp);
643
+ const configPath = r.filePath(name);
644
+ if (req.method === "GET") {
645
+ if (!import_fs2.default.existsSync(configPath)) {
646
+ jsonResponse(res, 404, { error: "Not found" });
647
+ return;
648
+ }
649
+ const data = JSON.parse(import_fs2.default.readFileSync(configPath, "utf-8"));
650
+ data._fileName = name;
651
+ jsonResponse(res, 200, data);
652
+ return;
653
+ }
654
+ if (req.method === "PUT") {
655
+ if (name === "default") {
656
+ jsonResponse(res, 403, { error: "Cannot modify default config (regenerated from source)" });
657
+ return;
658
+ }
659
+ const body = JSON.parse(await readBody(req));
660
+ body.component = comp;
661
+ body.name = body.name || name;
662
+ body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
663
+ if (import_fs2.default.existsSync(configPath)) {
664
+ try {
665
+ const existing = JSON.parse(import_fs2.default.readFileSync(configPath, "utf-8"));
666
+ if (existing.createdAt) body.createdAt = existing.createdAt;
667
+ } catch {
269
668
  }
270
- if (url === BACKUPS_ROUTE && req.method === "GET") {
271
- try {
272
- let fileTimestampToISO2 = function(ts) {
273
- return ts.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/, "T$1:$2:$3.$4Z");
274
- };
275
- var fileTimestampToISO = fileTimestampToISO2;
276
- const backups = [];
277
- if (import_fs.default.existsSync(TOKENS_BACKUP_DIR)) {
278
- for (const f of import_fs.default.readdirSync(TOKENS_BACKUP_DIR)) {
279
- if (!f.endsWith(".json")) continue;
280
- const match2 = f.match(/^(.+)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.json$/);
281
- if (!match2) continue;
282
- const stat = import_fs.default.statSync(import_path.default.join(TOKENS_BACKUP_DIR, f));
283
- backups.push({
284
- type: "tokens",
285
- file: f,
286
- name: match2[1],
287
- timestamp: fileTimestampToISO2(match2[2]),
288
- size: stat.size
289
- });
290
- }
291
- }
292
- if (import_fs.default.existsSync(CSS_BACKUP_DIR)) {
293
- for (const f of import_fs.default.readdirSync(CSS_BACKUP_DIR)) {
294
- if (!f.endsWith(".css")) continue;
295
- const match2 = f.match(/^(.+)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.css$/);
296
- if (!match2) continue;
297
- const stat = import_fs.default.statSync(import_path.default.join(CSS_BACKUP_DIR, f));
298
- backups.push({
299
- type: "css",
300
- file: f,
301
- name: match2[1],
302
- timestamp: fileTimestampToISO2(match2[2]),
303
- size: stat.size
304
- });
305
- }
306
- }
307
- backups.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
308
- jsonResponse(res, 200, { backups });
309
- } catch (err) {
310
- jsonResponse(res, 500, { error: err.message });
311
- }
312
- return;
669
+ }
670
+ if (!body.createdAt) body.createdAt = body.updatedAt;
671
+ r.ensureDir();
672
+ import_fs2.default.writeFileSync(configPath, JSON.stringify(body, null, 2));
673
+ if (r.getProductionName() === name) {
674
+ syncComponentsToCss();
675
+ }
676
+ jsonResponse(res, 200, { ok: true, fileName: name });
677
+ return;
678
+ }
679
+ if (req.method === "DELETE") {
680
+ if (name === "default") {
681
+ jsonResponse(res, 403, { error: "Cannot delete default config" });
682
+ return;
683
+ }
684
+ if (import_fs2.default.existsSync(configPath)) {
685
+ import_fs2.default.unlinkSync(configPath);
686
+ if (r.getActiveName() === name) {
687
+ r.setActiveName("default");
313
688
  }
314
- {
315
- const restoreMatch = url.match(BACKUP_RESTORE_REGEX);
316
- if (restoreMatch && req.method === "POST") {
317
- const [, type, file] = restoreMatch;
318
- try {
319
- if (type === "css") {
320
- const backupPath = import_path.default.join(CSS_BACKUP_DIR, decodeURIComponent(file));
321
- if (!backupPath.startsWith(CSS_BACKUP_DIR) || !import_fs.default.existsSync(backupPath)) {
322
- jsonResponse(res, 404, { error: "Backup not found" });
323
- return;
324
- }
325
- backupCssFile();
326
- import_fs.default.copyFileSync(backupPath, CSS_PATH);
327
- jsonResponse(res, 200, { ok: true, restored: file });
328
- } else {
329
- const backupPath = import_path.default.join(TOKENS_BACKUP_DIR, decodeURIComponent(file));
330
- if (!backupPath.startsWith(TOKENS_BACKUP_DIR) || !import_fs.default.existsSync(backupPath)) {
331
- jsonResponse(res, 404, { error: "Backup not found" });
332
- return;
333
- }
334
- const nameMatch = decodeURIComponent(file).match(/^(.+)_\d{4}-/);
335
- if (!nameMatch) {
336
- jsonResponse(res, 400, { error: "Cannot determine token file name from backup" });
337
- return;
338
- }
339
- const tokenFilePath = import_path.default.join(TOKENS_DIR, `${nameMatch[1]}.json`);
340
- backupTokenFile(tokenFilePath, nameMatch[1]);
341
- import_fs.default.copyFileSync(backupPath, tokenFilePath);
342
- jsonResponse(res, 200, { ok: true, restored: file });
343
- }
344
- } catch (err) {
345
- jsonResponse(res, 500, { error: err.message });
346
- }
347
- return;
348
- }
689
+ if (r.getProductionName() === name) {
690
+ r.setProductionName("default");
691
+ syncComponentsToCss();
349
692
  }
350
- {
351
- const backupMatch = url.match(BACKUP_GET_REGEX);
352
- if (backupMatch && req.method === "GET") {
353
- const [, type, file] = backupMatch;
354
- const dir = type === "css" ? CSS_BACKUP_DIR : TOKENS_BACKUP_DIR;
355
- const filePath = import_path.default.join(dir, decodeURIComponent(file));
356
- if (!filePath.startsWith(dir) || !import_fs.default.existsSync(filePath)) {
357
- jsonResponse(res, 404, { error: "Backup not found" });
358
- return;
359
- }
360
- const content = import_fs.default.readFileSync(filePath, "utf-8");
361
- jsonResponse(res, 200, { content, type, file });
362
- return;
363
- }
693
+ }
694
+ jsonResponse(res, 200, { ok: true });
695
+ return;
696
+ }
697
+ }
698
+ async function handleListComponentConfigs({ params, res }) {
699
+ const [comp] = params;
700
+ const r = componentResource(comp);
701
+ if (!import_fs2.default.existsSync(r.dir)) {
702
+ jsonResponse(res, 404, { error: "Component not found" });
703
+ return;
704
+ }
705
+ const activeFile = r.getActiveName();
706
+ const productionFile = r.getProductionName();
707
+ const files = import_fs2.default.readdirSync(r.dir).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
708
+ const filePath = import_path2.default.join(r.dir, f);
709
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
710
+ const fileName = f.replace(".json", "");
711
+ return {
712
+ name: data.name || fileName,
713
+ fileName,
714
+ updatedAt: data.updatedAt || "",
715
+ isActive: fileName === activeFile,
716
+ isProduction: fileName === productionFile
717
+ };
718
+ });
719
+ jsonResponse(res, 200, { component: comp, files, activeFile, productionFile });
720
+ }
721
+ async function handleListPresets({ res }) {
722
+ const activeFile = presetsResource.getActiveName();
723
+ const files = import_fs2.default.readdirSync(PRESETS_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
724
+ const filePath = import_path2.default.join(PRESETS_DIR, f);
725
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
726
+ const fileName = f.replace(".json", "");
727
+ return {
728
+ name: data.name || fileName,
729
+ fileName,
730
+ updatedAt: data.updatedAt || "",
731
+ isActive: fileName === activeFile
732
+ };
733
+ });
734
+ jsonResponse(res, 200, { files, activeFile });
735
+ }
736
+ async function handleGetActivePreset({ res }) {
737
+ const activeFile = presetsResource.getActiveName();
738
+ const filePath = presetsResource.filePath(activeFile);
739
+ if (!import_fs2.default.existsSync(filePath)) {
740
+ jsonResponse(res, 404, { error: "Active preset not found" });
741
+ return;
742
+ }
743
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
744
+ data._fileName = activeFile;
745
+ jsonResponse(res, 200, data);
746
+ }
747
+ async function handleSetActivePreset({ req, res }) {
748
+ const body = JSON.parse(await readBody(req));
749
+ const fileName = sanitizeFileName(body.name || "default");
750
+ if (!import_fs2.default.existsSync(presetsResource.filePath(fileName))) {
751
+ jsonResponse(res, 404, { error: "Preset not found" });
752
+ return;
753
+ }
754
+ presetsResource.setActiveName(fileName);
755
+ jsonResponse(res, 200, { ok: true, activeFile: fileName });
756
+ }
757
+ async function handlePresetByName({ params, req, res }) {
758
+ const [fileName] = params;
759
+ const filePath = presetsResource.filePath(fileName);
760
+ if (req.method === "GET") {
761
+ if (!import_fs2.default.existsSync(filePath)) {
762
+ jsonResponse(res, 404, { error: "Not found" });
763
+ return;
764
+ }
765
+ const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
766
+ data._fileName = fileName;
767
+ jsonResponse(res, 200, data);
768
+ return;
769
+ }
770
+ if (req.method === "PUT") {
771
+ const body = JSON.parse(await readBody(req));
772
+ body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
773
+ if (import_fs2.default.existsSync(filePath)) {
774
+ try {
775
+ const existing = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
776
+ if (existing.createdAt) body.createdAt = existing.createdAt;
777
+ } catch {
364
778
  }
365
- if (url === CURRENT_CSS_ROUTE && req.method === "GET") {
366
- try {
367
- const content = import_fs.default.readFileSync(CSS_PATH, "utf-8");
368
- jsonResponse(res, 200, { content });
369
- } catch (err) {
370
- jsonResponse(res, 500, { error: err.message });
371
- }
372
- return;
779
+ }
780
+ if (!body.createdAt) body.createdAt = body.updatedAt;
781
+ import_fs2.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
782
+ jsonResponse(res, 200, { ok: true, fileName });
783
+ return;
784
+ }
785
+ if (req.method === "DELETE") {
786
+ if (fileName === "default") {
787
+ jsonResponse(res, 403, { error: "Cannot delete the default preset" });
788
+ return;
789
+ }
790
+ if (import_fs2.default.existsSync(filePath)) {
791
+ import_fs2.default.unlinkSync(filePath);
792
+ if (presetsResource.getActiveName() === fileName) {
793
+ presetsResource.setActiveName("default");
373
794
  }
374
- const match = url.match(TOKEN_BY_NAME_REGEX);
375
- if (match) {
376
- const fileName = match[1];
377
- const filePath = import_path.default.join(TOKENS_DIR, `${fileName}.json`);
378
- if (req.method === "GET") {
379
- try {
380
- if (!import_fs.default.existsSync(filePath)) {
381
- jsonResponse(res, 404, { error: "Not found" });
382
- return;
383
- }
384
- const data = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
385
- data._fileName = fileName;
386
- jsonResponse(res, 200, data);
387
- } catch (err) {
388
- jsonResponse(res, 500, { error: err.message });
389
- }
390
- return;
391
- }
392
- if (req.method === "PUT") {
393
- try {
394
- const body = JSON.parse(await readBody(req));
395
- body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
396
- if (import_fs.default.existsSync(filePath)) {
397
- try {
398
- const existing = JSON.parse(import_fs.default.readFileSync(filePath, "utf-8"));
399
- if (existing.createdAt) body.createdAt = existing.createdAt;
400
- } catch {
401
- }
402
- }
403
- if (!body.createdAt) body.createdAt = body.updatedAt;
404
- backupTokenFile(filePath, fileName);
405
- import_fs.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
406
- if (fileName === getProductionFileName()) {
407
- syncTokensToCss(fileName);
408
- }
409
- jsonResponse(res, 200, { ok: true, fileName });
410
- } catch (err) {
411
- jsonResponse(res, 500, { error: err.message });
412
- }
413
- return;
414
- }
415
- if (req.method === "DELETE") {
416
- if (fileName === "default") {
417
- jsonResponse(res, 403, { error: "Cannot delete the default token file" });
418
- return;
419
- }
420
- try {
421
- if (import_fs.default.existsSync(filePath)) {
422
- import_fs.default.unlinkSync(filePath);
423
- if (getActiveFileName() === fileName) {
424
- import_fs.default.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: "default" }));
425
- }
426
- }
427
- jsonResponse(res, 200, { ok: true });
428
- } catch (err) {
429
- jsonResponse(res, 500, { error: err.message });
430
- }
431
- return;
432
- }
433
- jsonResponse(res, 405, { error: "Method not allowed" });
434
- return;
795
+ }
796
+ jsonResponse(res, 200, { ok: true });
797
+ return;
798
+ }
799
+ }
800
+ async function handleApplyPreset({ params, res }) {
801
+ const [fileName] = params;
802
+ const presetPath = presetsResource.filePath(fileName);
803
+ if (!import_fs2.default.existsSync(presetPath)) {
804
+ jsonResponse(res, 404, { error: "Preset not found" });
805
+ return;
806
+ }
807
+ const preset = JSON.parse(import_fs2.default.readFileSync(presetPath, "utf-8"));
808
+ const themeName = sanitizeFileName(preset.theme || "default");
809
+ const themePath = themesResource.filePath(themeName);
810
+ if (!import_fs2.default.existsSync(themePath)) {
811
+ jsonResponse(res, 422, { error: `Preset references missing theme: ${themeName}` });
812
+ return;
813
+ }
814
+ const knownComponents = new Set(listComponentNames());
815
+ const componentConfigs = preset.componentConfigs ?? {};
816
+ const resolvedConfigs = {};
817
+ const apply = [];
818
+ for (const [comp, configFile] of Object.entries(componentConfigs)) {
819
+ if (!knownComponents.has(comp)) continue;
820
+ const sanitized = sanitizeFileName(String(configFile) || "default");
821
+ const r = componentResource(comp);
822
+ const cfgPath = r.filePath(sanitized);
823
+ if (!import_fs2.default.existsSync(cfgPath)) {
824
+ jsonResponse(res, 422, {
825
+ error: `Preset references missing config: ${comp}/${sanitized}`
826
+ });
827
+ return;
828
+ }
829
+ apply.push([comp, sanitized]);
830
+ }
831
+ themesResource.setActiveName(themeName);
832
+ const themeData = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
833
+ themeData._fileName = themeName;
834
+ for (const [comp, configFile] of apply) {
835
+ const r = componentResource(comp);
836
+ r.setActiveName(configFile);
837
+ const cfg = readComponentConfig(comp, configFile);
838
+ if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
839
+ }
840
+ for (const comp of knownComponents) {
841
+ if (resolvedConfigs[comp]) continue;
842
+ const activeName = componentResource(comp).getActiveName();
843
+ const cfg = readComponentConfig(comp, activeName);
844
+ if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: activeName };
845
+ }
846
+ presetsResource.setActiveName(fileName);
847
+ jsonResponse(res, 200, {
848
+ ok: true,
849
+ preset: { ...preset, _fileName: fileName },
850
+ theme: themeData,
851
+ componentConfigs: resolvedConfigs
852
+ });
853
+ }
854
+ function methodNotAllowed({ res }) {
855
+ jsonResponse(res, 405, { error: "Method not allowed" });
856
+ }
857
+ const routes = [
858
+ // Themes — list / active / production are exact strings, must run before THEME_BY_NAME_REGEX
859
+ { method: "GET", pattern: THEMES_ROUTE, handler: handleListThemes },
860
+ { method: "GET", pattern: THEMES_ACTIVE_ROUTE, handler: handleGetActiveTheme },
861
+ { method: "PUT", pattern: THEMES_ACTIVE_ROUTE, handler: handleSetActiveTheme },
862
+ { method: "GET", pattern: THEMES_PRODUCTION_ROUTE, handler: handleGetProductionTheme },
863
+ { method: "PUT", pattern: THEMES_PRODUCTION_ROUTE, handler: handleSetProductionTheme },
864
+ // Component configs — list of components
865
+ { method: "GET", pattern: COMPONENT_CONFIGS_ROUTE, handler: handleListComponents },
866
+ // Component configs — :comp/active (must precede :comp/:name)
867
+ { method: "GET", pattern: COMP_ACTIVE_REGEX, handler: handleGetComponentActive },
868
+ { method: "PUT", pattern: COMP_ACTIVE_REGEX, handler: handleSetComponentActive },
869
+ { method: "POST", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
870
+ { method: "DELETE", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
871
+ // Component configs — :comp/production (must precede :comp/:name)
872
+ { method: "GET", pattern: COMP_PRODUCTION_REGEX, handler: handleGetComponentProduction },
873
+ { method: "PUT", pattern: COMP_PRODUCTION_REGEX, handler: handleSetComponentProduction },
874
+ { method: "POST", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
875
+ { method: "DELETE", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
876
+ // Component configs — :comp/:name (must precede :comp listing)
877
+ { method: "GET", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
878
+ { method: "PUT", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
879
+ { method: "DELETE", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
880
+ { method: "POST", pattern: COMP_BY_NAME_REGEX, handler: methodNotAllowed },
881
+ // Component configs — list configs for :comp (broadest, runs last among comp routes)
882
+ { method: "GET", pattern: COMP_LIST_REGEX, handler: handleListComponentConfigs },
883
+ // Themes — :name CRUD (broadest theme route, runs last)
884
+ { method: "GET", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
885
+ { method: "PUT", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
886
+ { method: "DELETE", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
887
+ // Presets — list / active are exact strings, must run before regexes
888
+ { method: "GET", pattern: PRESETS_ROUTE, handler: handleListPresets },
889
+ { method: "GET", pattern: PRESETS_ACTIVE_ROUTE, handler: handleGetActivePreset },
890
+ { method: "PUT", pattern: PRESETS_ACTIVE_ROUTE, handler: handleSetActivePreset },
891
+ // Presets — :name/apply (more specific than :name)
892
+ { method: "PUT", pattern: PRESET_APPLY_REGEX, handler: handleApplyPreset },
893
+ { method: "POST", pattern: PRESET_APPLY_REGEX, handler: methodNotAllowed },
894
+ { method: "GET", pattern: PRESET_APPLY_REGEX, handler: methodNotAllowed },
895
+ { method: "DELETE", pattern: PRESET_APPLY_REGEX, handler: methodNotAllowed },
896
+ // Presets — :name CRUD (broadest preset route, runs last)
897
+ { method: "GET", pattern: PRESET_BY_NAME_REGEX, handler: handlePresetByName },
898
+ { method: "PUT", pattern: PRESET_BY_NAME_REGEX, handler: handlePresetByName },
899
+ { method: "DELETE", pattern: PRESET_BY_NAME_REGEX, handler: handlePresetByName }
900
+ ];
901
+ return {
902
+ name: "theme-file-api",
903
+ config() {
904
+ return {
905
+ define: {
906
+ __PROJECT_ROOT__: JSON.stringify(process.cwd())
435
907
  }
436
- next();
908
+ };
909
+ },
910
+ configureServer(server) {
911
+ ensureThemesDir();
912
+ ensureComponentConfigsDir();
913
+ ensurePresetsDir();
914
+ server.middlewares.use(async (req, res, next) => {
915
+ const handled = await dispatch(req, res, routes);
916
+ if (!handled) next();
437
917
  });
918
+ },
919
+ handleHotUpdate(ctx) {
920
+ const normalized = import_path2.default.resolve(ctx.file);
921
+ if (!normalized.startsWith(COMPONENTS_SRC_DIR)) return;
922
+ if (!normalized.endsWith(".svelte")) return;
923
+ const comp = componentNameFromFile(normalized);
924
+ generateDefaultConfig(comp, normalized);
438
925
  }
439
926
  };
440
927
  }
441
928
  // Annotate the CommonJS export names for ESM import in node:
442
929
  0 && (module.exports = {
443
- tokenFileApi
930
+ themeFileApi
444
931
  });