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