@motion-proto/live-tokens 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +68 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +2 -2
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +152 -0
- package/CHANGELOG.md +292 -0
- package/README.md +37 -26
- package/bin/adjust.mjs +254 -0
- package/bin/cli.mjs +94 -7
- package/bin/generate-theme.mjs +251 -0
- package/bin/migrate.mjs +95 -6
- package/dist-plugin/adjust/index.cjs +259 -0
- package/dist-plugin/adjust/index.d.cts +45 -0
- package/dist-plugin/adjust/index.d.ts +45 -0
- package/dist-plugin/adjust/index.js +176 -0
- package/dist-plugin/{chunk-H4TRUINI.js → chunk-44RSTAII.js} +0 -48
- package/dist-plugin/chunk-6OZFXIQI.js +316 -0
- package/dist-plugin/chunk-76TFDTJO.js +556 -0
- package/dist-plugin/chunk-D3ZVKOR4.js +52 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.cts +54 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.ts +54 -0
- package/dist-plugin/generateColorsAndType/index.cjs +1781 -0
- package/dist-plugin/generateColorsAndType/index.d.cts +81 -0
- package/dist-plugin/generateColorsAndType/index.d.ts +81 -0
- package/dist-plugin/generateColorsAndType/index.js +1210 -0
- package/dist-plugin/index.cjs +901 -533
- package/dist-plugin/index.d.cts +3 -2
- package/dist-plugin/index.d.ts +3 -2
- package/dist-plugin/index.js +658 -1082
- package/dist-plugin/migrateData/index.cjs +728 -0
- package/dist-plugin/migrateData/index.d.cts +60 -0
- package/dist-plugin/migrateData/index.d.ts +60 -0
- package/dist-plugin/migrateData/index.js +348 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.cts +211 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.ts +211 -0
- package/dist-plugin/tokensCssMigrations/index.cjs +2 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +3 -15
- package/dist-plugin/tokensCssMigrations/index.d.ts +3 -15
- package/dist-plugin/tokensCssMigrations/index.js +4 -2
- package/package.json +18 -4
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +183 -158
- package/src/editor/component-editor/scaffolding/ComponentFileMenu.svelte +0 -4
- package/src/editor/component-editor/scaffolding/SaveAsDialog.svelte +4 -4
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +2 -59
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +1 -1
- package/src/editor/core/components/adjustAliases.ts +180 -0
- package/src/editor/core/components/aliasKinds.ts +73 -0
- package/src/editor/core/components/componentConfigService.ts +26 -38
- package/src/editor/core/flashStatus.ts +1 -1
- package/src/editor/core/fonts/fontMigration.ts +13 -13
- package/src/editor/core/fonts/fontPairing.ts +35 -0
- package/src/editor/core/palettes/paletteDerivation.ts +128 -16
- package/src/editor/core/preview/lookPreview.ts +141 -0
- package/src/editor/core/productionPulse.ts +34 -21
- package/src/editor/core/storage/files/versionedFileResourceClient.ts +18 -45
- package/src/editor/core/store/editorConfigStore.ts +2 -2
- package/src/editor/core/store/editorPersistence.ts +0 -1
- package/src/editor/core/store/editorStore.ts +114 -51
- package/src/editor/core/store/editorTypes.ts +0 -1
- package/src/editor/core/store/gradientSource.ts +2 -2
- package/src/editor/core/themes/colorsAndTypeService.ts +95 -0
- package/src/editor/core/themes/generateColorsAndType.ts +433 -0
- package/src/editor/core/themes/loadRows.ts +64 -0
- package/src/editor/core/themes/lookSummary.ts +75 -0
- package/src/editor/core/themes/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +3 -3
- package/src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts +2 -2
- package/src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts +3 -3
- package/src/editor/core/themes/migrations/2026-08-13-drop-legacy-shape-space-keys.ts +38 -0
- package/src/editor/core/themes/migrations/2026-08-15-line-height-scale-rename.ts +57 -0
- package/src/editor/core/themes/migrations/index.ts +19 -11
- package/src/editor/core/themes/slices/components.ts +4 -4
- package/src/editor/core/themes/slices/fonts.ts +1 -1
- package/src/editor/core/themes/slices/gradients.ts +22 -0
- package/src/editor/core/themes/slices/palettes.ts +2 -2
- package/src/editor/core/themes/themeInit.ts +21 -29
- package/src/editor/core/themes/themeService.ts +218 -78
- package/src/editor/core/themes/themeTypes.ts +94 -51
- package/src/editor/docs/Docs.svelte +1 -0
- package/src/editor/docs/chapters.ts +1 -0
- package/src/editor/docs/content/01-overview.md +1 -1
- package/src/editor/docs/content/editing-tokens.md +6 -6
- package/src/editor/docs/content/getting-started.md +9 -7
- package/src/editor/docs/content/themes-workflow.md +74 -34
- package/src/editor/docs/content/where-themes-live.md +61 -0
- package/src/editor/docs/content.generated.ts +5 -4
- package/src/editor/index.ts +27 -27
- package/src/editor/pages/ComponentEditorPage.svelte +2 -2
- package/src/editor/pages/EditorShell.svelte +4 -30
- package/src/editor/ui/BezierCurveEditor.svelte +2 -2
- package/src/editor/ui/ColorEditPanel.svelte +1 -1
- package/src/editor/ui/FileLoadList.svelte +67 -7
- package/src/editor/ui/GradientEditor.svelte +3 -3
- package/src/editor/ui/PaletteEditor.svelte +26 -12
- package/src/editor/ui/ThemePanel.svelte +1067 -0
- package/src/editor/ui/UIDialog.svelte +11 -9
- package/src/editor/ui/UIPillButton.svelte +7 -2
- package/src/editor/ui/colors/ColorWheel.svelte +34 -1
- package/src/editor/ui/curveEngine.ts +48 -0
- package/src/editor/ui/index.ts +4 -2
- package/src/editor/ui/palette/PaletteBase.svelte +75 -51
- package/src/editor/ui/palette/PaletteJumpButton.svelte +3 -8
- package/src/live-tokens/data/colors-and-type/autumn.json +2490 -0
- package/src/live-tokens/data/{themes → colors-and-type}/default.json +816 -293
- package/src/live-tokens/data/colors-and-type/halloween.json +2529 -0
- package/src/live-tokens/data/colors-and-type/midnight-study.json +2536 -0
- package/src/live-tokens/data/colors-and-type/ocean.json +2489 -0
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +2520 -0
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +2476 -0
- package/src/live-tokens/data/colors-and-type/sunset.json +2528 -0
- package/src/live-tokens/data/themes/autumn.json +3918 -0
- package/src/live-tokens/data/themes/halloween.json +3992 -0
- package/src/live-tokens/data/themes/midnight-study.json +3932 -0
- package/src/live-tokens/data/themes/ocean.json +3917 -0
- package/src/live-tokens/data/themes/royal-velvet.json +3983 -0
- package/src/live-tokens/data/themes/spring-meadow.json +3904 -0
- package/src/live-tokens/data/themes/sunset.json +3931 -0
- package/src/live-tokens/data/tokens.generated.css +106 -117
- package/src/system/styles/CONVENTIONS.md +1 -1
- package/src/system/styles/fonts.css +1 -1
- package/template/README.md +7 -5
- package/template/_gitignore +0 -6
- package/src/editor/core/components/componentPersist.ts +0 -62
- package/src/editor/core/manifests/manifestService.ts +0 -172
- package/src/editor/ui/ManifestFileManager.svelte +0 -446
- package/src/editor/ui/ThemeFileManager.svelte +0 -785
- package/src/live-tokens/data/manifests/default.json +0 -35
package/dist-plugin/index.js
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hexToOklch,
|
|
3
|
+
palettesToVars,
|
|
4
|
+
reconcilePalettesFromCssVars,
|
|
5
|
+
sanitizeFileName
|
|
6
|
+
} from "./chunk-76TFDTJO.js";
|
|
7
|
+
import {
|
|
8
|
+
THEME_SCHEMA_VERSION,
|
|
9
|
+
detectLegacyLayout,
|
|
10
|
+
isColorsAndTypeShaped,
|
|
11
|
+
nextAvailableName,
|
|
12
|
+
normalizeTheme,
|
|
13
|
+
versionedFileResourceServer
|
|
14
|
+
} from "./chunk-6OZFXIQI.js";
|
|
1
15
|
import {
|
|
2
16
|
TOKENS_CSS_MIGRATIONS,
|
|
3
17
|
extractGlobalRootBody,
|
|
4
|
-
resolveDataDirs,
|
|
5
18
|
runAdditiveTokensCssMigrations,
|
|
6
19
|
runTokensCssMigrations,
|
|
7
20
|
validateTokensCss
|
|
8
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-44RSTAII.js";
|
|
22
|
+
import {
|
|
23
|
+
resolveDataDirs
|
|
24
|
+
} from "./chunk-D3ZVKOR4.js";
|
|
9
25
|
|
|
10
26
|
// vite-plugin/themeFileApi.ts
|
|
11
|
-
import
|
|
12
|
-
import
|
|
27
|
+
import fs from "fs";
|
|
28
|
+
import path from "path";
|
|
13
29
|
|
|
14
30
|
// src/editor/core/themes/parsers/colorOpacity.ts
|
|
15
31
|
var COLOR_OPACITY_RE = /^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+)%,\s*transparent\)$/i;
|
|
@@ -19,459 +35,6 @@ function parseColorOpacity(value) {
|
|
|
19
35
|
return { name: m[1], opacity: parseInt(m[2], 10) };
|
|
20
36
|
}
|
|
21
37
|
|
|
22
|
-
// src/editor/core/storage/files/versionedFileResourceClient.ts
|
|
23
|
-
function sanitizeFileName(name) {
|
|
24
|
-
return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// src/editor/core/palettes/oklch.ts
|
|
28
|
-
function srgbToLinear(c) {
|
|
29
|
-
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
30
|
-
}
|
|
31
|
-
function linearToSrgb(c) {
|
|
32
|
-
return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
33
|
-
}
|
|
34
|
-
function hexToLinearRgb(hex) {
|
|
35
|
-
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
36
|
-
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
37
|
-
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
38
|
-
return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
|
|
39
|
-
}
|
|
40
|
-
function linearRgbToHex(r, g, b) {
|
|
41
|
-
const toHex = (c) => {
|
|
42
|
-
const v = Math.round(Math.max(0, Math.min(1, linearToSrgb(c))) * 255);
|
|
43
|
-
return v.toString(16).padStart(2, "0");
|
|
44
|
-
};
|
|
45
|
-
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
46
|
-
}
|
|
47
|
-
function linearRgbToOklab(r, g, b) {
|
|
48
|
-
const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
49
|
-
const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
50
|
-
const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
51
|
-
const l = Math.cbrt(l_);
|
|
52
|
-
const m = Math.cbrt(m_);
|
|
53
|
-
const s = Math.cbrt(s_);
|
|
54
|
-
return [
|
|
55
|
-
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
56
|
-
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
57
|
-
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
58
|
-
];
|
|
59
|
-
}
|
|
60
|
-
function oklabToLinearRgb(L, a, b) {
|
|
61
|
-
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
62
|
-
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
63
|
-
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
64
|
-
const l = l_ * l_ * l_;
|
|
65
|
-
const m = m_ * m_ * m_;
|
|
66
|
-
const s = s_ * s_ * s_;
|
|
67
|
-
return [
|
|
68
|
-
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
69
|
-
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
70
|
-
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
71
|
-
];
|
|
72
|
-
}
|
|
73
|
-
function oklabToOklch(L, a, b) {
|
|
74
|
-
const c = Math.sqrt(a * a + b * b);
|
|
75
|
-
let h = Math.atan2(b, a) * 180 / Math.PI;
|
|
76
|
-
if (h < 0) h += 360;
|
|
77
|
-
return { l: L, c, h };
|
|
78
|
-
}
|
|
79
|
-
function oklchToOklab(l, c, h) {
|
|
80
|
-
const hRad = h * Math.PI / 180;
|
|
81
|
-
return [l, c * Math.cos(hRad), c * Math.sin(hRad)];
|
|
82
|
-
}
|
|
83
|
-
function hexToOklch(hex) {
|
|
84
|
-
const [r, g, b] = hexToLinearRgb(hex);
|
|
85
|
-
const [L, a, bVal] = linearRgbToOklab(r, g, b);
|
|
86
|
-
return oklabToOklch(L, a, bVal);
|
|
87
|
-
}
|
|
88
|
-
function oklchToHex(l, c, h) {
|
|
89
|
-
const [L, a, b] = oklchToOklab(l, c, h);
|
|
90
|
-
const [r, g, bVal] = oklabToLinearRgb(L, a, b);
|
|
91
|
-
return linearRgbToHex(r, g, bVal);
|
|
92
|
-
}
|
|
93
|
-
function isInGamut(r, g, b) {
|
|
94
|
-
const eps = 1e-4;
|
|
95
|
-
return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;
|
|
96
|
-
}
|
|
97
|
-
function oklchToHexClamped(l, c, h) {
|
|
98
|
-
const g = gamutClamp(l, c, h);
|
|
99
|
-
return oklchToHex(g.l, g.c, g.h);
|
|
100
|
-
}
|
|
101
|
-
function gamutClamp(l, c, h) {
|
|
102
|
-
if (l <= 0) return { l: 0, c: 0, h };
|
|
103
|
-
if (l >= 1) return { l: 1, c: 0, h };
|
|
104
|
-
const [L, a, b] = oklchToOklab(l, c, h);
|
|
105
|
-
const [r, g, bVal] = oklabToLinearRgb(L, a, b);
|
|
106
|
-
if (isInGamut(r, g, bVal)) return { l, c, h };
|
|
107
|
-
let lo = 0;
|
|
108
|
-
let hi = c;
|
|
109
|
-
for (let i = 0; i < 20; i++) {
|
|
110
|
-
const mid = (lo + hi) / 2;
|
|
111
|
-
const [La, aa, ba] = oklchToOklab(l, mid, h);
|
|
112
|
-
const [rr, gg, bb] = oklabToLinearRgb(La, aa, ba);
|
|
113
|
-
if (isInGamut(rr, gg, bb)) {
|
|
114
|
-
lo = mid;
|
|
115
|
-
} else {
|
|
116
|
-
hi = mid;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return { l, c: lo, h };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// src/editor/ui/curveEngine.ts
|
|
123
|
-
function makeAnchor(x, y, tangentLen = 15) {
|
|
124
|
-
return { x, y, inDx: -tangentLen, inDy: 0, outDx: tangentLen, outDy: 0 };
|
|
125
|
-
}
|
|
126
|
-
function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
|
|
127
|
-
const u = 1 - t, u2 = u * u, u3 = u2 * u;
|
|
128
|
-
const t2 = t * t, t3 = t2 * t;
|
|
129
|
-
return {
|
|
130
|
-
x: u3 * p0x + 3 * u2 * t * c0x + 3 * u * t2 * c1x + t3 * p1x,
|
|
131
|
-
y: u3 * p0y + 3 * u2 * t * c0y + 3 * u * t2 * c1y + t3 * p1y
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
function sampleCurve(anchors, xPos) {
|
|
135
|
-
if (anchors.length === 0) return 0;
|
|
136
|
-
if (anchors.length === 1) return anchors[0].y;
|
|
137
|
-
if (xPos <= anchors[0].x) return anchors[0].y;
|
|
138
|
-
if (xPos >= anchors[anchors.length - 1].x) return anchors[anchors.length - 1].y;
|
|
139
|
-
let seg = 0;
|
|
140
|
-
while (seg < anchors.length - 2 && anchors[seg + 1].x < xPos) seg++;
|
|
141
|
-
const a0 = anchors[seg], a1 = anchors[seg + 1];
|
|
142
|
-
const p0x = a0.x, p0y = a0.y;
|
|
143
|
-
const c0x = a0.x + a0.outDx, c0y = a0.y + a0.outDy;
|
|
144
|
-
const c1x = a1.x + a1.inDx, c1y = a1.y + a1.inDy;
|
|
145
|
-
const p1x = a1.x, p1y = a1.y;
|
|
146
|
-
let lo = 0, hi = 1;
|
|
147
|
-
for (let i = 0; i < 20; i++) {
|
|
148
|
-
const mid = (lo + hi) / 2;
|
|
149
|
-
const pt = evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, mid);
|
|
150
|
-
if (pt.x < xPos) lo = mid;
|
|
151
|
-
else hi = mid;
|
|
152
|
-
}
|
|
153
|
-
return evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, (lo + hi) / 2).y;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// src/editor/core/palettes/paletteDerivation.ts
|
|
157
|
-
var PALETTE_SPECS = [
|
|
158
|
-
{ label: "Brand", cssNamespace: "brand", initialColor: { l: 0.5573, c: 0.1841, h: 25.49 } },
|
|
159
|
-
{ label: "Accent", cssNamespace: "accent", initialColor: { l: 0.7674, c: 0.164, h: 70.44 } },
|
|
160
|
-
{ label: "Special", cssNamespace: "special", initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
|
|
161
|
-
{ label: "Canvas", cssNamespace: "canvas", emptySelector: true, initialColor: { l: 0.2284, c: 0.0384, h: 282.93 } },
|
|
162
|
-
{ label: "Neutral", cssNamespace: "neutral", neutral: true, initialColor: { l: 0.5679, c: 0.0134, h: 240.07 } },
|
|
163
|
-
{ label: "Alternate", cssNamespace: "alternate", neutral: true, displayLabel: "Alternate (neutral)", initialColor: { l: 0.5873, c: 87e-4, h: 48.57 } },
|
|
164
|
-
{ label: "Info", cssNamespace: "info", initialColor: { l: 0.5871, c: 0.1855, h: 259.56 } },
|
|
165
|
-
{ label: "Success", cssNamespace: "success", initialColor: { l: 0.7198, c: 0.1918, h: 149.52 } },
|
|
166
|
-
{ label: "Warning", cssNamespace: "warning", initialColor: { l: 0.6704, c: 0.1716, h: 48.75 } },
|
|
167
|
-
{ label: "Danger", cssNamespace: "danger", initialColor: { l: 0.6103, c: 0.2165, h: 18.1 } }
|
|
168
|
-
];
|
|
169
|
-
var PALETTE_STEPS = [
|
|
170
|
-
{ label: "100" },
|
|
171
|
-
{ label: "200" },
|
|
172
|
-
{ label: "300" },
|
|
173
|
-
{ label: "400" },
|
|
174
|
-
{ label: "500" },
|
|
175
|
-
{ label: "600" },
|
|
176
|
-
{ label: "700" },
|
|
177
|
-
{ label: "800" },
|
|
178
|
-
{ label: "850" },
|
|
179
|
-
{ label: "900" },
|
|
180
|
-
{ label: "950" }
|
|
181
|
-
];
|
|
182
|
-
var SCALES = [
|
|
183
|
-
{
|
|
184
|
-
title: "Surfaces",
|
|
185
|
-
isText: false,
|
|
186
|
-
steps: [
|
|
187
|
-
{ name: "lowest", position: -1 },
|
|
188
|
-
{ name: "lower", position: -2 / 3 },
|
|
189
|
-
{ name: "low", position: -1 / 3 },
|
|
190
|
-
{ name: "default", position: 0 },
|
|
191
|
-
{ name: "high", position: 1 / 3 },
|
|
192
|
-
{ name: "higher", position: 2 / 3 },
|
|
193
|
-
{ name: "highest", position: 1 }
|
|
194
|
-
]
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
title: "Borders",
|
|
198
|
-
isText: false,
|
|
199
|
-
steps: [
|
|
200
|
-
{ name: "faint", position: -1 },
|
|
201
|
-
{ name: "subtle", position: -0.5 },
|
|
202
|
-
{ name: "default", position: 0 },
|
|
203
|
-
{ name: "medium", position: 0.5 },
|
|
204
|
-
{ name: "strong", position: 1 }
|
|
205
|
-
]
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
title: "Text",
|
|
209
|
-
isText: true,
|
|
210
|
-
steps: [
|
|
211
|
-
{ name: "primary", position: 0 },
|
|
212
|
-
{ name: "secondary", position: 0 },
|
|
213
|
-
{ name: "tertiary", position: 0 },
|
|
214
|
-
{ name: "muted", position: 0 },
|
|
215
|
-
{ name: "disabled", position: 0 }
|
|
216
|
-
]
|
|
217
|
-
}
|
|
218
|
-
];
|
|
219
|
-
var BW_GUARD_MIN_L = 0.17;
|
|
220
|
-
var BW_GUARD_MAX_L = 0.93;
|
|
221
|
-
var DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
|
|
222
|
-
var DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
|
|
223
|
-
function scaleCurveDefaults(scheme = "dark") {
|
|
224
|
-
if (scheme === "light") {
|
|
225
|
-
return {
|
|
226
|
-
Surfaces: {
|
|
227
|
-
lightness: () => [makeAnchor(0, 98, 5), makeAnchor(100, 82, 5)],
|
|
228
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
229
|
-
},
|
|
230
|
-
Borders: {
|
|
231
|
-
lightness: () => [makeAnchor(0, 92, 5), makeAnchor(100, 45, 5)],
|
|
232
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
233
|
-
},
|
|
234
|
-
Text: {
|
|
235
|
-
lightness: () => [makeAnchor(0, 30, 30), makeAnchor(100, 120, 30)],
|
|
236
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
return {
|
|
241
|
-
Surfaces: {
|
|
242
|
-
lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
|
|
243
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
244
|
-
},
|
|
245
|
-
Borders: {
|
|
246
|
-
lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
|
|
247
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
248
|
-
},
|
|
249
|
-
Text: {
|
|
250
|
-
lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
|
|
251
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
var defaultScaleCurves = scaleCurveDefaults("dark");
|
|
256
|
-
function paletteStepKey(label) {
|
|
257
|
-
return `Palette-${label}`;
|
|
258
|
-
}
|
|
259
|
-
function stepKey(scaleTitle, stepName) {
|
|
260
|
-
return `${scaleTitle}-${stepName}`;
|
|
261
|
-
}
|
|
262
|
-
function stepIndexToX(index) {
|
|
263
|
-
return index / (PALETTE_STEPS.length - 1) * 100;
|
|
264
|
-
}
|
|
265
|
-
function scaleStepToX(step, scale) {
|
|
266
|
-
const idx = scale.steps.indexOf(step);
|
|
267
|
-
return scale.steps.length > 1 ? idx / (scale.steps.length - 1) * 100 : 50;
|
|
268
|
-
}
|
|
269
|
-
function serializeDerivedValue(value) {
|
|
270
|
-
return value.kind === "raw" ? value.css : oklchToHexClamped(value.l, value.c, value.h);
|
|
271
|
-
}
|
|
272
|
-
function computePaletteOklch(index, base, lightnessCurve, saturationCurve, curveOffset) {
|
|
273
|
-
const { c: baseC, h } = base;
|
|
274
|
-
const xPos = stepIndexToX(index);
|
|
275
|
-
const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset.lightness ?? 0))) / 100;
|
|
276
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset.saturation ?? 0)) / 100));
|
|
277
|
-
const targetC = baseC * satMul;
|
|
278
|
-
return gamutClamp(targetL, targetC, h);
|
|
279
|
-
}
|
|
280
|
-
function nearestPaletteStep(lightnessCurve, baseL) {
|
|
281
|
-
let best = 0;
|
|
282
|
-
let bestD = Infinity;
|
|
283
|
-
for (let i = 0; i < PALETTE_STEPS.length; i++) {
|
|
284
|
-
const d = Math.abs(sampleCurve(lightnessCurve, stepIndexToX(i)) - baseL * 100);
|
|
285
|
-
if (d < bestD) {
|
|
286
|
-
bestD = d;
|
|
287
|
-
best = i;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return best;
|
|
291
|
-
}
|
|
292
|
-
function setCurveAnchor(curve, x, y) {
|
|
293
|
-
const existing = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
294
|
-
if (existing >= 0) {
|
|
295
|
-
const displacedY = curve[existing].y;
|
|
296
|
-
return { curve: curve.map((a, i) => i === existing ? { ...a, x, y } : a), displacedY };
|
|
297
|
-
}
|
|
298
|
-
let insertAt = curve.findIndex((a) => a.x > x);
|
|
299
|
-
if (insertAt < 0) insertAt = curve.length;
|
|
300
|
-
return { curve: [...curve.slice(0, insertAt), makeAnchor(x, y, 15), ...curve.slice(insertAt)] };
|
|
301
|
-
}
|
|
302
|
-
function liftCurveAnchor(curve, x, displacedY) {
|
|
303
|
-
const idx = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
304
|
-
if (idx < 0) return curve;
|
|
305
|
-
if (displacedY !== void 0) return curve.map((a, i) => i === idx ? { ...a, y: displacedY } : a);
|
|
306
|
-
if (idx === 0 || idx === curve.length - 1) return curve;
|
|
307
|
-
return curve.filter((_, i) => i !== idx);
|
|
308
|
-
}
|
|
309
|
-
function syncBaseAnchor(cfg) {
|
|
310
|
-
if (cfg.anchorToBase === false) return;
|
|
311
|
-
const prev = cfg.anchorPlacement;
|
|
312
|
-
let lCurve = cfg.lightnessCurve;
|
|
313
|
-
let sCurve = cfg.saturationCurve;
|
|
314
|
-
if (prev) {
|
|
315
|
-
const prevX = stepIndexToX(prev.step);
|
|
316
|
-
lCurve = liftCurveAnchor(lCurve, prevX, prev.displacedL);
|
|
317
|
-
sCurve = liftCurveAnchor(sCurve, prevX, prev.displacedS);
|
|
318
|
-
}
|
|
319
|
-
const step = nearestPaletteStep(lCurve, cfg.baseColor.l);
|
|
320
|
-
const x = stepIndexToX(step);
|
|
321
|
-
const l = setCurveAnchor(lCurve, x, cfg.baseColor.l * 100);
|
|
322
|
-
const s = setCurveAnchor(sCurve, x, 100);
|
|
323
|
-
cfg.lightnessCurve = l.curve;
|
|
324
|
-
cfg.saturationCurve = s.curve;
|
|
325
|
-
cfg.anchorPlacement = { step, displacedL: l.displacedY, displacedS: s.displacedY };
|
|
326
|
-
}
|
|
327
|
-
function computeDerivedOklch(step, base, scaleTitle, scaleCurves, curveOffset) {
|
|
328
|
-
const scale = SCALES.find((s) => s.title === scaleTitle);
|
|
329
|
-
const xPos = scaleStepToX(step, scale);
|
|
330
|
-
const defs = defaultScaleCurves[scaleTitle];
|
|
331
|
-
const lCurve = scaleCurves[scaleTitle]?.lightness ?? defs.lightness();
|
|
332
|
-
const sCurve = scaleCurves[scaleTitle]?.saturation ?? defs.saturation();
|
|
333
|
-
const lOff = curveOffset[`${scaleTitle}-lightness`] ?? 0;
|
|
334
|
-
const sOff = curveOffset[`${scaleTitle}-saturation`] ?? 0;
|
|
335
|
-
const { l: baseL, c: baseC, h: baseH } = base;
|
|
336
|
-
let targetL;
|
|
337
|
-
if (scale.isText) {
|
|
338
|
-
const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
|
|
339
|
-
targetL = Math.max(0, Math.min(1, baseL * lMul));
|
|
340
|
-
} else {
|
|
341
|
-
targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
|
|
342
|
-
}
|
|
343
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
|
|
344
|
-
const targetC = baseC * satMul;
|
|
345
|
-
return gamutClamp(targetL, targetC, baseH);
|
|
346
|
-
}
|
|
347
|
-
function scaleToCssVar(scaleTitle, stepName, cssNamespace) {
|
|
348
|
-
if (cssNamespace === null) return null;
|
|
349
|
-
if (scaleTitle === "Surfaces") {
|
|
350
|
-
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
351
|
-
return cssNamespace === "neutral" ? `--surface-neutral${suffix}` : `--surface-${cssNamespace}${suffix}`;
|
|
352
|
-
}
|
|
353
|
-
if (scaleTitle === "Borders") {
|
|
354
|
-
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
355
|
-
return cssNamespace === "neutral" ? `--border-neutral${suffix}` : `--border-${cssNamespace}${suffix}`;
|
|
356
|
-
}
|
|
357
|
-
if (scaleTitle === "Text") {
|
|
358
|
-
if (cssNamespace === "neutral") return `--text-${stepName}`;
|
|
359
|
-
return stepName === "primary" ? `--text-${cssNamespace}` : `--text-${cssNamespace}-${stepName}`;
|
|
360
|
-
}
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
function derivePaletteValues(spec, config) {
|
|
364
|
-
const out = {};
|
|
365
|
-
if (!config) return out;
|
|
366
|
-
const baseColor = config.baseColor ?? spec.initialColor;
|
|
367
|
-
const overrides = config.overrides ?? {};
|
|
368
|
-
const curveOffset = config.curveOffset ?? {};
|
|
369
|
-
const scaleCurves = config.scaleCurves ?? {};
|
|
370
|
-
const lightnessCurve = config.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS();
|
|
371
|
-
const saturationCurve = config.saturationCurve ?? DEFAULT_PALETTE_SATURATION();
|
|
372
|
-
PALETTE_STEPS.forEach((ps, index) => {
|
|
373
|
-
const k = paletteStepKey(ps.label);
|
|
374
|
-
const value = k in overrides ? { kind: "color", ...overrides[k] } : { kind: "color", ...computePaletteOklch(index, baseColor, lightnessCurve, saturationCurve, curveOffset) };
|
|
375
|
-
out[`--color-${spec.cssNamespace}-${ps.label}`] = value;
|
|
376
|
-
});
|
|
377
|
-
for (const scale of SCALES) {
|
|
378
|
-
for (const step of scale.steps) {
|
|
379
|
-
const k = stepKey(scale.title, step.name);
|
|
380
|
-
const value = k in overrides ? { kind: "color", ...overrides[k] } : { kind: "color", ...computeDerivedOklch(step, baseColor, scale.title, scaleCurves, curveOffset) };
|
|
381
|
-
const varName = scaleToCssVar(scale.title, step.name, spec.cssNamespace);
|
|
382
|
-
if (varName) out[varName] = value;
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
const textPrimary = out["--text-primary"];
|
|
386
|
-
if (textPrimary?.kind === "color") {
|
|
387
|
-
const mirroredL = Math.max(BW_GUARD_MIN_L, Math.min(BW_GUARD_MAX_L, 1 - textPrimary.l));
|
|
388
|
-
out["--text-inverted"] = { kind: "color", ...gamutClamp(mirroredL, textPrimary.c, textPrimary.h) };
|
|
389
|
-
}
|
|
390
|
-
if (spec.emptySelector) {
|
|
391
|
-
const emptyMode = config.emptyMode ?? "solid";
|
|
392
|
-
const gradientStyle = config.gradientStyle ?? "linear";
|
|
393
|
-
const gradientAngle = config.gradientAngle ?? 180;
|
|
394
|
-
const gradientReverse = config.gradientReverse ?? false;
|
|
395
|
-
const gradientSize = config.gradientSize ?? "page";
|
|
396
|
-
const gradientStops = config.gradientStops ?? [
|
|
397
|
-
{ position: 0, paletteLabel: "800" },
|
|
398
|
-
{ position: 100, paletteLabel: "950" }
|
|
399
|
-
];
|
|
400
|
-
if (emptyMode === "solid") {
|
|
401
|
-
out["--page-bg"] = { kind: "color", ...baseColor };
|
|
402
|
-
out["--page-bg-attachment"] = { kind: "raw", css: "scroll" };
|
|
403
|
-
} else {
|
|
404
|
-
const sortedStops = [...gradientStops].sort(
|
|
405
|
-
(a, b) => gradientReverse ? b.position - a.position : a.position - b.position
|
|
406
|
-
);
|
|
407
|
-
const stopsCss = sortedStops.map((s) => {
|
|
408
|
-
const stopValue = out[`--color-${spec.cssNamespace}-${s.paletteLabel}`];
|
|
409
|
-
const hex = stopValue ? serializeDerivedValue(stopValue) : "#000000";
|
|
410
|
-
return `${hex} ${s.position}%`;
|
|
411
|
-
}).join(", ");
|
|
412
|
-
let gradient;
|
|
413
|
-
switch (gradientStyle) {
|
|
414
|
-
case "radial":
|
|
415
|
-
gradient = `radial-gradient(circle, ${stopsCss})`;
|
|
416
|
-
break;
|
|
417
|
-
case "conic":
|
|
418
|
-
gradient = `conic-gradient(from ${gradientAngle}deg, ${stopsCss})`;
|
|
419
|
-
break;
|
|
420
|
-
default:
|
|
421
|
-
gradient = `linear-gradient(${gradientAngle}deg, ${stopsCss})`;
|
|
422
|
-
}
|
|
423
|
-
out["--page-bg"] = { kind: "raw", css: gradient };
|
|
424
|
-
out["--page-bg-attachment"] = { kind: "raw", css: gradientSize === "window" ? "fixed" : "scroll" };
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return out;
|
|
428
|
-
}
|
|
429
|
-
function derivePaletteVars(spec, config) {
|
|
430
|
-
const out = {};
|
|
431
|
-
for (const [k, v] of Object.entries(derivePaletteValues(spec, config))) {
|
|
432
|
-
out[k] = serializeDerivedValue(v);
|
|
433
|
-
}
|
|
434
|
-
return out;
|
|
435
|
-
}
|
|
436
|
-
function palettesToValues(palettes) {
|
|
437
|
-
const out = {};
|
|
438
|
-
for (const spec of PALETTE_SPECS) {
|
|
439
|
-
Object.assign(out, derivePaletteValues(spec, palettes[spec.label]));
|
|
440
|
-
}
|
|
441
|
-
return out;
|
|
442
|
-
}
|
|
443
|
-
function palettesToVars(palettes) {
|
|
444
|
-
const out = {};
|
|
445
|
-
for (const [k, v] of Object.entries(palettesToValues(palettes))) {
|
|
446
|
-
out[k] = serializeDerivedValue(v);
|
|
447
|
-
}
|
|
448
|
-
return out;
|
|
449
|
-
}
|
|
450
|
-
var HEX_RE = /^#[0-9a-f]{6}$/i;
|
|
451
|
-
function reconcilePalettesFromCssVars(palettes, cssVars) {
|
|
452
|
-
const next = structuredClone(palettes);
|
|
453
|
-
const consumed = /* @__PURE__ */ new Set();
|
|
454
|
-
const snapped = /* @__PURE__ */ new Set();
|
|
455
|
-
for (const spec of PALETTE_SPECS) {
|
|
456
|
-
const current = next[spec.label];
|
|
457
|
-
if (current === void 0) continue;
|
|
458
|
-
if (current._imported === true) {
|
|
459
|
-
const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
|
|
460
|
-
if (anchorHex && HEX_RE.test(anchorHex.trim())) {
|
|
461
|
-
next[spec.label] = { ...current, baseColor: hexToOklch(anchorHex.trim()), _imported: false };
|
|
462
|
-
syncBaseAnchor(next[spec.label]);
|
|
463
|
-
snapped.add(spec.label);
|
|
464
|
-
} else {
|
|
465
|
-
next[spec.label] = { ...current, _imported: false };
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
|
|
469
|
-
consumed.add(k);
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
return { palettes: next, consumed, snapped };
|
|
473
|
-
}
|
|
474
|
-
|
|
475
38
|
// src/editor/core/themes/migrations/2026-07-21-palette-oklch-basis.ts
|
|
476
39
|
var round = (v, dp) => {
|
|
477
40
|
const f = 10 ** dp;
|
|
@@ -492,102 +55,6 @@ function migratePaletteColorsToOklch(editorConfigs) {
|
|
|
492
55
|
return out;
|
|
493
56
|
}
|
|
494
57
|
|
|
495
|
-
// vite-plugin/files/versionedFileResourceServer.ts
|
|
496
|
-
import fs from "fs";
|
|
497
|
-
import path from "path";
|
|
498
|
-
function versionedFileResourceServer(opts) {
|
|
499
|
-
const dir = opts.dir;
|
|
500
|
-
const activePath = path.join(dir, "_active.json");
|
|
501
|
-
const productionPath = path.join(dir, "_production.json");
|
|
502
|
-
const defaultName = opts.defaultName ?? "default";
|
|
503
|
-
const resolvedDir = path.resolve(dir);
|
|
504
|
-
const resolvedPackageDir = opts.packageDir ? path.resolve(opts.packageDir) : null;
|
|
505
|
-
function ensureDir() {
|
|
506
|
-
if (!fs.existsSync(dir)) {
|
|
507
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
function ensureMeta() {
|
|
511
|
-
if (!fs.existsSync(activePath)) {
|
|
512
|
-
fs.writeFileSync(activePath, JSON.stringify({ activeFile: defaultName }));
|
|
513
|
-
}
|
|
514
|
-
if (!fs.existsSync(productionPath)) {
|
|
515
|
-
fs.writeFileSync(productionPath, JSON.stringify({ productionFile: defaultName }));
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
function filePath(name) {
|
|
519
|
-
return path.join(dir, `${name}.json`);
|
|
520
|
-
}
|
|
521
|
-
function existingPath(name) {
|
|
522
|
-
const local = filePath(name);
|
|
523
|
-
if (fs.existsSync(local)) return local;
|
|
524
|
-
if (resolvedPackageDir) {
|
|
525
|
-
const pkg = path.join(resolvedPackageDir, `${name}.json`);
|
|
526
|
-
if (fs.existsSync(pkg)) return pkg;
|
|
527
|
-
}
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
function readJson(name) {
|
|
531
|
-
const resolved = existingPath(name);
|
|
532
|
-
if (!resolved) return null;
|
|
533
|
-
return JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
534
|
-
}
|
|
535
|
-
function listNames() {
|
|
536
|
-
const names = [];
|
|
537
|
-
const seen = /* @__PURE__ */ new Set();
|
|
538
|
-
const collect = (d) => {
|
|
539
|
-
if (!d || !fs.existsSync(d)) return;
|
|
540
|
-
for (const f of fs.readdirSync(d)) {
|
|
541
|
-
if (!f.endsWith(".json") || f.startsWith("_")) continue;
|
|
542
|
-
const name = f.slice(0, -".json".length);
|
|
543
|
-
if (seen.has(name)) continue;
|
|
544
|
-
seen.add(name);
|
|
545
|
-
names.push(name);
|
|
546
|
-
}
|
|
547
|
-
};
|
|
548
|
-
collect(resolvedDir);
|
|
549
|
-
if (resolvedPackageDir && resolvedPackageDir !== resolvedDir) collect(resolvedPackageDir);
|
|
550
|
-
return names;
|
|
551
|
-
}
|
|
552
|
-
function getActiveName() {
|
|
553
|
-
try {
|
|
554
|
-
const data = JSON.parse(fs.readFileSync(activePath, "utf-8"));
|
|
555
|
-
return data.activeFile || defaultName;
|
|
556
|
-
} catch {
|
|
557
|
-
return defaultName;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
function getProductionName() {
|
|
561
|
-
try {
|
|
562
|
-
const data = JSON.parse(fs.readFileSync(productionPath, "utf-8"));
|
|
563
|
-
return data.productionFile || defaultName;
|
|
564
|
-
} catch {
|
|
565
|
-
return defaultName;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
function setActiveName(name) {
|
|
569
|
-
fs.writeFileSync(activePath, JSON.stringify({ activeFile: name }));
|
|
570
|
-
}
|
|
571
|
-
function setProductionName(name) {
|
|
572
|
-
fs.writeFileSync(productionPath, JSON.stringify({ productionFile: name }));
|
|
573
|
-
}
|
|
574
|
-
return {
|
|
575
|
-
dir,
|
|
576
|
-
activePath,
|
|
577
|
-
productionPath,
|
|
578
|
-
ensureDir,
|
|
579
|
-
ensureMeta,
|
|
580
|
-
filePath,
|
|
581
|
-
existingPath,
|
|
582
|
-
readJson,
|
|
583
|
-
listNames,
|
|
584
|
-
getActiveName,
|
|
585
|
-
getProductionName,
|
|
586
|
-
setActiveName,
|
|
587
|
-
setProductionName
|
|
588
|
-
};
|
|
589
|
-
}
|
|
590
|
-
|
|
591
58
|
// vite-plugin/files/routeTable.ts
|
|
592
59
|
async function dispatch(req, res, routes) {
|
|
593
60
|
const url = req.url || "";
|
|
@@ -616,28 +83,18 @@ async function dispatch(req, res, routes) {
|
|
|
616
83
|
return false;
|
|
617
84
|
}
|
|
618
85
|
|
|
619
|
-
// vite-plugin/files/nameAllocator.ts
|
|
620
|
-
function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
|
|
621
|
-
if (!exists(baseName)) return baseName;
|
|
622
|
-
for (let i = 2; i < maxAttempts; i++) {
|
|
623
|
-
const candidate = `${baseName}-${i}`;
|
|
624
|
-
if (!exists(candidate)) return candidate;
|
|
625
|
-
}
|
|
626
|
-
throw new Error(`Could not allocate a non-colliding name for "${baseName}"`);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
86
|
// vite-plugin/themeFileApi.ts
|
|
630
87
|
import { fileURLToPath } from "url";
|
|
631
88
|
var PKG_VERSION = (() => {
|
|
632
89
|
try {
|
|
633
|
-
let dir =
|
|
90
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
634
91
|
for (let i = 0; i < 4; i++) {
|
|
635
|
-
const p =
|
|
636
|
-
if (
|
|
637
|
-
const json = JSON.parse(
|
|
92
|
+
const p = path.join(dir, "package.json");
|
|
93
|
+
if (fs.existsSync(p)) {
|
|
94
|
+
const json = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
638
95
|
if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
|
|
639
96
|
}
|
|
640
|
-
const up =
|
|
97
|
+
const up = path.dirname(dir);
|
|
641
98
|
if (up === dir) break;
|
|
642
99
|
dir = up;
|
|
643
100
|
}
|
|
@@ -645,65 +102,79 @@ var PKG_VERSION = (() => {
|
|
|
645
102
|
}
|
|
646
103
|
return "";
|
|
647
104
|
})();
|
|
105
|
+
var BUNDLE_KIND_BY_VERSION = {
|
|
106
|
+
1: "manifest-bundle",
|
|
107
|
+
[THEME_SCHEMA_VERSION]: "theme-bundle"
|
|
108
|
+
};
|
|
648
109
|
function themeFileApi(opts) {
|
|
649
110
|
const dataDirs = resolveDataDirs({
|
|
650
111
|
dataDir: opts.dataDir,
|
|
651
|
-
|
|
112
|
+
colorsAndTypeDir: opts.colorsAndTypeDir,
|
|
652
113
|
componentConfigsDir: opts.componentConfigsDir,
|
|
653
|
-
|
|
114
|
+
themesDir: opts.themesDir
|
|
654
115
|
});
|
|
655
|
-
const
|
|
116
|
+
const COLORS_AND_TYPE_DIR = dataDirs.colorsAndTypeDir;
|
|
656
117
|
const COMPONENT_CONFIGS_DIR = dataDirs.componentConfigsDir;
|
|
657
|
-
const
|
|
658
|
-
const CSS_PATH =
|
|
659
|
-
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ?
|
|
660
|
-
const FONTS_CSS_PATH = opts.fontsCssPath ?
|
|
118
|
+
const THEMES_DIR = dataDirs.themesDir;
|
|
119
|
+
const CSS_PATH = path.resolve(opts.tokensCssPath);
|
|
120
|
+
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ? path.resolve(opts.tokensGeneratedCssPath) : path.join(dataDirs.dataDir, "tokens.generated.css");
|
|
121
|
+
const FONTS_CSS_PATH = opts.fontsCssPath ? path.resolve(opts.fontsCssPath) : path.join(path.dirname(CSS_PATH), "fonts.css");
|
|
661
122
|
const API_BASE = opts.apiBase ?? "/api/live-tokens";
|
|
662
|
-
const consumerComponentDirs = opts.componentsSrcDir ? [
|
|
663
|
-
const packageComponentsDir =
|
|
664
|
-
|
|
123
|
+
const consumerComponentDirs = opts.componentsSrcDir ? [path.resolve(opts.componentsSrcDir)] : [path.resolve("src/components"), path.resolve("src/system/components")];
|
|
124
|
+
const packageComponentsDir = path.resolve(
|
|
125
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
665
126
|
"..",
|
|
666
127
|
"src",
|
|
667
128
|
"system",
|
|
668
129
|
"components"
|
|
669
130
|
);
|
|
670
|
-
const packageDataDir =
|
|
671
|
-
|
|
131
|
+
const packageDataDir = path.resolve(
|
|
132
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
672
133
|
"..",
|
|
673
134
|
"src",
|
|
674
135
|
"live-tokens",
|
|
675
136
|
"data"
|
|
676
137
|
);
|
|
677
|
-
const
|
|
678
|
-
const
|
|
679
|
-
const packageComponentConfigsDir =
|
|
138
|
+
const packageColorsAndTypeDir = path.join(packageDataDir, "colors-and-type");
|
|
139
|
+
const packageThemesDir = path.join(packageDataDir, "themes");
|
|
140
|
+
const packageComponentConfigsDir = path.join(packageDataDir, "component-configs");
|
|
141
|
+
function shippedColorsAndTypeNames(dataDir) {
|
|
142
|
+
try {
|
|
143
|
+
const pkgRoot = path.resolve(dataDir, "..", "..", "..");
|
|
144
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(pkgRoot, "package.json"), "utf-8"));
|
|
145
|
+
const files = Array.isArray(pkg.files) ? pkg.files : [];
|
|
146
|
+
return files.filter((f) => /^src\/live-tokens\/data\/colors-and-type\/[^/]+\.json$/.test(f)).map((f) => path.basename(f, ".json"));
|
|
147
|
+
} catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
680
151
|
const COMPONENTS_SCAN_DIRS = [...consumerComponentDirs];
|
|
681
|
-
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) &&
|
|
152
|
+
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs.existsSync(packageComponentsDir)) {
|
|
682
153
|
COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
|
|
683
154
|
}
|
|
684
|
-
const
|
|
685
|
-
dir:
|
|
686
|
-
packageDir:
|
|
155
|
+
const colorsAndTypeResource = versionedFileResourceServer({
|
|
156
|
+
dir: COLORS_AND_TYPE_DIR,
|
|
157
|
+
packageDir: packageColorsAndTypeDir,
|
|
158
|
+
packageOwnedNames: shippedColorsAndTypeNames(packageDataDir)
|
|
687
159
|
});
|
|
688
160
|
const componentResourceCache = /* @__PURE__ */ new Map();
|
|
689
161
|
function componentResource(comp) {
|
|
690
162
|
let r = componentResourceCache.get(comp);
|
|
691
163
|
if (!r) {
|
|
692
164
|
r = versionedFileResourceServer({
|
|
693
|
-
dir:
|
|
694
|
-
packageDir:
|
|
165
|
+
dir: path.join(COMPONENT_CONFIGS_DIR, comp),
|
|
166
|
+
packageDir: path.join(packageComponentConfigsDir, comp)
|
|
695
167
|
});
|
|
696
168
|
componentResourceCache.set(comp, r);
|
|
697
169
|
}
|
|
698
170
|
return r;
|
|
699
171
|
}
|
|
700
|
-
const
|
|
701
|
-
dir:
|
|
702
|
-
packageDir:
|
|
172
|
+
const themesResource = versionedFileResourceServer({
|
|
173
|
+
dir: THEMES_DIR,
|
|
174
|
+
packageDir: packageThemesDir
|
|
703
175
|
});
|
|
704
|
-
function
|
|
705
|
-
|
|
706
|
-
themesResource.ensureMeta();
|
|
176
|
+
function ensureColorsAndTypeDir() {
|
|
177
|
+
colorsAndTypeResource.ensureDir();
|
|
707
178
|
}
|
|
708
179
|
function readBody(req) {
|
|
709
180
|
return new Promise((resolve, reject) => {
|
|
@@ -718,25 +189,33 @@ function themeFileApi(opts) {
|
|
|
718
189
|
res.setHeader("Content-Type", "application/json");
|
|
719
190
|
res.end(JSON.stringify(data));
|
|
720
191
|
}
|
|
721
|
-
function
|
|
722
|
-
if (!
|
|
723
|
-
|
|
724
|
-
|
|
192
|
+
function rejectReservedName(res, name) {
|
|
193
|
+
if (!name.startsWith("_")) return false;
|
|
194
|
+
jsonResponse(res, 400, {
|
|
195
|
+
error: 'Names starting with "_" are reserved',
|
|
196
|
+
code: "RESERVED_NAME"
|
|
197
|
+
});
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
function normalizeColorsAndType(colorsAndType) {
|
|
201
|
+
if (!colorsAndType || typeof colorsAndType !== "object") return colorsAndType;
|
|
202
|
+
const palettes = colorsAndType.editorConfigs ?? {};
|
|
203
|
+
const cssVars = colorsAndType.cssVariables ?? {};
|
|
725
204
|
const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
|
|
726
205
|
const nextCssVars = {};
|
|
727
206
|
for (const [k, v] of Object.entries(cssVars)) {
|
|
728
207
|
if (!consumed.has(k)) nextCssVars[k] = v;
|
|
729
208
|
}
|
|
730
|
-
return { ...
|
|
209
|
+
return { ...colorsAndType, editorConfigs: nextPalettes, cssVariables: nextCssVars };
|
|
731
210
|
}
|
|
732
211
|
const SYSTEM_CASCADES_SSR = {
|
|
733
212
|
"system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
734
213
|
"system-ui-serif": 'Georgia, "Times New Roman", serif',
|
|
735
214
|
"system-ui-mono": 'ui-monospace, "SF Mono", Menlo, Consolas, monospace'
|
|
736
215
|
};
|
|
737
|
-
function resolveFontStacks(
|
|
738
|
-
const stacks =
|
|
739
|
-
const sources =
|
|
216
|
+
function resolveFontStacks(colorsAndTypeData) {
|
|
217
|
+
const stacks = colorsAndTypeData.fontStacks;
|
|
218
|
+
const sources = colorsAndTypeData.fontSources;
|
|
740
219
|
if (!stacks || stacks.length === 0) return {};
|
|
741
220
|
const familyById = /* @__PURE__ */ new Map();
|
|
742
221
|
for (const src of sources ?? []) {
|
|
@@ -762,21 +241,28 @@ function themeFileApi(opts) {
|
|
|
762
241
|
}
|
|
763
242
|
function regenerateTokensCss() {
|
|
764
243
|
const lines = [];
|
|
765
|
-
lines.push("/* Generated by themeFileApi from the production theme
|
|
244
|
+
lines.push("/* Generated by themeFileApi from the production theme. Do not edit. */");
|
|
766
245
|
lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
|
|
767
246
|
lines.push("");
|
|
768
247
|
const productionThemeName = themesResource.getProductionName();
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
248
|
+
const productionTheme = readTheme(productionThemeName)?.theme ?? null;
|
|
249
|
+
if (themesResource.existingPath(productionThemeName) !== null && !productionTheme?.colorsAndType) {
|
|
250
|
+
const tail = `${path.basename(GENERATED_CSS_PATH)} was left as it was.`;
|
|
251
|
+
throw new Error(
|
|
252
|
+
unreadableThemeReason(productionThemeName) === "colors-and-type" ? `[live-tokens] ${notAThemeError(productionThemeName)} ${tail}` : `[live-tokens] Production theme "${productionThemeName}" is unreadable, so ${tail} Repair the theme file or adopt another theme.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const colorsAndTypeData = productionTheme?.colorsAndType;
|
|
256
|
+
let colorsAndTypeVarCount = 0;
|
|
257
|
+
if (colorsAndTypeData) {
|
|
258
|
+
const cssVars = { ...colorsAndTypeData.cssVariables || {} };
|
|
259
|
+
Object.assign(cssVars, palettesToVars(migratePaletteColorsToOklch(colorsAndTypeData.editorConfigs ?? {})));
|
|
260
|
+
const resolvedFontVars = resolveFontStacks(colorsAndTypeData);
|
|
775
261
|
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
776
262
|
cssVars[name] = value;
|
|
777
263
|
}
|
|
778
|
-
|
|
779
|
-
if (
|
|
264
|
+
colorsAndTypeVarCount = Object.keys(cssVars).length;
|
|
265
|
+
if (colorsAndTypeVarCount > 0) {
|
|
780
266
|
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
781
267
|
lines.push(":root:root {");
|
|
782
268
|
for (const [name, value] of Object.entries(cssVars)) {
|
|
@@ -787,12 +273,10 @@ function themeFileApi(opts) {
|
|
|
787
273
|
}
|
|
788
274
|
}
|
|
789
275
|
let componentOverrideCount = 0;
|
|
790
|
-
if (
|
|
276
|
+
if (fs.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
791
277
|
const blocks = [];
|
|
792
278
|
for (const comp of listComponentNames()) {
|
|
793
|
-
const
|
|
794
|
-
if (prod === "default") continue;
|
|
795
|
-
const prodCfg = readComponentConfig(comp, prod);
|
|
279
|
+
const prodCfg = productionTheme?.componentConfigs[comp];
|
|
796
280
|
const defaultCfg = readComponentConfig(comp, "default");
|
|
797
281
|
if (!prodCfg || !defaultCfg) continue;
|
|
798
282
|
const overrides = [];
|
|
@@ -803,7 +287,7 @@ function themeFileApi(opts) {
|
|
|
803
287
|
}
|
|
804
288
|
}
|
|
805
289
|
if (overrides.length === 0) continue;
|
|
806
|
-
const block = [` /* ${comp}
|
|
290
|
+
const block = [` /* ${comp} */`];
|
|
807
291
|
for (const [varName, semanticValue] of overrides) {
|
|
808
292
|
block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
|
|
809
293
|
}
|
|
@@ -821,24 +305,20 @@ function themeFileApi(opts) {
|
|
|
821
305
|
lines.push("");
|
|
822
306
|
}
|
|
823
307
|
}
|
|
824
|
-
if (!
|
|
825
|
-
|
|
308
|
+
if (!fs.existsSync(path.dirname(GENERATED_CSS_PATH))) {
|
|
309
|
+
fs.mkdirSync(path.dirname(GENERATED_CSS_PATH), { recursive: true });
|
|
826
310
|
}
|
|
827
|
-
|
|
311
|
+
fs.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
|
|
828
312
|
console.log(
|
|
829
|
-
`[regenerateTokensCss] Wrote ${
|
|
313
|
+
`[regenerateTokensCss] Wrote ${path.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
|
|
830
314
|
);
|
|
831
315
|
}
|
|
832
|
-
function
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
function syncFontsToCss(fileName) {
|
|
836
|
-
const themeData = themesResource.readJson(fileName);
|
|
837
|
-
if (!themeData) return;
|
|
838
|
-
const sources = themeData.fontSources;
|
|
316
|
+
function syncFontsToCss(colorsAndTypeData) {
|
|
317
|
+
if (!colorsAndTypeData) return;
|
|
318
|
+
const sources = colorsAndTypeData.fontSources;
|
|
839
319
|
if (!sources) return;
|
|
840
320
|
const lines = [];
|
|
841
|
-
lines.push("/* Generated from the production
|
|
321
|
+
lines.push("/* Generated from the production colors and type by syncFontsToCss. Do not edit. */");
|
|
842
322
|
lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
|
|
843
323
|
lines.push("");
|
|
844
324
|
const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
|
|
@@ -858,11 +338,11 @@ function themeFileApi(opts) {
|
|
|
858
338
|
lines.push("");
|
|
859
339
|
}
|
|
860
340
|
const content = lines.join("\n");
|
|
861
|
-
if (!
|
|
862
|
-
|
|
341
|
+
if (!fs.existsSync(path.dirname(FONTS_CSS_PATH))) {
|
|
342
|
+
fs.mkdirSync(path.dirname(FONTS_CSS_PATH), { recursive: true });
|
|
863
343
|
}
|
|
864
|
-
|
|
865
|
-
console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${
|
|
344
|
+
fs.writeFileSync(FONTS_CSS_PATH, content);
|
|
345
|
+
console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path.basename(FONTS_CSS_PATH)}`);
|
|
866
346
|
}
|
|
867
347
|
function extractAliasDeclarations(body) {
|
|
868
348
|
const aliases = {};
|
|
@@ -877,11 +357,11 @@ function themeFileApi(opts) {
|
|
|
877
357
|
return aliases;
|
|
878
358
|
}
|
|
879
359
|
function componentNameFromFile(filePath) {
|
|
880
|
-
return
|
|
360
|
+
return path.basename(filePath, ".svelte").toLowerCase();
|
|
881
361
|
}
|
|
882
362
|
function isThemeAwareComponent(filePath) {
|
|
883
363
|
try {
|
|
884
|
-
const source =
|
|
364
|
+
const source = fs.readFileSync(filePath, "utf-8");
|
|
885
365
|
return /:global\(:root\)\s*\{/.test(source);
|
|
886
366
|
} catch {
|
|
887
367
|
return false;
|
|
@@ -890,10 +370,10 @@ function themeFileApi(opts) {
|
|
|
890
370
|
function listComponentSourcePaths() {
|
|
891
371
|
const byName = /* @__PURE__ */ new Map();
|
|
892
372
|
for (const dir of COMPONENTS_SCAN_DIRS) {
|
|
893
|
-
if (!
|
|
894
|
-
for (const f of
|
|
373
|
+
if (!fs.existsSync(dir)) continue;
|
|
374
|
+
for (const f of fs.readdirSync(dir)) {
|
|
895
375
|
if (!f.endsWith(".svelte")) continue;
|
|
896
|
-
const full =
|
|
376
|
+
const full = path.join(dir, f);
|
|
897
377
|
if (!isThemeAwareComponent(full)) continue;
|
|
898
378
|
const name = componentNameFromFile(f);
|
|
899
379
|
if (!byName.has(name)) byName.set(name, full);
|
|
@@ -907,18 +387,18 @@ function themeFileApi(opts) {
|
|
|
907
387
|
function warnOnTokenDrift(log) {
|
|
908
388
|
let tokensCss = "";
|
|
909
389
|
try {
|
|
910
|
-
tokensCss =
|
|
390
|
+
tokensCss = fs.readFileSync(CSS_PATH, "utf-8");
|
|
911
391
|
} catch {
|
|
912
392
|
return;
|
|
913
393
|
}
|
|
914
394
|
let generatedCss = "";
|
|
915
395
|
try {
|
|
916
|
-
generatedCss =
|
|
396
|
+
generatedCss = fs.readFileSync(GENERATED_CSS_PATH, "utf-8");
|
|
917
397
|
} catch {
|
|
918
398
|
}
|
|
919
399
|
const componentSources = listComponentSourcePaths().map((p) => ({
|
|
920
400
|
name: componentNameFromFile(p),
|
|
921
|
-
source:
|
|
401
|
+
source: fs.readFileSync(p, "utf-8")
|
|
922
402
|
}));
|
|
923
403
|
const missing = validateTokensCss({ tokensCss, generatedCss, componentSources });
|
|
924
404
|
if (missing.length === 0) return;
|
|
@@ -926,7 +406,7 @@ function themeFileApi(opts) {
|
|
|
926
406
|
(m) => ` ${m.token} (referenced by ${m.referencedBy.join(", ")})`
|
|
927
407
|
);
|
|
928
408
|
log(
|
|
929
|
-
`[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${
|
|
409
|
+
`[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${path.relative(process.cwd(), CSS_PATH)}:
|
|
930
410
|
${lines.join("\n")}
|
|
931
411
|
These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
|
|
932
412
|
);
|
|
@@ -934,37 +414,37 @@ ${lines.join("\n")}
|
|
|
934
414
|
function autoMigrateAdditive(log) {
|
|
935
415
|
let before = "";
|
|
936
416
|
try {
|
|
937
|
-
before =
|
|
417
|
+
before = fs.readFileSync(CSS_PATH, "utf-8");
|
|
938
418
|
} catch {
|
|
939
419
|
return;
|
|
940
420
|
}
|
|
941
421
|
const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
|
|
942
422
|
if (!changed) return;
|
|
943
|
-
|
|
423
|
+
fs.writeFileSync(CSS_PATH, css);
|
|
944
424
|
log(
|
|
945
|
-
`[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${
|
|
425
|
+
`[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${path.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
|
|
946
426
|
);
|
|
947
427
|
}
|
|
948
428
|
function generateDefaultConfig(comp, sourcePath) {
|
|
949
|
-
if (!
|
|
429
|
+
if (!fs.existsSync(sourcePath)) return;
|
|
950
430
|
const r = componentResource(comp);
|
|
951
431
|
r.ensureDir();
|
|
952
432
|
const defaultPath = r.filePath("default");
|
|
953
|
-
const sourceStat =
|
|
954
|
-
if (
|
|
955
|
-
const defaultStat =
|
|
433
|
+
const sourceStat = fs.statSync(sourcePath);
|
|
434
|
+
if (fs.existsSync(defaultPath)) {
|
|
435
|
+
const defaultStat = fs.statSync(defaultPath);
|
|
956
436
|
if (defaultStat.mtimeMs >= sourceStat.mtimeMs) return;
|
|
957
437
|
}
|
|
958
|
-
const source =
|
|
438
|
+
const source = fs.readFileSync(sourcePath, "utf-8");
|
|
959
439
|
const body = extractGlobalRootBody(source);
|
|
960
440
|
const aliases = extractAliasDeclarations(body);
|
|
961
441
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
962
442
|
let createdAt = now;
|
|
963
443
|
let existingUpdatedAt;
|
|
964
444
|
let existingAliases;
|
|
965
|
-
if (
|
|
445
|
+
if (fs.existsSync(defaultPath)) {
|
|
966
446
|
try {
|
|
967
|
-
const existing = JSON.parse(
|
|
447
|
+
const existing = JSON.parse(fs.readFileSync(defaultPath, "utf-8"));
|
|
968
448
|
if (existing.createdAt) createdAt = existing.createdAt;
|
|
969
449
|
if (typeof existing.updatedAt === "string") existingUpdatedAt = existing.updatedAt;
|
|
970
450
|
if (existing.aliases && typeof existing.aliases === "object") {
|
|
@@ -990,57 +470,191 @@ ${lines.join("\n")}
|
|
|
990
470
|
};
|
|
991
471
|
if (aliasesUnchanged && existingUpdatedAt) {
|
|
992
472
|
const t = /* @__PURE__ */ new Date();
|
|
993
|
-
|
|
473
|
+
fs.utimesSync(defaultPath, t, t);
|
|
994
474
|
return;
|
|
995
475
|
}
|
|
996
|
-
|
|
476
|
+
fs.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
|
|
997
477
|
}
|
|
998
478
|
function ensureComponentConfigsDir() {
|
|
999
|
-
if (!
|
|
1000
|
-
|
|
479
|
+
if (!fs.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
480
|
+
fs.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
|
|
1001
481
|
}
|
|
1002
482
|
for (const sourcePath of listComponentSourcePaths()) {
|
|
1003
483
|
const comp = componentNameFromFile(sourcePath);
|
|
1004
484
|
const r = componentResource(comp);
|
|
1005
485
|
r.ensureDir();
|
|
1006
486
|
generateDefaultConfig(comp, sourcePath);
|
|
1007
|
-
r.ensureMeta();
|
|
1008
487
|
}
|
|
1009
488
|
}
|
|
1010
|
-
function
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
489
|
+
function listLegacyPointers() {
|
|
490
|
+
const legacy = [];
|
|
491
|
+
const check = (dir) => {
|
|
492
|
+
for (const name of ["_active.json", "_production.json"]) {
|
|
493
|
+
const p = path.join(dir, name);
|
|
494
|
+
if (fs.existsSync(p)) legacy.push(path.relative(process.cwd(), p));
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
check(COLORS_AND_TYPE_DIR);
|
|
498
|
+
if (fs.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
499
|
+
for (const entry of fs.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
|
|
500
|
+
if (entry.isDirectory()) check(path.join(COMPONENT_CONFIGS_DIR, entry.name));
|
|
1021
501
|
}
|
|
1022
502
|
}
|
|
503
|
+
return legacy;
|
|
1023
504
|
}
|
|
1024
|
-
function
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
505
|
+
function warnOnLegacyPointers(legacy, bakeHeld, warn) {
|
|
506
|
+
if (legacy.length === 0) return;
|
|
507
|
+
warn(
|
|
508
|
+
`[live-tokens] ${legacy.length} legacy pointer file(s) left from the pre-working-set data model, starting with ${legacy[0]}. They are no longer read. Run \`npx live-tokens migrate\` to heal the tree.` + (bakeHeld ? ` Until then this tree has named no production theme, so ${path.basename(GENERATED_CSS_PATH)} is left exactly as it is rather than rebuilt from the default.` : "")
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
function warnOnLegacyLayout(evidence, warn) {
|
|
512
|
+
warn(
|
|
513
|
+
`[live-tokens] This project still uses the data layout from 0.47 and earlier: ${evidence}. Since 0.48 the whole-look theme files live in \`themes/\` and the colors and type live in \`colors-and-type/\`. Nothing in the data directory was written, the editor cannot save, and ${path.basename(GENERATED_CSS_PATH)} is left exactly as it is. Run \`npx live-tokens migrate\` to move the directories and heal the tree, then restart the dev server.`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
const diskThemeResolvers = {
|
|
517
|
+
readColorsAndType: (name) => colorsAndTypeResource.readJson(sanitizeFileName(name)),
|
|
518
|
+
readComponentConfig: (comp, name) => readComponentConfig(comp, sanitizeFileName(name)),
|
|
519
|
+
normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
|
|
520
|
+
};
|
|
521
|
+
function readTheme(fileName) {
|
|
522
|
+
let raw;
|
|
1030
523
|
try {
|
|
1031
|
-
|
|
524
|
+
raw = themesResource.readJson(fileName);
|
|
1032
525
|
} catch {
|
|
1033
|
-
return
|
|
526
|
+
return null;
|
|
1034
527
|
}
|
|
1035
|
-
if (
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
528
|
+
if (!raw) return null;
|
|
529
|
+
if (isColorsAndTypeShaped(raw)) return null;
|
|
530
|
+
return normalizeTheme(raw, diskThemeResolvers);
|
|
531
|
+
}
|
|
532
|
+
function unreadableThemeReason(fileName) {
|
|
533
|
+
const resolved = themesResource.existingPath(fileName);
|
|
534
|
+
if (resolved === null) return "missing";
|
|
535
|
+
try {
|
|
536
|
+
if (isColorsAndTypeShaped(JSON.parse(fs.readFileSync(resolved, "utf-8")))) {
|
|
537
|
+
return "colors-and-type";
|
|
538
|
+
}
|
|
539
|
+
} catch {
|
|
1040
540
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
541
|
+
return "corrupt";
|
|
542
|
+
}
|
|
543
|
+
function notAThemeError(fileName) {
|
|
544
|
+
return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${path.basename(COLORS_AND_TYPE_DIR)}/ or delete it.`;
|
|
545
|
+
}
|
|
546
|
+
function respondUnreadableTheme(res, fileName, missingError) {
|
|
547
|
+
switch (unreadableThemeReason(fileName)) {
|
|
548
|
+
case "missing":
|
|
549
|
+
jsonResponse(res, 404, { error: missingError });
|
|
550
|
+
return;
|
|
551
|
+
case "colors-and-type":
|
|
552
|
+
jsonResponse(res, 422, { error: notAThemeError(fileName), code: "CORRUPT_THEME" });
|
|
553
|
+
return;
|
|
554
|
+
case "corrupt":
|
|
555
|
+
jsonResponse(res, 422, {
|
|
556
|
+
error: `Theme file "${fileName}" is not valid JSON`,
|
|
557
|
+
code: "CORRUPT_THEME"
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function writeTheme(fileName, theme) {
|
|
562
|
+
themesResource.ensureDir();
|
|
563
|
+
fs.writeFileSync(themesResource.filePath(fileName), JSON.stringify(theme, null, 2));
|
|
564
|
+
}
|
|
565
|
+
function migrateLocalThemes(warn) {
|
|
566
|
+
if (path.resolve(THEMES_DIR) === path.resolve(packageThemesDir)) return;
|
|
567
|
+
if (!fs.existsSync(THEMES_DIR)) return;
|
|
568
|
+
for (const file of fs.readdirSync(THEMES_DIR)) {
|
|
569
|
+
if (!file.endsWith(".json") || file.startsWith("_")) continue;
|
|
570
|
+
const fileName = file.slice(0, -".json".length);
|
|
571
|
+
if (fileName === "default") continue;
|
|
572
|
+
let raw;
|
|
573
|
+
try {
|
|
574
|
+
raw = JSON.parse(fs.readFileSync(path.join(THEMES_DIR, file), "utf-8"));
|
|
575
|
+
} catch {
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (isColorsAndTypeShaped(raw)) {
|
|
579
|
+
warn(`[live-tokens] ${notAThemeError(fileName)} It is left untouched.`);
|
|
580
|
+
continue;
|
|
581
|
+
}
|
|
582
|
+
const { theme, dropped, migrated } = normalizeTheme(raw, diskThemeResolvers);
|
|
583
|
+
if (!migrated) continue;
|
|
584
|
+
writeTheme(fileName, theme);
|
|
585
|
+
if (dropped.length > 0) {
|
|
586
|
+
warn(
|
|
587
|
+
`[live-tokens] Theme "${fileName}": ${dropped.join(", ")} no longer exists; those slices now use their defaults.`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function ensureDefaultTheme(warn) {
|
|
593
|
+
let existing = null;
|
|
594
|
+
try {
|
|
595
|
+
existing = JSON.parse(fs.readFileSync(themesResource.filePath("default"), "utf-8"));
|
|
596
|
+
} catch {
|
|
597
|
+
}
|
|
598
|
+
if (isColorsAndTypeShaped(existing)) {
|
|
599
|
+
warn(`[live-tokens] ${notAThemeError("default")} The Default theme was not materialised.`);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const colorsAndType = normalizeColorsAndType(colorsAndTypeResource.readJson("default"));
|
|
603
|
+
const componentConfigs = {};
|
|
604
|
+
for (const comp of listComponentNames()) {
|
|
605
|
+
const cfg = readComponentConfig(comp, "default");
|
|
606
|
+
if (cfg) componentConfigs[comp] = cfg;
|
|
607
|
+
}
|
|
608
|
+
if (existing?.schemaVersion === THEME_SCHEMA_VERSION && JSON.stringify(existing.colorsAndType) === JSON.stringify(colorsAndType) && JSON.stringify(existing.componentConfigs) === JSON.stringify(componentConfigs)) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
612
|
+
writeTheme("default", {
|
|
613
|
+
name: "Motion Proto",
|
|
614
|
+
createdAt: typeof existing?.createdAt === "string" ? existing.createdAt : now,
|
|
615
|
+
updatedAt: now,
|
|
616
|
+
schemaVersion: THEME_SCHEMA_VERSION,
|
|
617
|
+
colorsAndType,
|
|
618
|
+
componentConfigs
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
function ensureThemesDir(warn) {
|
|
622
|
+
themesResource.ensureDir();
|
|
623
|
+
ensureDefaultTheme(warn);
|
|
624
|
+
migrateLocalThemes(warn);
|
|
625
|
+
const productionPointerHeld = !fs.existsSync(themesResource.productionPath) && listLegacyPointers().length > 0;
|
|
626
|
+
if (productionPointerHeld) {
|
|
627
|
+
if (!fs.existsSync(themesResource.activePath)) themesResource.setActiveName("default");
|
|
628
|
+
} else {
|
|
629
|
+
themesResource.ensureMeta();
|
|
630
|
+
}
|
|
631
|
+
if (themesResource.existingPath(themesResource.getActiveName()) === null) {
|
|
632
|
+
themesResource.setActiveName("default");
|
|
633
|
+
}
|
|
634
|
+
if (themesResource.existingPath(themesResource.getProductionName()) === null) {
|
|
635
|
+
themesResource.setProductionName("default");
|
|
636
|
+
}
|
|
637
|
+
return { productionPointerHeld };
|
|
638
|
+
}
|
|
639
|
+
function activeTheme() {
|
|
640
|
+
return readTheme(themesResource.getActiveName())?.theme ?? null;
|
|
641
|
+
}
|
|
642
|
+
function resolveLiveColorsAndType(theme) {
|
|
643
|
+
const working = colorsAndTypeResource.readWorking();
|
|
644
|
+
if (working !== null) return { data: normalizeColorsAndType(working), source: "working" };
|
|
645
|
+
if (theme?.colorsAndType) return { data: theme.colorsAndType, source: "theme" };
|
|
646
|
+
const shipped = colorsAndTypeResource.readJson("default");
|
|
647
|
+
if (!shipped) return null;
|
|
648
|
+
return { data: normalizeColorsAndType(shipped), source: "default" };
|
|
649
|
+
}
|
|
650
|
+
function resolveLiveComponentConfig(comp, theme) {
|
|
651
|
+
const working = componentResource(comp).readWorking();
|
|
652
|
+
if (working !== null) return { data: working, source: "working" };
|
|
653
|
+
const embedded = theme?.componentConfigs[comp];
|
|
654
|
+
if (embedded) return { data: { ...embedded, component: comp }, source: "theme" };
|
|
655
|
+
const cfg = readComponentConfig(comp, "default");
|
|
656
|
+
if (!cfg) return null;
|
|
657
|
+
return { data: cfg, source: "default" };
|
|
1044
658
|
}
|
|
1045
659
|
function formatAliasGradient(v) {
|
|
1046
660
|
const stopColor = (s) => {
|
|
@@ -1076,237 +690,191 @@ ${lines.join("\n")}
|
|
|
1076
690
|
return null;
|
|
1077
691
|
}
|
|
1078
692
|
}
|
|
1079
|
-
function syncComponentsToCss() {
|
|
1080
|
-
regenerateTokensCss();
|
|
1081
|
-
}
|
|
1082
693
|
const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
694
|
+
const COLORS_AND_TYPE_ROUTE = `${API_BASE}/colors-and-type`;
|
|
695
|
+
const COLORS_AND_TYPE_ACTIVE_ROUTE = `${API_BASE}/colors-and-type/active`;
|
|
696
|
+
const COLORS_AND_TYPE_PRODUCTION_ROUTE = `${API_BASE}/colors-and-type/production`;
|
|
697
|
+
const COLORS_AND_TYPE_WORKING_ROUTE = `${API_BASE}/colors-and-type/working`;
|
|
698
|
+
const COMPONENT_CONFIGS_ROUTE = `${API_BASE}/component-configs`;
|
|
1083
699
|
const THEMES_ROUTE = `${API_BASE}/themes`;
|
|
1084
700
|
const THEMES_ACTIVE_ROUTE = `${API_BASE}/themes/active`;
|
|
1085
701
|
const THEMES_PRODUCTION_ROUTE = `${API_BASE}/themes/production`;
|
|
1086
|
-
const
|
|
1087
|
-
const MANIFESTS_ROUTE = `${API_BASE}/manifests`;
|
|
1088
|
-
const MANIFESTS_ACTIVE_ROUTE = `${API_BASE}/manifests/active`;
|
|
1089
|
-
const THEME_BY_NAME_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)$`);
|
|
702
|
+
const COLORS_AND_TYPE_BY_NAME_REGEX = new RegExp(`^${escapedBase}/colors-and-type/([a-z0-9\\-_]+)$`);
|
|
1090
703
|
const COMP_LIST_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)$`);
|
|
1091
704
|
const COMP_ACTIVE_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/active$`);
|
|
1092
705
|
const COMP_PRODUCTION_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/production$`);
|
|
706
|
+
const COMP_WORKING_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/working$`);
|
|
1093
707
|
const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
|
|
1094
|
-
const
|
|
1095
|
-
const
|
|
1096
|
-
const
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
const files =
|
|
1101
|
-
const data =
|
|
708
|
+
const THEME_APPLY_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)/apply$`);
|
|
709
|
+
const THEME_EXPORT_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)/export$`);
|
|
710
|
+
const THEME_IMPORT_ROUTE = `${API_BASE}/themes/import`;
|
|
711
|
+
const THEME_BY_NAME_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)$`);
|
|
712
|
+
const PRODUCTION_ROUTE = `${API_BASE}/production`;
|
|
713
|
+
async function handleListColorsAndType(_ctx) {
|
|
714
|
+
const files = colorsAndTypeResource.listNames().map((fileName) => {
|
|
715
|
+
const data = colorsAndTypeResource.readJson(fileName);
|
|
1102
716
|
return {
|
|
1103
717
|
name: data?.name || fileName,
|
|
1104
718
|
fileName,
|
|
1105
719
|
updatedAt: data?.updatedAt || "",
|
|
1106
|
-
|
|
720
|
+
// Package-owned rows are the shipped presets and the default. The Load
|
|
721
|
+
// window offers those as whole themes, so it lists only the rows a
|
|
722
|
+
// local file backs — including a local copy of a shipped name, which
|
|
723
|
+
// holds whatever the user did to it.
|
|
724
|
+
isPackage: colorsAndTypeResource.isPackageFile(fileName)
|
|
1107
725
|
};
|
|
1108
726
|
});
|
|
1109
727
|
jsonResponse(_ctx.res, 200, { files });
|
|
1110
728
|
}
|
|
1111
|
-
async function
|
|
1112
|
-
const
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
jsonResponse(res, 404, { error: "Active theme not found" });
|
|
1116
|
-
return;
|
|
1117
|
-
}
|
|
1118
|
-
const data = normalizeTheme(raw);
|
|
1119
|
-
data._fileName = activeFile;
|
|
1120
|
-
jsonResponse(res, 200, data);
|
|
1121
|
-
}
|
|
1122
|
-
async function handleSetActiveTheme({ req, res }) {
|
|
1123
|
-
const body = JSON.parse(await readBody(req));
|
|
1124
|
-
const fileName = sanitizeFileName(body.name || "default");
|
|
1125
|
-
if (themesResource.existingPath(fileName) === null) {
|
|
1126
|
-
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
themesResource.setActiveName(fileName);
|
|
1130
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1131
|
-
}
|
|
1132
|
-
async function handleGetProductionTheme({ res }) {
|
|
1133
|
-
const prodFile = themesResource.getProductionName();
|
|
1134
|
-
const raw = themesResource.readJson(prodFile);
|
|
1135
|
-
if (!raw) {
|
|
1136
|
-
jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
|
|
729
|
+
async function handleGetActiveColorsAndType({ res }) {
|
|
730
|
+
const resolved = resolveLiveColorsAndType(activeTheme());
|
|
731
|
+
if (!resolved) {
|
|
732
|
+
jsonResponse(res, 404, { error: "Active colors and type not found" });
|
|
1137
733
|
return;
|
|
1138
734
|
}
|
|
1139
|
-
const data = normalizeTheme(raw);
|
|
1140
735
|
jsonResponse(res, 200, {
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
cssVariables: data.cssVariables || {}
|
|
736
|
+
...resolved.data,
|
|
737
|
+
_fileName: themesResource.getActiveName(),
|
|
738
|
+
_source: resolved.source
|
|
1145
739
|
});
|
|
1146
740
|
}
|
|
1147
|
-
async function
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
741
|
+
async function readBufferBody(req, res) {
|
|
742
|
+
let parsed;
|
|
743
|
+
try {
|
|
744
|
+
parsed = JSON.parse(await readBody(req));
|
|
745
|
+
} catch {
|
|
746
|
+
parsed = void 0;
|
|
1153
747
|
}
|
|
1154
|
-
if (
|
|
1155
|
-
jsonResponse(res,
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
748
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
749
|
+
jsonResponse(res, 400, { error: "Body must be a JSON object", code: "INVALID_BODY" });
|
|
750
|
+
return null;
|
|
751
|
+
}
|
|
752
|
+
return parsed;
|
|
753
|
+
}
|
|
754
|
+
async function handleGetWorkingColorsAndType({ res }) {
|
|
755
|
+
const raw = colorsAndTypeResource.readWorking();
|
|
756
|
+
if (raw === null) {
|
|
757
|
+
jsonResponse(res, 404, { error: "No working colors and type" });
|
|
1159
758
|
return;
|
|
1160
759
|
}
|
|
1161
|
-
|
|
1162
|
-
syncTokensToCss(fileName);
|
|
1163
|
-
syncFontsToCss(fileName);
|
|
1164
|
-
syncComponentsToCss();
|
|
1165
|
-
patchActiveManifest("theme", null, fileName);
|
|
1166
|
-
const data = themesResource.readJson(fileName);
|
|
1167
|
-
jsonResponse(res, 200, {
|
|
1168
|
-
ok: true,
|
|
1169
|
-
fileName,
|
|
1170
|
-
name: data?.name || fileName,
|
|
1171
|
-
updatedAt: data?.updatedAt || ""
|
|
1172
|
-
});
|
|
760
|
+
jsonResponse(res, 200, normalizeColorsAndType(raw));
|
|
1173
761
|
}
|
|
1174
|
-
async function
|
|
762
|
+
async function handleSetWorkingColorsAndType({ req, res }) {
|
|
763
|
+
const body = await readBufferBody(req, res);
|
|
764
|
+
if (!body) return;
|
|
765
|
+
colorsAndTypeResource.writeWorking(body);
|
|
766
|
+
jsonResponse(res, 200, { ok: true });
|
|
767
|
+
}
|
|
768
|
+
async function handleClearWorkingColorsAndType({ res }) {
|
|
769
|
+
colorsAndTypeResource.clearWorking();
|
|
770
|
+
jsonResponse(res, 200, { ok: true });
|
|
771
|
+
}
|
|
772
|
+
async function handleColorsAndTypeByName({ params, req, res }) {
|
|
1175
773
|
const [fileName] = params;
|
|
1176
|
-
|
|
774
|
+
if (rejectReservedName(res, fileName)) return;
|
|
775
|
+
const filePath = colorsAndTypeResource.filePath(fileName);
|
|
1177
776
|
if (req.method === "GET") {
|
|
1178
|
-
const raw =
|
|
777
|
+
const raw = colorsAndTypeResource.readJson(fileName);
|
|
1179
778
|
if (!raw) {
|
|
1180
779
|
jsonResponse(res, 404, { error: "Not found" });
|
|
1181
780
|
return;
|
|
1182
781
|
}
|
|
1183
|
-
const data =
|
|
782
|
+
const data = normalizeColorsAndType(raw);
|
|
1184
783
|
data._fileName = fileName;
|
|
1185
784
|
jsonResponse(res, 200, data);
|
|
1186
785
|
return;
|
|
1187
786
|
}
|
|
1188
787
|
if (req.method === "PUT") {
|
|
1189
788
|
if (fileName === "default") {
|
|
1190
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
789
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default colors and type (live from package)" });
|
|
1191
790
|
return;
|
|
1192
791
|
}
|
|
1193
792
|
const body = JSON.parse(await readBody(req));
|
|
1194
793
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1195
|
-
if (
|
|
794
|
+
if (fs.existsSync(filePath)) {
|
|
1196
795
|
try {
|
|
1197
|
-
const existing = JSON.parse(
|
|
796
|
+
const existing = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1198
797
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1199
798
|
} catch {
|
|
1200
799
|
}
|
|
1201
800
|
}
|
|
1202
801
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1203
|
-
|
|
1204
|
-
if (fileName === themesResource.getProductionName()) {
|
|
1205
|
-
syncTokensToCss(fileName);
|
|
1206
|
-
syncFontsToCss(fileName);
|
|
1207
|
-
syncComponentsToCss();
|
|
1208
|
-
}
|
|
802
|
+
fs.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
1209
803
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1210
804
|
return;
|
|
1211
805
|
}
|
|
1212
806
|
if (req.method === "DELETE") {
|
|
1213
807
|
if (fileName === "default") {
|
|
1214
|
-
jsonResponse(res, 403, { error: "Cannot delete the default
|
|
808
|
+
jsonResponse(res, 403, { error: "Cannot delete the default colors and type" });
|
|
1215
809
|
return;
|
|
1216
810
|
}
|
|
1217
|
-
if (
|
|
811
|
+
if (fs.existsSync(filePath)) {
|
|
812
|
+
fs.unlinkSync(filePath);
|
|
813
|
+
} else if (colorsAndTypeResource.existingPath(fileName)) {
|
|
1218
814
|
jsonResponse(res, 403, {
|
|
1219
|
-
error: "Cannot delete the
|
|
1220
|
-
code: "
|
|
815
|
+
error: "Cannot delete colors and type shipped with the package. Saving it creates a local copy; deleting that copy restores the shipped version.",
|
|
816
|
+
code: "PACKAGE_COLORS_AND_TYPE"
|
|
1221
817
|
});
|
|
1222
818
|
return;
|
|
1223
819
|
}
|
|
1224
|
-
if (fs2.existsSync(filePath)) {
|
|
1225
|
-
fs2.unlinkSync(filePath);
|
|
1226
|
-
if (themesResource.getActiveName() === fileName) {
|
|
1227
|
-
themesResource.setActiveName("default");
|
|
1228
|
-
}
|
|
1229
|
-
}
|
|
1230
820
|
jsonResponse(res, 200, { ok: true });
|
|
1231
821
|
return;
|
|
1232
822
|
}
|
|
1233
823
|
}
|
|
1234
824
|
async function handleListComponents({ res }) {
|
|
1235
|
-
const
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
});
|
|
825
|
+
const theme = activeTheme();
|
|
826
|
+
const components = listComponentNames().map((comp) => ({
|
|
827
|
+
name: comp,
|
|
828
|
+
// Presence, not a parse: one corrupt buffer must not take the listing
|
|
829
|
+
// down, and the door only has to say where each component's live config
|
|
830
|
+
// comes from.
|
|
831
|
+
source: componentResource(comp).hasWorking() ? "working" : theme?.componentConfigs[comp] ? "theme" : "default"
|
|
832
|
+
}));
|
|
1243
833
|
jsonResponse(res, 200, { components });
|
|
1244
834
|
}
|
|
835
|
+
function rejectUnknownComponent(res, comp) {
|
|
836
|
+
if (listComponentNames().includes(comp)) return false;
|
|
837
|
+
jsonResponse(res, 404, { error: `Unknown component "${comp}"`, code: "UNKNOWN_COMPONENT" });
|
|
838
|
+
return true;
|
|
839
|
+
}
|
|
1245
840
|
async function handleGetComponentActive({ params, res }) {
|
|
1246
841
|
const [comp] = params;
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1249
|
-
const cfg = readComponentConfig(comp, activeFile);
|
|
1250
|
-
if (!cfg) {
|
|
842
|
+
const resolved = resolveLiveComponentConfig(comp, activeTheme());
|
|
843
|
+
if (!resolved) {
|
|
1251
844
|
jsonResponse(res, 404, { error: "Active config not found" });
|
|
1252
845
|
return;
|
|
1253
846
|
}
|
|
1254
|
-
jsonResponse(res, 200, {
|
|
847
|
+
jsonResponse(res, 200, {
|
|
848
|
+
...resolved.data,
|
|
849
|
+
_fileName: themesResource.getActiveName(),
|
|
850
|
+
_source: resolved.source
|
|
851
|
+
});
|
|
1255
852
|
}
|
|
1256
|
-
async function
|
|
853
|
+
async function handleGetComponentWorking({ params, res }) {
|
|
1257
854
|
const [comp] = params;
|
|
1258
|
-
const
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
if (r.existingPath(fileName) === null) {
|
|
1262
|
-
jsonResponse(res, 404, { error: "Config not found" });
|
|
855
|
+
const raw = componentResource(comp).readWorking();
|
|
856
|
+
if (raw === null) {
|
|
857
|
+
jsonResponse(res, 404, { error: "No working config" });
|
|
1263
858
|
return;
|
|
1264
859
|
}
|
|
1265
|
-
|
|
1266
|
-
r.setActiveName(fileName);
|
|
1267
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
860
|
+
jsonResponse(res, 200, raw);
|
|
1268
861
|
}
|
|
1269
|
-
async function
|
|
862
|
+
async function handleSetComponentWorking({ params, req, res }) {
|
|
1270
863
|
const [comp] = params;
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
name: cfg?.name || prodFile,
|
|
1277
|
-
aliases: cfg?.aliases || {}
|
|
1278
|
-
});
|
|
864
|
+
if (rejectUnknownComponent(res, comp)) return;
|
|
865
|
+
const body = await readBufferBody(req, res);
|
|
866
|
+
if (!body) return;
|
|
867
|
+
componentResource(comp).writeWorking(body);
|
|
868
|
+
jsonResponse(res, 200, { ok: true });
|
|
1279
869
|
}
|
|
1280
|
-
async function
|
|
870
|
+
async function handleClearComponentWorking({ params, res }) {
|
|
1281
871
|
const [comp] = params;
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
const r = componentResource(comp);
|
|
1285
|
-
if (r.existingPath(fileName) === null) {
|
|
1286
|
-
jsonResponse(res, 404, { error: "Config not found" });
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
if (manifestsResource.getActiveName() === "default") {
|
|
1290
|
-
jsonResponse(res, 409, {
|
|
1291
|
-
error: "Active manifest is protected. Save As first.",
|
|
1292
|
-
code: "ACTIVE_IS_PROTECTED"
|
|
1293
|
-
});
|
|
1294
|
-
return;
|
|
1295
|
-
}
|
|
1296
|
-
r.ensureDir();
|
|
1297
|
-
r.setProductionName(fileName);
|
|
1298
|
-
syncComponentsToCss();
|
|
1299
|
-
patchActiveManifest("component", comp, fileName);
|
|
1300
|
-
const cfg = readComponentConfig(comp, fileName);
|
|
1301
|
-
jsonResponse(res, 200, {
|
|
1302
|
-
ok: true,
|
|
1303
|
-
fileName,
|
|
1304
|
-
name: cfg?.name || fileName,
|
|
1305
|
-
aliases: cfg?.aliases || {}
|
|
1306
|
-
});
|
|
872
|
+
componentResource(comp).clearWorking();
|
|
873
|
+
jsonResponse(res, 200, { ok: true });
|
|
1307
874
|
}
|
|
1308
875
|
async function handleComponentConfigByName({ params, req, res }) {
|
|
1309
876
|
const [comp, name] = params;
|
|
877
|
+
if (rejectReservedName(res, name)) return;
|
|
1310
878
|
const r = componentResource(comp);
|
|
1311
879
|
const configPath = r.filePath(name);
|
|
1312
880
|
if (req.method === "GET") {
|
|
@@ -1329,19 +897,16 @@ ${lines.join("\n")}
|
|
|
1329
897
|
body.component = comp;
|
|
1330
898
|
body.name = body.name || name;
|
|
1331
899
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1332
|
-
if (
|
|
900
|
+
if (fs.existsSync(configPath)) {
|
|
1333
901
|
try {
|
|
1334
|
-
const existing = JSON.parse(
|
|
902
|
+
const existing = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1335
903
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1336
904
|
} catch {
|
|
1337
905
|
}
|
|
1338
906
|
}
|
|
1339
907
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1340
908
|
r.ensureDir();
|
|
1341
|
-
|
|
1342
|
-
if (r.getProductionName() === name) {
|
|
1343
|
-
syncComponentsToCss();
|
|
1344
|
-
}
|
|
909
|
+
fs.writeFileSync(configPath, JSON.stringify(body, null, 2));
|
|
1345
910
|
jsonResponse(res, 200, { ok: true, fileName: name });
|
|
1346
911
|
return;
|
|
1347
912
|
}
|
|
@@ -1350,16 +915,7 @@ ${lines.join("\n")}
|
|
|
1350
915
|
jsonResponse(res, 403, { error: "Cannot delete default config" });
|
|
1351
916
|
return;
|
|
1352
917
|
}
|
|
1353
|
-
if (
|
|
1354
|
-
fs2.unlinkSync(configPath);
|
|
1355
|
-
if (r.getActiveName() === name) {
|
|
1356
|
-
r.setActiveName("default");
|
|
1357
|
-
}
|
|
1358
|
-
if (r.getProductionName() === name) {
|
|
1359
|
-
r.setProductionName("default");
|
|
1360
|
-
syncComponentsToCss();
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
918
|
+
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
|
|
1363
919
|
jsonResponse(res, 200, { ok: true });
|
|
1364
920
|
return;
|
|
1365
921
|
}
|
|
@@ -1367,199 +923,190 @@ ${lines.join("\n")}
|
|
|
1367
923
|
async function handleListComponentConfigs({ params, res }) {
|
|
1368
924
|
const [comp] = params;
|
|
1369
925
|
const r = componentResource(comp);
|
|
1370
|
-
if (!
|
|
926
|
+
if (!fs.existsSync(r.dir)) {
|
|
1371
927
|
jsonResponse(res, 404, { error: "Component not found" });
|
|
1372
928
|
return;
|
|
1373
929
|
}
|
|
1374
|
-
const activeFile = r.getActiveName();
|
|
1375
|
-
const productionFile = r.getProductionName();
|
|
1376
930
|
const files = r.listNames().map((fileName) => {
|
|
1377
931
|
const data = r.readJson(fileName);
|
|
1378
932
|
return {
|
|
1379
933
|
name: data?.name || fileName,
|
|
1380
934
|
fileName,
|
|
1381
|
-
updatedAt: data?.updatedAt || ""
|
|
1382
|
-
isActive: fileName === activeFile,
|
|
1383
|
-
isProduction: fileName === productionFile
|
|
935
|
+
updatedAt: data?.updatedAt || ""
|
|
1384
936
|
};
|
|
1385
937
|
});
|
|
1386
|
-
jsonResponse(res, 200, { component: comp, files
|
|
938
|
+
jsonResponse(res, 200, { component: comp, files });
|
|
1387
939
|
}
|
|
1388
|
-
async function
|
|
1389
|
-
const activeFile =
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
940
|
+
async function handleListThemes({ res }) {
|
|
941
|
+
const activeFile = themesResource.getActiveName();
|
|
942
|
+
const productionFile = themesResource.getProductionName();
|
|
943
|
+
const files = [];
|
|
944
|
+
for (const fileName of themesResource.listNames()) {
|
|
945
|
+
const read = readTheme(fileName);
|
|
946
|
+
if (!read) {
|
|
947
|
+
console.warn(
|
|
948
|
+
unreadableThemeReason(fileName) === "colors-and-type" ? `[live-tokens] ${notAThemeError(fileName)} It is left out of the theme list.` : `[live-tokens] Theme "${fileName}" is unreadable and was left out of the list.`
|
|
949
|
+
);
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
files.push({
|
|
953
|
+
name: read.theme.name,
|
|
1394
954
|
fileName,
|
|
1395
|
-
updatedAt:
|
|
955
|
+
updatedAt: read.theme.updatedAt,
|
|
1396
956
|
isActive: fileName === activeFile,
|
|
957
|
+
isProduction: fileName === productionFile,
|
|
1397
958
|
isProtected: fileName === "default"
|
|
1398
|
-
};
|
|
1399
|
-
}
|
|
1400
|
-
jsonResponse(res, 200, { files, activeFile });
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
jsonResponse(res, 200, { files, activeFile, productionFile });
|
|
962
|
+
}
|
|
963
|
+
async function handleGetProductionTheme({ res }) {
|
|
964
|
+
const fileName = themesResource.getProductionName();
|
|
965
|
+
const read = readTheme(fileName);
|
|
966
|
+
if (!read) {
|
|
967
|
+
respondUnreadableTheme(res, fileName, "Production theme not found");
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: fileName });
|
|
1401
971
|
}
|
|
1402
|
-
async function
|
|
1403
|
-
const activeFile =
|
|
1404
|
-
const
|
|
1405
|
-
if (!
|
|
1406
|
-
|
|
972
|
+
async function handleGetActiveTheme({ res }) {
|
|
973
|
+
const activeFile = themesResource.getActiveName();
|
|
974
|
+
const read = readTheme(activeFile);
|
|
975
|
+
if (!read) {
|
|
976
|
+
respondUnreadableTheme(res, activeFile, "Active theme not found");
|
|
1407
977
|
return;
|
|
1408
978
|
}
|
|
1409
|
-
|
|
1410
|
-
data._fileName = activeFile;
|
|
1411
|
-
jsonResponse(res, 200, data);
|
|
979
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: activeFile });
|
|
1412
980
|
}
|
|
1413
|
-
async function
|
|
981
|
+
async function handleSetActiveTheme({ req, res }) {
|
|
1414
982
|
const body = JSON.parse(await readBody(req));
|
|
1415
983
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1416
|
-
if (
|
|
1417
|
-
jsonResponse(res, 404, { error: "
|
|
984
|
+
if (themesResource.existingPath(fileName) === null) {
|
|
985
|
+
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1418
986
|
return;
|
|
1419
987
|
}
|
|
1420
|
-
|
|
988
|
+
themesResource.setActiveName(fileName);
|
|
1421
989
|
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1422
990
|
}
|
|
1423
|
-
async function
|
|
991
|
+
async function handleThemeByName({ params, req, res }) {
|
|
1424
992
|
const [fileName] = params;
|
|
1425
|
-
|
|
993
|
+
if (rejectReservedName(res, fileName)) return;
|
|
994
|
+
const filePath = themesResource.filePath(fileName);
|
|
1426
995
|
if (req.method === "GET") {
|
|
1427
|
-
const
|
|
1428
|
-
if (!
|
|
1429
|
-
|
|
996
|
+
const read = readTheme(fileName);
|
|
997
|
+
if (!read) {
|
|
998
|
+
respondUnreadableTheme(res, fileName, "Not found");
|
|
1430
999
|
return;
|
|
1431
1000
|
}
|
|
1432
|
-
|
|
1433
|
-
data._fileName = fileName;
|
|
1434
|
-
jsonResponse(res, 200, data);
|
|
1001
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: fileName });
|
|
1435
1002
|
return;
|
|
1436
1003
|
}
|
|
1437
1004
|
if (req.method === "PUT") {
|
|
1438
1005
|
if (fileName === "default") {
|
|
1439
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
1006
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default theme" });
|
|
1440
1007
|
return;
|
|
1441
1008
|
}
|
|
1442
1009
|
const body = JSON.parse(await readBody(req));
|
|
1443
|
-
|
|
1444
|
-
|
|
1010
|
+
const { theme } = normalizeTheme(body, diskThemeResolvers);
|
|
1011
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1012
|
+
if (fs.existsSync(filePath)) {
|
|
1445
1013
|
try {
|
|
1446
|
-
const existing = JSON.parse(
|
|
1447
|
-
if (existing.createdAt)
|
|
1014
|
+
const existing = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1015
|
+
if (existing.createdAt) theme.createdAt = existing.createdAt;
|
|
1448
1016
|
} catch {
|
|
1449
1017
|
}
|
|
1450
1018
|
}
|
|
1451
|
-
|
|
1452
|
-
fs2.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
1019
|
+
writeTheme(fileName, theme);
|
|
1453
1020
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1454
1021
|
return;
|
|
1455
1022
|
}
|
|
1456
1023
|
if (req.method === "DELETE") {
|
|
1457
1024
|
if (fileName === "default") {
|
|
1458
|
-
jsonResponse(res, 403, { error: "Cannot delete the default
|
|
1025
|
+
jsonResponse(res, 403, { error: "Cannot delete the default theme" });
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (fileName === themesResource.getProductionName()) {
|
|
1029
|
+
jsonResponse(res, 403, {
|
|
1030
|
+
error: "Cannot delete the theme that is in production. Adopt another theme first.",
|
|
1031
|
+
code: "PRODUCTION_THEME"
|
|
1032
|
+
});
|
|
1459
1033
|
return;
|
|
1460
1034
|
}
|
|
1461
|
-
if (
|
|
1462
|
-
|
|
1463
|
-
if (
|
|
1464
|
-
|
|
1035
|
+
if (fs.existsSync(filePath)) {
|
|
1036
|
+
fs.unlinkSync(filePath);
|
|
1037
|
+
if (themesResource.existingPath(fileName) === null && themesResource.getActiveName() === fileName) {
|
|
1038
|
+
themesResource.setActiveName("default");
|
|
1465
1039
|
}
|
|
1040
|
+
} else if (themesResource.existingPath(fileName)) {
|
|
1041
|
+
jsonResponse(res, 403, {
|
|
1042
|
+
error: "Cannot delete a theme shipped with the package. Saving it creates a local copy; deleting that copy restores the shipped version.",
|
|
1043
|
+
code: "PACKAGE_THEME"
|
|
1044
|
+
});
|
|
1045
|
+
return;
|
|
1466
1046
|
}
|
|
1467
1047
|
jsonResponse(res, 200, { ok: true });
|
|
1468
1048
|
return;
|
|
1469
1049
|
}
|
|
1470
1050
|
}
|
|
1471
|
-
async function
|
|
1051
|
+
async function handleApplyTheme({ params, res }) {
|
|
1472
1052
|
const [fileName] = params;
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1053
|
+
if (rejectReservedName(res, fileName)) return;
|
|
1054
|
+
const read = readTheme(fileName);
|
|
1055
|
+
if (!read) {
|
|
1056
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
1476
1057
|
return;
|
|
1477
1058
|
}
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1480
|
-
|
|
1059
|
+
const { theme } = read;
|
|
1060
|
+
const isDefault = fileName === "default";
|
|
1061
|
+
if (!isDefault && !theme.colorsAndType) {
|
|
1062
|
+
jsonResponse(res, 422, { error: "This theme carries no colors and type" });
|
|
1481
1063
|
return;
|
|
1482
1064
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
const
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
if (!knownComponents.has(comp)) continue;
|
|
1489
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1490
|
-
const r = componentResource(comp);
|
|
1491
|
-
if (r.existingPath(sanitized) === null) {
|
|
1492
|
-
jsonResponse(res, 422, {
|
|
1493
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1494
|
-
});
|
|
1495
|
-
return;
|
|
1496
|
-
}
|
|
1497
|
-
apply.push([comp, sanitized]);
|
|
1498
|
-
}
|
|
1499
|
-
themesResource.setActiveName(themeName);
|
|
1500
|
-
themesResource.setProductionName(themeName);
|
|
1501
|
-
syncTokensToCss(themeName);
|
|
1502
|
-
syncFontsToCss(themeName);
|
|
1503
|
-
const themeData = normalizeTheme(themesResource.readJson(themeName));
|
|
1504
|
-
themeData._fileName = themeName;
|
|
1505
|
-
for (const [comp, configFile] of apply) {
|
|
1065
|
+
if (isDefault) colorsAndTypeResource.clearWorking();
|
|
1066
|
+
else colorsAndTypeResource.writeWorking(theme.colorsAndType);
|
|
1067
|
+
const knownComponents = listComponentNames();
|
|
1068
|
+
for (const comp of knownComponents) {
|
|
1069
|
+
const embedded = isDefault ? void 0 : theme.componentConfigs[comp];
|
|
1506
1070
|
const r = componentResource(comp);
|
|
1507
|
-
r.
|
|
1508
|
-
r.
|
|
1509
|
-
const cfg = readComponentConfig(comp, configFile);
|
|
1510
|
-
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
|
|
1071
|
+
if (embedded) r.writeWorking({ ...embedded, component: comp });
|
|
1072
|
+
else r.clearWorking();
|
|
1511
1073
|
}
|
|
1074
|
+
themesResource.setActiveName(fileName);
|
|
1075
|
+
const resolvedConfigs = {};
|
|
1512
1076
|
for (const comp of knownComponents) {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
const cfg = readComponentConfig(comp, activeName);
|
|
1516
|
-
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: activeName };
|
|
1077
|
+
const resolved = resolveLiveComponentConfig(comp, theme);
|
|
1078
|
+
if (resolved) resolvedConfigs[comp] = { ...resolved.data, _fileName: fileName, _source: resolved.source };
|
|
1517
1079
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1080
|
+
const resolvedColorsAndType = resolveLiveColorsAndType(theme);
|
|
1081
|
+
const skippedComponents = Object.keys(theme.componentConfigs).filter(
|
|
1082
|
+
(comp) => !knownComponents.includes(comp)
|
|
1083
|
+
);
|
|
1520
1084
|
jsonResponse(res, 200, {
|
|
1521
1085
|
ok: true,
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1086
|
+
theme: { ...theme, _fileName: fileName },
|
|
1087
|
+
colorsAndType: resolvedColorsAndType && {
|
|
1088
|
+
...resolvedColorsAndType.data,
|
|
1089
|
+
_fileName: fileName,
|
|
1090
|
+
_source: resolvedColorsAndType.source
|
|
1091
|
+
},
|
|
1092
|
+
componentConfigs: resolvedConfigs,
|
|
1093
|
+
skippedComponents
|
|
1525
1094
|
});
|
|
1526
1095
|
}
|
|
1527
|
-
async function
|
|
1096
|
+
async function handleExportTheme({ params, res }) {
|
|
1528
1097
|
const [fileName] = params;
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1098
|
+
if (rejectReservedName(res, fileName)) return;
|
|
1099
|
+
const read = readTheme(fileName);
|
|
1100
|
+
if (!read) {
|
|
1101
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
1532
1102
|
return;
|
|
1533
1103
|
}
|
|
1534
|
-
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
1535
|
-
const theme = themesResource.readJson(themeName);
|
|
1536
|
-
if (!theme) {
|
|
1537
|
-
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
const knownComponents = new Set(listComponentNames());
|
|
1541
|
-
const componentConfigs = {};
|
|
1542
|
-
for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
|
|
1543
|
-
if (!knownComponents.has(comp)) continue;
|
|
1544
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1545
|
-
if (sanitized === "default") continue;
|
|
1546
|
-
const cfg = componentResource(comp).readJson(sanitized);
|
|
1547
|
-
if (!cfg) {
|
|
1548
|
-
jsonResponse(res, 422, {
|
|
1549
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1550
|
-
});
|
|
1551
|
-
return;
|
|
1552
|
-
}
|
|
1553
|
-
componentConfigs[`${comp}/${sanitized}`] = cfg;
|
|
1554
|
-
}
|
|
1555
1104
|
const bundle = {
|
|
1556
|
-
kind: "
|
|
1557
|
-
schemaVersion:
|
|
1105
|
+
kind: "theme-bundle",
|
|
1106
|
+
schemaVersion: THEME_SCHEMA_VERSION,
|
|
1558
1107
|
liveTokensVersion: PKG_VERSION,
|
|
1559
1108
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1560
|
-
manifest
|
|
1561
|
-
theme,
|
|
1562
|
-
componentConfigs
|
|
1109
|
+
manifest: read.theme
|
|
1563
1110
|
};
|
|
1564
1111
|
res.statusCode = 200;
|
|
1565
1112
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -1572,7 +1119,7 @@ ${lines.join("\n")}
|
|
|
1572
1119
|
function nextAvailableName2(exists, baseName) {
|
|
1573
1120
|
return nextAvailableName(exists, sanitizeFileName(baseName));
|
|
1574
1121
|
}
|
|
1575
|
-
async function
|
|
1122
|
+
async function handleImportTheme({ req, res }) {
|
|
1576
1123
|
let bundle;
|
|
1577
1124
|
try {
|
|
1578
1125
|
bundle = JSON.parse(await readBody(req));
|
|
@@ -1580,117 +1127,112 @@ ${lines.join("\n")}
|
|
|
1580
1127
|
jsonResponse(res, 400, { error: "Body is not valid JSON" });
|
|
1581
1128
|
return;
|
|
1582
1129
|
}
|
|
1583
|
-
if (!bundle || bundle.kind
|
|
1130
|
+
if (!bundle || !Object.values(BUNDLE_KIND_BY_VERSION).includes(bundle.kind)) {
|
|
1584
1131
|
jsonResponse(res, 400, {
|
|
1585
|
-
error: "Not a
|
|
1132
|
+
error: "Not a theme bundle (kind discriminator missing or wrong)"
|
|
1586
1133
|
});
|
|
1587
1134
|
return;
|
|
1588
1135
|
}
|
|
1589
|
-
if (bundle.
|
|
1136
|
+
if (bundle.kind !== BUNDLE_KIND_BY_VERSION[bundle.schemaVersion]) {
|
|
1590
1137
|
jsonResponse(res, 400, {
|
|
1591
|
-
error: `Unsupported bundle schemaVersion
|
|
1138
|
+
error: `Unsupported bundle: kind "${bundle.kind}" with schemaVersion ${bundle.schemaVersion}`
|
|
1592
1139
|
});
|
|
1593
1140
|
return;
|
|
1594
1141
|
}
|
|
1595
|
-
if (!bundle.manifest
|
|
1596
|
-
jsonResponse(res, 400, { error: "Bundle missing
|
|
1142
|
+
if (!bundle.manifest) {
|
|
1143
|
+
jsonResponse(res, 400, { error: "Bundle missing theme" });
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const bundleResolvers = {
|
|
1147
|
+
readColorsAndType: () => bundle.theme,
|
|
1148
|
+
readComponentConfig: (comp, name) => bundle.componentConfigs?.[`${comp}/${name}`],
|
|
1149
|
+
normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
|
|
1150
|
+
};
|
|
1151
|
+
const { theme, dropped } = normalizeTheme(bundle.manifest, bundleResolvers);
|
|
1152
|
+
if (theme.colorsAndType) theme.colorsAndType = normalizeColorsAndType(theme.colorsAndType);
|
|
1153
|
+
if (!theme.colorsAndType) {
|
|
1154
|
+
jsonResponse(res, 400, { error: "Bundle carries no theme" });
|
|
1597
1155
|
return;
|
|
1598
1156
|
}
|
|
1157
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1599
1158
|
const renames = {};
|
|
1600
|
-
const
|
|
1601
|
-
const
|
|
1602
|
-
const finalThemeName = nextAvailableName2(
|
|
1159
|
+
const originalName = sanitizeFileName(theme.name || "imported");
|
|
1160
|
+
const finalName = nextAvailableName2(
|
|
1603
1161
|
(n) => themesResource.existingPath(n) !== null,
|
|
1604
|
-
|
|
1162
|
+
originalName
|
|
1605
1163
|
);
|
|
1606
|
-
if (
|
|
1607
|
-
renames[`theme:${
|
|
1164
|
+
if (finalName !== originalName) {
|
|
1165
|
+
renames[`theme:${originalName}`] = finalName;
|
|
1608
1166
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
if (finalName !== originalName) {
|
|
1626
|
-
renames[`componentConfig:${comp}/${originalName}`] = finalName;
|
|
1627
|
-
}
|
|
1628
|
-
componentRenames[`${comp}/${originalName}`] = finalName;
|
|
1629
|
-
const cfgBody = { ...cfgValue };
|
|
1630
|
-
cfgBody.component = comp;
|
|
1631
|
-
cfgBody.name = finalName;
|
|
1632
|
-
cfgBody.updatedAt = now;
|
|
1633
|
-
if (!cfgBody.createdAt) cfgBody.createdAt = now;
|
|
1634
|
-
r.ensureDir();
|
|
1635
|
-
fs2.writeFileSync(r.filePath(finalName), JSON.stringify(cfgBody, null, 2));
|
|
1167
|
+
writeTheme(finalName, theme);
|
|
1168
|
+
jsonResponse(res, 200, {
|
|
1169
|
+
ok: true,
|
|
1170
|
+
theme: finalName,
|
|
1171
|
+
renames,
|
|
1172
|
+
dropped
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
async function handleAdoptLook({ res }) {
|
|
1176
|
+
const activeName = themesResource.getActiveName();
|
|
1177
|
+
if (activeName === "default") {
|
|
1178
|
+
jsonResponse(res, 409, {
|
|
1179
|
+
error: "Active theme is protected. Save As first.",
|
|
1180
|
+
code: "ACTIVE_IS_PROTECTED"
|
|
1181
|
+
});
|
|
1182
|
+
return;
|
|
1636
1183
|
}
|
|
1637
|
-
const
|
|
1638
|
-
|
|
1639
|
-
theme
|
|
1640
|
-
|
|
1641
|
-
};
|
|
1642
|
-
for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
|
|
1643
|
-
const original = String(configName);
|
|
1644
|
-
if (original === "default") {
|
|
1645
|
-
rewrittenManifest.componentConfigs[comp] = "default";
|
|
1646
|
-
continue;
|
|
1647
|
-
}
|
|
1648
|
-
const finalName = componentRenames[`${comp}/${original}`] ?? original;
|
|
1649
|
-
rewrittenManifest.componentConfigs[comp] = finalName;
|
|
1650
|
-
}
|
|
1651
|
-
rewrittenManifest.updatedAt = now;
|
|
1652
|
-
if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
|
|
1653
|
-
const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
|
|
1654
|
-
const finalManifestName = nextAvailableName2(
|
|
1655
|
-
(n) => manifestsResource.existingPath(n) !== null,
|
|
1656
|
-
originalManifestName
|
|
1657
|
-
);
|
|
1658
|
-
if (finalManifestName !== originalManifestName) {
|
|
1659
|
-
renames[`manifest:${originalManifestName}`] = finalManifestName;
|
|
1184
|
+
const read = readTheme(activeName);
|
|
1185
|
+
if (!read) {
|
|
1186
|
+
respondUnreadableTheme(res, activeName, "Active theme not found");
|
|
1187
|
+
return;
|
|
1660
1188
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
JSON.stringify(rewrittenManifest, null, 2)
|
|
1665
|
-
);
|
|
1189
|
+
themesResource.setProductionName(activeName);
|
|
1190
|
+
regenerateTokensCss();
|
|
1191
|
+
syncFontsToCss(read.theme.colorsAndType);
|
|
1666
1192
|
jsonResponse(res, 200, {
|
|
1667
1193
|
ok: true,
|
|
1668
|
-
|
|
1669
|
-
|
|
1194
|
+
productionTheme: {
|
|
1195
|
+
fileName: activeName,
|
|
1196
|
+
name: read.theme.name,
|
|
1197
|
+
updatedAt: read.theme.updatedAt
|
|
1198
|
+
}
|
|
1670
1199
|
});
|
|
1671
1200
|
}
|
|
1672
1201
|
function methodNotAllowed({ res }) {
|
|
1673
1202
|
jsonResponse(res, 405, { error: "Method not allowed" });
|
|
1674
1203
|
}
|
|
1204
|
+
let legacyLayout = null;
|
|
1675
1205
|
const routes = [
|
|
1676
|
-
//
|
|
1677
|
-
|
|
1678
|
-
{ method: "GET", pattern:
|
|
1679
|
-
{ method: "
|
|
1680
|
-
{ method: "
|
|
1681
|
-
{ method: "
|
|
1206
|
+
// Colors and type — list / active / production / working are exact strings;
|
|
1207
|
+
// they must run before COLORS_AND_TYPE_BY_NAME_REGEX
|
|
1208
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_ROUTE, handler: handleListColorsAndType },
|
|
1209
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_ACTIVE_ROUTE, handler: handleGetActiveColorsAndType },
|
|
1210
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_ACTIVE_ROUTE, handler: methodNotAllowed },
|
|
1211
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1212
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1213
|
+
{ method: "POST", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1214
|
+
{ method: "DELETE", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1215
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: handleGetWorkingColorsAndType },
|
|
1216
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: handleSetWorkingColorsAndType },
|
|
1217
|
+
{ method: "DELETE", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: handleClearWorkingColorsAndType },
|
|
1218
|
+
{ method: "POST", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: methodNotAllowed },
|
|
1682
1219
|
// Component configs — list of components
|
|
1683
1220
|
{ method: "GET", pattern: COMPONENT_CONFIGS_ROUTE, handler: handleListComponents },
|
|
1684
1221
|
// Component configs — :comp/active (must precede :comp/:name)
|
|
1685
1222
|
{ method: "GET", pattern: COMP_ACTIVE_REGEX, handler: handleGetComponentActive },
|
|
1686
|
-
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler:
|
|
1223
|
+
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
1687
1224
|
{ method: "POST", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
1688
1225
|
{ method: "DELETE", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
1689
|
-
// Component configs — :comp/production
|
|
1690
|
-
{ method: "GET", pattern: COMP_PRODUCTION_REGEX, handler:
|
|
1691
|
-
{ method: "PUT", pattern: COMP_PRODUCTION_REGEX, handler:
|
|
1226
|
+
// Component configs — :comp/production, retired with the per-layer pointers
|
|
1227
|
+
{ method: "GET", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
1228
|
+
{ method: "PUT", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
1692
1229
|
{ method: "POST", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
1693
1230
|
{ method: "DELETE", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
1231
|
+
// Component configs — :comp/working (must precede :comp/:name)
|
|
1232
|
+
{ method: "GET", pattern: COMP_WORKING_REGEX, handler: handleGetComponentWorking },
|
|
1233
|
+
{ method: "PUT", pattern: COMP_WORKING_REGEX, handler: handleSetComponentWorking },
|
|
1234
|
+
{ method: "DELETE", pattern: COMP_WORKING_REGEX, handler: handleClearComponentWorking },
|
|
1235
|
+
{ method: "POST", pattern: COMP_WORKING_REGEX, handler: methodNotAllowed },
|
|
1694
1236
|
// Component configs — :comp/:name (must precede :comp listing)
|
|
1695
1237
|
{ method: "GET", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
1696
1238
|
{ method: "PUT", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
@@ -1698,33 +1240,44 @@ ${lines.join("\n")}
|
|
|
1698
1240
|
{ method: "POST", pattern: COMP_BY_NAME_REGEX, handler: methodNotAllowed },
|
|
1699
1241
|
// Component configs — list configs for :comp (broadest, runs last among comp routes)
|
|
1700
1242
|
{ method: "GET", pattern: COMP_LIST_REGEX, handler: handleListComponentConfigs },
|
|
1243
|
+
// Colors and type — :name CRUD (broadest colors-and-type route, runs last)
|
|
1244
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_BY_NAME_REGEX, handler: handleColorsAndTypeByName },
|
|
1245
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_BY_NAME_REGEX, handler: handleColorsAndTypeByName },
|
|
1246
|
+
{ method: "DELETE", pattern: COLORS_AND_TYPE_BY_NAME_REGEX, handler: handleColorsAndTypeByName },
|
|
1247
|
+
// Themes — list / active / production are exact strings, must run before regexes
|
|
1248
|
+
{ method: "GET", pattern: THEMES_ROUTE, handler: handleListThemes },
|
|
1249
|
+
{ method: "GET", pattern: THEMES_ACTIVE_ROUTE, handler: handleGetActiveTheme },
|
|
1250
|
+
{ method: "PUT", pattern: THEMES_ACTIVE_ROUTE, handler: handleSetActiveTheme },
|
|
1251
|
+
{ method: "GET", pattern: THEMES_PRODUCTION_ROUTE, handler: handleGetProductionTheme },
|
|
1252
|
+
// Adopt is the one door that moves the production pointer; naming a theme
|
|
1253
|
+
// here would be a second, silent way to publish.
|
|
1254
|
+
{ method: "PUT", pattern: THEMES_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1255
|
+
{ method: "POST", pattern: THEMES_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1256
|
+
{ method: "DELETE", pattern: THEMES_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1257
|
+
// Themes — exact import route runs before :name regexes
|
|
1258
|
+
{ method: "POST", pattern: THEME_IMPORT_ROUTE, handler: handleImportTheme },
|
|
1259
|
+
{ method: "PUT", pattern: THEME_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1260
|
+
{ method: "GET", pattern: THEME_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1261
|
+
{ method: "DELETE", pattern: THEME_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1262
|
+
// Themes — :name/apply (more specific than :name)
|
|
1263
|
+
{ method: "PUT", pattern: THEME_APPLY_REGEX, handler: handleApplyTheme },
|
|
1264
|
+
{ method: "POST", pattern: THEME_APPLY_REGEX, handler: methodNotAllowed },
|
|
1265
|
+
{ method: "GET", pattern: THEME_APPLY_REGEX, handler: methodNotAllowed },
|
|
1266
|
+
{ method: "DELETE", pattern: THEME_APPLY_REGEX, handler: methodNotAllowed },
|
|
1267
|
+
// Themes — :name/export (more specific than :name)
|
|
1268
|
+
{ method: "GET", pattern: THEME_EXPORT_REGEX, handler: handleExportTheme },
|
|
1269
|
+
{ method: "PUT", pattern: THEME_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1270
|
+
{ method: "POST", pattern: THEME_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1271
|
+
{ method: "DELETE", pattern: THEME_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1701
1272
|
// Themes — :name CRUD (broadest theme route, runs last)
|
|
1702
1273
|
{ method: "GET", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
1703
1274
|
{ method: "PUT", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
1704
1275
|
{ method: "DELETE", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
1705
|
-
//
|
|
1706
|
-
{ method: "
|
|
1707
|
-
{ method: "GET", pattern:
|
|
1708
|
-
{ method: "
|
|
1709
|
-
|
|
1710
|
-
{ method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
|
|
1711
|
-
{ method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1712
|
-
{ method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1713
|
-
{ method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1714
|
-
// Manifests — :name/apply (more specific than :name)
|
|
1715
|
-
{ method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
|
|
1716
|
-
{ method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1717
|
-
{ method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1718
|
-
{ method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1719
|
-
// Manifests — :name/export (more specific than :name)
|
|
1720
|
-
{ method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
|
|
1721
|
-
{ method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1722
|
-
{ method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1723
|
-
{ method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1724
|
-
// Manifests — :name CRUD (broadest manifest route, runs last)
|
|
1725
|
-
{ method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
1726
|
-
{ method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
1727
|
-
{ method: "DELETE", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName }
|
|
1276
|
+
// Production — the whole-look adopt door
|
|
1277
|
+
{ method: "PUT", pattern: PRODUCTION_ROUTE, handler: handleAdoptLook },
|
|
1278
|
+
{ method: "GET", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1279
|
+
{ method: "POST", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
1280
|
+
{ method: "DELETE", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed }
|
|
1728
1281
|
];
|
|
1729
1282
|
return {
|
|
1730
1283
|
name: "theme-file-api",
|
|
@@ -1738,19 +1291,42 @@ ${lines.join("\n")}
|
|
|
1738
1291
|
};
|
|
1739
1292
|
},
|
|
1740
1293
|
configureServer(server) {
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1294
|
+
legacyLayout = detectLegacyLayout({
|
|
1295
|
+
dataDir: dataDirs.dataDir,
|
|
1296
|
+
colorsAndTypeDir: COLORS_AND_TYPE_DIR,
|
|
1297
|
+
themesDir: THEMES_DIR,
|
|
1298
|
+
configuredManifestsDir: dataDirs.legacyManifestsDir
|
|
1299
|
+
});
|
|
1300
|
+
if (legacyLayout) {
|
|
1301
|
+
warnOnLegacyLayout(legacyLayout.evidence, (msg) => server.config.logger.warn(msg));
|
|
1302
|
+
} else {
|
|
1303
|
+
ensureColorsAndTypeDir();
|
|
1304
|
+
ensureComponentConfigsDir();
|
|
1305
|
+
const { productionPointerHeld } = ensureThemesDir((msg) => server.config.logger.warn(msg));
|
|
1306
|
+
if (!productionPointerHeld || !fs.existsSync(GENERATED_CSS_PATH)) regenerateTokensCss();
|
|
1307
|
+
if (opts.autoMigrate) autoMigrateAdditive((msg) => server.config.logger.info(msg));
|
|
1308
|
+
warnOnTokenDrift((msg) => server.config.logger.warn(msg));
|
|
1309
|
+
warnOnLegacyPointers(
|
|
1310
|
+
listLegacyPointers(),
|
|
1311
|
+
productionPointerHeld,
|
|
1312
|
+
(msg) => server.config.logger.warn(msg)
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1747
1315
|
server.middlewares.use(async (req, res, next) => {
|
|
1316
|
+
if (legacyLayout && req.method !== "GET" && (req.url ?? "").startsWith(API_BASE)) {
|
|
1317
|
+
jsonResponse(res, 409, {
|
|
1318
|
+
error: "This project still uses the data layout from 0.47 and earlier. Run `npx live-tokens migrate`, then restart the dev server.",
|
|
1319
|
+
code: "LEGACY_LAYOUT"
|
|
1320
|
+
});
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1748
1323
|
const handled = await dispatch(req, res, routes);
|
|
1749
1324
|
if (!handled) next();
|
|
1750
1325
|
});
|
|
1751
1326
|
},
|
|
1752
1327
|
handleHotUpdate(ctx) {
|
|
1753
|
-
|
|
1328
|
+
if (legacyLayout) return;
|
|
1329
|
+
const normalized = path.resolve(ctx.file);
|
|
1754
1330
|
if (!COMPONENTS_SCAN_DIRS.some((d) => normalized.startsWith(d))) return;
|
|
1755
1331
|
if (!normalized.endsWith(".svelte")) return;
|
|
1756
1332
|
if (!isThemeAwareComponent(normalized)) return;
|