@motion-proto/live-tokens 0.47.0 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +68 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +2 -2
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +152 -0
- package/CHANGELOG.md +292 -0
- package/README.md +37 -26
- package/bin/adjust.mjs +254 -0
- package/bin/cli.mjs +94 -7
- package/bin/generate-theme.mjs +251 -0
- package/bin/migrate.mjs +95 -6
- package/dist-plugin/adjust/index.cjs +259 -0
- package/dist-plugin/adjust/index.d.cts +45 -0
- package/dist-plugin/adjust/index.d.ts +45 -0
- package/dist-plugin/adjust/index.js +176 -0
- package/dist-plugin/{chunk-H4TRUINI.js → chunk-44RSTAII.js} +0 -48
- package/dist-plugin/chunk-6OZFXIQI.js +316 -0
- package/dist-plugin/chunk-76TFDTJO.js +556 -0
- package/dist-plugin/chunk-D3ZVKOR4.js +52 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.cts +54 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.ts +54 -0
- package/dist-plugin/generateColorsAndType/index.cjs +1781 -0
- package/dist-plugin/generateColorsAndType/index.d.cts +81 -0
- package/dist-plugin/generateColorsAndType/index.d.ts +81 -0
- package/dist-plugin/generateColorsAndType/index.js +1210 -0
- package/dist-plugin/index.cjs +901 -533
- package/dist-plugin/index.d.cts +3 -2
- package/dist-plugin/index.d.ts +3 -2
- package/dist-plugin/index.js +658 -1082
- package/dist-plugin/migrateData/index.cjs +728 -0
- package/dist-plugin/migrateData/index.d.cts +60 -0
- package/dist-plugin/migrateData/index.d.ts +60 -0
- package/dist-plugin/migrateData/index.js +348 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.cts +211 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.ts +211 -0
- package/dist-plugin/tokensCssMigrations/index.cjs +2 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +3 -15
- package/dist-plugin/tokensCssMigrations/index.d.ts +3 -15
- package/dist-plugin/tokensCssMigrations/index.js +4 -2
- package/package.json +18 -4
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +183 -158
- package/src/editor/component-editor/scaffolding/ComponentFileMenu.svelte +0 -4
- package/src/editor/component-editor/scaffolding/SaveAsDialog.svelte +4 -4
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +2 -59
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +1 -1
- package/src/editor/core/components/adjustAliases.ts +180 -0
- package/src/editor/core/components/aliasKinds.ts +73 -0
- package/src/editor/core/components/componentConfigService.ts +26 -38
- package/src/editor/core/flashStatus.ts +1 -1
- package/src/editor/core/fonts/fontMigration.ts +13 -13
- package/src/editor/core/fonts/fontPairing.ts +35 -0
- package/src/editor/core/palettes/paletteDerivation.ts +128 -16
- package/src/editor/core/preview/lookPreview.ts +141 -0
- package/src/editor/core/productionPulse.ts +34 -21
- package/src/editor/core/storage/files/versionedFileResourceClient.ts +18 -45
- package/src/editor/core/store/editorConfigStore.ts +2 -2
- package/src/editor/core/store/editorPersistence.ts +0 -1
- package/src/editor/core/store/editorStore.ts +114 -51
- package/src/editor/core/store/editorTypes.ts +0 -1
- package/src/editor/core/store/gradientSource.ts +2 -2
- package/src/editor/core/themes/colorsAndTypeService.ts +95 -0
- package/src/editor/core/themes/generateColorsAndType.ts +433 -0
- package/src/editor/core/themes/loadRows.ts +64 -0
- package/src/editor/core/themes/lookSummary.ts +75 -0
- package/src/editor/core/themes/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +3 -3
- package/src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts +2 -2
- package/src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts +3 -3
- package/src/editor/core/themes/migrations/2026-08-13-drop-legacy-shape-space-keys.ts +38 -0
- package/src/editor/core/themes/migrations/2026-08-15-line-height-scale-rename.ts +57 -0
- package/src/editor/core/themes/migrations/index.ts +19 -11
- package/src/editor/core/themes/slices/components.ts +4 -4
- package/src/editor/core/themes/slices/fonts.ts +1 -1
- package/src/editor/core/themes/slices/gradients.ts +22 -0
- package/src/editor/core/themes/slices/palettes.ts +2 -2
- package/src/editor/core/themes/themeInit.ts +21 -29
- package/src/editor/core/themes/themeService.ts +218 -78
- package/src/editor/core/themes/themeTypes.ts +94 -51
- package/src/editor/docs/Docs.svelte +1 -0
- package/src/editor/docs/chapters.ts +1 -0
- package/src/editor/docs/content/01-overview.md +1 -1
- package/src/editor/docs/content/editing-tokens.md +6 -6
- package/src/editor/docs/content/getting-started.md +9 -7
- package/src/editor/docs/content/themes-workflow.md +74 -34
- package/src/editor/docs/content/where-themes-live.md +61 -0
- package/src/editor/docs/content.generated.ts +5 -4
- package/src/editor/index.ts +27 -27
- package/src/editor/pages/ComponentEditorPage.svelte +2 -2
- package/src/editor/pages/EditorShell.svelte +4 -30
- package/src/editor/ui/BezierCurveEditor.svelte +2 -2
- package/src/editor/ui/ColorEditPanel.svelte +1 -1
- package/src/editor/ui/FileLoadList.svelte +67 -7
- package/src/editor/ui/GradientEditor.svelte +3 -3
- package/src/editor/ui/PaletteEditor.svelte +26 -12
- package/src/editor/ui/ThemePanel.svelte +1067 -0
- package/src/editor/ui/UIDialog.svelte +11 -9
- package/src/editor/ui/UIPillButton.svelte +7 -2
- package/src/editor/ui/colors/ColorWheel.svelte +34 -1
- package/src/editor/ui/curveEngine.ts +48 -0
- package/src/editor/ui/index.ts +4 -2
- package/src/editor/ui/palette/PaletteBase.svelte +75 -51
- package/src/editor/ui/palette/PaletteJumpButton.svelte +3 -8
- package/src/live-tokens/data/colors-and-type/autumn.json +2490 -0
- package/src/live-tokens/data/{themes → colors-and-type}/default.json +816 -293
- package/src/live-tokens/data/colors-and-type/halloween.json +2529 -0
- package/src/live-tokens/data/colors-and-type/midnight-study.json +2536 -0
- package/src/live-tokens/data/colors-and-type/ocean.json +2489 -0
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +2520 -0
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +2476 -0
- package/src/live-tokens/data/colors-and-type/sunset.json +2528 -0
- package/src/live-tokens/data/themes/autumn.json +3918 -0
- package/src/live-tokens/data/themes/halloween.json +3992 -0
- package/src/live-tokens/data/themes/midnight-study.json +3932 -0
- package/src/live-tokens/data/themes/ocean.json +3917 -0
- package/src/live-tokens/data/themes/royal-velvet.json +3983 -0
- package/src/live-tokens/data/themes/spring-meadow.json +3904 -0
- package/src/live-tokens/data/themes/sunset.json +3931 -0
- package/src/live-tokens/data/tokens.generated.css +106 -117
- package/src/system/styles/CONVENTIONS.md +1 -1
- package/src/system/styles/fonts.css +1 -1
- package/template/README.md +7 -5
- package/template/_gitignore +0 -6
- package/src/editor/core/components/componentPersist.ts +0 -62
- package/src/editor/core/manifests/manifestService.ts +0 -172
- package/src/editor/ui/ManifestFileManager.svelte +0 -446
- package/src/editor/ui/ThemeFileManager.svelte +0 -785
- package/src/live-tokens/data/manifests/default.json +0 -35
package/dist-plugin/index.cjs
CHANGED
|
@@ -38,8 +38,8 @@ __export(index_exports, {
|
|
|
38
38
|
module.exports = __toCommonJS(index_exports);
|
|
39
39
|
|
|
40
40
|
// vite-plugin/themeFileApi.ts
|
|
41
|
-
var
|
|
42
|
-
var
|
|
41
|
+
var import_fs4 = __toESM(require("fs"), 1);
|
|
42
|
+
var import_path4 = __toESM(require("path"), 1);
|
|
43
43
|
|
|
44
44
|
// src/editor/core/themes/parsers/globalRootBlock.ts
|
|
45
45
|
function extractGlobalRootBody(source) {
|
|
@@ -62,7 +62,7 @@ function parseColorOpacity(value) {
|
|
|
62
62
|
|
|
63
63
|
// src/editor/core/storage/files/versionedFileResourceClient.ts
|
|
64
64
|
function sanitizeFileName(name) {
|
|
65
|
-
return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(
|
|
65
|
+
return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^[-_]+|-+$/g, "") || "unnamed";
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// src/editor/core/palettes/oklch.ts
|
|
@@ -164,6 +164,25 @@ function gamutClamp(l, c, h) {
|
|
|
164
164
|
function makeAnchor(x, y, tangentLen = 15) {
|
|
165
165
|
return { x, y, inDx: -tangentLen, inDy: 0, outDx: tangentLen, outDy: 0 };
|
|
166
166
|
}
|
|
167
|
+
var HANDLE_SPAN = 1 / 3;
|
|
168
|
+
function tangentSlope(prev, x, y, next) {
|
|
169
|
+
const hPrev = prev ? x - prev.x : 0;
|
|
170
|
+
const hNext = next ? next.x - x : 0;
|
|
171
|
+
const dPrev = hPrev > 0 ? (y - prev.y) / hPrev : null;
|
|
172
|
+
const dNext = hNext > 0 ? (next.y - y) / hNext : null;
|
|
173
|
+
if (dPrev === null) return dNext ?? 0;
|
|
174
|
+
if (dNext === null) return dPrev;
|
|
175
|
+
if (dPrev * dNext <= 0) return 0;
|
|
176
|
+
const wPrev = 2 * hNext + hPrev;
|
|
177
|
+
const wNext = hNext + 2 * hPrev;
|
|
178
|
+
return (wPrev + wNext) / (wPrev / dPrev + wNext / dNext);
|
|
179
|
+
}
|
|
180
|
+
function tangentAnchor(x, y, prev, next) {
|
|
181
|
+
const m = tangentSlope(prev, x, y, next);
|
|
182
|
+
const inDx = prev ? -(x - prev.x) * HANDLE_SPAN : 0;
|
|
183
|
+
const outDx = next ? (next.x - x) * HANDLE_SPAN : 0;
|
|
184
|
+
return { x, y, inDx, inDy: m * inDx, outDx, outDy: m * outDx };
|
|
185
|
+
}
|
|
167
186
|
function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
|
|
168
187
|
const u = 1 - t, u2 = u * u, u3 = u2 * u;
|
|
169
188
|
const t2 = t * t, t3 = t2 * t;
|
|
@@ -330,7 +349,33 @@ function nearestPaletteStep(lightnessCurve, baseL) {
|
|
|
330
349
|
}
|
|
331
350
|
return best;
|
|
332
351
|
}
|
|
333
|
-
function setCurveAnchor(curve, x, y) {
|
|
352
|
+
function setCurveAnchor(curve, x, y, smooth = false) {
|
|
353
|
+
if (smooth && curve.length === 2) {
|
|
354
|
+
const [p0, p2] = curve;
|
|
355
|
+
const mid = { x, y, inDx: 0, inDy: 0, outDx: 0, outDy: 0 };
|
|
356
|
+
if (Math.abs(p0.x - x) < 0.5) {
|
|
357
|
+
return {
|
|
358
|
+
curve: [tangentAnchor(x, y, null, p2), tangentAnchor(p2.x, p2.y, mid, null)],
|
|
359
|
+
displacedY: p0.y,
|
|
360
|
+
priorEndpoints: [p0, p2]
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (Math.abs(p2.x - x) < 0.5) {
|
|
364
|
+
return {
|
|
365
|
+
curve: [tangentAnchor(p0.x, p0.y, null, mid), tangentAnchor(x, y, p0, null)],
|
|
366
|
+
displacedY: p2.y,
|
|
367
|
+
priorEndpoints: [p0, p2]
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
curve: [
|
|
372
|
+
tangentAnchor(p0.x, p0.y, null, mid),
|
|
373
|
+
tangentAnchor(x, y, p0, p2),
|
|
374
|
+
tangentAnchor(p2.x, p2.y, mid, null)
|
|
375
|
+
],
|
|
376
|
+
priorEndpoints: [p0, p2]
|
|
377
|
+
};
|
|
378
|
+
}
|
|
334
379
|
const existing = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
335
380
|
if (existing >= 0) {
|
|
336
381
|
const displacedY = curve[existing].y;
|
|
@@ -338,18 +383,51 @@ function setCurveAnchor(curve, x, y) {
|
|
|
338
383
|
}
|
|
339
384
|
let insertAt = curve.findIndex((a) => a.x > x);
|
|
340
385
|
if (insertAt < 0) insertAt = curve.length;
|
|
341
|
-
|
|
386
|
+
const prev = curve[insertAt - 1] ?? null;
|
|
387
|
+
const next = curve[insertAt] ?? null;
|
|
388
|
+
const out = [...curve];
|
|
389
|
+
if (prev && next) {
|
|
390
|
+
const span = next.x - prev.x;
|
|
391
|
+
out[insertAt - 1] = scaleOutHandle(prev, (x - prev.x) / span);
|
|
392
|
+
out[insertAt] = scaleInHandle(next, (next.x - x) / span);
|
|
393
|
+
}
|
|
394
|
+
out.splice(insertAt, 0, fitBetween(tangentAnchor(x, y, prev, next), out[insertAt - 1] ?? null, out[insertAt] ?? null));
|
|
395
|
+
return { curve: out };
|
|
396
|
+
}
|
|
397
|
+
var scaleOutHandle = (a, k) => ({ ...a, outDx: a.outDx * k, outDy: a.outDy * k });
|
|
398
|
+
var scaleInHandle = (a, k) => ({ ...a, inDx: a.inDx * k, inDy: a.inDy * k });
|
|
399
|
+
function fitBetween(a, prev, next) {
|
|
400
|
+
let out = a;
|
|
401
|
+
if (prev) {
|
|
402
|
+
const room = a.x - (prev.x + prev.outDx);
|
|
403
|
+
if (room < -out.inDx) out = scaleInHandle(out, room <= 0 ? 0 : room / -out.inDx);
|
|
404
|
+
}
|
|
405
|
+
if (next) {
|
|
406
|
+
const room = next.x + next.inDx - a.x;
|
|
407
|
+
if (room < out.outDx) out = scaleOutHandle(out, room <= 0 ? 0 : room / out.outDx);
|
|
408
|
+
}
|
|
409
|
+
return out;
|
|
342
410
|
}
|
|
343
|
-
function liftCurveAnchor(curve, x, displacedY) {
|
|
411
|
+
function liftCurveAnchor(curve, x, displacedY, priorEndpoints) {
|
|
412
|
+
if (priorEndpoints) return priorEndpoints;
|
|
344
413
|
const idx = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
345
414
|
if (idx < 0) return curve;
|
|
346
415
|
if (displacedY !== void 0) return curve.map((a, i) => i === idx ? { ...a, y: displacedY } : a);
|
|
347
416
|
if (idx === 0 || idx === curve.length - 1) return curve;
|
|
348
|
-
|
|
417
|
+
const out = curve.filter((_, i) => i !== idx);
|
|
418
|
+
const prev = curve[idx - 1];
|
|
419
|
+
const next = curve[idx + 1];
|
|
420
|
+
if (prev && next) {
|
|
421
|
+
const span = next.x - prev.x;
|
|
422
|
+
out[idx - 1] = scaleOutHandle(prev, span / (curve[idx].x - prev.x));
|
|
423
|
+
out[idx] = scaleInHandle(next, span / (next.x - curve[idx].x));
|
|
424
|
+
}
|
|
425
|
+
return out;
|
|
349
426
|
}
|
|
350
427
|
function syncBaseAnchor(cfg) {
|
|
351
428
|
if (cfg.anchorToBase === false) return;
|
|
352
429
|
const prev = cfg.anchorPlacement;
|
|
430
|
+
const fresh = prev === void 0;
|
|
353
431
|
let lCurve = cfg.lightnessCurve;
|
|
354
432
|
let sCurve = cfg.saturationCurve;
|
|
355
433
|
if (prev) {
|
|
@@ -359,11 +437,17 @@ function syncBaseAnchor(cfg) {
|
|
|
359
437
|
}
|
|
360
438
|
const step = nearestPaletteStep(lCurve, cfg.baseColor.l);
|
|
361
439
|
const x = stepIndexToX(step);
|
|
362
|
-
const l = setCurveAnchor(lCurve, x, cfg.baseColor.l * 100);
|
|
363
|
-
const s = setCurveAnchor(sCurve, x, 100);
|
|
440
|
+
const l = setCurveAnchor(lCurve, x, cfg.baseColor.l * 100, fresh);
|
|
441
|
+
const s = setCurveAnchor(sCurve, x, 100, fresh);
|
|
364
442
|
cfg.lightnessCurve = l.curve;
|
|
365
443
|
cfg.saturationCurve = s.curve;
|
|
366
|
-
cfg.anchorPlacement = {
|
|
444
|
+
cfg.anchorPlacement = {
|
|
445
|
+
step,
|
|
446
|
+
displacedL: l.displacedY,
|
|
447
|
+
displacedS: s.displacedY,
|
|
448
|
+
priorLightnessEndpoints: prev?.priorLightnessEndpoints ?? l.priorEndpoints,
|
|
449
|
+
priorSaturationEndpoints: prev?.priorSaturationEndpoints ?? s.priorEndpoints
|
|
450
|
+
};
|
|
367
451
|
}
|
|
368
452
|
function computeDerivedOklch(step, base, scaleTitle, scaleCurves, curveOffset) {
|
|
369
453
|
const scale = SCALES.find((s) => s.title === scaleTitle);
|
|
@@ -540,6 +624,7 @@ function versionedFileResourceServer(opts) {
|
|
|
540
624
|
const dir = opts.dir;
|
|
541
625
|
const activePath = import_path.default.join(dir, "_active.json");
|
|
542
626
|
const productionPath = import_path.default.join(dir, "_production.json");
|
|
627
|
+
const workingPath = import_path.default.join(dir, "_working.json");
|
|
543
628
|
const defaultName = opts.defaultName ?? "default";
|
|
544
629
|
const resolvedDir = import_path.default.resolve(dir);
|
|
545
630
|
const resolvedPackageDir = opts.packageDir ? import_path.default.resolve(opts.packageDir) : null;
|
|
@@ -568,6 +653,16 @@ function versionedFileResourceServer(opts) {
|
|
|
568
653
|
}
|
|
569
654
|
return null;
|
|
570
655
|
}
|
|
656
|
+
function isPackageFile(name) {
|
|
657
|
+
if (!resolvedPackageDir) return false;
|
|
658
|
+
const pkg = import_path.default.join(resolvedPackageDir, `${name}.json`);
|
|
659
|
+
if (!import_fs.default.existsSync(pkg)) return false;
|
|
660
|
+
const local = import_path.default.resolve(filePath(name));
|
|
661
|
+
if (local === pkg) {
|
|
662
|
+
return opts.packageOwnedNames ? opts.packageOwnedNames.includes(name) : true;
|
|
663
|
+
}
|
|
664
|
+
return !import_fs.default.existsSync(local);
|
|
665
|
+
}
|
|
571
666
|
function readJson(name) {
|
|
572
667
|
const resolved = existingPath(name);
|
|
573
668
|
if (!resolved) return null;
|
|
@@ -612,20 +707,44 @@ function versionedFileResourceServer(opts) {
|
|
|
612
707
|
function setProductionName(name) {
|
|
613
708
|
import_fs.default.writeFileSync(productionPath, JSON.stringify({ productionFile: name }));
|
|
614
709
|
}
|
|
710
|
+
function hasWorking() {
|
|
711
|
+
return import_fs.default.existsSync(workingPath);
|
|
712
|
+
}
|
|
713
|
+
function readWorking() {
|
|
714
|
+
if (!import_fs.default.existsSync(workingPath)) return null;
|
|
715
|
+
const parsed = JSON.parse(import_fs.default.readFileSync(workingPath, "utf-8"));
|
|
716
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
717
|
+
throw new Error(`${workingPath} does not hold a JSON object`);
|
|
718
|
+
}
|
|
719
|
+
return parsed;
|
|
720
|
+
}
|
|
721
|
+
function writeWorking(data) {
|
|
722
|
+
ensureDir();
|
|
723
|
+
import_fs.default.writeFileSync(workingPath, JSON.stringify(data, null, 2));
|
|
724
|
+
}
|
|
725
|
+
function clearWorking() {
|
|
726
|
+
if (import_fs.default.existsSync(workingPath)) import_fs.default.unlinkSync(workingPath);
|
|
727
|
+
}
|
|
615
728
|
return {
|
|
616
729
|
dir,
|
|
617
730
|
activePath,
|
|
618
731
|
productionPath,
|
|
732
|
+
workingPath,
|
|
619
733
|
ensureDir,
|
|
620
734
|
ensureMeta,
|
|
621
735
|
filePath,
|
|
622
736
|
existingPath,
|
|
737
|
+
isPackageFile,
|
|
623
738
|
readJson,
|
|
624
739
|
listNames,
|
|
625
740
|
getActiveName,
|
|
626
741
|
getProductionName,
|
|
627
742
|
setActiveName,
|
|
628
|
-
setProductionName
|
|
743
|
+
setProductionName,
|
|
744
|
+
hasWorking,
|
|
745
|
+
readWorking,
|
|
746
|
+
writeWorking,
|
|
747
|
+
clearWorking
|
|
629
748
|
};
|
|
630
749
|
}
|
|
631
750
|
|
|
@@ -657,6 +776,78 @@ async function dispatch(req, res, routes) {
|
|
|
657
776
|
return false;
|
|
658
777
|
}
|
|
659
778
|
|
|
779
|
+
// vite-plugin/themes/normalizeTheme.ts
|
|
780
|
+
var THEME_SCHEMA_VERSION = 3;
|
|
781
|
+
function asObject(value) {
|
|
782
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
783
|
+
}
|
|
784
|
+
function isColorsAndTypeShaped(raw) {
|
|
785
|
+
const obj = asObject(raw);
|
|
786
|
+
if (!obj) return false;
|
|
787
|
+
if ("theme" in obj || "colorsAndType" in obj) return false;
|
|
788
|
+
return "cssVariables" in obj || "editorConfigs" in obj;
|
|
789
|
+
}
|
|
790
|
+
function stripFileMarker(value) {
|
|
791
|
+
if (!("_fileName" in value)) return value;
|
|
792
|
+
const { _fileName, ...rest } = value;
|
|
793
|
+
return rest;
|
|
794
|
+
}
|
|
795
|
+
function asString(value, fallback) {
|
|
796
|
+
return typeof value === "string" && value ? value : fallback;
|
|
797
|
+
}
|
|
798
|
+
function migrateEmbeddedKey(src) {
|
|
799
|
+
const { theme, ...rest } = src;
|
|
800
|
+
return { ...rest, colorsAndType: theme };
|
|
801
|
+
}
|
|
802
|
+
function normalizeTheme(raw, resolvers) {
|
|
803
|
+
const input = asObject(raw) ?? {};
|
|
804
|
+
const migrated = input.schemaVersion !== THEME_SCHEMA_VERSION;
|
|
805
|
+
const src = migrated ? migrateEmbeddedKey(input) : input;
|
|
806
|
+
const dropped = [];
|
|
807
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
808
|
+
const embeddedColorsAndType = asObject(src.colorsAndType);
|
|
809
|
+
let colorsAndType = null;
|
|
810
|
+
if (embeddedColorsAndType) {
|
|
811
|
+
colorsAndType = stripFileMarker(embeddedColorsAndType);
|
|
812
|
+
} else {
|
|
813
|
+
const colorsAndTypeName = asString(src.colorsAndType, "default");
|
|
814
|
+
colorsAndType = asObject(resolvers.readColorsAndType(colorsAndTypeName));
|
|
815
|
+
if (!colorsAndType) {
|
|
816
|
+
dropped.push(`colors-and-type:${colorsAndTypeName}`);
|
|
817
|
+
if (colorsAndTypeName !== "default") colorsAndType = asObject(resolvers.readColorsAndType("default"));
|
|
818
|
+
}
|
|
819
|
+
if (colorsAndType) colorsAndType = resolvers.normalizeColorsAndType(stripFileMarker(colorsAndType));
|
|
820
|
+
}
|
|
821
|
+
const componentConfigs = {};
|
|
822
|
+
for (const [comp, value] of Object.entries(asObject(src.componentConfigs) ?? {})) {
|
|
823
|
+
const embedded = asObject(value);
|
|
824
|
+
if (embedded) {
|
|
825
|
+
componentConfigs[comp] = stripFileMarker(embedded);
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
const configName = asString(value, "default");
|
|
829
|
+
if (configName === "default") continue;
|
|
830
|
+
const resolved = asObject(resolvers.readComponentConfig(comp, configName));
|
|
831
|
+
if (!resolved) {
|
|
832
|
+
dropped.push(`${comp}/${configName}`);
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
componentConfigs[comp] = stripFileMarker(resolved);
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
theme: {
|
|
839
|
+
name: asString(src.name, "Untitled"),
|
|
840
|
+
createdAt: asString(src.createdAt, now),
|
|
841
|
+
updatedAt: asString(src.updatedAt, now),
|
|
842
|
+
schemaVersion: THEME_SCHEMA_VERSION,
|
|
843
|
+
colorsAndType,
|
|
844
|
+
componentConfigs
|
|
845
|
+
},
|
|
846
|
+
dropped,
|
|
847
|
+
migrated
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
|
|
660
851
|
// vite-plugin/files/nameAllocator.ts
|
|
661
852
|
function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
|
|
662
853
|
if (!exists(baseName)) return baseName;
|
|
@@ -673,8 +864,9 @@ var import_path2 = __toESM(require("path"), 1);
|
|
|
673
864
|
var DEFAULT_DATA_DIR = "src/live-tokens/data";
|
|
674
865
|
var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
675
866
|
"dataDir",
|
|
676
|
-
"
|
|
867
|
+
"colorsAndTypeDir",
|
|
677
868
|
"componentConfigsDir",
|
|
869
|
+
"themesDir",
|
|
678
870
|
"manifestsDir",
|
|
679
871
|
"tokensCssPath"
|
|
680
872
|
]);
|
|
@@ -707,12 +899,69 @@ function resolveDataDirs(opts = {}) {
|
|
|
707
899
|
const sub = (name) => import_path2.default.resolve(dataDir, name);
|
|
708
900
|
return {
|
|
709
901
|
dataDir,
|
|
710
|
-
|
|
902
|
+
colorsAndTypeDir: opts.colorsAndTypeDir ? import_path2.default.resolve(opts.colorsAndTypeDir) : fileConfig.colorsAndTypeDir ? import_path2.default.resolve(fileConfig.colorsAndTypeDir) : sub("colors-and-type"),
|
|
711
903
|
componentConfigsDir: opts.componentConfigsDir ? import_path2.default.resolve(opts.componentConfigsDir) : fileConfig.componentConfigsDir ? import_path2.default.resolve(fileConfig.componentConfigsDir) : sub("component-configs"),
|
|
712
|
-
|
|
904
|
+
themesDir: opts.themesDir ? import_path2.default.resolve(opts.themesDir) : fileConfig.themesDir ? import_path2.default.resolve(fileConfig.themesDir) : sub("themes"),
|
|
905
|
+
legacyManifestsDir: fileConfig.manifestsDir ? import_path2.default.resolve(fileConfig.manifestsDir) : void 0
|
|
713
906
|
};
|
|
714
907
|
}
|
|
715
908
|
|
|
909
|
+
// vite-plugin/files/legacyLayout.ts
|
|
910
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
911
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
912
|
+
function jsonFiles(dir) {
|
|
913
|
+
if (!import_fs3.default.existsSync(dir)) return [];
|
|
914
|
+
try {
|
|
915
|
+
return import_fs3.default.readdirSync(dir).filter((f) => f.endsWith(".json")).map((f) => import_path3.default.join(dir, f));
|
|
916
|
+
} catch {
|
|
917
|
+
return [];
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
function isDirectory(dir) {
|
|
921
|
+
try {
|
|
922
|
+
return import_fs3.default.statSync(dir).isDirectory();
|
|
923
|
+
} catch {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
function parseJsonFile(file) {
|
|
928
|
+
try {
|
|
929
|
+
return JSON.parse(import_fs3.default.readFileSync(file, "utf-8"));
|
|
930
|
+
} catch {
|
|
931
|
+
return null;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
function holdsATheme(themesDir) {
|
|
935
|
+
return jsonFiles(themesDir).some((file) => {
|
|
936
|
+
if (import_path3.default.basename(file).startsWith("_")) return false;
|
|
937
|
+
const raw = parseJsonFile(file);
|
|
938
|
+
if (!raw || typeof raw !== "object") return false;
|
|
939
|
+
return "colorsAndType" in raw || "theme" in raw;
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
function detectLegacyLayout(input) {
|
|
943
|
+
const manifestsDir = input.configuredManifestsDir ?? import_path3.default.join(input.dataDir, "manifests");
|
|
944
|
+
const themesHoldAThemeFile = holdsATheme(input.themesDir);
|
|
945
|
+
if (isDirectory(manifestsDir) && !themesHoldAThemeFile) {
|
|
946
|
+
return { manifestsDir, evidence: `${manifestsDir} is the pre-0.48 whole-look directory` };
|
|
947
|
+
}
|
|
948
|
+
if (jsonFiles(input.colorsAndTypeDir).length > 0) return null;
|
|
949
|
+
for (const file of jsonFiles(input.themesDir)) {
|
|
950
|
+
if (import_path3.default.basename(file).startsWith("_")) continue;
|
|
951
|
+
if (isColorsAndTypeShaped(parseJsonFile(file))) {
|
|
952
|
+
return { manifestsDir, evidence: `${file} is a colors-and-type file, not a theme` };
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const pointer = ["_active.json", "_production.json"].map((name) => import_path3.default.join(input.themesDir, name)).find((p) => import_fs3.default.existsSync(p));
|
|
956
|
+
if (pointer && !themesHoldAThemeFile && !import_fs3.default.existsSync(input.colorsAndTypeDir)) {
|
|
957
|
+
return {
|
|
958
|
+
manifestsDir,
|
|
959
|
+
evidence: `${pointer} points at colors and type, and there is no ${import_path3.default.basename(input.colorsAndTypeDir)} directory`
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
return null;
|
|
963
|
+
}
|
|
964
|
+
|
|
716
965
|
// vite-plugin/tokensCssMigrations/cssTokenOps.ts
|
|
717
966
|
var DECL_RE = /(^|[\s;{])(--[a-z0-9-]+)\s*:/gi;
|
|
718
967
|
var REF_RE = /var\(\s*(--[a-z0-9-]+)/gi;
|
|
@@ -1113,14 +1362,14 @@ var import_node_url = require("url");
|
|
|
1113
1362
|
var import_meta = {};
|
|
1114
1363
|
var PKG_VERSION = (() => {
|
|
1115
1364
|
try {
|
|
1116
|
-
let dir =
|
|
1365
|
+
let dir = import_path4.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
1117
1366
|
for (let i = 0; i < 4; i++) {
|
|
1118
|
-
const p =
|
|
1119
|
-
if (
|
|
1120
|
-
const json = JSON.parse(
|
|
1367
|
+
const p = import_path4.default.join(dir, "package.json");
|
|
1368
|
+
if (import_fs4.default.existsSync(p)) {
|
|
1369
|
+
const json = JSON.parse(import_fs4.default.readFileSync(p, "utf-8"));
|
|
1121
1370
|
if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
|
|
1122
1371
|
}
|
|
1123
|
-
const up =
|
|
1372
|
+
const up = import_path4.default.dirname(dir);
|
|
1124
1373
|
if (up === dir) break;
|
|
1125
1374
|
dir = up;
|
|
1126
1375
|
}
|
|
@@ -1128,65 +1377,79 @@ var PKG_VERSION = (() => {
|
|
|
1128
1377
|
}
|
|
1129
1378
|
return "";
|
|
1130
1379
|
})();
|
|
1380
|
+
var BUNDLE_KIND_BY_VERSION = {
|
|
1381
|
+
1: "manifest-bundle",
|
|
1382
|
+
[THEME_SCHEMA_VERSION]: "theme-bundle"
|
|
1383
|
+
};
|
|
1131
1384
|
function themeFileApi(opts) {
|
|
1132
1385
|
const dataDirs = resolveDataDirs({
|
|
1133
1386
|
dataDir: opts.dataDir,
|
|
1134
|
-
|
|
1387
|
+
colorsAndTypeDir: opts.colorsAndTypeDir,
|
|
1135
1388
|
componentConfigsDir: opts.componentConfigsDir,
|
|
1136
|
-
|
|
1389
|
+
themesDir: opts.themesDir
|
|
1137
1390
|
});
|
|
1138
|
-
const
|
|
1391
|
+
const COLORS_AND_TYPE_DIR = dataDirs.colorsAndTypeDir;
|
|
1139
1392
|
const COMPONENT_CONFIGS_DIR = dataDirs.componentConfigsDir;
|
|
1140
|
-
const
|
|
1141
|
-
const CSS_PATH =
|
|
1142
|
-
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ?
|
|
1143
|
-
const FONTS_CSS_PATH = opts.fontsCssPath ?
|
|
1393
|
+
const THEMES_DIR = dataDirs.themesDir;
|
|
1394
|
+
const CSS_PATH = import_path4.default.resolve(opts.tokensCssPath);
|
|
1395
|
+
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ? import_path4.default.resolve(opts.tokensGeneratedCssPath) : import_path4.default.join(dataDirs.dataDir, "tokens.generated.css");
|
|
1396
|
+
const FONTS_CSS_PATH = opts.fontsCssPath ? import_path4.default.resolve(opts.fontsCssPath) : import_path4.default.join(import_path4.default.dirname(CSS_PATH), "fonts.css");
|
|
1144
1397
|
const API_BASE = opts.apiBase ?? "/api/live-tokens";
|
|
1145
|
-
const consumerComponentDirs = opts.componentsSrcDir ? [
|
|
1146
|
-
const packageComponentsDir =
|
|
1147
|
-
|
|
1398
|
+
const consumerComponentDirs = opts.componentsSrcDir ? [import_path4.default.resolve(opts.componentsSrcDir)] : [import_path4.default.resolve("src/components"), import_path4.default.resolve("src/system/components")];
|
|
1399
|
+
const packageComponentsDir = import_path4.default.resolve(
|
|
1400
|
+
import_path4.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)),
|
|
1148
1401
|
"..",
|
|
1149
1402
|
"src",
|
|
1150
1403
|
"system",
|
|
1151
1404
|
"components"
|
|
1152
1405
|
);
|
|
1153
|
-
const packageDataDir =
|
|
1154
|
-
|
|
1406
|
+
const packageDataDir = import_path4.default.resolve(
|
|
1407
|
+
import_path4.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)),
|
|
1155
1408
|
"..",
|
|
1156
1409
|
"src",
|
|
1157
1410
|
"live-tokens",
|
|
1158
1411
|
"data"
|
|
1159
1412
|
);
|
|
1160
|
-
const
|
|
1161
|
-
const
|
|
1162
|
-
const packageComponentConfigsDir =
|
|
1413
|
+
const packageColorsAndTypeDir = import_path4.default.join(packageDataDir, "colors-and-type");
|
|
1414
|
+
const packageThemesDir = import_path4.default.join(packageDataDir, "themes");
|
|
1415
|
+
const packageComponentConfigsDir = import_path4.default.join(packageDataDir, "component-configs");
|
|
1416
|
+
function shippedColorsAndTypeNames(dataDir) {
|
|
1417
|
+
try {
|
|
1418
|
+
const pkgRoot = import_path4.default.resolve(dataDir, "..", "..", "..");
|
|
1419
|
+
const pkg = JSON.parse(import_fs4.default.readFileSync(import_path4.default.join(pkgRoot, "package.json"), "utf-8"));
|
|
1420
|
+
const files = Array.isArray(pkg.files) ? pkg.files : [];
|
|
1421
|
+
return files.filter((f) => /^src\/live-tokens\/data\/colors-and-type\/[^/]+\.json$/.test(f)).map((f) => import_path4.default.basename(f, ".json"));
|
|
1422
|
+
} catch {
|
|
1423
|
+
return [];
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1163
1426
|
const COMPONENTS_SCAN_DIRS = [...consumerComponentDirs];
|
|
1164
|
-
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) &&
|
|
1427
|
+
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && import_fs4.default.existsSync(packageComponentsDir)) {
|
|
1165
1428
|
COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
|
|
1166
1429
|
}
|
|
1167
|
-
const
|
|
1168
|
-
dir:
|
|
1169
|
-
packageDir:
|
|
1430
|
+
const colorsAndTypeResource = versionedFileResourceServer({
|
|
1431
|
+
dir: COLORS_AND_TYPE_DIR,
|
|
1432
|
+
packageDir: packageColorsAndTypeDir,
|
|
1433
|
+
packageOwnedNames: shippedColorsAndTypeNames(packageDataDir)
|
|
1170
1434
|
});
|
|
1171
1435
|
const componentResourceCache = /* @__PURE__ */ new Map();
|
|
1172
1436
|
function componentResource(comp) {
|
|
1173
1437
|
let r = componentResourceCache.get(comp);
|
|
1174
1438
|
if (!r) {
|
|
1175
1439
|
r = versionedFileResourceServer({
|
|
1176
|
-
dir:
|
|
1177
|
-
packageDir:
|
|
1440
|
+
dir: import_path4.default.join(COMPONENT_CONFIGS_DIR, comp),
|
|
1441
|
+
packageDir: import_path4.default.join(packageComponentConfigsDir, comp)
|
|
1178
1442
|
});
|
|
1179
1443
|
componentResourceCache.set(comp, r);
|
|
1180
1444
|
}
|
|
1181
1445
|
return r;
|
|
1182
1446
|
}
|
|
1183
|
-
const
|
|
1184
|
-
dir:
|
|
1185
|
-
packageDir:
|
|
1447
|
+
const themesResource = versionedFileResourceServer({
|
|
1448
|
+
dir: THEMES_DIR,
|
|
1449
|
+
packageDir: packageThemesDir
|
|
1186
1450
|
});
|
|
1187
|
-
function
|
|
1188
|
-
|
|
1189
|
-
themesResource.ensureMeta();
|
|
1451
|
+
function ensureColorsAndTypeDir() {
|
|
1452
|
+
colorsAndTypeResource.ensureDir();
|
|
1190
1453
|
}
|
|
1191
1454
|
function readBody(req) {
|
|
1192
1455
|
return new Promise((resolve, reject) => {
|
|
@@ -1201,25 +1464,33 @@ function themeFileApi(opts) {
|
|
|
1201
1464
|
res.setHeader("Content-Type", "application/json");
|
|
1202
1465
|
res.end(JSON.stringify(data));
|
|
1203
1466
|
}
|
|
1204
|
-
function
|
|
1205
|
-
if (!
|
|
1206
|
-
|
|
1207
|
-
|
|
1467
|
+
function rejectReservedName(res, name) {
|
|
1468
|
+
if (!name.startsWith("_")) return false;
|
|
1469
|
+
jsonResponse(res, 400, {
|
|
1470
|
+
error: 'Names starting with "_" are reserved',
|
|
1471
|
+
code: "RESERVED_NAME"
|
|
1472
|
+
});
|
|
1473
|
+
return true;
|
|
1474
|
+
}
|
|
1475
|
+
function normalizeColorsAndType(colorsAndType) {
|
|
1476
|
+
if (!colorsAndType || typeof colorsAndType !== "object") return colorsAndType;
|
|
1477
|
+
const palettes = colorsAndType.editorConfigs ?? {};
|
|
1478
|
+
const cssVars = colorsAndType.cssVariables ?? {};
|
|
1208
1479
|
const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
|
|
1209
1480
|
const nextCssVars = {};
|
|
1210
1481
|
for (const [k, v] of Object.entries(cssVars)) {
|
|
1211
1482
|
if (!consumed.has(k)) nextCssVars[k] = v;
|
|
1212
1483
|
}
|
|
1213
|
-
return { ...
|
|
1484
|
+
return { ...colorsAndType, editorConfigs: nextPalettes, cssVariables: nextCssVars };
|
|
1214
1485
|
}
|
|
1215
1486
|
const SYSTEM_CASCADES_SSR = {
|
|
1216
1487
|
"system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
1217
1488
|
"system-ui-serif": 'Georgia, "Times New Roman", serif',
|
|
1218
1489
|
"system-ui-mono": 'ui-monospace, "SF Mono", Menlo, Consolas, monospace'
|
|
1219
1490
|
};
|
|
1220
|
-
function resolveFontStacks(
|
|
1221
|
-
const stacks =
|
|
1222
|
-
const sources =
|
|
1491
|
+
function resolveFontStacks(colorsAndTypeData) {
|
|
1492
|
+
const stacks = colorsAndTypeData.fontStacks;
|
|
1493
|
+
const sources = colorsAndTypeData.fontSources;
|
|
1223
1494
|
if (!stacks || stacks.length === 0) return {};
|
|
1224
1495
|
const familyById = /* @__PURE__ */ new Map();
|
|
1225
1496
|
for (const src of sources ?? []) {
|
|
@@ -1245,21 +1516,28 @@ function themeFileApi(opts) {
|
|
|
1245
1516
|
}
|
|
1246
1517
|
function regenerateTokensCss() {
|
|
1247
1518
|
const lines = [];
|
|
1248
|
-
lines.push("/* Generated by themeFileApi from the production theme
|
|
1519
|
+
lines.push("/* Generated by themeFileApi from the production theme. Do not edit. */");
|
|
1249
1520
|
lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
|
|
1250
1521
|
lines.push("");
|
|
1251
1522
|
const productionThemeName = themesResource.getProductionName();
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1523
|
+
const productionTheme = readTheme(productionThemeName)?.theme ?? null;
|
|
1524
|
+
if (themesResource.existingPath(productionThemeName) !== null && !productionTheme?.colorsAndType) {
|
|
1525
|
+
const tail = `${import_path4.default.basename(GENERATED_CSS_PATH)} was left as it was.`;
|
|
1526
|
+
throw new Error(
|
|
1527
|
+
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.`
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
const colorsAndTypeData = productionTheme?.colorsAndType;
|
|
1531
|
+
let colorsAndTypeVarCount = 0;
|
|
1532
|
+
if (colorsAndTypeData) {
|
|
1533
|
+
const cssVars = { ...colorsAndTypeData.cssVariables || {} };
|
|
1534
|
+
Object.assign(cssVars, palettesToVars(migratePaletteColorsToOklch(colorsAndTypeData.editorConfigs ?? {})));
|
|
1535
|
+
const resolvedFontVars = resolveFontStacks(colorsAndTypeData);
|
|
1258
1536
|
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
1259
1537
|
cssVars[name] = value;
|
|
1260
1538
|
}
|
|
1261
|
-
|
|
1262
|
-
if (
|
|
1539
|
+
colorsAndTypeVarCount = Object.keys(cssVars).length;
|
|
1540
|
+
if (colorsAndTypeVarCount > 0) {
|
|
1263
1541
|
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
1264
1542
|
lines.push(":root:root {");
|
|
1265
1543
|
for (const [name, value] of Object.entries(cssVars)) {
|
|
@@ -1270,12 +1548,10 @@ function themeFileApi(opts) {
|
|
|
1270
1548
|
}
|
|
1271
1549
|
}
|
|
1272
1550
|
let componentOverrideCount = 0;
|
|
1273
|
-
if (
|
|
1551
|
+
if (import_fs4.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
1274
1552
|
const blocks = [];
|
|
1275
1553
|
for (const comp of listComponentNames()) {
|
|
1276
|
-
const
|
|
1277
|
-
if (prod === "default") continue;
|
|
1278
|
-
const prodCfg = readComponentConfig(comp, prod);
|
|
1554
|
+
const prodCfg = productionTheme?.componentConfigs[comp];
|
|
1279
1555
|
const defaultCfg = readComponentConfig(comp, "default");
|
|
1280
1556
|
if (!prodCfg || !defaultCfg) continue;
|
|
1281
1557
|
const overrides = [];
|
|
@@ -1286,7 +1562,7 @@ function themeFileApi(opts) {
|
|
|
1286
1562
|
}
|
|
1287
1563
|
}
|
|
1288
1564
|
if (overrides.length === 0) continue;
|
|
1289
|
-
const block = [` /* ${comp}
|
|
1565
|
+
const block = [` /* ${comp} */`];
|
|
1290
1566
|
for (const [varName, semanticValue] of overrides) {
|
|
1291
1567
|
block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
|
|
1292
1568
|
}
|
|
@@ -1304,24 +1580,20 @@ function themeFileApi(opts) {
|
|
|
1304
1580
|
lines.push("");
|
|
1305
1581
|
}
|
|
1306
1582
|
}
|
|
1307
|
-
if (!
|
|
1308
|
-
|
|
1583
|
+
if (!import_fs4.default.existsSync(import_path4.default.dirname(GENERATED_CSS_PATH))) {
|
|
1584
|
+
import_fs4.default.mkdirSync(import_path4.default.dirname(GENERATED_CSS_PATH), { recursive: true });
|
|
1309
1585
|
}
|
|
1310
|
-
|
|
1586
|
+
import_fs4.default.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
|
|
1311
1587
|
console.log(
|
|
1312
|
-
`[regenerateTokensCss] Wrote ${
|
|
1588
|
+
`[regenerateTokensCss] Wrote ${import_path4.default.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
|
|
1313
1589
|
);
|
|
1314
1590
|
}
|
|
1315
|
-
function
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
function syncFontsToCss(fileName) {
|
|
1319
|
-
const themeData = themesResource.readJson(fileName);
|
|
1320
|
-
if (!themeData) return;
|
|
1321
|
-
const sources = themeData.fontSources;
|
|
1591
|
+
function syncFontsToCss(colorsAndTypeData) {
|
|
1592
|
+
if (!colorsAndTypeData) return;
|
|
1593
|
+
const sources = colorsAndTypeData.fontSources;
|
|
1322
1594
|
if (!sources) return;
|
|
1323
1595
|
const lines = [];
|
|
1324
|
-
lines.push("/* Generated from the production
|
|
1596
|
+
lines.push("/* Generated from the production colors and type by syncFontsToCss. Do not edit. */");
|
|
1325
1597
|
lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
|
|
1326
1598
|
lines.push("");
|
|
1327
1599
|
const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
|
|
@@ -1341,11 +1613,11 @@ function themeFileApi(opts) {
|
|
|
1341
1613
|
lines.push("");
|
|
1342
1614
|
}
|
|
1343
1615
|
const content = lines.join("\n");
|
|
1344
|
-
if (!
|
|
1345
|
-
|
|
1616
|
+
if (!import_fs4.default.existsSync(import_path4.default.dirname(FONTS_CSS_PATH))) {
|
|
1617
|
+
import_fs4.default.mkdirSync(import_path4.default.dirname(FONTS_CSS_PATH), { recursive: true });
|
|
1346
1618
|
}
|
|
1347
|
-
|
|
1348
|
-
console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${
|
|
1619
|
+
import_fs4.default.writeFileSync(FONTS_CSS_PATH, content);
|
|
1620
|
+
console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${import_path4.default.basename(FONTS_CSS_PATH)}`);
|
|
1349
1621
|
}
|
|
1350
1622
|
function extractAliasDeclarations(body) {
|
|
1351
1623
|
const aliases = {};
|
|
@@ -1360,11 +1632,11 @@ function themeFileApi(opts) {
|
|
|
1360
1632
|
return aliases;
|
|
1361
1633
|
}
|
|
1362
1634
|
function componentNameFromFile(filePath) {
|
|
1363
|
-
return
|
|
1635
|
+
return import_path4.default.basename(filePath, ".svelte").toLowerCase();
|
|
1364
1636
|
}
|
|
1365
1637
|
function isThemeAwareComponent(filePath) {
|
|
1366
1638
|
try {
|
|
1367
|
-
const source =
|
|
1639
|
+
const source = import_fs4.default.readFileSync(filePath, "utf-8");
|
|
1368
1640
|
return /:global\(:root\)\s*\{/.test(source);
|
|
1369
1641
|
} catch {
|
|
1370
1642
|
return false;
|
|
@@ -1373,10 +1645,10 @@ function themeFileApi(opts) {
|
|
|
1373
1645
|
function listComponentSourcePaths() {
|
|
1374
1646
|
const byName = /* @__PURE__ */ new Map();
|
|
1375
1647
|
for (const dir of COMPONENTS_SCAN_DIRS) {
|
|
1376
|
-
if (!
|
|
1377
|
-
for (const f of
|
|
1648
|
+
if (!import_fs4.default.existsSync(dir)) continue;
|
|
1649
|
+
for (const f of import_fs4.default.readdirSync(dir)) {
|
|
1378
1650
|
if (!f.endsWith(".svelte")) continue;
|
|
1379
|
-
const full =
|
|
1651
|
+
const full = import_path4.default.join(dir, f);
|
|
1380
1652
|
if (!isThemeAwareComponent(full)) continue;
|
|
1381
1653
|
const name = componentNameFromFile(f);
|
|
1382
1654
|
if (!byName.has(name)) byName.set(name, full);
|
|
@@ -1390,18 +1662,18 @@ function themeFileApi(opts) {
|
|
|
1390
1662
|
function warnOnTokenDrift(log) {
|
|
1391
1663
|
let tokensCss = "";
|
|
1392
1664
|
try {
|
|
1393
|
-
tokensCss =
|
|
1665
|
+
tokensCss = import_fs4.default.readFileSync(CSS_PATH, "utf-8");
|
|
1394
1666
|
} catch {
|
|
1395
1667
|
return;
|
|
1396
1668
|
}
|
|
1397
1669
|
let generatedCss = "";
|
|
1398
1670
|
try {
|
|
1399
|
-
generatedCss =
|
|
1671
|
+
generatedCss = import_fs4.default.readFileSync(GENERATED_CSS_PATH, "utf-8");
|
|
1400
1672
|
} catch {
|
|
1401
1673
|
}
|
|
1402
1674
|
const componentSources = listComponentSourcePaths().map((p) => ({
|
|
1403
1675
|
name: componentNameFromFile(p),
|
|
1404
|
-
source:
|
|
1676
|
+
source: import_fs4.default.readFileSync(p, "utf-8")
|
|
1405
1677
|
}));
|
|
1406
1678
|
const missing = validateTokensCss({ tokensCss, generatedCss, componentSources });
|
|
1407
1679
|
if (missing.length === 0) return;
|
|
@@ -1409,7 +1681,7 @@ function themeFileApi(opts) {
|
|
|
1409
1681
|
(m) => ` ${m.token} (referenced by ${m.referencedBy.join(", ")})`
|
|
1410
1682
|
);
|
|
1411
1683
|
log(
|
|
1412
|
-
`[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${
|
|
1684
|
+
`[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${import_path4.default.relative(process.cwd(), CSS_PATH)}:
|
|
1413
1685
|
${lines.join("\n")}
|
|
1414
1686
|
These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
|
|
1415
1687
|
);
|
|
@@ -1417,37 +1689,37 @@ ${lines.join("\n")}
|
|
|
1417
1689
|
function autoMigrateAdditive(log) {
|
|
1418
1690
|
let before = "";
|
|
1419
1691
|
try {
|
|
1420
|
-
before =
|
|
1692
|
+
before = import_fs4.default.readFileSync(CSS_PATH, "utf-8");
|
|
1421
1693
|
} catch {
|
|
1422
1694
|
return;
|
|
1423
1695
|
}
|
|
1424
1696
|
const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
|
|
1425
1697
|
if (!changed) return;
|
|
1426
|
-
|
|
1698
|
+
import_fs4.default.writeFileSync(CSS_PATH, css);
|
|
1427
1699
|
log(
|
|
1428
|
-
`[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${
|
|
1700
|
+
`[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${import_path4.default.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
|
|
1429
1701
|
);
|
|
1430
1702
|
}
|
|
1431
1703
|
function generateDefaultConfig(comp, sourcePath) {
|
|
1432
|
-
if (!
|
|
1704
|
+
if (!import_fs4.default.existsSync(sourcePath)) return;
|
|
1433
1705
|
const r = componentResource(comp);
|
|
1434
1706
|
r.ensureDir();
|
|
1435
1707
|
const defaultPath = r.filePath("default");
|
|
1436
|
-
const sourceStat =
|
|
1437
|
-
if (
|
|
1438
|
-
const defaultStat =
|
|
1708
|
+
const sourceStat = import_fs4.default.statSync(sourcePath);
|
|
1709
|
+
if (import_fs4.default.existsSync(defaultPath)) {
|
|
1710
|
+
const defaultStat = import_fs4.default.statSync(defaultPath);
|
|
1439
1711
|
if (defaultStat.mtimeMs >= sourceStat.mtimeMs) return;
|
|
1440
1712
|
}
|
|
1441
|
-
const source =
|
|
1713
|
+
const source = import_fs4.default.readFileSync(sourcePath, "utf-8");
|
|
1442
1714
|
const body = extractGlobalRootBody(source);
|
|
1443
1715
|
const aliases = extractAliasDeclarations(body);
|
|
1444
1716
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1445
1717
|
let createdAt = now;
|
|
1446
1718
|
let existingUpdatedAt;
|
|
1447
1719
|
let existingAliases;
|
|
1448
|
-
if (
|
|
1720
|
+
if (import_fs4.default.existsSync(defaultPath)) {
|
|
1449
1721
|
try {
|
|
1450
|
-
const existing = JSON.parse(
|
|
1722
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(defaultPath, "utf-8"));
|
|
1451
1723
|
if (existing.createdAt) createdAt = existing.createdAt;
|
|
1452
1724
|
if (typeof existing.updatedAt === "string") existingUpdatedAt = existing.updatedAt;
|
|
1453
1725
|
if (existing.aliases && typeof existing.aliases === "object") {
|
|
@@ -1473,57 +1745,191 @@ ${lines.join("\n")}
|
|
|
1473
1745
|
};
|
|
1474
1746
|
if (aliasesUnchanged && existingUpdatedAt) {
|
|
1475
1747
|
const t = /* @__PURE__ */ new Date();
|
|
1476
|
-
|
|
1748
|
+
import_fs4.default.utimesSync(defaultPath, t, t);
|
|
1477
1749
|
return;
|
|
1478
1750
|
}
|
|
1479
|
-
|
|
1751
|
+
import_fs4.default.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
|
|
1480
1752
|
}
|
|
1481
1753
|
function ensureComponentConfigsDir() {
|
|
1482
|
-
if (!
|
|
1483
|
-
|
|
1754
|
+
if (!import_fs4.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
1755
|
+
import_fs4.default.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
|
|
1484
1756
|
}
|
|
1485
1757
|
for (const sourcePath of listComponentSourcePaths()) {
|
|
1486
1758
|
const comp = componentNameFromFile(sourcePath);
|
|
1487
1759
|
const r = componentResource(comp);
|
|
1488
1760
|
r.ensureDir();
|
|
1489
1761
|
generateDefaultConfig(comp, sourcePath);
|
|
1490
|
-
r.ensureMeta();
|
|
1491
1762
|
}
|
|
1492
1763
|
}
|
|
1493
|
-
function
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1764
|
+
function listLegacyPointers() {
|
|
1765
|
+
const legacy = [];
|
|
1766
|
+
const check = (dir) => {
|
|
1767
|
+
for (const name of ["_active.json", "_production.json"]) {
|
|
1768
|
+
const p = import_path4.default.join(dir, name);
|
|
1769
|
+
if (import_fs4.default.existsSync(p)) legacy.push(import_path4.default.relative(process.cwd(), p));
|
|
1770
|
+
}
|
|
1771
|
+
};
|
|
1772
|
+
check(COLORS_AND_TYPE_DIR);
|
|
1773
|
+
if (import_fs4.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
1774
|
+
for (const entry of import_fs4.default.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
|
|
1775
|
+
if (entry.isDirectory()) check(import_path4.default.join(COMPONENT_CONFIGS_DIR, entry.name));
|
|
1504
1776
|
}
|
|
1505
1777
|
}
|
|
1778
|
+
return legacy;
|
|
1779
|
+
}
|
|
1780
|
+
function warnOnLegacyPointers(legacy, bakeHeld, warn) {
|
|
1781
|
+
if (legacy.length === 0) return;
|
|
1782
|
+
warn(
|
|
1783
|
+
`[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 ${import_path4.default.basename(GENERATED_CSS_PATH)} is left exactly as it is rather than rebuilt from the default.` : "")
|
|
1784
|
+
);
|
|
1785
|
+
}
|
|
1786
|
+
function warnOnLegacyLayout(evidence, warn) {
|
|
1787
|
+
warn(
|
|
1788
|
+
`[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 ${import_path4.default.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.`
|
|
1789
|
+
);
|
|
1506
1790
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1791
|
+
const diskThemeResolvers = {
|
|
1792
|
+
readColorsAndType: (name) => colorsAndTypeResource.readJson(sanitizeFileName(name)),
|
|
1793
|
+
readComponentConfig: (comp, name) => readComponentConfig(comp, sanitizeFileName(name)),
|
|
1794
|
+
normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
|
|
1795
|
+
};
|
|
1796
|
+
function readTheme(fileName) {
|
|
1797
|
+
let raw;
|
|
1513
1798
|
try {
|
|
1514
|
-
|
|
1799
|
+
raw = themesResource.readJson(fileName);
|
|
1800
|
+
} catch {
|
|
1801
|
+
return null;
|
|
1802
|
+
}
|
|
1803
|
+
if (!raw) return null;
|
|
1804
|
+
if (isColorsAndTypeShaped(raw)) return null;
|
|
1805
|
+
return normalizeTheme(raw, diskThemeResolvers);
|
|
1806
|
+
}
|
|
1807
|
+
function unreadableThemeReason(fileName) {
|
|
1808
|
+
const resolved = themesResource.existingPath(fileName);
|
|
1809
|
+
if (resolved === null) return "missing";
|
|
1810
|
+
try {
|
|
1811
|
+
if (isColorsAndTypeShaped(JSON.parse(import_fs4.default.readFileSync(resolved, "utf-8")))) {
|
|
1812
|
+
return "colors-and-type";
|
|
1813
|
+
}
|
|
1515
1814
|
} catch {
|
|
1516
|
-
return false;
|
|
1517
1815
|
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1816
|
+
return "corrupt";
|
|
1817
|
+
}
|
|
1818
|
+
function notAThemeError(fileName) {
|
|
1819
|
+
return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${import_path4.default.basename(COLORS_AND_TYPE_DIR)}/ or delete it.`;
|
|
1820
|
+
}
|
|
1821
|
+
function respondUnreadableTheme(res, fileName, missingError) {
|
|
1822
|
+
switch (unreadableThemeReason(fileName)) {
|
|
1823
|
+
case "missing":
|
|
1824
|
+
jsonResponse(res, 404, { error: missingError });
|
|
1825
|
+
return;
|
|
1826
|
+
case "colors-and-type":
|
|
1827
|
+
jsonResponse(res, 422, { error: notAThemeError(fileName), code: "CORRUPT_THEME" });
|
|
1828
|
+
return;
|
|
1829
|
+
case "corrupt":
|
|
1830
|
+
jsonResponse(res, 422, {
|
|
1831
|
+
error: `Theme file "${fileName}" is not valid JSON`,
|
|
1832
|
+
code: "CORRUPT_THEME"
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
function writeTheme(fileName, theme) {
|
|
1837
|
+
themesResource.ensureDir();
|
|
1838
|
+
import_fs4.default.writeFileSync(themesResource.filePath(fileName), JSON.stringify(theme, null, 2));
|
|
1839
|
+
}
|
|
1840
|
+
function migrateLocalThemes(warn) {
|
|
1841
|
+
if (import_path4.default.resolve(THEMES_DIR) === import_path4.default.resolve(packageThemesDir)) return;
|
|
1842
|
+
if (!import_fs4.default.existsSync(THEMES_DIR)) return;
|
|
1843
|
+
for (const file of import_fs4.default.readdirSync(THEMES_DIR)) {
|
|
1844
|
+
if (!file.endsWith(".json") || file.startsWith("_")) continue;
|
|
1845
|
+
const fileName = file.slice(0, -".json".length);
|
|
1846
|
+
if (fileName === "default") continue;
|
|
1847
|
+
let raw;
|
|
1848
|
+
try {
|
|
1849
|
+
raw = JSON.parse(import_fs4.default.readFileSync(import_path4.default.join(THEMES_DIR, file), "utf-8"));
|
|
1850
|
+
} catch {
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
if (isColorsAndTypeShaped(raw)) {
|
|
1854
|
+
warn(`[live-tokens] ${notAThemeError(fileName)} It is left untouched.`);
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
const { theme, dropped, migrated } = normalizeTheme(raw, diskThemeResolvers);
|
|
1858
|
+
if (!migrated) continue;
|
|
1859
|
+
writeTheme(fileName, theme);
|
|
1860
|
+
if (dropped.length > 0) {
|
|
1861
|
+
warn(
|
|
1862
|
+
`[live-tokens] Theme "${fileName}": ${dropped.join(", ")} no longer exists; those slices now use their defaults.`
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1523
1865
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1866
|
+
}
|
|
1867
|
+
function ensureDefaultTheme(warn) {
|
|
1868
|
+
let existing = null;
|
|
1869
|
+
try {
|
|
1870
|
+
existing = JSON.parse(import_fs4.default.readFileSync(themesResource.filePath("default"), "utf-8"));
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1873
|
+
if (isColorsAndTypeShaped(existing)) {
|
|
1874
|
+
warn(`[live-tokens] ${notAThemeError("default")} The Default theme was not materialised.`);
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
const colorsAndType = normalizeColorsAndType(colorsAndTypeResource.readJson("default"));
|
|
1878
|
+
const componentConfigs = {};
|
|
1879
|
+
for (const comp of listComponentNames()) {
|
|
1880
|
+
const cfg = readComponentConfig(comp, "default");
|
|
1881
|
+
if (cfg) componentConfigs[comp] = cfg;
|
|
1882
|
+
}
|
|
1883
|
+
if (existing?.schemaVersion === THEME_SCHEMA_VERSION && JSON.stringify(existing.colorsAndType) === JSON.stringify(colorsAndType) && JSON.stringify(existing.componentConfigs) === JSON.stringify(componentConfigs)) {
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1887
|
+
writeTheme("default", {
|
|
1888
|
+
name: "Motion Proto",
|
|
1889
|
+
createdAt: typeof existing?.createdAt === "string" ? existing.createdAt : now,
|
|
1890
|
+
updatedAt: now,
|
|
1891
|
+
schemaVersion: THEME_SCHEMA_VERSION,
|
|
1892
|
+
colorsAndType,
|
|
1893
|
+
componentConfigs
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
function ensureThemesDir(warn) {
|
|
1897
|
+
themesResource.ensureDir();
|
|
1898
|
+
ensureDefaultTheme(warn);
|
|
1899
|
+
migrateLocalThemes(warn);
|
|
1900
|
+
const productionPointerHeld = !import_fs4.default.existsSync(themesResource.productionPath) && listLegacyPointers().length > 0;
|
|
1901
|
+
if (productionPointerHeld) {
|
|
1902
|
+
if (!import_fs4.default.existsSync(themesResource.activePath)) themesResource.setActiveName("default");
|
|
1903
|
+
} else {
|
|
1904
|
+
themesResource.ensureMeta();
|
|
1905
|
+
}
|
|
1906
|
+
if (themesResource.existingPath(themesResource.getActiveName()) === null) {
|
|
1907
|
+
themesResource.setActiveName("default");
|
|
1908
|
+
}
|
|
1909
|
+
if (themesResource.existingPath(themesResource.getProductionName()) === null) {
|
|
1910
|
+
themesResource.setProductionName("default");
|
|
1911
|
+
}
|
|
1912
|
+
return { productionPointerHeld };
|
|
1913
|
+
}
|
|
1914
|
+
function activeTheme() {
|
|
1915
|
+
return readTheme(themesResource.getActiveName())?.theme ?? null;
|
|
1916
|
+
}
|
|
1917
|
+
function resolveLiveColorsAndType(theme) {
|
|
1918
|
+
const working = colorsAndTypeResource.readWorking();
|
|
1919
|
+
if (working !== null) return { data: normalizeColorsAndType(working), source: "working" };
|
|
1920
|
+
if (theme?.colorsAndType) return { data: theme.colorsAndType, source: "theme" };
|
|
1921
|
+
const shipped = colorsAndTypeResource.readJson("default");
|
|
1922
|
+
if (!shipped) return null;
|
|
1923
|
+
return { data: normalizeColorsAndType(shipped), source: "default" };
|
|
1924
|
+
}
|
|
1925
|
+
function resolveLiveComponentConfig(comp, theme) {
|
|
1926
|
+
const working = componentResource(comp).readWorking();
|
|
1927
|
+
if (working !== null) return { data: working, source: "working" };
|
|
1928
|
+
const embedded = theme?.componentConfigs[comp];
|
|
1929
|
+
if (embedded) return { data: { ...embedded, component: comp }, source: "theme" };
|
|
1930
|
+
const cfg = readComponentConfig(comp, "default");
|
|
1931
|
+
if (!cfg) return null;
|
|
1932
|
+
return { data: cfg, source: "default" };
|
|
1527
1933
|
}
|
|
1528
1934
|
function formatAliasGradient(v) {
|
|
1529
1935
|
const stopColor = (s) => {
|
|
@@ -1559,237 +1965,191 @@ ${lines.join("\n")}
|
|
|
1559
1965
|
return null;
|
|
1560
1966
|
}
|
|
1561
1967
|
}
|
|
1562
|
-
function syncComponentsToCss() {
|
|
1563
|
-
regenerateTokensCss();
|
|
1564
|
-
}
|
|
1565
1968
|
const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1969
|
+
const COLORS_AND_TYPE_ROUTE = `${API_BASE}/colors-and-type`;
|
|
1970
|
+
const COLORS_AND_TYPE_ACTIVE_ROUTE = `${API_BASE}/colors-and-type/active`;
|
|
1971
|
+
const COLORS_AND_TYPE_PRODUCTION_ROUTE = `${API_BASE}/colors-and-type/production`;
|
|
1972
|
+
const COLORS_AND_TYPE_WORKING_ROUTE = `${API_BASE}/colors-and-type/working`;
|
|
1973
|
+
const COMPONENT_CONFIGS_ROUTE = `${API_BASE}/component-configs`;
|
|
1566
1974
|
const THEMES_ROUTE = `${API_BASE}/themes`;
|
|
1567
1975
|
const THEMES_ACTIVE_ROUTE = `${API_BASE}/themes/active`;
|
|
1568
1976
|
const THEMES_PRODUCTION_ROUTE = `${API_BASE}/themes/production`;
|
|
1569
|
-
const
|
|
1570
|
-
const MANIFESTS_ROUTE = `${API_BASE}/manifests`;
|
|
1571
|
-
const MANIFESTS_ACTIVE_ROUTE = `${API_BASE}/manifests/active`;
|
|
1572
|
-
const THEME_BY_NAME_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)$`);
|
|
1977
|
+
const COLORS_AND_TYPE_BY_NAME_REGEX = new RegExp(`^${escapedBase}/colors-and-type/([a-z0-9\\-_]+)$`);
|
|
1573
1978
|
const COMP_LIST_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)$`);
|
|
1574
1979
|
const COMP_ACTIVE_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/active$`);
|
|
1575
1980
|
const COMP_PRODUCTION_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/production$`);
|
|
1981
|
+
const COMP_WORKING_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/working$`);
|
|
1576
1982
|
const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
|
|
1577
|
-
const
|
|
1578
|
-
const
|
|
1579
|
-
const
|
|
1580
|
-
const
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
const files =
|
|
1584
|
-
const data =
|
|
1983
|
+
const THEME_APPLY_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)/apply$`);
|
|
1984
|
+
const THEME_EXPORT_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)/export$`);
|
|
1985
|
+
const THEME_IMPORT_ROUTE = `${API_BASE}/themes/import`;
|
|
1986
|
+
const THEME_BY_NAME_REGEX = new RegExp(`^${escapedBase}/themes/([a-z0-9\\-_]+)$`);
|
|
1987
|
+
const PRODUCTION_ROUTE = `${API_BASE}/production`;
|
|
1988
|
+
async function handleListColorsAndType(_ctx) {
|
|
1989
|
+
const files = colorsAndTypeResource.listNames().map((fileName) => {
|
|
1990
|
+
const data = colorsAndTypeResource.readJson(fileName);
|
|
1585
1991
|
return {
|
|
1586
1992
|
name: data?.name || fileName,
|
|
1587
1993
|
fileName,
|
|
1588
1994
|
updatedAt: data?.updatedAt || "",
|
|
1589
|
-
|
|
1995
|
+
// Package-owned rows are the shipped presets and the default. The Load
|
|
1996
|
+
// window offers those as whole themes, so it lists only the rows a
|
|
1997
|
+
// local file backs — including a local copy of a shipped name, which
|
|
1998
|
+
// holds whatever the user did to it.
|
|
1999
|
+
isPackage: colorsAndTypeResource.isPackageFile(fileName)
|
|
1590
2000
|
};
|
|
1591
2001
|
});
|
|
1592
2002
|
jsonResponse(_ctx.res, 200, { files });
|
|
1593
2003
|
}
|
|
1594
|
-
async function
|
|
1595
|
-
const
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
jsonResponse(res, 404, { error: "Active theme not found" });
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1601
|
-
const data = normalizeTheme(raw);
|
|
1602
|
-
data._fileName = activeFile;
|
|
1603
|
-
jsonResponse(res, 200, data);
|
|
1604
|
-
}
|
|
1605
|
-
async function handleSetActiveTheme({ req, res }) {
|
|
1606
|
-
const body = JSON.parse(await readBody(req));
|
|
1607
|
-
const fileName = sanitizeFileName(body.name || "default");
|
|
1608
|
-
if (themesResource.existingPath(fileName) === null) {
|
|
1609
|
-
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
themesResource.setActiveName(fileName);
|
|
1613
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1614
|
-
}
|
|
1615
|
-
async function handleGetProductionTheme({ res }) {
|
|
1616
|
-
const prodFile = themesResource.getProductionName();
|
|
1617
|
-
const raw = themesResource.readJson(prodFile);
|
|
1618
|
-
if (!raw) {
|
|
1619
|
-
jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
|
|
2004
|
+
async function handleGetActiveColorsAndType({ res }) {
|
|
2005
|
+
const resolved = resolveLiveColorsAndType(activeTheme());
|
|
2006
|
+
if (!resolved) {
|
|
2007
|
+
jsonResponse(res, 404, { error: "Active colors and type not found" });
|
|
1620
2008
|
return;
|
|
1621
2009
|
}
|
|
1622
|
-
const data = normalizeTheme(raw);
|
|
1623
2010
|
jsonResponse(res, 200, {
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
cssVariables: data.cssVariables || {}
|
|
2011
|
+
...resolved.data,
|
|
2012
|
+
_fileName: themesResource.getActiveName(),
|
|
2013
|
+
_source: resolved.source
|
|
1628
2014
|
});
|
|
1629
2015
|
}
|
|
1630
|
-
async function
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
2016
|
+
async function readBufferBody(req, res) {
|
|
2017
|
+
let parsed;
|
|
2018
|
+
try {
|
|
2019
|
+
parsed = JSON.parse(await readBody(req));
|
|
2020
|
+
} catch {
|
|
2021
|
+
parsed = void 0;
|
|
1636
2022
|
}
|
|
1637
|
-
if (
|
|
1638
|
-
jsonResponse(res,
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
2023
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2024
|
+
jsonResponse(res, 400, { error: "Body must be a JSON object", code: "INVALID_BODY" });
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
return parsed;
|
|
2028
|
+
}
|
|
2029
|
+
async function handleGetWorkingColorsAndType({ res }) {
|
|
2030
|
+
const raw = colorsAndTypeResource.readWorking();
|
|
2031
|
+
if (raw === null) {
|
|
2032
|
+
jsonResponse(res, 404, { error: "No working colors and type" });
|
|
1642
2033
|
return;
|
|
1643
2034
|
}
|
|
1644
|
-
|
|
1645
|
-
syncTokensToCss(fileName);
|
|
1646
|
-
syncFontsToCss(fileName);
|
|
1647
|
-
syncComponentsToCss();
|
|
1648
|
-
patchActiveManifest("theme", null, fileName);
|
|
1649
|
-
const data = themesResource.readJson(fileName);
|
|
1650
|
-
jsonResponse(res, 200, {
|
|
1651
|
-
ok: true,
|
|
1652
|
-
fileName,
|
|
1653
|
-
name: data?.name || fileName,
|
|
1654
|
-
updatedAt: data?.updatedAt || ""
|
|
1655
|
-
});
|
|
2035
|
+
jsonResponse(res, 200, normalizeColorsAndType(raw));
|
|
1656
2036
|
}
|
|
1657
|
-
async function
|
|
2037
|
+
async function handleSetWorkingColorsAndType({ req, res }) {
|
|
2038
|
+
const body = await readBufferBody(req, res);
|
|
2039
|
+
if (!body) return;
|
|
2040
|
+
colorsAndTypeResource.writeWorking(body);
|
|
2041
|
+
jsonResponse(res, 200, { ok: true });
|
|
2042
|
+
}
|
|
2043
|
+
async function handleClearWorkingColorsAndType({ res }) {
|
|
2044
|
+
colorsAndTypeResource.clearWorking();
|
|
2045
|
+
jsonResponse(res, 200, { ok: true });
|
|
2046
|
+
}
|
|
2047
|
+
async function handleColorsAndTypeByName({ params, req, res }) {
|
|
1658
2048
|
const [fileName] = params;
|
|
1659
|
-
|
|
2049
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2050
|
+
const filePath = colorsAndTypeResource.filePath(fileName);
|
|
1660
2051
|
if (req.method === "GET") {
|
|
1661
|
-
const raw =
|
|
2052
|
+
const raw = colorsAndTypeResource.readJson(fileName);
|
|
1662
2053
|
if (!raw) {
|
|
1663
2054
|
jsonResponse(res, 404, { error: "Not found" });
|
|
1664
2055
|
return;
|
|
1665
2056
|
}
|
|
1666
|
-
const data =
|
|
2057
|
+
const data = normalizeColorsAndType(raw);
|
|
1667
2058
|
data._fileName = fileName;
|
|
1668
2059
|
jsonResponse(res, 200, data);
|
|
1669
2060
|
return;
|
|
1670
2061
|
}
|
|
1671
2062
|
if (req.method === "PUT") {
|
|
1672
2063
|
if (fileName === "default") {
|
|
1673
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
2064
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default colors and type (live from package)" });
|
|
1674
2065
|
return;
|
|
1675
2066
|
}
|
|
1676
2067
|
const body = JSON.parse(await readBody(req));
|
|
1677
2068
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1678
|
-
if (
|
|
2069
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
1679
2070
|
try {
|
|
1680
|
-
const existing = JSON.parse(
|
|
2071
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(filePath, "utf-8"));
|
|
1681
2072
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1682
2073
|
} catch {
|
|
1683
2074
|
}
|
|
1684
2075
|
}
|
|
1685
2076
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1686
|
-
|
|
1687
|
-
if (fileName === themesResource.getProductionName()) {
|
|
1688
|
-
syncTokensToCss(fileName);
|
|
1689
|
-
syncFontsToCss(fileName);
|
|
1690
|
-
syncComponentsToCss();
|
|
1691
|
-
}
|
|
2077
|
+
import_fs4.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
1692
2078
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1693
2079
|
return;
|
|
1694
2080
|
}
|
|
1695
2081
|
if (req.method === "DELETE") {
|
|
1696
2082
|
if (fileName === "default") {
|
|
1697
|
-
jsonResponse(res, 403, { error: "Cannot delete the default
|
|
2083
|
+
jsonResponse(res, 403, { error: "Cannot delete the default colors and type" });
|
|
1698
2084
|
return;
|
|
1699
2085
|
}
|
|
1700
|
-
if (
|
|
2086
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
2087
|
+
import_fs4.default.unlinkSync(filePath);
|
|
2088
|
+
} else if (colorsAndTypeResource.existingPath(fileName)) {
|
|
1701
2089
|
jsonResponse(res, 403, {
|
|
1702
|
-
error: "Cannot delete the
|
|
1703
|
-
code: "
|
|
2090
|
+
error: "Cannot delete colors and type shipped with the package. Saving it creates a local copy; deleting that copy restores the shipped version.",
|
|
2091
|
+
code: "PACKAGE_COLORS_AND_TYPE"
|
|
1704
2092
|
});
|
|
1705
2093
|
return;
|
|
1706
2094
|
}
|
|
1707
|
-
if (import_fs3.default.existsSync(filePath)) {
|
|
1708
|
-
import_fs3.default.unlinkSync(filePath);
|
|
1709
|
-
if (themesResource.getActiveName() === fileName) {
|
|
1710
|
-
themesResource.setActiveName("default");
|
|
1711
|
-
}
|
|
1712
|
-
}
|
|
1713
2095
|
jsonResponse(res, 200, { ok: true });
|
|
1714
2096
|
return;
|
|
1715
2097
|
}
|
|
1716
2098
|
}
|
|
1717
2099
|
async function handleListComponents({ res }) {
|
|
1718
|
-
const
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
});
|
|
2100
|
+
const theme = activeTheme();
|
|
2101
|
+
const components = listComponentNames().map((comp) => ({
|
|
2102
|
+
name: comp,
|
|
2103
|
+
// Presence, not a parse: one corrupt buffer must not take the listing
|
|
2104
|
+
// down, and the door only has to say where each component's live config
|
|
2105
|
+
// comes from.
|
|
2106
|
+
source: componentResource(comp).hasWorking() ? "working" : theme?.componentConfigs[comp] ? "theme" : "default"
|
|
2107
|
+
}));
|
|
1726
2108
|
jsonResponse(res, 200, { components });
|
|
1727
2109
|
}
|
|
2110
|
+
function rejectUnknownComponent(res, comp) {
|
|
2111
|
+
if (listComponentNames().includes(comp)) return false;
|
|
2112
|
+
jsonResponse(res, 404, { error: `Unknown component "${comp}"`, code: "UNKNOWN_COMPONENT" });
|
|
2113
|
+
return true;
|
|
2114
|
+
}
|
|
1728
2115
|
async function handleGetComponentActive({ params, res }) {
|
|
1729
2116
|
const [comp] = params;
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
const cfg = readComponentConfig(comp, activeFile);
|
|
1733
|
-
if (!cfg) {
|
|
2117
|
+
const resolved = resolveLiveComponentConfig(comp, activeTheme());
|
|
2118
|
+
if (!resolved) {
|
|
1734
2119
|
jsonResponse(res, 404, { error: "Active config not found" });
|
|
1735
2120
|
return;
|
|
1736
2121
|
}
|
|
1737
|
-
jsonResponse(res, 200, {
|
|
2122
|
+
jsonResponse(res, 200, {
|
|
2123
|
+
...resolved.data,
|
|
2124
|
+
_fileName: themesResource.getActiveName(),
|
|
2125
|
+
_source: resolved.source
|
|
2126
|
+
});
|
|
1738
2127
|
}
|
|
1739
|
-
async function
|
|
2128
|
+
async function handleGetComponentWorking({ params, res }) {
|
|
1740
2129
|
const [comp] = params;
|
|
1741
|
-
const
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
if (r.existingPath(fileName) === null) {
|
|
1745
|
-
jsonResponse(res, 404, { error: "Config not found" });
|
|
2130
|
+
const raw = componentResource(comp).readWorking();
|
|
2131
|
+
if (raw === null) {
|
|
2132
|
+
jsonResponse(res, 404, { error: "No working config" });
|
|
1746
2133
|
return;
|
|
1747
2134
|
}
|
|
1748
|
-
|
|
1749
|
-
r.setActiveName(fileName);
|
|
1750
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
2135
|
+
jsonResponse(res, 200, raw);
|
|
1751
2136
|
}
|
|
1752
|
-
async function
|
|
2137
|
+
async function handleSetComponentWorking({ params, req, res }) {
|
|
1753
2138
|
const [comp] = params;
|
|
1754
|
-
|
|
1755
|
-
const
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
name: cfg?.name || prodFile,
|
|
1760
|
-
aliases: cfg?.aliases || {}
|
|
1761
|
-
});
|
|
2139
|
+
if (rejectUnknownComponent(res, comp)) return;
|
|
2140
|
+
const body = await readBufferBody(req, res);
|
|
2141
|
+
if (!body) return;
|
|
2142
|
+
componentResource(comp).writeWorking(body);
|
|
2143
|
+
jsonResponse(res, 200, { ok: true });
|
|
1762
2144
|
}
|
|
1763
|
-
async function
|
|
2145
|
+
async function handleClearComponentWorking({ params, res }) {
|
|
1764
2146
|
const [comp] = params;
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
const r = componentResource(comp);
|
|
1768
|
-
if (r.existingPath(fileName) === null) {
|
|
1769
|
-
jsonResponse(res, 404, { error: "Config not found" });
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
if (manifestsResource.getActiveName() === "default") {
|
|
1773
|
-
jsonResponse(res, 409, {
|
|
1774
|
-
error: "Active manifest is protected. Save As first.",
|
|
1775
|
-
code: "ACTIVE_IS_PROTECTED"
|
|
1776
|
-
});
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1779
|
-
r.ensureDir();
|
|
1780
|
-
r.setProductionName(fileName);
|
|
1781
|
-
syncComponentsToCss();
|
|
1782
|
-
patchActiveManifest("component", comp, fileName);
|
|
1783
|
-
const cfg = readComponentConfig(comp, fileName);
|
|
1784
|
-
jsonResponse(res, 200, {
|
|
1785
|
-
ok: true,
|
|
1786
|
-
fileName,
|
|
1787
|
-
name: cfg?.name || fileName,
|
|
1788
|
-
aliases: cfg?.aliases || {}
|
|
1789
|
-
});
|
|
2147
|
+
componentResource(comp).clearWorking();
|
|
2148
|
+
jsonResponse(res, 200, { ok: true });
|
|
1790
2149
|
}
|
|
1791
2150
|
async function handleComponentConfigByName({ params, req, res }) {
|
|
1792
2151
|
const [comp, name] = params;
|
|
2152
|
+
if (rejectReservedName(res, name)) return;
|
|
1793
2153
|
const r = componentResource(comp);
|
|
1794
2154
|
const configPath = r.filePath(name);
|
|
1795
2155
|
if (req.method === "GET") {
|
|
@@ -1812,19 +2172,16 @@ ${lines.join("\n")}
|
|
|
1812
2172
|
body.component = comp;
|
|
1813
2173
|
body.name = body.name || name;
|
|
1814
2174
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1815
|
-
if (
|
|
2175
|
+
if (import_fs4.default.existsSync(configPath)) {
|
|
1816
2176
|
try {
|
|
1817
|
-
const existing = JSON.parse(
|
|
2177
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(configPath, "utf-8"));
|
|
1818
2178
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1819
2179
|
} catch {
|
|
1820
2180
|
}
|
|
1821
2181
|
}
|
|
1822
2182
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1823
2183
|
r.ensureDir();
|
|
1824
|
-
|
|
1825
|
-
if (r.getProductionName() === name) {
|
|
1826
|
-
syncComponentsToCss();
|
|
1827
|
-
}
|
|
2184
|
+
import_fs4.default.writeFileSync(configPath, JSON.stringify(body, null, 2));
|
|
1828
2185
|
jsonResponse(res, 200, { ok: true, fileName: name });
|
|
1829
2186
|
return;
|
|
1830
2187
|
}
|
|
@@ -1833,16 +2190,7 @@ ${lines.join("\n")}
|
|
|
1833
2190
|
jsonResponse(res, 403, { error: "Cannot delete default config" });
|
|
1834
2191
|
return;
|
|
1835
2192
|
}
|
|
1836
|
-
if (
|
|
1837
|
-
import_fs3.default.unlinkSync(configPath);
|
|
1838
|
-
if (r.getActiveName() === name) {
|
|
1839
|
-
r.setActiveName("default");
|
|
1840
|
-
}
|
|
1841
|
-
if (r.getProductionName() === name) {
|
|
1842
|
-
r.setProductionName("default");
|
|
1843
|
-
syncComponentsToCss();
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
2193
|
+
if (import_fs4.default.existsSync(configPath)) import_fs4.default.unlinkSync(configPath);
|
|
1846
2194
|
jsonResponse(res, 200, { ok: true });
|
|
1847
2195
|
return;
|
|
1848
2196
|
}
|
|
@@ -1850,199 +2198,190 @@ ${lines.join("\n")}
|
|
|
1850
2198
|
async function handleListComponentConfigs({ params, res }) {
|
|
1851
2199
|
const [comp] = params;
|
|
1852
2200
|
const r = componentResource(comp);
|
|
1853
|
-
if (!
|
|
2201
|
+
if (!import_fs4.default.existsSync(r.dir)) {
|
|
1854
2202
|
jsonResponse(res, 404, { error: "Component not found" });
|
|
1855
2203
|
return;
|
|
1856
2204
|
}
|
|
1857
|
-
const activeFile = r.getActiveName();
|
|
1858
|
-
const productionFile = r.getProductionName();
|
|
1859
2205
|
const files = r.listNames().map((fileName) => {
|
|
1860
2206
|
const data = r.readJson(fileName);
|
|
1861
2207
|
return {
|
|
1862
2208
|
name: data?.name || fileName,
|
|
1863
2209
|
fileName,
|
|
1864
|
-
updatedAt: data?.updatedAt || ""
|
|
1865
|
-
isActive: fileName === activeFile,
|
|
1866
|
-
isProduction: fileName === productionFile
|
|
2210
|
+
updatedAt: data?.updatedAt || ""
|
|
1867
2211
|
};
|
|
1868
2212
|
});
|
|
1869
|
-
jsonResponse(res, 200, { component: comp, files
|
|
2213
|
+
jsonResponse(res, 200, { component: comp, files });
|
|
1870
2214
|
}
|
|
1871
|
-
async function
|
|
1872
|
-
const activeFile =
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
2215
|
+
async function handleListThemes({ res }) {
|
|
2216
|
+
const activeFile = themesResource.getActiveName();
|
|
2217
|
+
const productionFile = themesResource.getProductionName();
|
|
2218
|
+
const files = [];
|
|
2219
|
+
for (const fileName of themesResource.listNames()) {
|
|
2220
|
+
const read = readTheme(fileName);
|
|
2221
|
+
if (!read) {
|
|
2222
|
+
console.warn(
|
|
2223
|
+
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.`
|
|
2224
|
+
);
|
|
2225
|
+
continue;
|
|
2226
|
+
}
|
|
2227
|
+
files.push({
|
|
2228
|
+
name: read.theme.name,
|
|
1877
2229
|
fileName,
|
|
1878
|
-
updatedAt:
|
|
2230
|
+
updatedAt: read.theme.updatedAt,
|
|
1879
2231
|
isActive: fileName === activeFile,
|
|
2232
|
+
isProduction: fileName === productionFile,
|
|
1880
2233
|
isProtected: fileName === "default"
|
|
1881
|
-
};
|
|
1882
|
-
}
|
|
1883
|
-
jsonResponse(res, 200, { files, activeFile });
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
jsonResponse(res, 200, { files, activeFile, productionFile });
|
|
1884
2237
|
}
|
|
1885
|
-
async function
|
|
1886
|
-
const
|
|
1887
|
-
const
|
|
1888
|
-
if (!
|
|
1889
|
-
|
|
2238
|
+
async function handleGetProductionTheme({ res }) {
|
|
2239
|
+
const fileName = themesResource.getProductionName();
|
|
2240
|
+
const read = readTheme(fileName);
|
|
2241
|
+
if (!read) {
|
|
2242
|
+
respondUnreadableTheme(res, fileName, "Production theme not found");
|
|
1890
2243
|
return;
|
|
1891
2244
|
}
|
|
1892
|
-
|
|
1893
|
-
data._fileName = activeFile;
|
|
1894
|
-
jsonResponse(res, 200, data);
|
|
2245
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: fileName });
|
|
1895
2246
|
}
|
|
1896
|
-
async function
|
|
2247
|
+
async function handleGetActiveTheme({ res }) {
|
|
2248
|
+
const activeFile = themesResource.getActiveName();
|
|
2249
|
+
const read = readTheme(activeFile);
|
|
2250
|
+
if (!read) {
|
|
2251
|
+
respondUnreadableTheme(res, activeFile, "Active theme not found");
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: activeFile });
|
|
2255
|
+
}
|
|
2256
|
+
async function handleSetActiveTheme({ req, res }) {
|
|
1897
2257
|
const body = JSON.parse(await readBody(req));
|
|
1898
2258
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1899
|
-
if (
|
|
1900
|
-
jsonResponse(res, 404, { error: "
|
|
2259
|
+
if (themesResource.existingPath(fileName) === null) {
|
|
2260
|
+
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1901
2261
|
return;
|
|
1902
2262
|
}
|
|
1903
|
-
|
|
2263
|
+
themesResource.setActiveName(fileName);
|
|
1904
2264
|
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1905
2265
|
}
|
|
1906
|
-
async function
|
|
2266
|
+
async function handleThemeByName({ params, req, res }) {
|
|
1907
2267
|
const [fileName] = params;
|
|
1908
|
-
|
|
2268
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2269
|
+
const filePath = themesResource.filePath(fileName);
|
|
1909
2270
|
if (req.method === "GET") {
|
|
1910
|
-
const
|
|
1911
|
-
if (!
|
|
1912
|
-
|
|
2271
|
+
const read = readTheme(fileName);
|
|
2272
|
+
if (!read) {
|
|
2273
|
+
respondUnreadableTheme(res, fileName, "Not found");
|
|
1913
2274
|
return;
|
|
1914
2275
|
}
|
|
1915
|
-
|
|
1916
|
-
data._fileName = fileName;
|
|
1917
|
-
jsonResponse(res, 200, data);
|
|
2276
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: fileName });
|
|
1918
2277
|
return;
|
|
1919
2278
|
}
|
|
1920
2279
|
if (req.method === "PUT") {
|
|
1921
2280
|
if (fileName === "default") {
|
|
1922
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
2281
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default theme" });
|
|
1923
2282
|
return;
|
|
1924
2283
|
}
|
|
1925
2284
|
const body = JSON.parse(await readBody(req));
|
|
1926
|
-
|
|
1927
|
-
|
|
2285
|
+
const { theme } = normalizeTheme(body, diskThemeResolvers);
|
|
2286
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2287
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
1928
2288
|
try {
|
|
1929
|
-
const existing = JSON.parse(
|
|
1930
|
-
if (existing.createdAt)
|
|
2289
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(filePath, "utf-8"));
|
|
2290
|
+
if (existing.createdAt) theme.createdAt = existing.createdAt;
|
|
1931
2291
|
} catch {
|
|
1932
2292
|
}
|
|
1933
2293
|
}
|
|
1934
|
-
|
|
1935
|
-
import_fs3.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
2294
|
+
writeTheme(fileName, theme);
|
|
1936
2295
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1937
2296
|
return;
|
|
1938
2297
|
}
|
|
1939
2298
|
if (req.method === "DELETE") {
|
|
1940
2299
|
if (fileName === "default") {
|
|
1941
|
-
jsonResponse(res, 403, { error: "Cannot delete the default
|
|
2300
|
+
jsonResponse(res, 403, { error: "Cannot delete the default theme" });
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
if (fileName === themesResource.getProductionName()) {
|
|
2304
|
+
jsonResponse(res, 403, {
|
|
2305
|
+
error: "Cannot delete the theme that is in production. Adopt another theme first.",
|
|
2306
|
+
code: "PRODUCTION_THEME"
|
|
2307
|
+
});
|
|
1942
2308
|
return;
|
|
1943
2309
|
}
|
|
1944
|
-
if (
|
|
1945
|
-
|
|
1946
|
-
if (
|
|
1947
|
-
|
|
2310
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
2311
|
+
import_fs4.default.unlinkSync(filePath);
|
|
2312
|
+
if (themesResource.existingPath(fileName) === null && themesResource.getActiveName() === fileName) {
|
|
2313
|
+
themesResource.setActiveName("default");
|
|
1948
2314
|
}
|
|
2315
|
+
} else if (themesResource.existingPath(fileName)) {
|
|
2316
|
+
jsonResponse(res, 403, {
|
|
2317
|
+
error: "Cannot delete a theme shipped with the package. Saving it creates a local copy; deleting that copy restores the shipped version.",
|
|
2318
|
+
code: "PACKAGE_THEME"
|
|
2319
|
+
});
|
|
2320
|
+
return;
|
|
1949
2321
|
}
|
|
1950
2322
|
jsonResponse(res, 200, { ok: true });
|
|
1951
2323
|
return;
|
|
1952
2324
|
}
|
|
1953
2325
|
}
|
|
1954
|
-
async function
|
|
2326
|
+
async function handleApplyTheme({ params, res }) {
|
|
1955
2327
|
const [fileName] = params;
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
2328
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2329
|
+
const read = readTheme(fileName);
|
|
2330
|
+
if (!read) {
|
|
2331
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
1959
2332
|
return;
|
|
1960
2333
|
}
|
|
1961
|
-
const
|
|
1962
|
-
|
|
1963
|
-
|
|
2334
|
+
const { theme } = read;
|
|
2335
|
+
const isDefault = fileName === "default";
|
|
2336
|
+
if (!isDefault && !theme.colorsAndType) {
|
|
2337
|
+
jsonResponse(res, 422, { error: "This theme carries no colors and type" });
|
|
1964
2338
|
return;
|
|
1965
2339
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
const
|
|
1969
|
-
const
|
|
1970
|
-
|
|
1971
|
-
if (!knownComponents.has(comp)) continue;
|
|
1972
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1973
|
-
const r = componentResource(comp);
|
|
1974
|
-
if (r.existingPath(sanitized) === null) {
|
|
1975
|
-
jsonResponse(res, 422, {
|
|
1976
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1977
|
-
});
|
|
1978
|
-
return;
|
|
1979
|
-
}
|
|
1980
|
-
apply.push([comp, sanitized]);
|
|
1981
|
-
}
|
|
1982
|
-
themesResource.setActiveName(themeName);
|
|
1983
|
-
themesResource.setProductionName(themeName);
|
|
1984
|
-
syncTokensToCss(themeName);
|
|
1985
|
-
syncFontsToCss(themeName);
|
|
1986
|
-
const themeData = normalizeTheme(themesResource.readJson(themeName));
|
|
1987
|
-
themeData._fileName = themeName;
|
|
1988
|
-
for (const [comp, configFile] of apply) {
|
|
2340
|
+
if (isDefault) colorsAndTypeResource.clearWorking();
|
|
2341
|
+
else colorsAndTypeResource.writeWorking(theme.colorsAndType);
|
|
2342
|
+
const knownComponents = listComponentNames();
|
|
2343
|
+
for (const comp of knownComponents) {
|
|
2344
|
+
const embedded = isDefault ? void 0 : theme.componentConfigs[comp];
|
|
1989
2345
|
const r = componentResource(comp);
|
|
1990
|
-
r.
|
|
1991
|
-
r.
|
|
1992
|
-
const cfg = readComponentConfig(comp, configFile);
|
|
1993
|
-
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
|
|
2346
|
+
if (embedded) r.writeWorking({ ...embedded, component: comp });
|
|
2347
|
+
else r.clearWorking();
|
|
1994
2348
|
}
|
|
2349
|
+
themesResource.setActiveName(fileName);
|
|
2350
|
+
const resolvedConfigs = {};
|
|
1995
2351
|
for (const comp of knownComponents) {
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
const cfg = readComponentConfig(comp, activeName);
|
|
1999
|
-
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: activeName };
|
|
2352
|
+
const resolved = resolveLiveComponentConfig(comp, theme);
|
|
2353
|
+
if (resolved) resolvedConfigs[comp] = { ...resolved.data, _fileName: fileName, _source: resolved.source };
|
|
2000
2354
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2355
|
+
const resolvedColorsAndType = resolveLiveColorsAndType(theme);
|
|
2356
|
+
const skippedComponents = Object.keys(theme.componentConfigs).filter(
|
|
2357
|
+
(comp) => !knownComponents.includes(comp)
|
|
2358
|
+
);
|
|
2003
2359
|
jsonResponse(res, 200, {
|
|
2004
2360
|
ok: true,
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2361
|
+
theme: { ...theme, _fileName: fileName },
|
|
2362
|
+
colorsAndType: resolvedColorsAndType && {
|
|
2363
|
+
...resolvedColorsAndType.data,
|
|
2364
|
+
_fileName: fileName,
|
|
2365
|
+
_source: resolvedColorsAndType.source
|
|
2366
|
+
},
|
|
2367
|
+
componentConfigs: resolvedConfigs,
|
|
2368
|
+
skippedComponents
|
|
2008
2369
|
});
|
|
2009
2370
|
}
|
|
2010
|
-
async function
|
|
2371
|
+
async function handleExportTheme({ params, res }) {
|
|
2011
2372
|
const [fileName] = params;
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2373
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2374
|
+
const read = readTheme(fileName);
|
|
2375
|
+
if (!read) {
|
|
2376
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
2015
2377
|
return;
|
|
2016
2378
|
}
|
|
2017
|
-
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
2018
|
-
const theme = themesResource.readJson(themeName);
|
|
2019
|
-
if (!theme) {
|
|
2020
|
-
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
2021
|
-
return;
|
|
2022
|
-
}
|
|
2023
|
-
const knownComponents = new Set(listComponentNames());
|
|
2024
|
-
const componentConfigs = {};
|
|
2025
|
-
for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
|
|
2026
|
-
if (!knownComponents.has(comp)) continue;
|
|
2027
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
2028
|
-
if (sanitized === "default") continue;
|
|
2029
|
-
const cfg = componentResource(comp).readJson(sanitized);
|
|
2030
|
-
if (!cfg) {
|
|
2031
|
-
jsonResponse(res, 422, {
|
|
2032
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
2033
|
-
});
|
|
2034
|
-
return;
|
|
2035
|
-
}
|
|
2036
|
-
componentConfigs[`${comp}/${sanitized}`] = cfg;
|
|
2037
|
-
}
|
|
2038
2379
|
const bundle = {
|
|
2039
|
-
kind: "
|
|
2040
|
-
schemaVersion:
|
|
2380
|
+
kind: "theme-bundle",
|
|
2381
|
+
schemaVersion: THEME_SCHEMA_VERSION,
|
|
2041
2382
|
liveTokensVersion: PKG_VERSION,
|
|
2042
2383
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2043
|
-
manifest
|
|
2044
|
-
theme,
|
|
2045
|
-
componentConfigs
|
|
2384
|
+
manifest: read.theme
|
|
2046
2385
|
};
|
|
2047
2386
|
res.statusCode = 200;
|
|
2048
2387
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -2055,7 +2394,7 @@ ${lines.join("\n")}
|
|
|
2055
2394
|
function nextAvailableName2(exists, baseName) {
|
|
2056
2395
|
return nextAvailableName(exists, sanitizeFileName(baseName));
|
|
2057
2396
|
}
|
|
2058
|
-
async function
|
|
2397
|
+
async function handleImportTheme({ req, res }) {
|
|
2059
2398
|
let bundle;
|
|
2060
2399
|
try {
|
|
2061
2400
|
bundle = JSON.parse(await readBody(req));
|
|
@@ -2063,117 +2402,112 @@ ${lines.join("\n")}
|
|
|
2063
2402
|
jsonResponse(res, 400, { error: "Body is not valid JSON" });
|
|
2064
2403
|
return;
|
|
2065
2404
|
}
|
|
2066
|
-
if (!bundle || bundle.kind
|
|
2405
|
+
if (!bundle || !Object.values(BUNDLE_KIND_BY_VERSION).includes(bundle.kind)) {
|
|
2067
2406
|
jsonResponse(res, 400, {
|
|
2068
|
-
error: "Not a
|
|
2407
|
+
error: "Not a theme bundle (kind discriminator missing or wrong)"
|
|
2069
2408
|
});
|
|
2070
2409
|
return;
|
|
2071
2410
|
}
|
|
2072
|
-
if (bundle.
|
|
2411
|
+
if (bundle.kind !== BUNDLE_KIND_BY_VERSION[bundle.schemaVersion]) {
|
|
2073
2412
|
jsonResponse(res, 400, {
|
|
2074
|
-
error: `Unsupported bundle schemaVersion
|
|
2413
|
+
error: `Unsupported bundle: kind "${bundle.kind}" with schemaVersion ${bundle.schemaVersion}`
|
|
2075
2414
|
});
|
|
2076
2415
|
return;
|
|
2077
2416
|
}
|
|
2078
|
-
if (!bundle.manifest
|
|
2079
|
-
jsonResponse(res, 400, { error: "Bundle missing
|
|
2417
|
+
if (!bundle.manifest) {
|
|
2418
|
+
jsonResponse(res, 400, { error: "Bundle missing theme" });
|
|
2080
2419
|
return;
|
|
2081
2420
|
}
|
|
2421
|
+
const bundleResolvers = {
|
|
2422
|
+
readColorsAndType: () => bundle.theme,
|
|
2423
|
+
readComponentConfig: (comp, name) => bundle.componentConfigs?.[`${comp}/${name}`],
|
|
2424
|
+
normalizeColorsAndType: (colorsAndType) => normalizeColorsAndType(colorsAndType)
|
|
2425
|
+
};
|
|
2426
|
+
const { theme, dropped } = normalizeTheme(bundle.manifest, bundleResolvers);
|
|
2427
|
+
if (theme.colorsAndType) theme.colorsAndType = normalizeColorsAndType(theme.colorsAndType);
|
|
2428
|
+
if (!theme.colorsAndType) {
|
|
2429
|
+
jsonResponse(res, 400, { error: "Bundle carries no theme" });
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2082
2433
|
const renames = {};
|
|
2083
|
-
const
|
|
2084
|
-
const
|
|
2085
|
-
const finalThemeName = nextAvailableName2(
|
|
2434
|
+
const originalName = sanitizeFileName(theme.name || "imported");
|
|
2435
|
+
const finalName = nextAvailableName2(
|
|
2086
2436
|
(n) => themesResource.existingPath(n) !== null,
|
|
2087
|
-
|
|
2437
|
+
originalName
|
|
2088
2438
|
);
|
|
2089
|
-
if (
|
|
2090
|
-
renames[`theme:${
|
|
2439
|
+
if (finalName !== originalName) {
|
|
2440
|
+
renames[`theme:${originalName}`] = finalName;
|
|
2091
2441
|
}
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
if (finalName !== originalName) {
|
|
2109
|
-
renames[`componentConfig:${comp}/${originalName}`] = finalName;
|
|
2110
|
-
}
|
|
2111
|
-
componentRenames[`${comp}/${originalName}`] = finalName;
|
|
2112
|
-
const cfgBody = { ...cfgValue };
|
|
2113
|
-
cfgBody.component = comp;
|
|
2114
|
-
cfgBody.name = finalName;
|
|
2115
|
-
cfgBody.updatedAt = now;
|
|
2116
|
-
if (!cfgBody.createdAt) cfgBody.createdAt = now;
|
|
2117
|
-
r.ensureDir();
|
|
2118
|
-
import_fs3.default.writeFileSync(r.filePath(finalName), JSON.stringify(cfgBody, null, 2));
|
|
2442
|
+
writeTheme(finalName, theme);
|
|
2443
|
+
jsonResponse(res, 200, {
|
|
2444
|
+
ok: true,
|
|
2445
|
+
theme: finalName,
|
|
2446
|
+
renames,
|
|
2447
|
+
dropped
|
|
2448
|
+
});
|
|
2449
|
+
}
|
|
2450
|
+
async function handleAdoptLook({ res }) {
|
|
2451
|
+
const activeName = themesResource.getActiveName();
|
|
2452
|
+
if (activeName === "default") {
|
|
2453
|
+
jsonResponse(res, 409, {
|
|
2454
|
+
error: "Active theme is protected. Save As first.",
|
|
2455
|
+
code: "ACTIVE_IS_PROTECTED"
|
|
2456
|
+
});
|
|
2457
|
+
return;
|
|
2119
2458
|
}
|
|
2120
|
-
const
|
|
2121
|
-
|
|
2122
|
-
theme
|
|
2123
|
-
|
|
2124
|
-
};
|
|
2125
|
-
for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
|
|
2126
|
-
const original = String(configName);
|
|
2127
|
-
if (original === "default") {
|
|
2128
|
-
rewrittenManifest.componentConfigs[comp] = "default";
|
|
2129
|
-
continue;
|
|
2130
|
-
}
|
|
2131
|
-
const finalName = componentRenames[`${comp}/${original}`] ?? original;
|
|
2132
|
-
rewrittenManifest.componentConfigs[comp] = finalName;
|
|
2133
|
-
}
|
|
2134
|
-
rewrittenManifest.updatedAt = now;
|
|
2135
|
-
if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
|
|
2136
|
-
const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
|
|
2137
|
-
const finalManifestName = nextAvailableName2(
|
|
2138
|
-
(n) => manifestsResource.existingPath(n) !== null,
|
|
2139
|
-
originalManifestName
|
|
2140
|
-
);
|
|
2141
|
-
if (finalManifestName !== originalManifestName) {
|
|
2142
|
-
renames[`manifest:${originalManifestName}`] = finalManifestName;
|
|
2459
|
+
const read = readTheme(activeName);
|
|
2460
|
+
if (!read) {
|
|
2461
|
+
respondUnreadableTheme(res, activeName, "Active theme not found");
|
|
2462
|
+
return;
|
|
2143
2463
|
}
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
JSON.stringify(rewrittenManifest, null, 2)
|
|
2148
|
-
);
|
|
2464
|
+
themesResource.setProductionName(activeName);
|
|
2465
|
+
regenerateTokensCss();
|
|
2466
|
+
syncFontsToCss(read.theme.colorsAndType);
|
|
2149
2467
|
jsonResponse(res, 200, {
|
|
2150
2468
|
ok: true,
|
|
2151
|
-
|
|
2152
|
-
|
|
2469
|
+
productionTheme: {
|
|
2470
|
+
fileName: activeName,
|
|
2471
|
+
name: read.theme.name,
|
|
2472
|
+
updatedAt: read.theme.updatedAt
|
|
2473
|
+
}
|
|
2153
2474
|
});
|
|
2154
2475
|
}
|
|
2155
2476
|
function methodNotAllowed({ res }) {
|
|
2156
2477
|
jsonResponse(res, 405, { error: "Method not allowed" });
|
|
2157
2478
|
}
|
|
2479
|
+
let legacyLayout = null;
|
|
2158
2480
|
const routes = [
|
|
2159
|
-
//
|
|
2160
|
-
|
|
2161
|
-
{ method: "GET", pattern:
|
|
2162
|
-
{ method: "
|
|
2163
|
-
{ method: "
|
|
2164
|
-
{ method: "
|
|
2481
|
+
// Colors and type — list / active / production / working are exact strings;
|
|
2482
|
+
// they must run before COLORS_AND_TYPE_BY_NAME_REGEX
|
|
2483
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_ROUTE, handler: handleListColorsAndType },
|
|
2484
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_ACTIVE_ROUTE, handler: handleGetActiveColorsAndType },
|
|
2485
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_ACTIVE_ROUTE, handler: methodNotAllowed },
|
|
2486
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2487
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2488
|
+
{ method: "POST", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2489
|
+
{ method: "DELETE", pattern: COLORS_AND_TYPE_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2490
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: handleGetWorkingColorsAndType },
|
|
2491
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: handleSetWorkingColorsAndType },
|
|
2492
|
+
{ method: "DELETE", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: handleClearWorkingColorsAndType },
|
|
2493
|
+
{ method: "POST", pattern: COLORS_AND_TYPE_WORKING_ROUTE, handler: methodNotAllowed },
|
|
2165
2494
|
// Component configs — list of components
|
|
2166
2495
|
{ method: "GET", pattern: COMPONENT_CONFIGS_ROUTE, handler: handleListComponents },
|
|
2167
2496
|
// Component configs — :comp/active (must precede :comp/:name)
|
|
2168
2497
|
{ method: "GET", pattern: COMP_ACTIVE_REGEX, handler: handleGetComponentActive },
|
|
2169
|
-
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler:
|
|
2498
|
+
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
2170
2499
|
{ method: "POST", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
2171
2500
|
{ method: "DELETE", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
2172
|
-
// Component configs — :comp/production
|
|
2173
|
-
{ method: "GET", pattern: COMP_PRODUCTION_REGEX, handler:
|
|
2174
|
-
{ method: "PUT", pattern: COMP_PRODUCTION_REGEX, handler:
|
|
2501
|
+
// Component configs — :comp/production, retired with the per-layer pointers
|
|
2502
|
+
{ method: "GET", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
2503
|
+
{ method: "PUT", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
2175
2504
|
{ method: "POST", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
2176
2505
|
{ method: "DELETE", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
2506
|
+
// Component configs — :comp/working (must precede :comp/:name)
|
|
2507
|
+
{ method: "GET", pattern: COMP_WORKING_REGEX, handler: handleGetComponentWorking },
|
|
2508
|
+
{ method: "PUT", pattern: COMP_WORKING_REGEX, handler: handleSetComponentWorking },
|
|
2509
|
+
{ method: "DELETE", pattern: COMP_WORKING_REGEX, handler: handleClearComponentWorking },
|
|
2510
|
+
{ method: "POST", pattern: COMP_WORKING_REGEX, handler: methodNotAllowed },
|
|
2177
2511
|
// Component configs — :comp/:name (must precede :comp listing)
|
|
2178
2512
|
{ method: "GET", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
2179
2513
|
{ method: "PUT", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
@@ -2181,33 +2515,44 @@ ${lines.join("\n")}
|
|
|
2181
2515
|
{ method: "POST", pattern: COMP_BY_NAME_REGEX, handler: methodNotAllowed },
|
|
2182
2516
|
// Component configs — list configs for :comp (broadest, runs last among comp routes)
|
|
2183
2517
|
{ method: "GET", pattern: COMP_LIST_REGEX, handler: handleListComponentConfigs },
|
|
2518
|
+
// Colors and type — :name CRUD (broadest colors-and-type route, runs last)
|
|
2519
|
+
{ method: "GET", pattern: COLORS_AND_TYPE_BY_NAME_REGEX, handler: handleColorsAndTypeByName },
|
|
2520
|
+
{ method: "PUT", pattern: COLORS_AND_TYPE_BY_NAME_REGEX, handler: handleColorsAndTypeByName },
|
|
2521
|
+
{ method: "DELETE", pattern: COLORS_AND_TYPE_BY_NAME_REGEX, handler: handleColorsAndTypeByName },
|
|
2522
|
+
// Themes — list / active / production are exact strings, must run before regexes
|
|
2523
|
+
{ method: "GET", pattern: THEMES_ROUTE, handler: handleListThemes },
|
|
2524
|
+
{ method: "GET", pattern: THEMES_ACTIVE_ROUTE, handler: handleGetActiveTheme },
|
|
2525
|
+
{ method: "PUT", pattern: THEMES_ACTIVE_ROUTE, handler: handleSetActiveTheme },
|
|
2526
|
+
{ method: "GET", pattern: THEMES_PRODUCTION_ROUTE, handler: handleGetProductionTheme },
|
|
2527
|
+
// Adopt is the one door that moves the production pointer; naming a theme
|
|
2528
|
+
// here would be a second, silent way to publish.
|
|
2529
|
+
{ method: "PUT", pattern: THEMES_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2530
|
+
{ method: "POST", pattern: THEMES_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2531
|
+
{ method: "DELETE", pattern: THEMES_PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2532
|
+
// Themes — exact import route runs before :name regexes
|
|
2533
|
+
{ method: "POST", pattern: THEME_IMPORT_ROUTE, handler: handleImportTheme },
|
|
2534
|
+
{ method: "PUT", pattern: THEME_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2535
|
+
{ method: "GET", pattern: THEME_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2536
|
+
{ method: "DELETE", pattern: THEME_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2537
|
+
// Themes — :name/apply (more specific than :name)
|
|
2538
|
+
{ method: "PUT", pattern: THEME_APPLY_REGEX, handler: handleApplyTheme },
|
|
2539
|
+
{ method: "POST", pattern: THEME_APPLY_REGEX, handler: methodNotAllowed },
|
|
2540
|
+
{ method: "GET", pattern: THEME_APPLY_REGEX, handler: methodNotAllowed },
|
|
2541
|
+
{ method: "DELETE", pattern: THEME_APPLY_REGEX, handler: methodNotAllowed },
|
|
2542
|
+
// Themes — :name/export (more specific than :name)
|
|
2543
|
+
{ method: "GET", pattern: THEME_EXPORT_REGEX, handler: handleExportTheme },
|
|
2544
|
+
{ method: "PUT", pattern: THEME_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2545
|
+
{ method: "POST", pattern: THEME_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2546
|
+
{ method: "DELETE", pattern: THEME_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2184
2547
|
// Themes — :name CRUD (broadest theme route, runs last)
|
|
2185
2548
|
{ method: "GET", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
2186
2549
|
{ method: "PUT", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
2187
2550
|
{ method: "DELETE", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
2188
|
-
//
|
|
2189
|
-
{ method: "
|
|
2190
|
-
{ method: "GET", pattern:
|
|
2191
|
-
{ method: "
|
|
2192
|
-
|
|
2193
|
-
{ method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
|
|
2194
|
-
{ method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2195
|
-
{ method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2196
|
-
{ method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2197
|
-
// Manifests — :name/apply (more specific than :name)
|
|
2198
|
-
{ method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
|
|
2199
|
-
{ method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
2200
|
-
{ method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
2201
|
-
{ method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
2202
|
-
// Manifests — :name/export (more specific than :name)
|
|
2203
|
-
{ method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
|
|
2204
|
-
{ method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2205
|
-
{ method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2206
|
-
{ method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2207
|
-
// Manifests — :name CRUD (broadest manifest route, runs last)
|
|
2208
|
-
{ method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
2209
|
-
{ method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
2210
|
-
{ method: "DELETE", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName }
|
|
2551
|
+
// Production — the whole-look adopt door
|
|
2552
|
+
{ method: "PUT", pattern: PRODUCTION_ROUTE, handler: handleAdoptLook },
|
|
2553
|
+
{ method: "GET", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2554
|
+
{ method: "POST", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
|
|
2555
|
+
{ method: "DELETE", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed }
|
|
2211
2556
|
];
|
|
2212
2557
|
return {
|
|
2213
2558
|
name: "theme-file-api",
|
|
@@ -2221,19 +2566,42 @@ ${lines.join("\n")}
|
|
|
2221
2566
|
};
|
|
2222
2567
|
},
|
|
2223
2568
|
configureServer(server) {
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2569
|
+
legacyLayout = detectLegacyLayout({
|
|
2570
|
+
dataDir: dataDirs.dataDir,
|
|
2571
|
+
colorsAndTypeDir: COLORS_AND_TYPE_DIR,
|
|
2572
|
+
themesDir: THEMES_DIR,
|
|
2573
|
+
configuredManifestsDir: dataDirs.legacyManifestsDir
|
|
2574
|
+
});
|
|
2575
|
+
if (legacyLayout) {
|
|
2576
|
+
warnOnLegacyLayout(legacyLayout.evidence, (msg) => server.config.logger.warn(msg));
|
|
2577
|
+
} else {
|
|
2578
|
+
ensureColorsAndTypeDir();
|
|
2579
|
+
ensureComponentConfigsDir();
|
|
2580
|
+
const { productionPointerHeld } = ensureThemesDir((msg) => server.config.logger.warn(msg));
|
|
2581
|
+
if (!productionPointerHeld || !import_fs4.default.existsSync(GENERATED_CSS_PATH)) regenerateTokensCss();
|
|
2582
|
+
if (opts.autoMigrate) autoMigrateAdditive((msg) => server.config.logger.info(msg));
|
|
2583
|
+
warnOnTokenDrift((msg) => server.config.logger.warn(msg));
|
|
2584
|
+
warnOnLegacyPointers(
|
|
2585
|
+
listLegacyPointers(),
|
|
2586
|
+
productionPointerHeld,
|
|
2587
|
+
(msg) => server.config.logger.warn(msg)
|
|
2588
|
+
);
|
|
2589
|
+
}
|
|
2230
2590
|
server.middlewares.use(async (req, res, next) => {
|
|
2591
|
+
if (legacyLayout && req.method !== "GET" && (req.url ?? "").startsWith(API_BASE)) {
|
|
2592
|
+
jsonResponse(res, 409, {
|
|
2593
|
+
error: "This project still uses the data layout from 0.47 and earlier. Run `npx live-tokens migrate`, then restart the dev server.",
|
|
2594
|
+
code: "LEGACY_LAYOUT"
|
|
2595
|
+
});
|
|
2596
|
+
return;
|
|
2597
|
+
}
|
|
2231
2598
|
const handled = await dispatch(req, res, routes);
|
|
2232
2599
|
if (!handled) next();
|
|
2233
2600
|
});
|
|
2234
2601
|
},
|
|
2235
2602
|
handleHotUpdate(ctx) {
|
|
2236
|
-
|
|
2603
|
+
if (legacyLayout) return;
|
|
2604
|
+
const normalized = import_path4.default.resolve(ctx.file);
|
|
2237
2605
|
if (!COMPONENTS_SCAN_DIRS.some((d) => normalized.startsWith(d))) return;
|
|
2238
2606
|
if (!normalized.endsWith(".svelte")) return;
|
|
2239
2607
|
if (!isThemeAwareComponent(normalized)) return;
|