@motion-proto/live-tokens 0.47.1 → 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 +270 -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 +849 -531
- package/dist-plugin/index.d.cts +3 -2
- package/dist-plugin/index.d.ts +3 -2
- package/dist-plugin/index.js +658 -1132
- 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 +83 -13
- 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/FileLoadList.svelte +67 -7
- package/src/editor/ui/GradientEditor.svelte +3 -3
- package/src/editor/ui/ThemePanel.svelte +1067 -0
- package/src/editor/ui/UIDialog.svelte +11 -9
- package/src/editor/ui/index.ts +4 -2
- package/src/live-tokens/data/colors-and-type/autumn.json +2490 -0
- package/src/live-tokens/data/{themes → colors-and-type}/default.json +962 -549
- 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 +129 -140
- 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,509 +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
|
-
var HANDLE_SPAN = 1 / 3;
|
|
127
|
-
function tangentSlope(prev, x, y, next) {
|
|
128
|
-
const hPrev = prev ? x - prev.x : 0;
|
|
129
|
-
const hNext = next ? next.x - x : 0;
|
|
130
|
-
const dPrev = hPrev > 0 ? (y - prev.y) / hPrev : null;
|
|
131
|
-
const dNext = hNext > 0 ? (next.y - y) / hNext : null;
|
|
132
|
-
if (dPrev === null) return dNext ?? 0;
|
|
133
|
-
if (dNext === null) return dPrev;
|
|
134
|
-
if (dPrev * dNext <= 0) return 0;
|
|
135
|
-
const wPrev = 2 * hNext + hPrev;
|
|
136
|
-
const wNext = hNext + 2 * hPrev;
|
|
137
|
-
return (wPrev + wNext) / (wPrev / dPrev + wNext / dNext);
|
|
138
|
-
}
|
|
139
|
-
function tangentAnchor(x, y, prev, next) {
|
|
140
|
-
const m = tangentSlope(prev, x, y, next);
|
|
141
|
-
const inDx = prev ? -(x - prev.x) * HANDLE_SPAN : 0;
|
|
142
|
-
const outDx = next ? (next.x - x) * HANDLE_SPAN : 0;
|
|
143
|
-
return { x, y, inDx, inDy: m * inDx, outDx, outDy: m * outDx };
|
|
144
|
-
}
|
|
145
|
-
function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
|
|
146
|
-
const u = 1 - t, u2 = u * u, u3 = u2 * u;
|
|
147
|
-
const t2 = t * t, t3 = t2 * t;
|
|
148
|
-
return {
|
|
149
|
-
x: u3 * p0x + 3 * u2 * t * c0x + 3 * u * t2 * c1x + t3 * p1x,
|
|
150
|
-
y: u3 * p0y + 3 * u2 * t * c0y + 3 * u * t2 * c1y + t3 * p1y
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
function sampleCurve(anchors, xPos) {
|
|
154
|
-
if (anchors.length === 0) return 0;
|
|
155
|
-
if (anchors.length === 1) return anchors[0].y;
|
|
156
|
-
if (xPos <= anchors[0].x) return anchors[0].y;
|
|
157
|
-
if (xPos >= anchors[anchors.length - 1].x) return anchors[anchors.length - 1].y;
|
|
158
|
-
let seg = 0;
|
|
159
|
-
while (seg < anchors.length - 2 && anchors[seg + 1].x < xPos) seg++;
|
|
160
|
-
const a0 = anchors[seg], a1 = anchors[seg + 1];
|
|
161
|
-
const p0x = a0.x, p0y = a0.y;
|
|
162
|
-
const c0x = a0.x + a0.outDx, c0y = a0.y + a0.outDy;
|
|
163
|
-
const c1x = a1.x + a1.inDx, c1y = a1.y + a1.inDy;
|
|
164
|
-
const p1x = a1.x, p1y = a1.y;
|
|
165
|
-
let lo = 0, hi = 1;
|
|
166
|
-
for (let i = 0; i < 20; i++) {
|
|
167
|
-
const mid = (lo + hi) / 2;
|
|
168
|
-
const pt = evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, mid);
|
|
169
|
-
if (pt.x < xPos) lo = mid;
|
|
170
|
-
else hi = mid;
|
|
171
|
-
}
|
|
172
|
-
return evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, (lo + hi) / 2).y;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// src/editor/core/palettes/paletteDerivation.ts
|
|
176
|
-
var PALETTE_SPECS = [
|
|
177
|
-
{ label: "Brand", cssNamespace: "brand", initialColor: { l: 0.5573, c: 0.1841, h: 25.49 } },
|
|
178
|
-
{ label: "Accent", cssNamespace: "accent", initialColor: { l: 0.7674, c: 0.164, h: 70.44 } },
|
|
179
|
-
{ label: "Special", cssNamespace: "special", initialColor: { l: 0.6056, c: 0.2189, h: 292.72 } },
|
|
180
|
-
{ label: "Canvas", cssNamespace: "canvas", emptySelector: true, initialColor: { l: 0.2284, c: 0.0384, h: 282.93 } },
|
|
181
|
-
{ label: "Neutral", cssNamespace: "neutral", neutral: true, initialColor: { l: 0.5679, c: 0.0134, h: 240.07 } },
|
|
182
|
-
{ label: "Alternate", cssNamespace: "alternate", neutral: true, displayLabel: "Alternate (neutral)", initialColor: { l: 0.5873, c: 87e-4, h: 48.57 } },
|
|
183
|
-
{ label: "Info", cssNamespace: "info", initialColor: { l: 0.5871, c: 0.1855, h: 259.56 } },
|
|
184
|
-
{ label: "Success", cssNamespace: "success", initialColor: { l: 0.7198, c: 0.1918, h: 149.52 } },
|
|
185
|
-
{ label: "Warning", cssNamespace: "warning", initialColor: { l: 0.6704, c: 0.1716, h: 48.75 } },
|
|
186
|
-
{ label: "Danger", cssNamespace: "danger", initialColor: { l: 0.6103, c: 0.2165, h: 18.1 } }
|
|
187
|
-
];
|
|
188
|
-
var PALETTE_STEPS = [
|
|
189
|
-
{ label: "100" },
|
|
190
|
-
{ label: "200" },
|
|
191
|
-
{ label: "300" },
|
|
192
|
-
{ label: "400" },
|
|
193
|
-
{ label: "500" },
|
|
194
|
-
{ label: "600" },
|
|
195
|
-
{ label: "700" },
|
|
196
|
-
{ label: "800" },
|
|
197
|
-
{ label: "850" },
|
|
198
|
-
{ label: "900" },
|
|
199
|
-
{ label: "950" }
|
|
200
|
-
];
|
|
201
|
-
var SCALES = [
|
|
202
|
-
{
|
|
203
|
-
title: "Surfaces",
|
|
204
|
-
isText: false,
|
|
205
|
-
steps: [
|
|
206
|
-
{ name: "lowest", position: -1 },
|
|
207
|
-
{ name: "lower", position: -2 / 3 },
|
|
208
|
-
{ name: "low", position: -1 / 3 },
|
|
209
|
-
{ name: "default", position: 0 },
|
|
210
|
-
{ name: "high", position: 1 / 3 },
|
|
211
|
-
{ name: "higher", position: 2 / 3 },
|
|
212
|
-
{ name: "highest", position: 1 }
|
|
213
|
-
]
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
title: "Borders",
|
|
217
|
-
isText: false,
|
|
218
|
-
steps: [
|
|
219
|
-
{ name: "faint", position: -1 },
|
|
220
|
-
{ name: "subtle", position: -0.5 },
|
|
221
|
-
{ name: "default", position: 0 },
|
|
222
|
-
{ name: "medium", position: 0.5 },
|
|
223
|
-
{ name: "strong", position: 1 }
|
|
224
|
-
]
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
title: "Text",
|
|
228
|
-
isText: true,
|
|
229
|
-
steps: [
|
|
230
|
-
{ name: "primary", position: 0 },
|
|
231
|
-
{ name: "secondary", position: 0 },
|
|
232
|
-
{ name: "tertiary", position: 0 },
|
|
233
|
-
{ name: "muted", position: 0 },
|
|
234
|
-
{ name: "disabled", position: 0 }
|
|
235
|
-
]
|
|
236
|
-
}
|
|
237
|
-
];
|
|
238
|
-
var BW_GUARD_MIN_L = 0.17;
|
|
239
|
-
var BW_GUARD_MAX_L = 0.93;
|
|
240
|
-
var DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
|
|
241
|
-
var DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
|
|
242
|
-
function scaleCurveDefaults(scheme = "dark") {
|
|
243
|
-
if (scheme === "light") {
|
|
244
|
-
return {
|
|
245
|
-
Surfaces: {
|
|
246
|
-
lightness: () => [makeAnchor(0, 98, 5), makeAnchor(100, 82, 5)],
|
|
247
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
248
|
-
},
|
|
249
|
-
Borders: {
|
|
250
|
-
lightness: () => [makeAnchor(0, 92, 5), makeAnchor(100, 45, 5)],
|
|
251
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
252
|
-
},
|
|
253
|
-
Text: {
|
|
254
|
-
lightness: () => [makeAnchor(0, 30, 30), makeAnchor(100, 120, 30)],
|
|
255
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
|
|
256
|
-
}
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
return {
|
|
260
|
-
Surfaces: {
|
|
261
|
-
lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
|
|
262
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
263
|
-
},
|
|
264
|
-
Borders: {
|
|
265
|
-
lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
|
|
266
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
267
|
-
},
|
|
268
|
-
Text: {
|
|
269
|
-
lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
|
|
270
|
-
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
var defaultScaleCurves = scaleCurveDefaults("dark");
|
|
275
|
-
function paletteStepKey(label) {
|
|
276
|
-
return `Palette-${label}`;
|
|
277
|
-
}
|
|
278
|
-
function stepKey(scaleTitle, stepName) {
|
|
279
|
-
return `${scaleTitle}-${stepName}`;
|
|
280
|
-
}
|
|
281
|
-
function stepIndexToX(index) {
|
|
282
|
-
return index / (PALETTE_STEPS.length - 1) * 100;
|
|
283
|
-
}
|
|
284
|
-
function scaleStepToX(step, scale) {
|
|
285
|
-
const idx = scale.steps.indexOf(step);
|
|
286
|
-
return scale.steps.length > 1 ? idx / (scale.steps.length - 1) * 100 : 50;
|
|
287
|
-
}
|
|
288
|
-
function serializeDerivedValue(value) {
|
|
289
|
-
return value.kind === "raw" ? value.css : oklchToHexClamped(value.l, value.c, value.h);
|
|
290
|
-
}
|
|
291
|
-
function computePaletteOklch(index, base, lightnessCurve, saturationCurve, curveOffset) {
|
|
292
|
-
const { c: baseC, h } = base;
|
|
293
|
-
const xPos = stepIndexToX(index);
|
|
294
|
-
const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset.lightness ?? 0))) / 100;
|
|
295
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset.saturation ?? 0)) / 100));
|
|
296
|
-
const targetC = baseC * satMul;
|
|
297
|
-
return gamutClamp(targetL, targetC, h);
|
|
298
|
-
}
|
|
299
|
-
function nearestPaletteStep(lightnessCurve, baseL) {
|
|
300
|
-
let best = 0;
|
|
301
|
-
let bestD = Infinity;
|
|
302
|
-
for (let i = 0; i < PALETTE_STEPS.length; i++) {
|
|
303
|
-
const d = Math.abs(sampleCurve(lightnessCurve, stepIndexToX(i)) - baseL * 100);
|
|
304
|
-
if (d < bestD) {
|
|
305
|
-
bestD = d;
|
|
306
|
-
best = i;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return best;
|
|
310
|
-
}
|
|
311
|
-
function setCurveAnchor(curve, x, y) {
|
|
312
|
-
const existing = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
313
|
-
if (existing >= 0) {
|
|
314
|
-
const displacedY = curve[existing].y;
|
|
315
|
-
return { curve: curve.map((a, i) => i === existing ? { ...a, x, y } : a), displacedY };
|
|
316
|
-
}
|
|
317
|
-
let insertAt = curve.findIndex((a) => a.x > x);
|
|
318
|
-
if (insertAt < 0) insertAt = curve.length;
|
|
319
|
-
const prev = curve[insertAt - 1] ?? null;
|
|
320
|
-
const next = curve[insertAt] ?? null;
|
|
321
|
-
const out = [...curve];
|
|
322
|
-
if (prev && next) {
|
|
323
|
-
const span = next.x - prev.x;
|
|
324
|
-
out[insertAt - 1] = scaleOutHandle(prev, (x - prev.x) / span);
|
|
325
|
-
out[insertAt] = scaleInHandle(next, (next.x - x) / span);
|
|
326
|
-
}
|
|
327
|
-
out.splice(insertAt, 0, fitBetween(tangentAnchor(x, y, prev, next), out[insertAt - 1] ?? null, out[insertAt] ?? null));
|
|
328
|
-
return { curve: out };
|
|
329
|
-
}
|
|
330
|
-
var scaleOutHandle = (a, k) => ({ ...a, outDx: a.outDx * k, outDy: a.outDy * k });
|
|
331
|
-
var scaleInHandle = (a, k) => ({ ...a, inDx: a.inDx * k, inDy: a.inDy * k });
|
|
332
|
-
function fitBetween(a, prev, next) {
|
|
333
|
-
let out = a;
|
|
334
|
-
if (prev) {
|
|
335
|
-
const room = a.x - (prev.x + prev.outDx);
|
|
336
|
-
if (room < -out.inDx) out = scaleInHandle(out, room <= 0 ? 0 : room / -out.inDx);
|
|
337
|
-
}
|
|
338
|
-
if (next) {
|
|
339
|
-
const room = next.x + next.inDx - a.x;
|
|
340
|
-
if (room < out.outDx) out = scaleOutHandle(out, room <= 0 ? 0 : room / out.outDx);
|
|
341
|
-
}
|
|
342
|
-
return out;
|
|
343
|
-
}
|
|
344
|
-
function liftCurveAnchor(curve, x, displacedY) {
|
|
345
|
-
const idx = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
346
|
-
if (idx < 0) return curve;
|
|
347
|
-
if (displacedY !== void 0) return curve.map((a, i) => i === idx ? { ...a, y: displacedY } : a);
|
|
348
|
-
if (idx === 0 || idx === curve.length - 1) return curve;
|
|
349
|
-
const out = curve.filter((_, i) => i !== idx);
|
|
350
|
-
const prev = curve[idx - 1];
|
|
351
|
-
const next = curve[idx + 1];
|
|
352
|
-
if (prev && next) {
|
|
353
|
-
const span = next.x - prev.x;
|
|
354
|
-
out[idx - 1] = scaleOutHandle(prev, span / (curve[idx].x - prev.x));
|
|
355
|
-
out[idx] = scaleInHandle(next, span / (next.x - curve[idx].x));
|
|
356
|
-
}
|
|
357
|
-
return out;
|
|
358
|
-
}
|
|
359
|
-
function syncBaseAnchor(cfg) {
|
|
360
|
-
if (cfg.anchorToBase === false) return;
|
|
361
|
-
const prev = cfg.anchorPlacement;
|
|
362
|
-
let lCurve = cfg.lightnessCurve;
|
|
363
|
-
let sCurve = cfg.saturationCurve;
|
|
364
|
-
if (prev) {
|
|
365
|
-
const prevX = stepIndexToX(prev.step);
|
|
366
|
-
lCurve = liftCurveAnchor(lCurve, prevX, prev.displacedL);
|
|
367
|
-
sCurve = liftCurveAnchor(sCurve, prevX, prev.displacedS);
|
|
368
|
-
}
|
|
369
|
-
const step = nearestPaletteStep(lCurve, cfg.baseColor.l);
|
|
370
|
-
const x = stepIndexToX(step);
|
|
371
|
-
const l = setCurveAnchor(lCurve, x, cfg.baseColor.l * 100);
|
|
372
|
-
const s = setCurveAnchor(sCurve, x, 100);
|
|
373
|
-
cfg.lightnessCurve = l.curve;
|
|
374
|
-
cfg.saturationCurve = s.curve;
|
|
375
|
-
cfg.anchorPlacement = { step, displacedL: l.displacedY, displacedS: s.displacedY };
|
|
376
|
-
}
|
|
377
|
-
function computeDerivedOklch(step, base, scaleTitle, scaleCurves, curveOffset) {
|
|
378
|
-
const scale = SCALES.find((s) => s.title === scaleTitle);
|
|
379
|
-
const xPos = scaleStepToX(step, scale);
|
|
380
|
-
const defs = defaultScaleCurves[scaleTitle];
|
|
381
|
-
const lCurve = scaleCurves[scaleTitle]?.lightness ?? defs.lightness();
|
|
382
|
-
const sCurve = scaleCurves[scaleTitle]?.saturation ?? defs.saturation();
|
|
383
|
-
const lOff = curveOffset[`${scaleTitle}-lightness`] ?? 0;
|
|
384
|
-
const sOff = curveOffset[`${scaleTitle}-saturation`] ?? 0;
|
|
385
|
-
const { l: baseL, c: baseC, h: baseH } = base;
|
|
386
|
-
let targetL;
|
|
387
|
-
if (scale.isText) {
|
|
388
|
-
const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
|
|
389
|
-
targetL = Math.max(0, Math.min(1, baseL * lMul));
|
|
390
|
-
} else {
|
|
391
|
-
targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
|
|
392
|
-
}
|
|
393
|
-
const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
|
|
394
|
-
const targetC = baseC * satMul;
|
|
395
|
-
return gamutClamp(targetL, targetC, baseH);
|
|
396
|
-
}
|
|
397
|
-
function scaleToCssVar(scaleTitle, stepName, cssNamespace) {
|
|
398
|
-
if (cssNamespace === null) return null;
|
|
399
|
-
if (scaleTitle === "Surfaces") {
|
|
400
|
-
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
401
|
-
return cssNamespace === "neutral" ? `--surface-neutral${suffix}` : `--surface-${cssNamespace}${suffix}`;
|
|
402
|
-
}
|
|
403
|
-
if (scaleTitle === "Borders") {
|
|
404
|
-
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
405
|
-
return cssNamespace === "neutral" ? `--border-neutral${suffix}` : `--border-${cssNamespace}${suffix}`;
|
|
406
|
-
}
|
|
407
|
-
if (scaleTitle === "Text") {
|
|
408
|
-
if (cssNamespace === "neutral") return `--text-${stepName}`;
|
|
409
|
-
return stepName === "primary" ? `--text-${cssNamespace}` : `--text-${cssNamespace}-${stepName}`;
|
|
410
|
-
}
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
function derivePaletteValues(spec, config) {
|
|
414
|
-
const out = {};
|
|
415
|
-
if (!config) return out;
|
|
416
|
-
const baseColor = config.baseColor ?? spec.initialColor;
|
|
417
|
-
const overrides = config.overrides ?? {};
|
|
418
|
-
const curveOffset = config.curveOffset ?? {};
|
|
419
|
-
const scaleCurves = config.scaleCurves ?? {};
|
|
420
|
-
const lightnessCurve = config.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS();
|
|
421
|
-
const saturationCurve = config.saturationCurve ?? DEFAULT_PALETTE_SATURATION();
|
|
422
|
-
PALETTE_STEPS.forEach((ps, index) => {
|
|
423
|
-
const k = paletteStepKey(ps.label);
|
|
424
|
-
const value = k in overrides ? { kind: "color", ...overrides[k] } : { kind: "color", ...computePaletteOklch(index, baseColor, lightnessCurve, saturationCurve, curveOffset) };
|
|
425
|
-
out[`--color-${spec.cssNamespace}-${ps.label}`] = value;
|
|
426
|
-
});
|
|
427
|
-
for (const scale of SCALES) {
|
|
428
|
-
for (const step of scale.steps) {
|
|
429
|
-
const k = stepKey(scale.title, step.name);
|
|
430
|
-
const value = k in overrides ? { kind: "color", ...overrides[k] } : { kind: "color", ...computeDerivedOklch(step, baseColor, scale.title, scaleCurves, curveOffset) };
|
|
431
|
-
const varName = scaleToCssVar(scale.title, step.name, spec.cssNamespace);
|
|
432
|
-
if (varName) out[varName] = value;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
const textPrimary = out["--text-primary"];
|
|
436
|
-
if (textPrimary?.kind === "color") {
|
|
437
|
-
const mirroredL = Math.max(BW_GUARD_MIN_L, Math.min(BW_GUARD_MAX_L, 1 - textPrimary.l));
|
|
438
|
-
out["--text-inverted"] = { kind: "color", ...gamutClamp(mirroredL, textPrimary.c, textPrimary.h) };
|
|
439
|
-
}
|
|
440
|
-
if (spec.emptySelector) {
|
|
441
|
-
const emptyMode = config.emptyMode ?? "solid";
|
|
442
|
-
const gradientStyle = config.gradientStyle ?? "linear";
|
|
443
|
-
const gradientAngle = config.gradientAngle ?? 180;
|
|
444
|
-
const gradientReverse = config.gradientReverse ?? false;
|
|
445
|
-
const gradientSize = config.gradientSize ?? "page";
|
|
446
|
-
const gradientStops = config.gradientStops ?? [
|
|
447
|
-
{ position: 0, paletteLabel: "800" },
|
|
448
|
-
{ position: 100, paletteLabel: "950" }
|
|
449
|
-
];
|
|
450
|
-
if (emptyMode === "solid") {
|
|
451
|
-
out["--page-bg"] = { kind: "color", ...baseColor };
|
|
452
|
-
out["--page-bg-attachment"] = { kind: "raw", css: "scroll" };
|
|
453
|
-
} else {
|
|
454
|
-
const sortedStops = [...gradientStops].sort(
|
|
455
|
-
(a, b) => gradientReverse ? b.position - a.position : a.position - b.position
|
|
456
|
-
);
|
|
457
|
-
const stopsCss = sortedStops.map((s) => {
|
|
458
|
-
const stopValue = out[`--color-${spec.cssNamespace}-${s.paletteLabel}`];
|
|
459
|
-
const hex = stopValue ? serializeDerivedValue(stopValue) : "#000000";
|
|
460
|
-
return `${hex} ${s.position}%`;
|
|
461
|
-
}).join(", ");
|
|
462
|
-
let gradient;
|
|
463
|
-
switch (gradientStyle) {
|
|
464
|
-
case "radial":
|
|
465
|
-
gradient = `radial-gradient(circle, ${stopsCss})`;
|
|
466
|
-
break;
|
|
467
|
-
case "conic":
|
|
468
|
-
gradient = `conic-gradient(from ${gradientAngle}deg, ${stopsCss})`;
|
|
469
|
-
break;
|
|
470
|
-
default:
|
|
471
|
-
gradient = `linear-gradient(${gradientAngle}deg, ${stopsCss})`;
|
|
472
|
-
}
|
|
473
|
-
out["--page-bg"] = { kind: "raw", css: gradient };
|
|
474
|
-
out["--page-bg-attachment"] = { kind: "raw", css: gradientSize === "window" ? "fixed" : "scroll" };
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
return out;
|
|
478
|
-
}
|
|
479
|
-
function derivePaletteVars(spec, config) {
|
|
480
|
-
const out = {};
|
|
481
|
-
for (const [k, v] of Object.entries(derivePaletteValues(spec, config))) {
|
|
482
|
-
out[k] = serializeDerivedValue(v);
|
|
483
|
-
}
|
|
484
|
-
return out;
|
|
485
|
-
}
|
|
486
|
-
function palettesToValues(palettes) {
|
|
487
|
-
const out = {};
|
|
488
|
-
for (const spec of PALETTE_SPECS) {
|
|
489
|
-
Object.assign(out, derivePaletteValues(spec, palettes[spec.label]));
|
|
490
|
-
}
|
|
491
|
-
return out;
|
|
492
|
-
}
|
|
493
|
-
function palettesToVars(palettes) {
|
|
494
|
-
const out = {};
|
|
495
|
-
for (const [k, v] of Object.entries(palettesToValues(palettes))) {
|
|
496
|
-
out[k] = serializeDerivedValue(v);
|
|
497
|
-
}
|
|
498
|
-
return out;
|
|
499
|
-
}
|
|
500
|
-
var HEX_RE = /^#[0-9a-f]{6}$/i;
|
|
501
|
-
function reconcilePalettesFromCssVars(palettes, cssVars) {
|
|
502
|
-
const next = structuredClone(palettes);
|
|
503
|
-
const consumed = /* @__PURE__ */ new Set();
|
|
504
|
-
const snapped = /* @__PURE__ */ new Set();
|
|
505
|
-
for (const spec of PALETTE_SPECS) {
|
|
506
|
-
const current = next[spec.label];
|
|
507
|
-
if (current === void 0) continue;
|
|
508
|
-
if (current._imported === true) {
|
|
509
|
-
const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
|
|
510
|
-
if (anchorHex && HEX_RE.test(anchorHex.trim())) {
|
|
511
|
-
next[spec.label] = { ...current, baseColor: hexToOklch(anchorHex.trim()), _imported: false };
|
|
512
|
-
syncBaseAnchor(next[spec.label]);
|
|
513
|
-
snapped.add(spec.label);
|
|
514
|
-
} else {
|
|
515
|
-
next[spec.label] = { ...current, _imported: false };
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
|
|
519
|
-
consumed.add(k);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
return { palettes: next, consumed, snapped };
|
|
523
|
-
}
|
|
524
|
-
|
|
525
38
|
// src/editor/core/themes/migrations/2026-07-21-palette-oklch-basis.ts
|
|
526
39
|
var round = (v, dp) => {
|
|
527
40
|
const f = 10 ** dp;
|
|
@@ -542,102 +55,6 @@ function migratePaletteColorsToOklch(editorConfigs) {
|
|
|
542
55
|
return out;
|
|
543
56
|
}
|
|
544
57
|
|
|
545
|
-
// vite-plugin/files/versionedFileResourceServer.ts
|
|
546
|
-
import fs from "fs";
|
|
547
|
-
import path from "path";
|
|
548
|
-
function versionedFileResourceServer(opts) {
|
|
549
|
-
const dir = opts.dir;
|
|
550
|
-
const activePath = path.join(dir, "_active.json");
|
|
551
|
-
const productionPath = path.join(dir, "_production.json");
|
|
552
|
-
const defaultName = opts.defaultName ?? "default";
|
|
553
|
-
const resolvedDir = path.resolve(dir);
|
|
554
|
-
const resolvedPackageDir = opts.packageDir ? path.resolve(opts.packageDir) : null;
|
|
555
|
-
function ensureDir() {
|
|
556
|
-
if (!fs.existsSync(dir)) {
|
|
557
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
function ensureMeta() {
|
|
561
|
-
if (!fs.existsSync(activePath)) {
|
|
562
|
-
fs.writeFileSync(activePath, JSON.stringify({ activeFile: defaultName }));
|
|
563
|
-
}
|
|
564
|
-
if (!fs.existsSync(productionPath)) {
|
|
565
|
-
fs.writeFileSync(productionPath, JSON.stringify({ productionFile: defaultName }));
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
function filePath(name) {
|
|
569
|
-
return path.join(dir, `${name}.json`);
|
|
570
|
-
}
|
|
571
|
-
function existingPath(name) {
|
|
572
|
-
const local = filePath(name);
|
|
573
|
-
if (fs.existsSync(local)) return local;
|
|
574
|
-
if (resolvedPackageDir) {
|
|
575
|
-
const pkg = path.join(resolvedPackageDir, `${name}.json`);
|
|
576
|
-
if (fs.existsSync(pkg)) return pkg;
|
|
577
|
-
}
|
|
578
|
-
return null;
|
|
579
|
-
}
|
|
580
|
-
function readJson(name) {
|
|
581
|
-
const resolved = existingPath(name);
|
|
582
|
-
if (!resolved) return null;
|
|
583
|
-
return JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
584
|
-
}
|
|
585
|
-
function listNames() {
|
|
586
|
-
const names = [];
|
|
587
|
-
const seen = /* @__PURE__ */ new Set();
|
|
588
|
-
const collect = (d) => {
|
|
589
|
-
if (!d || !fs.existsSync(d)) return;
|
|
590
|
-
for (const f of fs.readdirSync(d)) {
|
|
591
|
-
if (!f.endsWith(".json") || f.startsWith("_")) continue;
|
|
592
|
-
const name = f.slice(0, -".json".length);
|
|
593
|
-
if (seen.has(name)) continue;
|
|
594
|
-
seen.add(name);
|
|
595
|
-
names.push(name);
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
collect(resolvedDir);
|
|
599
|
-
if (resolvedPackageDir && resolvedPackageDir !== resolvedDir) collect(resolvedPackageDir);
|
|
600
|
-
return names;
|
|
601
|
-
}
|
|
602
|
-
function getActiveName() {
|
|
603
|
-
try {
|
|
604
|
-
const data = JSON.parse(fs.readFileSync(activePath, "utf-8"));
|
|
605
|
-
return data.activeFile || defaultName;
|
|
606
|
-
} catch {
|
|
607
|
-
return defaultName;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function getProductionName() {
|
|
611
|
-
try {
|
|
612
|
-
const data = JSON.parse(fs.readFileSync(productionPath, "utf-8"));
|
|
613
|
-
return data.productionFile || defaultName;
|
|
614
|
-
} catch {
|
|
615
|
-
return defaultName;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
function setActiveName(name) {
|
|
619
|
-
fs.writeFileSync(activePath, JSON.stringify({ activeFile: name }));
|
|
620
|
-
}
|
|
621
|
-
function setProductionName(name) {
|
|
622
|
-
fs.writeFileSync(productionPath, JSON.stringify({ productionFile: name }));
|
|
623
|
-
}
|
|
624
|
-
return {
|
|
625
|
-
dir,
|
|
626
|
-
activePath,
|
|
627
|
-
productionPath,
|
|
628
|
-
ensureDir,
|
|
629
|
-
ensureMeta,
|
|
630
|
-
filePath,
|
|
631
|
-
existingPath,
|
|
632
|
-
readJson,
|
|
633
|
-
listNames,
|
|
634
|
-
getActiveName,
|
|
635
|
-
getProductionName,
|
|
636
|
-
setActiveName,
|
|
637
|
-
setProductionName
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
|
|
641
58
|
// vite-plugin/files/routeTable.ts
|
|
642
59
|
async function dispatch(req, res, routes) {
|
|
643
60
|
const url = req.url || "";
|
|
@@ -666,28 +83,18 @@ async function dispatch(req, res, routes) {
|
|
|
666
83
|
return false;
|
|
667
84
|
}
|
|
668
85
|
|
|
669
|
-
// vite-plugin/files/nameAllocator.ts
|
|
670
|
-
function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
|
|
671
|
-
if (!exists(baseName)) return baseName;
|
|
672
|
-
for (let i = 2; i < maxAttempts; i++) {
|
|
673
|
-
const candidate = `${baseName}-${i}`;
|
|
674
|
-
if (!exists(candidate)) return candidate;
|
|
675
|
-
}
|
|
676
|
-
throw new Error(`Could not allocate a non-colliding name for "${baseName}"`);
|
|
677
|
-
}
|
|
678
|
-
|
|
679
86
|
// vite-plugin/themeFileApi.ts
|
|
680
87
|
import { fileURLToPath } from "url";
|
|
681
88
|
var PKG_VERSION = (() => {
|
|
682
89
|
try {
|
|
683
|
-
let dir =
|
|
90
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
684
91
|
for (let i = 0; i < 4; i++) {
|
|
685
|
-
const p =
|
|
686
|
-
if (
|
|
687
|
-
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"));
|
|
688
95
|
if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
|
|
689
96
|
}
|
|
690
|
-
const up =
|
|
97
|
+
const up = path.dirname(dir);
|
|
691
98
|
if (up === dir) break;
|
|
692
99
|
dir = up;
|
|
693
100
|
}
|
|
@@ -695,65 +102,79 @@ var PKG_VERSION = (() => {
|
|
|
695
102
|
}
|
|
696
103
|
return "";
|
|
697
104
|
})();
|
|
105
|
+
var BUNDLE_KIND_BY_VERSION = {
|
|
106
|
+
1: "manifest-bundle",
|
|
107
|
+
[THEME_SCHEMA_VERSION]: "theme-bundle"
|
|
108
|
+
};
|
|
698
109
|
function themeFileApi(opts) {
|
|
699
110
|
const dataDirs = resolveDataDirs({
|
|
700
111
|
dataDir: opts.dataDir,
|
|
701
|
-
|
|
112
|
+
colorsAndTypeDir: opts.colorsAndTypeDir,
|
|
702
113
|
componentConfigsDir: opts.componentConfigsDir,
|
|
703
|
-
|
|
114
|
+
themesDir: opts.themesDir
|
|
704
115
|
});
|
|
705
|
-
const
|
|
116
|
+
const COLORS_AND_TYPE_DIR = dataDirs.colorsAndTypeDir;
|
|
706
117
|
const COMPONENT_CONFIGS_DIR = dataDirs.componentConfigsDir;
|
|
707
|
-
const
|
|
708
|
-
const CSS_PATH =
|
|
709
|
-
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ?
|
|
710
|
-
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");
|
|
711
122
|
const API_BASE = opts.apiBase ?? "/api/live-tokens";
|
|
712
|
-
const consumerComponentDirs = opts.componentsSrcDir ? [
|
|
713
|
-
const packageComponentsDir =
|
|
714
|
-
|
|
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)),
|
|
715
126
|
"..",
|
|
716
127
|
"src",
|
|
717
128
|
"system",
|
|
718
129
|
"components"
|
|
719
130
|
);
|
|
720
|
-
const packageDataDir =
|
|
721
|
-
|
|
131
|
+
const packageDataDir = path.resolve(
|
|
132
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
722
133
|
"..",
|
|
723
134
|
"src",
|
|
724
135
|
"live-tokens",
|
|
725
136
|
"data"
|
|
726
137
|
);
|
|
727
|
-
const
|
|
728
|
-
const
|
|
729
|
-
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
|
+
}
|
|
730
151
|
const COMPONENTS_SCAN_DIRS = [...consumerComponentDirs];
|
|
731
|
-
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) &&
|
|
152
|
+
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs.existsSync(packageComponentsDir)) {
|
|
732
153
|
COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
|
|
733
154
|
}
|
|
734
|
-
const
|
|
735
|
-
dir:
|
|
736
|
-
packageDir:
|
|
155
|
+
const colorsAndTypeResource = versionedFileResourceServer({
|
|
156
|
+
dir: COLORS_AND_TYPE_DIR,
|
|
157
|
+
packageDir: packageColorsAndTypeDir,
|
|
158
|
+
packageOwnedNames: shippedColorsAndTypeNames(packageDataDir)
|
|
737
159
|
});
|
|
738
160
|
const componentResourceCache = /* @__PURE__ */ new Map();
|
|
739
161
|
function componentResource(comp) {
|
|
740
162
|
let r = componentResourceCache.get(comp);
|
|
741
163
|
if (!r) {
|
|
742
164
|
r = versionedFileResourceServer({
|
|
743
|
-
dir:
|
|
744
|
-
packageDir:
|
|
165
|
+
dir: path.join(COMPONENT_CONFIGS_DIR, comp),
|
|
166
|
+
packageDir: path.join(packageComponentConfigsDir, comp)
|
|
745
167
|
});
|
|
746
168
|
componentResourceCache.set(comp, r);
|
|
747
169
|
}
|
|
748
170
|
return r;
|
|
749
171
|
}
|
|
750
|
-
const
|
|
751
|
-
dir:
|
|
752
|
-
packageDir:
|
|
172
|
+
const themesResource = versionedFileResourceServer({
|
|
173
|
+
dir: THEMES_DIR,
|
|
174
|
+
packageDir: packageThemesDir
|
|
753
175
|
});
|
|
754
|
-
function
|
|
755
|
-
|
|
756
|
-
themesResource.ensureMeta();
|
|
176
|
+
function ensureColorsAndTypeDir() {
|
|
177
|
+
colorsAndTypeResource.ensureDir();
|
|
757
178
|
}
|
|
758
179
|
function readBody(req) {
|
|
759
180
|
return new Promise((resolve, reject) => {
|
|
@@ -768,25 +189,33 @@ function themeFileApi(opts) {
|
|
|
768
189
|
res.setHeader("Content-Type", "application/json");
|
|
769
190
|
res.end(JSON.stringify(data));
|
|
770
191
|
}
|
|
771
|
-
function
|
|
772
|
-
if (!
|
|
773
|
-
|
|
774
|
-
|
|
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 ?? {};
|
|
775
204
|
const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
|
|
776
205
|
const nextCssVars = {};
|
|
777
206
|
for (const [k, v] of Object.entries(cssVars)) {
|
|
778
207
|
if (!consumed.has(k)) nextCssVars[k] = v;
|
|
779
208
|
}
|
|
780
|
-
return { ...
|
|
209
|
+
return { ...colorsAndType, editorConfigs: nextPalettes, cssVariables: nextCssVars };
|
|
781
210
|
}
|
|
782
211
|
const SYSTEM_CASCADES_SSR = {
|
|
783
212
|
"system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
784
213
|
"system-ui-serif": 'Georgia, "Times New Roman", serif',
|
|
785
214
|
"system-ui-mono": 'ui-monospace, "SF Mono", Menlo, Consolas, monospace'
|
|
786
215
|
};
|
|
787
|
-
function resolveFontStacks(
|
|
788
|
-
const stacks =
|
|
789
|
-
const sources =
|
|
216
|
+
function resolveFontStacks(colorsAndTypeData) {
|
|
217
|
+
const stacks = colorsAndTypeData.fontStacks;
|
|
218
|
+
const sources = colorsAndTypeData.fontSources;
|
|
790
219
|
if (!stacks || stacks.length === 0) return {};
|
|
791
220
|
const familyById = /* @__PURE__ */ new Map();
|
|
792
221
|
for (const src of sources ?? []) {
|
|
@@ -812,21 +241,28 @@ function themeFileApi(opts) {
|
|
|
812
241
|
}
|
|
813
242
|
function regenerateTokensCss() {
|
|
814
243
|
const lines = [];
|
|
815
|
-
lines.push("/* Generated by themeFileApi from the production theme
|
|
244
|
+
lines.push("/* Generated by themeFileApi from the production theme. Do not edit. */");
|
|
816
245
|
lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
|
|
817
246
|
lines.push("");
|
|
818
247
|
const productionThemeName = themesResource.getProductionName();
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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);
|
|
825
261
|
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
826
262
|
cssVars[name] = value;
|
|
827
263
|
}
|
|
828
|
-
|
|
829
|
-
if (
|
|
264
|
+
colorsAndTypeVarCount = Object.keys(cssVars).length;
|
|
265
|
+
if (colorsAndTypeVarCount > 0) {
|
|
830
266
|
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
831
267
|
lines.push(":root:root {");
|
|
832
268
|
for (const [name, value] of Object.entries(cssVars)) {
|
|
@@ -837,12 +273,10 @@ function themeFileApi(opts) {
|
|
|
837
273
|
}
|
|
838
274
|
}
|
|
839
275
|
let componentOverrideCount = 0;
|
|
840
|
-
if (
|
|
276
|
+
if (fs.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
841
277
|
const blocks = [];
|
|
842
278
|
for (const comp of listComponentNames()) {
|
|
843
|
-
const
|
|
844
|
-
if (prod === "default") continue;
|
|
845
|
-
const prodCfg = readComponentConfig(comp, prod);
|
|
279
|
+
const prodCfg = productionTheme?.componentConfigs[comp];
|
|
846
280
|
const defaultCfg = readComponentConfig(comp, "default");
|
|
847
281
|
if (!prodCfg || !defaultCfg) continue;
|
|
848
282
|
const overrides = [];
|
|
@@ -853,7 +287,7 @@ function themeFileApi(opts) {
|
|
|
853
287
|
}
|
|
854
288
|
}
|
|
855
289
|
if (overrides.length === 0) continue;
|
|
856
|
-
const block = [` /* ${comp}
|
|
290
|
+
const block = [` /* ${comp} */`];
|
|
857
291
|
for (const [varName, semanticValue] of overrides) {
|
|
858
292
|
block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
|
|
859
293
|
}
|
|
@@ -871,24 +305,20 @@ function themeFileApi(opts) {
|
|
|
871
305
|
lines.push("");
|
|
872
306
|
}
|
|
873
307
|
}
|
|
874
|
-
if (!
|
|
875
|
-
|
|
308
|
+
if (!fs.existsSync(path.dirname(GENERATED_CSS_PATH))) {
|
|
309
|
+
fs.mkdirSync(path.dirname(GENERATED_CSS_PATH), { recursive: true });
|
|
876
310
|
}
|
|
877
|
-
|
|
311
|
+
fs.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
|
|
878
312
|
console.log(
|
|
879
|
-
`[regenerateTokensCss] Wrote ${
|
|
313
|
+
`[regenerateTokensCss] Wrote ${path.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
|
|
880
314
|
);
|
|
881
315
|
}
|
|
882
|
-
function
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
function syncFontsToCss(fileName) {
|
|
886
|
-
const themeData = themesResource.readJson(fileName);
|
|
887
|
-
if (!themeData) return;
|
|
888
|
-
const sources = themeData.fontSources;
|
|
316
|
+
function syncFontsToCss(colorsAndTypeData) {
|
|
317
|
+
if (!colorsAndTypeData) return;
|
|
318
|
+
const sources = colorsAndTypeData.fontSources;
|
|
889
319
|
if (!sources) return;
|
|
890
320
|
const lines = [];
|
|
891
|
-
lines.push("/* Generated from the production
|
|
321
|
+
lines.push("/* Generated from the production colors and type by syncFontsToCss. Do not edit. */");
|
|
892
322
|
lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
|
|
893
323
|
lines.push("");
|
|
894
324
|
const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
|
|
@@ -908,11 +338,11 @@ function themeFileApi(opts) {
|
|
|
908
338
|
lines.push("");
|
|
909
339
|
}
|
|
910
340
|
const content = lines.join("\n");
|
|
911
|
-
if (!
|
|
912
|
-
|
|
341
|
+
if (!fs.existsSync(path.dirname(FONTS_CSS_PATH))) {
|
|
342
|
+
fs.mkdirSync(path.dirname(FONTS_CSS_PATH), { recursive: true });
|
|
913
343
|
}
|
|
914
|
-
|
|
915
|
-
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)}`);
|
|
916
346
|
}
|
|
917
347
|
function extractAliasDeclarations(body) {
|
|
918
348
|
const aliases = {};
|
|
@@ -927,11 +357,11 @@ function themeFileApi(opts) {
|
|
|
927
357
|
return aliases;
|
|
928
358
|
}
|
|
929
359
|
function componentNameFromFile(filePath) {
|
|
930
|
-
return
|
|
360
|
+
return path.basename(filePath, ".svelte").toLowerCase();
|
|
931
361
|
}
|
|
932
362
|
function isThemeAwareComponent(filePath) {
|
|
933
363
|
try {
|
|
934
|
-
const source =
|
|
364
|
+
const source = fs.readFileSync(filePath, "utf-8");
|
|
935
365
|
return /:global\(:root\)\s*\{/.test(source);
|
|
936
366
|
} catch {
|
|
937
367
|
return false;
|
|
@@ -940,10 +370,10 @@ function themeFileApi(opts) {
|
|
|
940
370
|
function listComponentSourcePaths() {
|
|
941
371
|
const byName = /* @__PURE__ */ new Map();
|
|
942
372
|
for (const dir of COMPONENTS_SCAN_DIRS) {
|
|
943
|
-
if (!
|
|
944
|
-
for (const f of
|
|
373
|
+
if (!fs.existsSync(dir)) continue;
|
|
374
|
+
for (const f of fs.readdirSync(dir)) {
|
|
945
375
|
if (!f.endsWith(".svelte")) continue;
|
|
946
|
-
const full =
|
|
376
|
+
const full = path.join(dir, f);
|
|
947
377
|
if (!isThemeAwareComponent(full)) continue;
|
|
948
378
|
const name = componentNameFromFile(f);
|
|
949
379
|
if (!byName.has(name)) byName.set(name, full);
|
|
@@ -957,18 +387,18 @@ function themeFileApi(opts) {
|
|
|
957
387
|
function warnOnTokenDrift(log) {
|
|
958
388
|
let tokensCss = "";
|
|
959
389
|
try {
|
|
960
|
-
tokensCss =
|
|
390
|
+
tokensCss = fs.readFileSync(CSS_PATH, "utf-8");
|
|
961
391
|
} catch {
|
|
962
392
|
return;
|
|
963
393
|
}
|
|
964
394
|
let generatedCss = "";
|
|
965
395
|
try {
|
|
966
|
-
generatedCss =
|
|
396
|
+
generatedCss = fs.readFileSync(GENERATED_CSS_PATH, "utf-8");
|
|
967
397
|
} catch {
|
|
968
398
|
}
|
|
969
399
|
const componentSources = listComponentSourcePaths().map((p) => ({
|
|
970
400
|
name: componentNameFromFile(p),
|
|
971
|
-
source:
|
|
401
|
+
source: fs.readFileSync(p, "utf-8")
|
|
972
402
|
}));
|
|
973
403
|
const missing = validateTokensCss({ tokensCss, generatedCss, componentSources });
|
|
974
404
|
if (missing.length === 0) return;
|
|
@@ -976,7 +406,7 @@ function themeFileApi(opts) {
|
|
|
976
406
|
(m) => ` ${m.token} (referenced by ${m.referencedBy.join(", ")})`
|
|
977
407
|
);
|
|
978
408
|
log(
|
|
979
|
-
`[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)}:
|
|
980
410
|
${lines.join("\n")}
|
|
981
411
|
These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
|
|
982
412
|
);
|
|
@@ -984,37 +414,37 @@ ${lines.join("\n")}
|
|
|
984
414
|
function autoMigrateAdditive(log) {
|
|
985
415
|
let before = "";
|
|
986
416
|
try {
|
|
987
|
-
before =
|
|
417
|
+
before = fs.readFileSync(CSS_PATH, "utf-8");
|
|
988
418
|
} catch {
|
|
989
419
|
return;
|
|
990
420
|
}
|
|
991
421
|
const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
|
|
992
422
|
if (!changed) return;
|
|
993
|
-
|
|
423
|
+
fs.writeFileSync(CSS_PATH, css);
|
|
994
424
|
log(
|
|
995
|
-
`[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.`
|
|
996
426
|
);
|
|
997
427
|
}
|
|
998
428
|
function generateDefaultConfig(comp, sourcePath) {
|
|
999
|
-
if (!
|
|
429
|
+
if (!fs.existsSync(sourcePath)) return;
|
|
1000
430
|
const r = componentResource(comp);
|
|
1001
431
|
r.ensureDir();
|
|
1002
432
|
const defaultPath = r.filePath("default");
|
|
1003
|
-
const sourceStat =
|
|
1004
|
-
if (
|
|
1005
|
-
const defaultStat =
|
|
433
|
+
const sourceStat = fs.statSync(sourcePath);
|
|
434
|
+
if (fs.existsSync(defaultPath)) {
|
|
435
|
+
const defaultStat = fs.statSync(defaultPath);
|
|
1006
436
|
if (defaultStat.mtimeMs >= sourceStat.mtimeMs) return;
|
|
1007
437
|
}
|
|
1008
|
-
const source =
|
|
438
|
+
const source = fs.readFileSync(sourcePath, "utf-8");
|
|
1009
439
|
const body = extractGlobalRootBody(source);
|
|
1010
440
|
const aliases = extractAliasDeclarations(body);
|
|
1011
441
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1012
442
|
let createdAt = now;
|
|
1013
443
|
let existingUpdatedAt;
|
|
1014
444
|
let existingAliases;
|
|
1015
|
-
if (
|
|
445
|
+
if (fs.existsSync(defaultPath)) {
|
|
1016
446
|
try {
|
|
1017
|
-
const existing = JSON.parse(
|
|
447
|
+
const existing = JSON.parse(fs.readFileSync(defaultPath, "utf-8"));
|
|
1018
448
|
if (existing.createdAt) createdAt = existing.createdAt;
|
|
1019
449
|
if (typeof existing.updatedAt === "string") existingUpdatedAt = existing.updatedAt;
|
|
1020
450
|
if (existing.aliases && typeof existing.aliases === "object") {
|
|
@@ -1040,57 +470,191 @@ ${lines.join("\n")}
|
|
|
1040
470
|
};
|
|
1041
471
|
if (aliasesUnchanged && existingUpdatedAt) {
|
|
1042
472
|
const t = /* @__PURE__ */ new Date();
|
|
1043
|
-
|
|
473
|
+
fs.utimesSync(defaultPath, t, t);
|
|
1044
474
|
return;
|
|
1045
475
|
}
|
|
1046
|
-
|
|
476
|
+
fs.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
|
|
1047
477
|
}
|
|
1048
478
|
function ensureComponentConfigsDir() {
|
|
1049
|
-
if (!
|
|
1050
|
-
|
|
479
|
+
if (!fs.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
480
|
+
fs.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
|
|
1051
481
|
}
|
|
1052
482
|
for (const sourcePath of listComponentSourcePaths()) {
|
|
1053
483
|
const comp = componentNameFromFile(sourcePath);
|
|
1054
484
|
const r = componentResource(comp);
|
|
1055
485
|
r.ensureDir();
|
|
1056
486
|
generateDefaultConfig(comp, sourcePath);
|
|
1057
|
-
r.ensureMeta();
|
|
1058
487
|
}
|
|
1059
488
|
}
|
|
1060
|
-
function
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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));
|
|
1071
501
|
}
|
|
1072
502
|
}
|
|
503
|
+
return legacy;
|
|
1073
504
|
}
|
|
1074
|
-
function
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
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;
|
|
1080
523
|
try {
|
|
1081
|
-
|
|
524
|
+
raw = themesResource.readJson(fileName);
|
|
1082
525
|
} catch {
|
|
1083
|
-
return
|
|
526
|
+
return null;
|
|
1084
527
|
}
|
|
1085
|
-
if (
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
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 {
|
|
1090
540
|
}
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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" };
|
|
1094
658
|
}
|
|
1095
659
|
function formatAliasGradient(v) {
|
|
1096
660
|
const stopColor = (s) => {
|
|
@@ -1126,237 +690,191 @@ ${lines.join("\n")}
|
|
|
1126
690
|
return null;
|
|
1127
691
|
}
|
|
1128
692
|
}
|
|
1129
|
-
function syncComponentsToCss() {
|
|
1130
|
-
regenerateTokensCss();
|
|
1131
|
-
}
|
|
1132
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`;
|
|
1133
699
|
const THEMES_ROUTE = `${API_BASE}/themes`;
|
|
1134
700
|
const THEMES_ACTIVE_ROUTE = `${API_BASE}/themes/active`;
|
|
1135
701
|
const THEMES_PRODUCTION_ROUTE = `${API_BASE}/themes/production`;
|
|
1136
|
-
const
|
|
1137
|
-
const MANIFESTS_ROUTE = `${API_BASE}/manifests`;
|
|
1138
|
-
const MANIFESTS_ACTIVE_ROUTE = `${API_BASE}/manifests/active`;
|
|
1139
|
-
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\\-_]+)$`);
|
|
1140
703
|
const COMP_LIST_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)$`);
|
|
1141
704
|
const COMP_ACTIVE_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/active$`);
|
|
1142
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$`);
|
|
1143
707
|
const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
|
|
1144
|
-
const
|
|
1145
|
-
const
|
|
1146
|
-
const
|
|
1147
|
-
const
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const files =
|
|
1151
|
-
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);
|
|
1152
716
|
return {
|
|
1153
717
|
name: data?.name || fileName,
|
|
1154
718
|
fileName,
|
|
1155
719
|
updatedAt: data?.updatedAt || "",
|
|
1156
|
-
|
|
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)
|
|
1157
725
|
};
|
|
1158
726
|
});
|
|
1159
727
|
jsonResponse(_ctx.res, 200, { files });
|
|
1160
728
|
}
|
|
1161
|
-
async function
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
jsonResponse(res, 404, { error: "Active theme not found" });
|
|
1166
|
-
return;
|
|
1167
|
-
}
|
|
1168
|
-
const data = normalizeTheme(raw);
|
|
1169
|
-
data._fileName = activeFile;
|
|
1170
|
-
jsonResponse(res, 200, data);
|
|
1171
|
-
}
|
|
1172
|
-
async function handleSetActiveTheme({ req, res }) {
|
|
1173
|
-
const body = JSON.parse(await readBody(req));
|
|
1174
|
-
const fileName = sanitizeFileName(body.name || "default");
|
|
1175
|
-
if (themesResource.existingPath(fileName) === null) {
|
|
1176
|
-
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1177
|
-
return;
|
|
1178
|
-
}
|
|
1179
|
-
themesResource.setActiveName(fileName);
|
|
1180
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1181
|
-
}
|
|
1182
|
-
async function handleGetProductionTheme({ res }) {
|
|
1183
|
-
const prodFile = themesResource.getProductionName();
|
|
1184
|
-
const raw = themesResource.readJson(prodFile);
|
|
1185
|
-
if (!raw) {
|
|
1186
|
-
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" });
|
|
1187
733
|
return;
|
|
1188
734
|
}
|
|
1189
|
-
const data = normalizeTheme(raw);
|
|
1190
735
|
jsonResponse(res, 200, {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
cssVariables: data.cssVariables || {}
|
|
736
|
+
...resolved.data,
|
|
737
|
+
_fileName: themesResource.getActiveName(),
|
|
738
|
+
_source: resolved.source
|
|
1195
739
|
});
|
|
1196
740
|
}
|
|
1197
|
-
async function
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
741
|
+
async function readBufferBody(req, res) {
|
|
742
|
+
let parsed;
|
|
743
|
+
try {
|
|
744
|
+
parsed = JSON.parse(await readBody(req));
|
|
745
|
+
} catch {
|
|
746
|
+
parsed = void 0;
|
|
1203
747
|
}
|
|
1204
|
-
if (
|
|
1205
|
-
jsonResponse(res,
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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" });
|
|
1209
758
|
return;
|
|
1210
759
|
}
|
|
1211
|
-
|
|
1212
|
-
syncTokensToCss(fileName);
|
|
1213
|
-
syncFontsToCss(fileName);
|
|
1214
|
-
syncComponentsToCss();
|
|
1215
|
-
patchActiveManifest("theme", null, fileName);
|
|
1216
|
-
const data = themesResource.readJson(fileName);
|
|
1217
|
-
jsonResponse(res, 200, {
|
|
1218
|
-
ok: true,
|
|
1219
|
-
fileName,
|
|
1220
|
-
name: data?.name || fileName,
|
|
1221
|
-
updatedAt: data?.updatedAt || ""
|
|
1222
|
-
});
|
|
760
|
+
jsonResponse(res, 200, normalizeColorsAndType(raw));
|
|
1223
761
|
}
|
|
1224
|
-
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 }) {
|
|
1225
773
|
const [fileName] = params;
|
|
1226
|
-
|
|
774
|
+
if (rejectReservedName(res, fileName)) return;
|
|
775
|
+
const filePath = colorsAndTypeResource.filePath(fileName);
|
|
1227
776
|
if (req.method === "GET") {
|
|
1228
|
-
const raw =
|
|
777
|
+
const raw = colorsAndTypeResource.readJson(fileName);
|
|
1229
778
|
if (!raw) {
|
|
1230
779
|
jsonResponse(res, 404, { error: "Not found" });
|
|
1231
780
|
return;
|
|
1232
781
|
}
|
|
1233
|
-
const data =
|
|
782
|
+
const data = normalizeColorsAndType(raw);
|
|
1234
783
|
data._fileName = fileName;
|
|
1235
784
|
jsonResponse(res, 200, data);
|
|
1236
785
|
return;
|
|
1237
786
|
}
|
|
1238
787
|
if (req.method === "PUT") {
|
|
1239
788
|
if (fileName === "default") {
|
|
1240
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
789
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default colors and type (live from package)" });
|
|
1241
790
|
return;
|
|
1242
791
|
}
|
|
1243
792
|
const body = JSON.parse(await readBody(req));
|
|
1244
793
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1245
|
-
if (
|
|
794
|
+
if (fs.existsSync(filePath)) {
|
|
1246
795
|
try {
|
|
1247
|
-
const existing = JSON.parse(
|
|
796
|
+
const existing = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1248
797
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1249
798
|
} catch {
|
|
1250
799
|
}
|
|
1251
800
|
}
|
|
1252
801
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1253
|
-
|
|
1254
|
-
if (fileName === themesResource.getProductionName()) {
|
|
1255
|
-
syncTokensToCss(fileName);
|
|
1256
|
-
syncFontsToCss(fileName);
|
|
1257
|
-
syncComponentsToCss();
|
|
1258
|
-
}
|
|
802
|
+
fs.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
1259
803
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1260
804
|
return;
|
|
1261
805
|
}
|
|
1262
806
|
if (req.method === "DELETE") {
|
|
1263
807
|
if (fileName === "default") {
|
|
1264
|
-
jsonResponse(res, 403, { error: "Cannot delete the default
|
|
808
|
+
jsonResponse(res, 403, { error: "Cannot delete the default colors and type" });
|
|
1265
809
|
return;
|
|
1266
810
|
}
|
|
1267
|
-
if (
|
|
811
|
+
if (fs.existsSync(filePath)) {
|
|
812
|
+
fs.unlinkSync(filePath);
|
|
813
|
+
} else if (colorsAndTypeResource.existingPath(fileName)) {
|
|
1268
814
|
jsonResponse(res, 403, {
|
|
1269
|
-
error: "Cannot delete the
|
|
1270
|
-
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"
|
|
1271
817
|
});
|
|
1272
818
|
return;
|
|
1273
819
|
}
|
|
1274
|
-
if (fs2.existsSync(filePath)) {
|
|
1275
|
-
fs2.unlinkSync(filePath);
|
|
1276
|
-
if (themesResource.getActiveName() === fileName) {
|
|
1277
|
-
themesResource.setActiveName("default");
|
|
1278
|
-
}
|
|
1279
|
-
}
|
|
1280
820
|
jsonResponse(res, 200, { ok: true });
|
|
1281
821
|
return;
|
|
1282
822
|
}
|
|
1283
823
|
}
|
|
1284
824
|
async function handleListComponents({ res }) {
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
});
|
|
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
|
+
}));
|
|
1293
833
|
jsonResponse(res, 200, { components });
|
|
1294
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
|
+
}
|
|
1295
840
|
async function handleGetComponentActive({ params, res }) {
|
|
1296
841
|
const [comp] = params;
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
const cfg = readComponentConfig(comp, activeFile);
|
|
1300
|
-
if (!cfg) {
|
|
842
|
+
const resolved = resolveLiveComponentConfig(comp, activeTheme());
|
|
843
|
+
if (!resolved) {
|
|
1301
844
|
jsonResponse(res, 404, { error: "Active config not found" });
|
|
1302
845
|
return;
|
|
1303
846
|
}
|
|
1304
|
-
jsonResponse(res, 200, {
|
|
847
|
+
jsonResponse(res, 200, {
|
|
848
|
+
...resolved.data,
|
|
849
|
+
_fileName: themesResource.getActiveName(),
|
|
850
|
+
_source: resolved.source
|
|
851
|
+
});
|
|
1305
852
|
}
|
|
1306
|
-
async function
|
|
853
|
+
async function handleGetComponentWorking({ params, res }) {
|
|
1307
854
|
const [comp] = params;
|
|
1308
|
-
const
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
if (r.existingPath(fileName) === null) {
|
|
1312
|
-
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" });
|
|
1313
858
|
return;
|
|
1314
859
|
}
|
|
1315
|
-
|
|
1316
|
-
r.setActiveName(fileName);
|
|
1317
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
860
|
+
jsonResponse(res, 200, raw);
|
|
1318
861
|
}
|
|
1319
|
-
async function
|
|
862
|
+
async function handleSetComponentWorking({ params, req, res }) {
|
|
1320
863
|
const [comp] = params;
|
|
1321
|
-
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
name: cfg?.name || prodFile,
|
|
1327
|
-
aliases: cfg?.aliases || {}
|
|
1328
|
-
});
|
|
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 });
|
|
1329
869
|
}
|
|
1330
|
-
async function
|
|
870
|
+
async function handleClearComponentWorking({ params, res }) {
|
|
1331
871
|
const [comp] = params;
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
const r = componentResource(comp);
|
|
1335
|
-
if (r.existingPath(fileName) === null) {
|
|
1336
|
-
jsonResponse(res, 404, { error: "Config not found" });
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
if (manifestsResource.getActiveName() === "default") {
|
|
1340
|
-
jsonResponse(res, 409, {
|
|
1341
|
-
error: "Active manifest is protected. Save As first.",
|
|
1342
|
-
code: "ACTIVE_IS_PROTECTED"
|
|
1343
|
-
});
|
|
1344
|
-
return;
|
|
1345
|
-
}
|
|
1346
|
-
r.ensureDir();
|
|
1347
|
-
r.setProductionName(fileName);
|
|
1348
|
-
syncComponentsToCss();
|
|
1349
|
-
patchActiveManifest("component", comp, fileName);
|
|
1350
|
-
const cfg = readComponentConfig(comp, fileName);
|
|
1351
|
-
jsonResponse(res, 200, {
|
|
1352
|
-
ok: true,
|
|
1353
|
-
fileName,
|
|
1354
|
-
name: cfg?.name || fileName,
|
|
1355
|
-
aliases: cfg?.aliases || {}
|
|
1356
|
-
});
|
|
872
|
+
componentResource(comp).clearWorking();
|
|
873
|
+
jsonResponse(res, 200, { ok: true });
|
|
1357
874
|
}
|
|
1358
875
|
async function handleComponentConfigByName({ params, req, res }) {
|
|
1359
876
|
const [comp, name] = params;
|
|
877
|
+
if (rejectReservedName(res, name)) return;
|
|
1360
878
|
const r = componentResource(comp);
|
|
1361
879
|
const configPath = r.filePath(name);
|
|
1362
880
|
if (req.method === "GET") {
|
|
@@ -1379,19 +897,16 @@ ${lines.join("\n")}
|
|
|
1379
897
|
body.component = comp;
|
|
1380
898
|
body.name = body.name || name;
|
|
1381
899
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1382
|
-
if (
|
|
900
|
+
if (fs.existsSync(configPath)) {
|
|
1383
901
|
try {
|
|
1384
|
-
const existing = JSON.parse(
|
|
902
|
+
const existing = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1385
903
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1386
904
|
} catch {
|
|
1387
905
|
}
|
|
1388
906
|
}
|
|
1389
907
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1390
908
|
r.ensureDir();
|
|
1391
|
-
|
|
1392
|
-
if (r.getProductionName() === name) {
|
|
1393
|
-
syncComponentsToCss();
|
|
1394
|
-
}
|
|
909
|
+
fs.writeFileSync(configPath, JSON.stringify(body, null, 2));
|
|
1395
910
|
jsonResponse(res, 200, { ok: true, fileName: name });
|
|
1396
911
|
return;
|
|
1397
912
|
}
|
|
@@ -1400,16 +915,7 @@ ${lines.join("\n")}
|
|
|
1400
915
|
jsonResponse(res, 403, { error: "Cannot delete default config" });
|
|
1401
916
|
return;
|
|
1402
917
|
}
|
|
1403
|
-
if (
|
|
1404
|
-
fs2.unlinkSync(configPath);
|
|
1405
|
-
if (r.getActiveName() === name) {
|
|
1406
|
-
r.setActiveName("default");
|
|
1407
|
-
}
|
|
1408
|
-
if (r.getProductionName() === name) {
|
|
1409
|
-
r.setProductionName("default");
|
|
1410
|
-
syncComponentsToCss();
|
|
1411
|
-
}
|
|
1412
|
-
}
|
|
918
|
+
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
|
|
1413
919
|
jsonResponse(res, 200, { ok: true });
|
|
1414
920
|
return;
|
|
1415
921
|
}
|
|
@@ -1417,199 +923,190 @@ ${lines.join("\n")}
|
|
|
1417
923
|
async function handleListComponentConfigs({ params, res }) {
|
|
1418
924
|
const [comp] = params;
|
|
1419
925
|
const r = componentResource(comp);
|
|
1420
|
-
if (!
|
|
926
|
+
if (!fs.existsSync(r.dir)) {
|
|
1421
927
|
jsonResponse(res, 404, { error: "Component not found" });
|
|
1422
928
|
return;
|
|
1423
929
|
}
|
|
1424
|
-
const activeFile = r.getActiveName();
|
|
1425
|
-
const productionFile = r.getProductionName();
|
|
1426
930
|
const files = r.listNames().map((fileName) => {
|
|
1427
931
|
const data = r.readJson(fileName);
|
|
1428
932
|
return {
|
|
1429
933
|
name: data?.name || fileName,
|
|
1430
934
|
fileName,
|
|
1431
|
-
updatedAt: data?.updatedAt || ""
|
|
1432
|
-
isActive: fileName === activeFile,
|
|
1433
|
-
isProduction: fileName === productionFile
|
|
935
|
+
updatedAt: data?.updatedAt || ""
|
|
1434
936
|
};
|
|
1435
937
|
});
|
|
1436
|
-
jsonResponse(res, 200, { component: comp, files
|
|
938
|
+
jsonResponse(res, 200, { component: comp, files });
|
|
1437
939
|
}
|
|
1438
|
-
async function
|
|
1439
|
-
const activeFile =
|
|
1440
|
-
const
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
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,
|
|
1444
954
|
fileName,
|
|
1445
|
-
updatedAt:
|
|
955
|
+
updatedAt: read.theme.updatedAt,
|
|
1446
956
|
isActive: fileName === activeFile,
|
|
957
|
+
isProduction: fileName === productionFile,
|
|
1447
958
|
isProtected: fileName === "default"
|
|
1448
|
-
};
|
|
1449
|
-
}
|
|
1450
|
-
jsonResponse(res, 200, { files, activeFile });
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
jsonResponse(res, 200, { files, activeFile, productionFile });
|
|
1451
962
|
}
|
|
1452
|
-
async function
|
|
1453
|
-
const
|
|
1454
|
-
const
|
|
1455
|
-
if (!
|
|
1456
|
-
|
|
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 });
|
|
971
|
+
}
|
|
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");
|
|
1457
977
|
return;
|
|
1458
978
|
}
|
|
1459
|
-
|
|
1460
|
-
data._fileName = activeFile;
|
|
1461
|
-
jsonResponse(res, 200, data);
|
|
979
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: activeFile });
|
|
1462
980
|
}
|
|
1463
|
-
async function
|
|
981
|
+
async function handleSetActiveTheme({ req, res }) {
|
|
1464
982
|
const body = JSON.parse(await readBody(req));
|
|
1465
983
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1466
|
-
if (
|
|
1467
|
-
jsonResponse(res, 404, { error: "
|
|
984
|
+
if (themesResource.existingPath(fileName) === null) {
|
|
985
|
+
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1468
986
|
return;
|
|
1469
987
|
}
|
|
1470
|
-
|
|
988
|
+
themesResource.setActiveName(fileName);
|
|
1471
989
|
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1472
990
|
}
|
|
1473
|
-
async function
|
|
991
|
+
async function handleThemeByName({ params, req, res }) {
|
|
1474
992
|
const [fileName] = params;
|
|
1475
|
-
|
|
993
|
+
if (rejectReservedName(res, fileName)) return;
|
|
994
|
+
const filePath = themesResource.filePath(fileName);
|
|
1476
995
|
if (req.method === "GET") {
|
|
1477
|
-
const
|
|
1478
|
-
if (!
|
|
1479
|
-
|
|
996
|
+
const read = readTheme(fileName);
|
|
997
|
+
if (!read) {
|
|
998
|
+
respondUnreadableTheme(res, fileName, "Not found");
|
|
1480
999
|
return;
|
|
1481
1000
|
}
|
|
1482
|
-
|
|
1483
|
-
data._fileName = fileName;
|
|
1484
|
-
jsonResponse(res, 200, data);
|
|
1001
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: fileName });
|
|
1485
1002
|
return;
|
|
1486
1003
|
}
|
|
1487
1004
|
if (req.method === "PUT") {
|
|
1488
1005
|
if (fileName === "default") {
|
|
1489
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
1006
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default theme" });
|
|
1490
1007
|
return;
|
|
1491
1008
|
}
|
|
1492
1009
|
const body = JSON.parse(await readBody(req));
|
|
1493
|
-
|
|
1494
|
-
|
|
1010
|
+
const { theme } = normalizeTheme(body, diskThemeResolvers);
|
|
1011
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1012
|
+
if (fs.existsSync(filePath)) {
|
|
1495
1013
|
try {
|
|
1496
|
-
const existing = JSON.parse(
|
|
1497
|
-
if (existing.createdAt)
|
|
1014
|
+
const existing = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
1015
|
+
if (existing.createdAt) theme.createdAt = existing.createdAt;
|
|
1498
1016
|
} catch {
|
|
1499
1017
|
}
|
|
1500
1018
|
}
|
|
1501
|
-
|
|
1502
|
-
fs2.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
1019
|
+
writeTheme(fileName, theme);
|
|
1503
1020
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1504
1021
|
return;
|
|
1505
1022
|
}
|
|
1506
1023
|
if (req.method === "DELETE") {
|
|
1507
1024
|
if (fileName === "default") {
|
|
1508
|
-
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
|
+
});
|
|
1509
1033
|
return;
|
|
1510
1034
|
}
|
|
1511
|
-
if (
|
|
1512
|
-
|
|
1513
|
-
if (
|
|
1514
|
-
|
|
1035
|
+
if (fs.existsSync(filePath)) {
|
|
1036
|
+
fs.unlinkSync(filePath);
|
|
1037
|
+
if (themesResource.existingPath(fileName) === null && themesResource.getActiveName() === fileName) {
|
|
1038
|
+
themesResource.setActiveName("default");
|
|
1515
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;
|
|
1516
1046
|
}
|
|
1517
1047
|
jsonResponse(res, 200, { ok: true });
|
|
1518
1048
|
return;
|
|
1519
1049
|
}
|
|
1520
1050
|
}
|
|
1521
|
-
async function
|
|
1051
|
+
async function handleApplyTheme({ params, res }) {
|
|
1522
1052
|
const [fileName] = params;
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1053
|
+
if (rejectReservedName(res, fileName)) return;
|
|
1054
|
+
const read = readTheme(fileName);
|
|
1055
|
+
if (!read) {
|
|
1056
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
1526
1057
|
return;
|
|
1527
1058
|
}
|
|
1528
|
-
const
|
|
1529
|
-
|
|
1530
|
-
|
|
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" });
|
|
1531
1063
|
return;
|
|
1532
1064
|
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
const
|
|
1536
|
-
const
|
|
1537
|
-
|
|
1538
|
-
if (!knownComponents.has(comp)) continue;
|
|
1539
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1540
|
-
const r = componentResource(comp);
|
|
1541
|
-
if (r.existingPath(sanitized) === null) {
|
|
1542
|
-
jsonResponse(res, 422, {
|
|
1543
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1544
|
-
});
|
|
1545
|
-
return;
|
|
1546
|
-
}
|
|
1547
|
-
apply.push([comp, sanitized]);
|
|
1548
|
-
}
|
|
1549
|
-
themesResource.setActiveName(themeName);
|
|
1550
|
-
themesResource.setProductionName(themeName);
|
|
1551
|
-
syncTokensToCss(themeName);
|
|
1552
|
-
syncFontsToCss(themeName);
|
|
1553
|
-
const themeData = normalizeTheme(themesResource.readJson(themeName));
|
|
1554
|
-
themeData._fileName = themeName;
|
|
1555
|
-
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];
|
|
1556
1070
|
const r = componentResource(comp);
|
|
1557
|
-
r.
|
|
1558
|
-
r.
|
|
1559
|
-
const cfg = readComponentConfig(comp, configFile);
|
|
1560
|
-
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
|
|
1071
|
+
if (embedded) r.writeWorking({ ...embedded, component: comp });
|
|
1072
|
+
else r.clearWorking();
|
|
1561
1073
|
}
|
|
1074
|
+
themesResource.setActiveName(fileName);
|
|
1075
|
+
const resolvedConfigs = {};
|
|
1562
1076
|
for (const comp of knownComponents) {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
const cfg = readComponentConfig(comp, activeName);
|
|
1566
|
-
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 };
|
|
1567
1079
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1080
|
+
const resolvedColorsAndType = resolveLiveColorsAndType(theme);
|
|
1081
|
+
const skippedComponents = Object.keys(theme.componentConfigs).filter(
|
|
1082
|
+
(comp) => !knownComponents.includes(comp)
|
|
1083
|
+
);
|
|
1570
1084
|
jsonResponse(res, 200, {
|
|
1571
1085
|
ok: true,
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1086
|
+
theme: { ...theme, _fileName: fileName },
|
|
1087
|
+
colorsAndType: resolvedColorsAndType && {
|
|
1088
|
+
...resolvedColorsAndType.data,
|
|
1089
|
+
_fileName: fileName,
|
|
1090
|
+
_source: resolvedColorsAndType.source
|
|
1091
|
+
},
|
|
1092
|
+
componentConfigs: resolvedConfigs,
|
|
1093
|
+
skippedComponents
|
|
1575
1094
|
});
|
|
1576
1095
|
}
|
|
1577
|
-
async function
|
|
1096
|
+
async function handleExportTheme({ params, res }) {
|
|
1578
1097
|
const [fileName] = params;
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
}
|
|
1584
|
-
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
1585
|
-
const theme = themesResource.readJson(themeName);
|
|
1586
|
-
if (!theme) {
|
|
1587
|
-
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
1098
|
+
if (rejectReservedName(res, fileName)) return;
|
|
1099
|
+
const read = readTheme(fileName);
|
|
1100
|
+
if (!read) {
|
|
1101
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
1588
1102
|
return;
|
|
1589
1103
|
}
|
|
1590
|
-
const knownComponents = new Set(listComponentNames());
|
|
1591
|
-
const componentConfigs = {};
|
|
1592
|
-
for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
|
|
1593
|
-
if (!knownComponents.has(comp)) continue;
|
|
1594
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1595
|
-
if (sanitized === "default") continue;
|
|
1596
|
-
const cfg = componentResource(comp).readJson(sanitized);
|
|
1597
|
-
if (!cfg) {
|
|
1598
|
-
jsonResponse(res, 422, {
|
|
1599
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1600
|
-
});
|
|
1601
|
-
return;
|
|
1602
|
-
}
|
|
1603
|
-
componentConfigs[`${comp}/${sanitized}`] = cfg;
|
|
1604
|
-
}
|
|
1605
1104
|
const bundle = {
|
|
1606
|
-
kind: "
|
|
1607
|
-
schemaVersion:
|
|
1105
|
+
kind: "theme-bundle",
|
|
1106
|
+
schemaVersion: THEME_SCHEMA_VERSION,
|
|
1608
1107
|
liveTokensVersion: PKG_VERSION,
|
|
1609
1108
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1610
|
-
manifest
|
|
1611
|
-
theme,
|
|
1612
|
-
componentConfigs
|
|
1109
|
+
manifest: read.theme
|
|
1613
1110
|
};
|
|
1614
1111
|
res.statusCode = 200;
|
|
1615
1112
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -1622,7 +1119,7 @@ ${lines.join("\n")}
|
|
|
1622
1119
|
function nextAvailableName2(exists, baseName) {
|
|
1623
1120
|
return nextAvailableName(exists, sanitizeFileName(baseName));
|
|
1624
1121
|
}
|
|
1625
|
-
async function
|
|
1122
|
+
async function handleImportTheme({ req, res }) {
|
|
1626
1123
|
let bundle;
|
|
1627
1124
|
try {
|
|
1628
1125
|
bundle = JSON.parse(await readBody(req));
|
|
@@ -1630,117 +1127,112 @@ ${lines.join("\n")}
|
|
|
1630
1127
|
jsonResponse(res, 400, { error: "Body is not valid JSON" });
|
|
1631
1128
|
return;
|
|
1632
1129
|
}
|
|
1633
|
-
if (!bundle || bundle.kind
|
|
1130
|
+
if (!bundle || !Object.values(BUNDLE_KIND_BY_VERSION).includes(bundle.kind)) {
|
|
1634
1131
|
jsonResponse(res, 400, {
|
|
1635
|
-
error: "Not a
|
|
1132
|
+
error: "Not a theme bundle (kind discriminator missing or wrong)"
|
|
1636
1133
|
});
|
|
1637
1134
|
return;
|
|
1638
1135
|
}
|
|
1639
|
-
if (bundle.
|
|
1136
|
+
if (bundle.kind !== BUNDLE_KIND_BY_VERSION[bundle.schemaVersion]) {
|
|
1640
1137
|
jsonResponse(res, 400, {
|
|
1641
|
-
error: `Unsupported bundle schemaVersion
|
|
1138
|
+
error: `Unsupported bundle: kind "${bundle.kind}" with schemaVersion ${bundle.schemaVersion}`
|
|
1642
1139
|
});
|
|
1643
1140
|
return;
|
|
1644
1141
|
}
|
|
1645
|
-
if (!bundle.manifest
|
|
1646
|
-
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" });
|
|
1647
1155
|
return;
|
|
1648
1156
|
}
|
|
1157
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1649
1158
|
const renames = {};
|
|
1650
|
-
const
|
|
1651
|
-
const
|
|
1652
|
-
const finalThemeName = nextAvailableName2(
|
|
1159
|
+
const originalName = sanitizeFileName(theme.name || "imported");
|
|
1160
|
+
const finalName = nextAvailableName2(
|
|
1653
1161
|
(n) => themesResource.existingPath(n) !== null,
|
|
1654
|
-
|
|
1162
|
+
originalName
|
|
1655
1163
|
);
|
|
1656
|
-
if (
|
|
1657
|
-
renames[`theme:${
|
|
1164
|
+
if (finalName !== originalName) {
|
|
1165
|
+
renames[`theme:${originalName}`] = finalName;
|
|
1658
1166
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
if (finalName !== originalName) {
|
|
1676
|
-
renames[`componentConfig:${comp}/${originalName}`] = finalName;
|
|
1677
|
-
}
|
|
1678
|
-
componentRenames[`${comp}/${originalName}`] = finalName;
|
|
1679
|
-
const cfgBody = { ...cfgValue };
|
|
1680
|
-
cfgBody.component = comp;
|
|
1681
|
-
cfgBody.name = finalName;
|
|
1682
|
-
cfgBody.updatedAt = now;
|
|
1683
|
-
if (!cfgBody.createdAt) cfgBody.createdAt = now;
|
|
1684
|
-
r.ensureDir();
|
|
1685
|
-
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;
|
|
1686
1183
|
}
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
theme
|
|
1690
|
-
|
|
1691
|
-
};
|
|
1692
|
-
for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
|
|
1693
|
-
const original = String(configName);
|
|
1694
|
-
if (original === "default") {
|
|
1695
|
-
rewrittenManifest.componentConfigs[comp] = "default";
|
|
1696
|
-
continue;
|
|
1697
|
-
}
|
|
1698
|
-
const finalName = componentRenames[`${comp}/${original}`] ?? original;
|
|
1699
|
-
rewrittenManifest.componentConfigs[comp] = finalName;
|
|
1700
|
-
}
|
|
1701
|
-
rewrittenManifest.updatedAt = now;
|
|
1702
|
-
if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
|
|
1703
|
-
const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
|
|
1704
|
-
const finalManifestName = nextAvailableName2(
|
|
1705
|
-
(n) => manifestsResource.existingPath(n) !== null,
|
|
1706
|
-
originalManifestName
|
|
1707
|
-
);
|
|
1708
|
-
if (finalManifestName !== originalManifestName) {
|
|
1709
|
-
renames[`manifest:${originalManifestName}`] = finalManifestName;
|
|
1184
|
+
const read = readTheme(activeName);
|
|
1185
|
+
if (!read) {
|
|
1186
|
+
respondUnreadableTheme(res, activeName, "Active theme not found");
|
|
1187
|
+
return;
|
|
1710
1188
|
}
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
JSON.stringify(rewrittenManifest, null, 2)
|
|
1715
|
-
);
|
|
1189
|
+
themesResource.setProductionName(activeName);
|
|
1190
|
+
regenerateTokensCss();
|
|
1191
|
+
syncFontsToCss(read.theme.colorsAndType);
|
|
1716
1192
|
jsonResponse(res, 200, {
|
|
1717
1193
|
ok: true,
|
|
1718
|
-
|
|
1719
|
-
|
|
1194
|
+
productionTheme: {
|
|
1195
|
+
fileName: activeName,
|
|
1196
|
+
name: read.theme.name,
|
|
1197
|
+
updatedAt: read.theme.updatedAt
|
|
1198
|
+
}
|
|
1720
1199
|
});
|
|
1721
1200
|
}
|
|
1722
1201
|
function methodNotAllowed({ res }) {
|
|
1723
1202
|
jsonResponse(res, 405, { error: "Method not allowed" });
|
|
1724
1203
|
}
|
|
1204
|
+
let legacyLayout = null;
|
|
1725
1205
|
const routes = [
|
|
1726
|
-
//
|
|
1727
|
-
|
|
1728
|
-
{ method: "GET", pattern:
|
|
1729
|
-
{ method: "
|
|
1730
|
-
{ method: "
|
|
1731
|
-
{ 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 },
|
|
1732
1219
|
// Component configs — list of components
|
|
1733
1220
|
{ method: "GET", pattern: COMPONENT_CONFIGS_ROUTE, handler: handleListComponents },
|
|
1734
1221
|
// Component configs — :comp/active (must precede :comp/:name)
|
|
1735
1222
|
{ method: "GET", pattern: COMP_ACTIVE_REGEX, handler: handleGetComponentActive },
|
|
1736
|
-
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler:
|
|
1223
|
+
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
1737
1224
|
{ method: "POST", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
1738
1225
|
{ method: "DELETE", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
1739
|
-
// Component configs — :comp/production
|
|
1740
|
-
{ method: "GET", pattern: COMP_PRODUCTION_REGEX, handler:
|
|
1741
|
-
{ 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 },
|
|
1742
1229
|
{ method: "POST", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
1743
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 },
|
|
1744
1236
|
// Component configs — :comp/:name (must precede :comp listing)
|
|
1745
1237
|
{ method: "GET", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
1746
1238
|
{ method: "PUT", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
@@ -1748,33 +1240,44 @@ ${lines.join("\n")}
|
|
|
1748
1240
|
{ method: "POST", pattern: COMP_BY_NAME_REGEX, handler: methodNotAllowed },
|
|
1749
1241
|
// Component configs — list configs for :comp (broadest, runs last among comp routes)
|
|
1750
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 },
|
|
1751
1272
|
// Themes — :name CRUD (broadest theme route, runs last)
|
|
1752
1273
|
{ method: "GET", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
1753
1274
|
{ method: "PUT", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
1754
1275
|
{ method: "DELETE", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
1755
|
-
//
|
|
1756
|
-
{ method: "
|
|
1757
|
-
{ method: "GET", pattern:
|
|
1758
|
-
{ method: "
|
|
1759
|
-
|
|
1760
|
-
{ method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
|
|
1761
|
-
{ method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1762
|
-
{ method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1763
|
-
{ method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1764
|
-
// Manifests — :name/apply (more specific than :name)
|
|
1765
|
-
{ method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
|
|
1766
|
-
{ method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1767
|
-
{ method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1768
|
-
{ method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1769
|
-
// Manifests — :name/export (more specific than :name)
|
|
1770
|
-
{ method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
|
|
1771
|
-
{ method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1772
|
-
{ method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1773
|
-
{ method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1774
|
-
// Manifests — :name CRUD (broadest manifest route, runs last)
|
|
1775
|
-
{ method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
1776
|
-
{ method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
1777
|
-
{ 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 }
|
|
1778
1281
|
];
|
|
1779
1282
|
return {
|
|
1780
1283
|
name: "theme-file-api",
|
|
@@ -1788,19 +1291,42 @@ ${lines.join("\n")}
|
|
|
1788
1291
|
};
|
|
1789
1292
|
},
|
|
1790
1293
|
configureServer(server) {
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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
|
+
}
|
|
1797
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
|
+
}
|
|
1798
1323
|
const handled = await dispatch(req, res, routes);
|
|
1799
1324
|
if (!handled) next();
|
|
1800
1325
|
});
|
|
1801
1326
|
},
|
|
1802
1327
|
handleHotUpdate(ctx) {
|
|
1803
|
-
|
|
1328
|
+
if (legacyLayout) return;
|
|
1329
|
+
const normalized = path.resolve(ctx.file);
|
|
1804
1330
|
if (!COMPONENTS_SCAN_DIRS.some((d) => normalized.startsWith(d))) return;
|
|
1805
1331
|
if (!normalized.endsWith(".svelte")) return;
|
|
1806
1332
|
if (!isThemeAwareComponent(normalized)) return;
|