@motion-proto/live-tokens 0.47.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/live-tokens-adjust-shape-space/SKILL.md +68 -0
- package/.claude/skills/live-tokens-create-component/SKILL.md +2 -2
- package/.claude/skills/live-tokens-generate-theme/SKILL.md +152 -0
- package/CHANGELOG.md +270 -0
- package/README.md +37 -26
- package/bin/adjust.mjs +254 -0
- package/bin/cli.mjs +94 -7
- package/bin/generate-theme.mjs +251 -0
- package/bin/migrate.mjs +95 -6
- package/dist-plugin/adjust/index.cjs +259 -0
- package/dist-plugin/adjust/index.d.cts +45 -0
- package/dist-plugin/adjust/index.d.ts +45 -0
- package/dist-plugin/adjust/index.js +176 -0
- package/dist-plugin/{chunk-H4TRUINI.js → chunk-44RSTAII.js} +0 -48
- package/dist-plugin/chunk-6OZFXIQI.js +316 -0
- package/dist-plugin/chunk-76TFDTJO.js +556 -0
- package/dist-plugin/chunk-D3ZVKOR4.js +52 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.cts +54 -0
- package/dist-plugin/dataPaths-DBN0RPuT.d.ts +54 -0
- package/dist-plugin/generateColorsAndType/index.cjs +1781 -0
- package/dist-plugin/generateColorsAndType/index.d.cts +81 -0
- package/dist-plugin/generateColorsAndType/index.d.ts +81 -0
- package/dist-plugin/generateColorsAndType/index.js +1210 -0
- package/dist-plugin/index.cjs +849 -531
- package/dist-plugin/index.d.cts +3 -2
- package/dist-plugin/index.d.ts +3 -2
- package/dist-plugin/index.js +658 -1132
- package/dist-plugin/migrateData/index.cjs +728 -0
- package/dist-plugin/migrateData/index.d.cts +60 -0
- package/dist-plugin/migrateData/index.d.ts +60 -0
- package/dist-plugin/migrateData/index.js +348 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.cts +211 -0
- package/dist-plugin/themeTypes-DMHZOnUn.d.ts +211 -0
- package/dist-plugin/tokensCssMigrations/index.cjs +2 -1
- package/dist-plugin/tokensCssMigrations/index.d.cts +3 -15
- package/dist-plugin/tokensCssMigrations/index.d.ts +3 -15
- package/dist-plugin/tokensCssMigrations/index.js +4 -2
- package/package.json +18 -4
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +183 -158
- package/src/editor/component-editor/scaffolding/ComponentFileMenu.svelte +0 -4
- package/src/editor/component-editor/scaffolding/SaveAsDialog.svelte +4 -4
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +2 -59
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +1 -1
- package/src/editor/core/components/adjustAliases.ts +180 -0
- package/src/editor/core/components/aliasKinds.ts +73 -0
- package/src/editor/core/components/componentConfigService.ts +26 -38
- package/src/editor/core/flashStatus.ts +1 -1
- package/src/editor/core/fonts/fontMigration.ts +13 -13
- package/src/editor/core/fonts/fontPairing.ts +35 -0
- package/src/editor/core/palettes/paletteDerivation.ts +83 -13
- package/src/editor/core/preview/lookPreview.ts +141 -0
- package/src/editor/core/productionPulse.ts +34 -21
- package/src/editor/core/storage/files/versionedFileResourceClient.ts +18 -45
- package/src/editor/core/store/editorConfigStore.ts +2 -2
- package/src/editor/core/store/editorPersistence.ts +0 -1
- package/src/editor/core/store/editorStore.ts +114 -51
- package/src/editor/core/store/editorTypes.ts +0 -1
- package/src/editor/core/store/gradientSource.ts +2 -2
- package/src/editor/core/themes/colorsAndTypeService.ts +95 -0
- package/src/editor/core/themes/generateColorsAndType.ts +433 -0
- package/src/editor/core/themes/loadRows.ts +64 -0
- package/src/editor/core/themes/lookSummary.ts +75 -0
- package/src/editor/core/themes/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +3 -3
- package/src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts +2 -2
- package/src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts +3 -3
- package/src/editor/core/themes/migrations/2026-08-13-drop-legacy-shape-space-keys.ts +38 -0
- package/src/editor/core/themes/migrations/2026-08-15-line-height-scale-rename.ts +57 -0
- package/src/editor/core/themes/migrations/index.ts +19 -11
- package/src/editor/core/themes/slices/components.ts +4 -4
- package/src/editor/core/themes/slices/fonts.ts +1 -1
- package/src/editor/core/themes/slices/gradients.ts +22 -0
- package/src/editor/core/themes/slices/palettes.ts +2 -2
- package/src/editor/core/themes/themeInit.ts +21 -29
- package/src/editor/core/themes/themeService.ts +218 -78
- package/src/editor/core/themes/themeTypes.ts +94 -51
- package/src/editor/docs/Docs.svelte +1 -0
- package/src/editor/docs/chapters.ts +1 -0
- package/src/editor/docs/content/01-overview.md +1 -1
- package/src/editor/docs/content/editing-tokens.md +6 -6
- package/src/editor/docs/content/getting-started.md +9 -7
- package/src/editor/docs/content/themes-workflow.md +74 -34
- package/src/editor/docs/content/where-themes-live.md +61 -0
- package/src/editor/docs/content.generated.ts +5 -4
- package/src/editor/index.ts +27 -27
- package/src/editor/pages/ComponentEditorPage.svelte +2 -2
- package/src/editor/pages/EditorShell.svelte +4 -30
- package/src/editor/ui/FileLoadList.svelte +67 -7
- package/src/editor/ui/GradientEditor.svelte +3 -3
- package/src/editor/ui/ThemePanel.svelte +1067 -0
- package/src/editor/ui/UIDialog.svelte +11 -9
- package/src/editor/ui/index.ts +4 -2
- package/src/live-tokens/data/colors-and-type/autumn.json +2490 -0
- package/src/live-tokens/data/{themes → colors-and-type}/default.json +962 -549
- package/src/live-tokens/data/colors-and-type/halloween.json +2529 -0
- package/src/live-tokens/data/colors-and-type/midnight-study.json +2536 -0
- package/src/live-tokens/data/colors-and-type/ocean.json +2489 -0
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +2520 -0
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +2476 -0
- package/src/live-tokens/data/colors-and-type/sunset.json +2528 -0
- package/src/live-tokens/data/themes/autumn.json +3918 -0
- package/src/live-tokens/data/themes/halloween.json +3992 -0
- package/src/live-tokens/data/themes/midnight-study.json +3932 -0
- package/src/live-tokens/data/themes/ocean.json +3917 -0
- package/src/live-tokens/data/themes/royal-velvet.json +3983 -0
- package/src/live-tokens/data/themes/spring-meadow.json +3904 -0
- package/src/live-tokens/data/themes/sunset.json +3931 -0
- package/src/live-tokens/data/tokens.generated.css +129 -140
- package/src/system/styles/CONVENTIONS.md +1 -1
- package/src/system/styles/fonts.css +1 -1
- package/template/README.md +7 -5
- package/template/_gitignore +0 -6
- package/src/editor/core/components/componentPersist.ts +0 -62
- package/src/editor/core/manifests/manifestService.ts +0 -172
- package/src/editor/ui/ManifestFileManager.svelte +0 -446
- package/src/editor/ui/ThemeFileManager.svelte +0 -785
- package/src/live-tokens/data/manifests/default.json +0 -35
package/dist-plugin/index.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
|
|
@@ -349,7 +349,33 @@ function nearestPaletteStep(lightnessCurve, baseL) {
|
|
|
349
349
|
}
|
|
350
350
|
return best;
|
|
351
351
|
}
|
|
352
|
-
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
|
+
}
|
|
353
379
|
const existing = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
354
380
|
if (existing >= 0) {
|
|
355
381
|
const displacedY = curve[existing].y;
|
|
@@ -382,7 +408,8 @@ function fitBetween(a, prev, next) {
|
|
|
382
408
|
}
|
|
383
409
|
return out;
|
|
384
410
|
}
|
|
385
|
-
function liftCurveAnchor(curve, x, displacedY) {
|
|
411
|
+
function liftCurveAnchor(curve, x, displacedY, priorEndpoints) {
|
|
412
|
+
if (priorEndpoints) return priorEndpoints;
|
|
386
413
|
const idx = curve.findIndex((a) => Math.abs(a.x - x) < 0.5);
|
|
387
414
|
if (idx < 0) return curve;
|
|
388
415
|
if (displacedY !== void 0) return curve.map((a, i) => i === idx ? { ...a, y: displacedY } : a);
|
|
@@ -400,6 +427,7 @@ function liftCurveAnchor(curve, x, displacedY) {
|
|
|
400
427
|
function syncBaseAnchor(cfg) {
|
|
401
428
|
if (cfg.anchorToBase === false) return;
|
|
402
429
|
const prev = cfg.anchorPlacement;
|
|
430
|
+
const fresh = prev === void 0;
|
|
403
431
|
let lCurve = cfg.lightnessCurve;
|
|
404
432
|
let sCurve = cfg.saturationCurve;
|
|
405
433
|
if (prev) {
|
|
@@ -409,11 +437,17 @@ function syncBaseAnchor(cfg) {
|
|
|
409
437
|
}
|
|
410
438
|
const step = nearestPaletteStep(lCurve, cfg.baseColor.l);
|
|
411
439
|
const x = stepIndexToX(step);
|
|
412
|
-
const l = setCurveAnchor(lCurve, x, cfg.baseColor.l * 100);
|
|
413
|
-
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);
|
|
414
442
|
cfg.lightnessCurve = l.curve;
|
|
415
443
|
cfg.saturationCurve = s.curve;
|
|
416
|
-
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
|
+
};
|
|
417
451
|
}
|
|
418
452
|
function computeDerivedOklch(step, base, scaleTitle, scaleCurves, curveOffset) {
|
|
419
453
|
const scale = SCALES.find((s) => s.title === scaleTitle);
|
|
@@ -590,6 +624,7 @@ function versionedFileResourceServer(opts) {
|
|
|
590
624
|
const dir = opts.dir;
|
|
591
625
|
const activePath = import_path.default.join(dir, "_active.json");
|
|
592
626
|
const productionPath = import_path.default.join(dir, "_production.json");
|
|
627
|
+
const workingPath = import_path.default.join(dir, "_working.json");
|
|
593
628
|
const defaultName = opts.defaultName ?? "default";
|
|
594
629
|
const resolvedDir = import_path.default.resolve(dir);
|
|
595
630
|
const resolvedPackageDir = opts.packageDir ? import_path.default.resolve(opts.packageDir) : null;
|
|
@@ -618,6 +653,16 @@ function versionedFileResourceServer(opts) {
|
|
|
618
653
|
}
|
|
619
654
|
return null;
|
|
620
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
|
+
}
|
|
621
666
|
function readJson(name) {
|
|
622
667
|
const resolved = existingPath(name);
|
|
623
668
|
if (!resolved) return null;
|
|
@@ -662,20 +707,44 @@ function versionedFileResourceServer(opts) {
|
|
|
662
707
|
function setProductionName(name) {
|
|
663
708
|
import_fs.default.writeFileSync(productionPath, JSON.stringify({ productionFile: name }));
|
|
664
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
|
+
}
|
|
665
728
|
return {
|
|
666
729
|
dir,
|
|
667
730
|
activePath,
|
|
668
731
|
productionPath,
|
|
732
|
+
workingPath,
|
|
669
733
|
ensureDir,
|
|
670
734
|
ensureMeta,
|
|
671
735
|
filePath,
|
|
672
736
|
existingPath,
|
|
737
|
+
isPackageFile,
|
|
673
738
|
readJson,
|
|
674
739
|
listNames,
|
|
675
740
|
getActiveName,
|
|
676
741
|
getProductionName,
|
|
677
742
|
setActiveName,
|
|
678
|
-
setProductionName
|
|
743
|
+
setProductionName,
|
|
744
|
+
hasWorking,
|
|
745
|
+
readWorking,
|
|
746
|
+
writeWorking,
|
|
747
|
+
clearWorking
|
|
679
748
|
};
|
|
680
749
|
}
|
|
681
750
|
|
|
@@ -707,6 +776,78 @@ async function dispatch(req, res, routes) {
|
|
|
707
776
|
return false;
|
|
708
777
|
}
|
|
709
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
|
+
|
|
710
851
|
// vite-plugin/files/nameAllocator.ts
|
|
711
852
|
function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
|
|
712
853
|
if (!exists(baseName)) return baseName;
|
|
@@ -723,8 +864,9 @@ var import_path2 = __toESM(require("path"), 1);
|
|
|
723
864
|
var DEFAULT_DATA_DIR = "src/live-tokens/data";
|
|
724
865
|
var KNOWN_CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
725
866
|
"dataDir",
|
|
726
|
-
"
|
|
867
|
+
"colorsAndTypeDir",
|
|
727
868
|
"componentConfigsDir",
|
|
869
|
+
"themesDir",
|
|
728
870
|
"manifestsDir",
|
|
729
871
|
"tokensCssPath"
|
|
730
872
|
]);
|
|
@@ -757,12 +899,69 @@ function resolveDataDirs(opts = {}) {
|
|
|
757
899
|
const sub = (name) => import_path2.default.resolve(dataDir, name);
|
|
758
900
|
return {
|
|
759
901
|
dataDir,
|
|
760
|
-
|
|
902
|
+
colorsAndTypeDir: opts.colorsAndTypeDir ? import_path2.default.resolve(opts.colorsAndTypeDir) : fileConfig.colorsAndTypeDir ? import_path2.default.resolve(fileConfig.colorsAndTypeDir) : sub("colors-and-type"),
|
|
761
903
|
componentConfigsDir: opts.componentConfigsDir ? import_path2.default.resolve(opts.componentConfigsDir) : fileConfig.componentConfigsDir ? import_path2.default.resolve(fileConfig.componentConfigsDir) : sub("component-configs"),
|
|
762
|
-
|
|
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
|
|
763
906
|
};
|
|
764
907
|
}
|
|
765
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
|
+
|
|
766
965
|
// vite-plugin/tokensCssMigrations/cssTokenOps.ts
|
|
767
966
|
var DECL_RE = /(^|[\s;{])(--[a-z0-9-]+)\s*:/gi;
|
|
768
967
|
var REF_RE = /var\(\s*(--[a-z0-9-]+)/gi;
|
|
@@ -1163,14 +1362,14 @@ var import_node_url = require("url");
|
|
|
1163
1362
|
var import_meta = {};
|
|
1164
1363
|
var PKG_VERSION = (() => {
|
|
1165
1364
|
try {
|
|
1166
|
-
let dir =
|
|
1365
|
+
let dir = import_path4.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
1167
1366
|
for (let i = 0; i < 4; i++) {
|
|
1168
|
-
const p =
|
|
1169
|
-
if (
|
|
1170
|
-
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"));
|
|
1171
1370
|
if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
|
|
1172
1371
|
}
|
|
1173
|
-
const up =
|
|
1372
|
+
const up = import_path4.default.dirname(dir);
|
|
1174
1373
|
if (up === dir) break;
|
|
1175
1374
|
dir = up;
|
|
1176
1375
|
}
|
|
@@ -1178,65 +1377,79 @@ var PKG_VERSION = (() => {
|
|
|
1178
1377
|
}
|
|
1179
1378
|
return "";
|
|
1180
1379
|
})();
|
|
1380
|
+
var BUNDLE_KIND_BY_VERSION = {
|
|
1381
|
+
1: "manifest-bundle",
|
|
1382
|
+
[THEME_SCHEMA_VERSION]: "theme-bundle"
|
|
1383
|
+
};
|
|
1181
1384
|
function themeFileApi(opts) {
|
|
1182
1385
|
const dataDirs = resolveDataDirs({
|
|
1183
1386
|
dataDir: opts.dataDir,
|
|
1184
|
-
|
|
1387
|
+
colorsAndTypeDir: opts.colorsAndTypeDir,
|
|
1185
1388
|
componentConfigsDir: opts.componentConfigsDir,
|
|
1186
|
-
|
|
1389
|
+
themesDir: opts.themesDir
|
|
1187
1390
|
});
|
|
1188
|
-
const
|
|
1391
|
+
const COLORS_AND_TYPE_DIR = dataDirs.colorsAndTypeDir;
|
|
1189
1392
|
const COMPONENT_CONFIGS_DIR = dataDirs.componentConfigsDir;
|
|
1190
|
-
const
|
|
1191
|
-
const CSS_PATH =
|
|
1192
|
-
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ?
|
|
1193
|
-
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");
|
|
1194
1397
|
const API_BASE = opts.apiBase ?? "/api/live-tokens";
|
|
1195
|
-
const consumerComponentDirs = opts.componentsSrcDir ? [
|
|
1196
|
-
const packageComponentsDir =
|
|
1197
|
-
|
|
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)),
|
|
1198
1401
|
"..",
|
|
1199
1402
|
"src",
|
|
1200
1403
|
"system",
|
|
1201
1404
|
"components"
|
|
1202
1405
|
);
|
|
1203
|
-
const packageDataDir =
|
|
1204
|
-
|
|
1406
|
+
const packageDataDir = import_path4.default.resolve(
|
|
1407
|
+
import_path4.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url)),
|
|
1205
1408
|
"..",
|
|
1206
1409
|
"src",
|
|
1207
1410
|
"live-tokens",
|
|
1208
1411
|
"data"
|
|
1209
1412
|
);
|
|
1210
|
-
const
|
|
1211
|
-
const
|
|
1212
|
-
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
|
+
}
|
|
1213
1426
|
const COMPONENTS_SCAN_DIRS = [...consumerComponentDirs];
|
|
1214
|
-
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) &&
|
|
1427
|
+
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && import_fs4.default.existsSync(packageComponentsDir)) {
|
|
1215
1428
|
COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
|
|
1216
1429
|
}
|
|
1217
|
-
const
|
|
1218
|
-
dir:
|
|
1219
|
-
packageDir:
|
|
1430
|
+
const colorsAndTypeResource = versionedFileResourceServer({
|
|
1431
|
+
dir: COLORS_AND_TYPE_DIR,
|
|
1432
|
+
packageDir: packageColorsAndTypeDir,
|
|
1433
|
+
packageOwnedNames: shippedColorsAndTypeNames(packageDataDir)
|
|
1220
1434
|
});
|
|
1221
1435
|
const componentResourceCache = /* @__PURE__ */ new Map();
|
|
1222
1436
|
function componentResource(comp) {
|
|
1223
1437
|
let r = componentResourceCache.get(comp);
|
|
1224
1438
|
if (!r) {
|
|
1225
1439
|
r = versionedFileResourceServer({
|
|
1226
|
-
dir:
|
|
1227
|
-
packageDir:
|
|
1440
|
+
dir: import_path4.default.join(COMPONENT_CONFIGS_DIR, comp),
|
|
1441
|
+
packageDir: import_path4.default.join(packageComponentConfigsDir, comp)
|
|
1228
1442
|
});
|
|
1229
1443
|
componentResourceCache.set(comp, r);
|
|
1230
1444
|
}
|
|
1231
1445
|
return r;
|
|
1232
1446
|
}
|
|
1233
|
-
const
|
|
1234
|
-
dir:
|
|
1235
|
-
packageDir:
|
|
1447
|
+
const themesResource = versionedFileResourceServer({
|
|
1448
|
+
dir: THEMES_DIR,
|
|
1449
|
+
packageDir: packageThemesDir
|
|
1236
1450
|
});
|
|
1237
|
-
function
|
|
1238
|
-
|
|
1239
|
-
themesResource.ensureMeta();
|
|
1451
|
+
function ensureColorsAndTypeDir() {
|
|
1452
|
+
colorsAndTypeResource.ensureDir();
|
|
1240
1453
|
}
|
|
1241
1454
|
function readBody(req) {
|
|
1242
1455
|
return new Promise((resolve, reject) => {
|
|
@@ -1251,25 +1464,33 @@ function themeFileApi(opts) {
|
|
|
1251
1464
|
res.setHeader("Content-Type", "application/json");
|
|
1252
1465
|
res.end(JSON.stringify(data));
|
|
1253
1466
|
}
|
|
1254
|
-
function
|
|
1255
|
-
if (!
|
|
1256
|
-
|
|
1257
|
-
|
|
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 ?? {};
|
|
1258
1479
|
const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
|
|
1259
1480
|
const nextCssVars = {};
|
|
1260
1481
|
for (const [k, v] of Object.entries(cssVars)) {
|
|
1261
1482
|
if (!consumed.has(k)) nextCssVars[k] = v;
|
|
1262
1483
|
}
|
|
1263
|
-
return { ...
|
|
1484
|
+
return { ...colorsAndType, editorConfigs: nextPalettes, cssVariables: nextCssVars };
|
|
1264
1485
|
}
|
|
1265
1486
|
const SYSTEM_CASCADES_SSR = {
|
|
1266
1487
|
"system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
1267
1488
|
"system-ui-serif": 'Georgia, "Times New Roman", serif',
|
|
1268
1489
|
"system-ui-mono": 'ui-monospace, "SF Mono", Menlo, Consolas, monospace'
|
|
1269
1490
|
};
|
|
1270
|
-
function resolveFontStacks(
|
|
1271
|
-
const stacks =
|
|
1272
|
-
const sources =
|
|
1491
|
+
function resolveFontStacks(colorsAndTypeData) {
|
|
1492
|
+
const stacks = colorsAndTypeData.fontStacks;
|
|
1493
|
+
const sources = colorsAndTypeData.fontSources;
|
|
1273
1494
|
if (!stacks || stacks.length === 0) return {};
|
|
1274
1495
|
const familyById = /* @__PURE__ */ new Map();
|
|
1275
1496
|
for (const src of sources ?? []) {
|
|
@@ -1295,21 +1516,28 @@ function themeFileApi(opts) {
|
|
|
1295
1516
|
}
|
|
1296
1517
|
function regenerateTokensCss() {
|
|
1297
1518
|
const lines = [];
|
|
1298
|
-
lines.push("/* Generated by themeFileApi from the production theme
|
|
1519
|
+
lines.push("/* Generated by themeFileApi from the production theme. Do not edit. */");
|
|
1299
1520
|
lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
|
|
1300
1521
|
lines.push("");
|
|
1301
1522
|
const productionThemeName = themesResource.getProductionName();
|
|
1302
|
-
const
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
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);
|
|
1308
1536
|
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
1309
1537
|
cssVars[name] = value;
|
|
1310
1538
|
}
|
|
1311
|
-
|
|
1312
|
-
if (
|
|
1539
|
+
colorsAndTypeVarCount = Object.keys(cssVars).length;
|
|
1540
|
+
if (colorsAndTypeVarCount > 0) {
|
|
1313
1541
|
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
1314
1542
|
lines.push(":root:root {");
|
|
1315
1543
|
for (const [name, value] of Object.entries(cssVars)) {
|
|
@@ -1320,12 +1548,10 @@ function themeFileApi(opts) {
|
|
|
1320
1548
|
}
|
|
1321
1549
|
}
|
|
1322
1550
|
let componentOverrideCount = 0;
|
|
1323
|
-
if (
|
|
1551
|
+
if (import_fs4.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
1324
1552
|
const blocks = [];
|
|
1325
1553
|
for (const comp of listComponentNames()) {
|
|
1326
|
-
const
|
|
1327
|
-
if (prod === "default") continue;
|
|
1328
|
-
const prodCfg = readComponentConfig(comp, prod);
|
|
1554
|
+
const prodCfg = productionTheme?.componentConfigs[comp];
|
|
1329
1555
|
const defaultCfg = readComponentConfig(comp, "default");
|
|
1330
1556
|
if (!prodCfg || !defaultCfg) continue;
|
|
1331
1557
|
const overrides = [];
|
|
@@ -1336,7 +1562,7 @@ function themeFileApi(opts) {
|
|
|
1336
1562
|
}
|
|
1337
1563
|
}
|
|
1338
1564
|
if (overrides.length === 0) continue;
|
|
1339
|
-
const block = [` /* ${comp}
|
|
1565
|
+
const block = [` /* ${comp} */`];
|
|
1340
1566
|
for (const [varName, semanticValue] of overrides) {
|
|
1341
1567
|
block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
|
|
1342
1568
|
}
|
|
@@ -1354,24 +1580,20 @@ function themeFileApi(opts) {
|
|
|
1354
1580
|
lines.push("");
|
|
1355
1581
|
}
|
|
1356
1582
|
}
|
|
1357
|
-
if (!
|
|
1358
|
-
|
|
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 });
|
|
1359
1585
|
}
|
|
1360
|
-
|
|
1586
|
+
import_fs4.default.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
|
|
1361
1587
|
console.log(
|
|
1362
|
-
`[regenerateTokensCss] Wrote ${
|
|
1588
|
+
`[regenerateTokensCss] Wrote ${import_path4.default.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
|
|
1363
1589
|
);
|
|
1364
1590
|
}
|
|
1365
|
-
function
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
function syncFontsToCss(fileName) {
|
|
1369
|
-
const themeData = themesResource.readJson(fileName);
|
|
1370
|
-
if (!themeData) return;
|
|
1371
|
-
const sources = themeData.fontSources;
|
|
1591
|
+
function syncFontsToCss(colorsAndTypeData) {
|
|
1592
|
+
if (!colorsAndTypeData) return;
|
|
1593
|
+
const sources = colorsAndTypeData.fontSources;
|
|
1372
1594
|
if (!sources) return;
|
|
1373
1595
|
const lines = [];
|
|
1374
|
-
lines.push("/* Generated from the production
|
|
1596
|
+
lines.push("/* Generated from the production colors and type by syncFontsToCss. Do not edit. */");
|
|
1375
1597
|
lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
|
|
1376
1598
|
lines.push("");
|
|
1377
1599
|
const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
|
|
@@ -1391,11 +1613,11 @@ function themeFileApi(opts) {
|
|
|
1391
1613
|
lines.push("");
|
|
1392
1614
|
}
|
|
1393
1615
|
const content = lines.join("\n");
|
|
1394
|
-
if (!
|
|
1395
|
-
|
|
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 });
|
|
1396
1618
|
}
|
|
1397
|
-
|
|
1398
|
-
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)}`);
|
|
1399
1621
|
}
|
|
1400
1622
|
function extractAliasDeclarations(body) {
|
|
1401
1623
|
const aliases = {};
|
|
@@ -1410,11 +1632,11 @@ function themeFileApi(opts) {
|
|
|
1410
1632
|
return aliases;
|
|
1411
1633
|
}
|
|
1412
1634
|
function componentNameFromFile(filePath) {
|
|
1413
|
-
return
|
|
1635
|
+
return import_path4.default.basename(filePath, ".svelte").toLowerCase();
|
|
1414
1636
|
}
|
|
1415
1637
|
function isThemeAwareComponent(filePath) {
|
|
1416
1638
|
try {
|
|
1417
|
-
const source =
|
|
1639
|
+
const source = import_fs4.default.readFileSync(filePath, "utf-8");
|
|
1418
1640
|
return /:global\(:root\)\s*\{/.test(source);
|
|
1419
1641
|
} catch {
|
|
1420
1642
|
return false;
|
|
@@ -1423,10 +1645,10 @@ function themeFileApi(opts) {
|
|
|
1423
1645
|
function listComponentSourcePaths() {
|
|
1424
1646
|
const byName = /* @__PURE__ */ new Map();
|
|
1425
1647
|
for (const dir of COMPONENTS_SCAN_DIRS) {
|
|
1426
|
-
if (!
|
|
1427
|
-
for (const f of
|
|
1648
|
+
if (!import_fs4.default.existsSync(dir)) continue;
|
|
1649
|
+
for (const f of import_fs4.default.readdirSync(dir)) {
|
|
1428
1650
|
if (!f.endsWith(".svelte")) continue;
|
|
1429
|
-
const full =
|
|
1651
|
+
const full = import_path4.default.join(dir, f);
|
|
1430
1652
|
if (!isThemeAwareComponent(full)) continue;
|
|
1431
1653
|
const name = componentNameFromFile(f);
|
|
1432
1654
|
if (!byName.has(name)) byName.set(name, full);
|
|
@@ -1440,18 +1662,18 @@ function themeFileApi(opts) {
|
|
|
1440
1662
|
function warnOnTokenDrift(log) {
|
|
1441
1663
|
let tokensCss = "";
|
|
1442
1664
|
try {
|
|
1443
|
-
tokensCss =
|
|
1665
|
+
tokensCss = import_fs4.default.readFileSync(CSS_PATH, "utf-8");
|
|
1444
1666
|
} catch {
|
|
1445
1667
|
return;
|
|
1446
1668
|
}
|
|
1447
1669
|
let generatedCss = "";
|
|
1448
1670
|
try {
|
|
1449
|
-
generatedCss =
|
|
1671
|
+
generatedCss = import_fs4.default.readFileSync(GENERATED_CSS_PATH, "utf-8");
|
|
1450
1672
|
} catch {
|
|
1451
1673
|
}
|
|
1452
1674
|
const componentSources = listComponentSourcePaths().map((p) => ({
|
|
1453
1675
|
name: componentNameFromFile(p),
|
|
1454
|
-
source:
|
|
1676
|
+
source: import_fs4.default.readFileSync(p, "utf-8")
|
|
1455
1677
|
}));
|
|
1456
1678
|
const missing = validateTokensCss({ tokensCss, generatedCss, componentSources });
|
|
1457
1679
|
if (missing.length === 0) return;
|
|
@@ -1459,7 +1681,7 @@ function themeFileApi(opts) {
|
|
|
1459
1681
|
(m) => ` ${m.token} (referenced by ${m.referencedBy.join(", ")})`
|
|
1460
1682
|
);
|
|
1461
1683
|
log(
|
|
1462
|
-
`[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)}:
|
|
1463
1685
|
${lines.join("\n")}
|
|
1464
1686
|
These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
|
|
1465
1687
|
);
|
|
@@ -1467,37 +1689,37 @@ ${lines.join("\n")}
|
|
|
1467
1689
|
function autoMigrateAdditive(log) {
|
|
1468
1690
|
let before = "";
|
|
1469
1691
|
try {
|
|
1470
|
-
before =
|
|
1692
|
+
before = import_fs4.default.readFileSync(CSS_PATH, "utf-8");
|
|
1471
1693
|
} catch {
|
|
1472
1694
|
return;
|
|
1473
1695
|
}
|
|
1474
1696
|
const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
|
|
1475
1697
|
if (!changed) return;
|
|
1476
|
-
|
|
1698
|
+
import_fs4.default.writeFileSync(CSS_PATH, css);
|
|
1477
1699
|
log(
|
|
1478
|
-
`[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.`
|
|
1479
1701
|
);
|
|
1480
1702
|
}
|
|
1481
1703
|
function generateDefaultConfig(comp, sourcePath) {
|
|
1482
|
-
if (!
|
|
1704
|
+
if (!import_fs4.default.existsSync(sourcePath)) return;
|
|
1483
1705
|
const r = componentResource(comp);
|
|
1484
1706
|
r.ensureDir();
|
|
1485
1707
|
const defaultPath = r.filePath("default");
|
|
1486
|
-
const sourceStat =
|
|
1487
|
-
if (
|
|
1488
|
-
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);
|
|
1489
1711
|
if (defaultStat.mtimeMs >= sourceStat.mtimeMs) return;
|
|
1490
1712
|
}
|
|
1491
|
-
const source =
|
|
1713
|
+
const source = import_fs4.default.readFileSync(sourcePath, "utf-8");
|
|
1492
1714
|
const body = extractGlobalRootBody(source);
|
|
1493
1715
|
const aliases = extractAliasDeclarations(body);
|
|
1494
1716
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1495
1717
|
let createdAt = now;
|
|
1496
1718
|
let existingUpdatedAt;
|
|
1497
1719
|
let existingAliases;
|
|
1498
|
-
if (
|
|
1720
|
+
if (import_fs4.default.existsSync(defaultPath)) {
|
|
1499
1721
|
try {
|
|
1500
|
-
const existing = JSON.parse(
|
|
1722
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(defaultPath, "utf-8"));
|
|
1501
1723
|
if (existing.createdAt) createdAt = existing.createdAt;
|
|
1502
1724
|
if (typeof existing.updatedAt === "string") existingUpdatedAt = existing.updatedAt;
|
|
1503
1725
|
if (existing.aliases && typeof existing.aliases === "object") {
|
|
@@ -1523,57 +1745,191 @@ ${lines.join("\n")}
|
|
|
1523
1745
|
};
|
|
1524
1746
|
if (aliasesUnchanged && existingUpdatedAt) {
|
|
1525
1747
|
const t = /* @__PURE__ */ new Date();
|
|
1526
|
-
|
|
1748
|
+
import_fs4.default.utimesSync(defaultPath, t, t);
|
|
1527
1749
|
return;
|
|
1528
1750
|
}
|
|
1529
|
-
|
|
1751
|
+
import_fs4.default.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
|
|
1530
1752
|
}
|
|
1531
1753
|
function ensureComponentConfigsDir() {
|
|
1532
|
-
if (!
|
|
1533
|
-
|
|
1754
|
+
if (!import_fs4.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
1755
|
+
import_fs4.default.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
|
|
1534
1756
|
}
|
|
1535
1757
|
for (const sourcePath of listComponentSourcePaths()) {
|
|
1536
1758
|
const comp = componentNameFromFile(sourcePath);
|
|
1537
1759
|
const r = componentResource(comp);
|
|
1538
1760
|
r.ensureDir();
|
|
1539
1761
|
generateDefaultConfig(comp, sourcePath);
|
|
1540
|
-
r.ensureMeta();
|
|
1541
1762
|
}
|
|
1542
1763
|
}
|
|
1543
|
-
function
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
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));
|
|
1554
1776
|
}
|
|
1555
1777
|
}
|
|
1778
|
+
return legacy;
|
|
1556
1779
|
}
|
|
1557
|
-
function
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
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
|
+
);
|
|
1790
|
+
}
|
|
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;
|
|
1563
1798
|
try {
|
|
1564
|
-
|
|
1799
|
+
raw = themesResource.readJson(fileName);
|
|
1565
1800
|
} catch {
|
|
1566
|
-
return
|
|
1801
|
+
return null;
|
|
1567
1802
|
}
|
|
1568
|
-
if (
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
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
|
+
}
|
|
1814
|
+
} catch {
|
|
1573
1815
|
}
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
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
|
+
}
|
|
1865
|
+
}
|
|
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" };
|
|
1577
1933
|
}
|
|
1578
1934
|
function formatAliasGradient(v) {
|
|
1579
1935
|
const stopColor = (s) => {
|
|
@@ -1609,237 +1965,191 @@ ${lines.join("\n")}
|
|
|
1609
1965
|
return null;
|
|
1610
1966
|
}
|
|
1611
1967
|
}
|
|
1612
|
-
function syncComponentsToCss() {
|
|
1613
|
-
regenerateTokensCss();
|
|
1614
|
-
}
|
|
1615
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`;
|
|
1616
1974
|
const THEMES_ROUTE = `${API_BASE}/themes`;
|
|
1617
1975
|
const THEMES_ACTIVE_ROUTE = `${API_BASE}/themes/active`;
|
|
1618
1976
|
const THEMES_PRODUCTION_ROUTE = `${API_BASE}/themes/production`;
|
|
1619
|
-
const
|
|
1620
|
-
const MANIFESTS_ROUTE = `${API_BASE}/manifests`;
|
|
1621
|
-
const MANIFESTS_ACTIVE_ROUTE = `${API_BASE}/manifests/active`;
|
|
1622
|
-
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\\-_]+)$`);
|
|
1623
1978
|
const COMP_LIST_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)$`);
|
|
1624
1979
|
const COMP_ACTIVE_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/active$`);
|
|
1625
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$`);
|
|
1626
1982
|
const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
|
|
1627
|
-
const
|
|
1628
|
-
const
|
|
1629
|
-
const
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
const files =
|
|
1634
|
-
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);
|
|
1635
1991
|
return {
|
|
1636
1992
|
name: data?.name || fileName,
|
|
1637
1993
|
fileName,
|
|
1638
1994
|
updatedAt: data?.updatedAt || "",
|
|
1639
|
-
|
|
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)
|
|
1640
2000
|
};
|
|
1641
2001
|
});
|
|
1642
2002
|
jsonResponse(_ctx.res, 200, { files });
|
|
1643
2003
|
}
|
|
1644
|
-
async function
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
jsonResponse(res, 404, { error: "Active theme not found" });
|
|
1649
|
-
return;
|
|
1650
|
-
}
|
|
1651
|
-
const data = normalizeTheme(raw);
|
|
1652
|
-
data._fileName = activeFile;
|
|
1653
|
-
jsonResponse(res, 200, data);
|
|
1654
|
-
}
|
|
1655
|
-
async function handleSetActiveTheme({ req, res }) {
|
|
1656
|
-
const body = JSON.parse(await readBody(req));
|
|
1657
|
-
const fileName = sanitizeFileName(body.name || "default");
|
|
1658
|
-
if (themesResource.existingPath(fileName) === null) {
|
|
1659
|
-
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1660
|
-
return;
|
|
1661
|
-
}
|
|
1662
|
-
themesResource.setActiveName(fileName);
|
|
1663
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1664
|
-
}
|
|
1665
|
-
async function handleGetProductionTheme({ res }) {
|
|
1666
|
-
const prodFile = themesResource.getProductionName();
|
|
1667
|
-
const raw = themesResource.readJson(prodFile);
|
|
1668
|
-
if (!raw) {
|
|
1669
|
-
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" });
|
|
1670
2008
|
return;
|
|
1671
2009
|
}
|
|
1672
|
-
const data = normalizeTheme(raw);
|
|
1673
2010
|
jsonResponse(res, 200, {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
cssVariables: data.cssVariables || {}
|
|
2011
|
+
...resolved.data,
|
|
2012
|
+
_fileName: themesResource.getActiveName(),
|
|
2013
|
+
_source: resolved.source
|
|
1678
2014
|
});
|
|
1679
2015
|
}
|
|
1680
|
-
async function
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
2016
|
+
async function readBufferBody(req, res) {
|
|
2017
|
+
let parsed;
|
|
2018
|
+
try {
|
|
2019
|
+
parsed = JSON.parse(await readBody(req));
|
|
2020
|
+
} catch {
|
|
2021
|
+
parsed = void 0;
|
|
1686
2022
|
}
|
|
1687
|
-
if (
|
|
1688
|
-
jsonResponse(res,
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
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" });
|
|
1692
2033
|
return;
|
|
1693
2034
|
}
|
|
1694
|
-
|
|
1695
|
-
syncTokensToCss(fileName);
|
|
1696
|
-
syncFontsToCss(fileName);
|
|
1697
|
-
syncComponentsToCss();
|
|
1698
|
-
patchActiveManifest("theme", null, fileName);
|
|
1699
|
-
const data = themesResource.readJson(fileName);
|
|
1700
|
-
jsonResponse(res, 200, {
|
|
1701
|
-
ok: true,
|
|
1702
|
-
fileName,
|
|
1703
|
-
name: data?.name || fileName,
|
|
1704
|
-
updatedAt: data?.updatedAt || ""
|
|
1705
|
-
});
|
|
2035
|
+
jsonResponse(res, 200, normalizeColorsAndType(raw));
|
|
1706
2036
|
}
|
|
1707
|
-
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 }) {
|
|
1708
2048
|
const [fileName] = params;
|
|
1709
|
-
|
|
2049
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2050
|
+
const filePath = colorsAndTypeResource.filePath(fileName);
|
|
1710
2051
|
if (req.method === "GET") {
|
|
1711
|
-
const raw =
|
|
2052
|
+
const raw = colorsAndTypeResource.readJson(fileName);
|
|
1712
2053
|
if (!raw) {
|
|
1713
2054
|
jsonResponse(res, 404, { error: "Not found" });
|
|
1714
2055
|
return;
|
|
1715
2056
|
}
|
|
1716
|
-
const data =
|
|
2057
|
+
const data = normalizeColorsAndType(raw);
|
|
1717
2058
|
data._fileName = fileName;
|
|
1718
2059
|
jsonResponse(res, 200, data);
|
|
1719
2060
|
return;
|
|
1720
2061
|
}
|
|
1721
2062
|
if (req.method === "PUT") {
|
|
1722
2063
|
if (fileName === "default") {
|
|
1723
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
2064
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default colors and type (live from package)" });
|
|
1724
2065
|
return;
|
|
1725
2066
|
}
|
|
1726
2067
|
const body = JSON.parse(await readBody(req));
|
|
1727
2068
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1728
|
-
if (
|
|
2069
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
1729
2070
|
try {
|
|
1730
|
-
const existing = JSON.parse(
|
|
2071
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(filePath, "utf-8"));
|
|
1731
2072
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1732
2073
|
} catch {
|
|
1733
2074
|
}
|
|
1734
2075
|
}
|
|
1735
2076
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1736
|
-
|
|
1737
|
-
if (fileName === themesResource.getProductionName()) {
|
|
1738
|
-
syncTokensToCss(fileName);
|
|
1739
|
-
syncFontsToCss(fileName);
|
|
1740
|
-
syncComponentsToCss();
|
|
1741
|
-
}
|
|
2077
|
+
import_fs4.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
1742
2078
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1743
2079
|
return;
|
|
1744
2080
|
}
|
|
1745
2081
|
if (req.method === "DELETE") {
|
|
1746
2082
|
if (fileName === "default") {
|
|
1747
|
-
jsonResponse(res, 403, { error: "Cannot delete the default
|
|
2083
|
+
jsonResponse(res, 403, { error: "Cannot delete the default colors and type" });
|
|
1748
2084
|
return;
|
|
1749
2085
|
}
|
|
1750
|
-
if (
|
|
2086
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
2087
|
+
import_fs4.default.unlinkSync(filePath);
|
|
2088
|
+
} else if (colorsAndTypeResource.existingPath(fileName)) {
|
|
1751
2089
|
jsonResponse(res, 403, {
|
|
1752
|
-
error: "Cannot delete the
|
|
1753
|
-
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"
|
|
1754
2092
|
});
|
|
1755
2093
|
return;
|
|
1756
2094
|
}
|
|
1757
|
-
if (import_fs3.default.existsSync(filePath)) {
|
|
1758
|
-
import_fs3.default.unlinkSync(filePath);
|
|
1759
|
-
if (themesResource.getActiveName() === fileName) {
|
|
1760
|
-
themesResource.setActiveName("default");
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
2095
|
jsonResponse(res, 200, { ok: true });
|
|
1764
2096
|
return;
|
|
1765
2097
|
}
|
|
1766
2098
|
}
|
|
1767
2099
|
async function handleListComponents({ res }) {
|
|
1768
|
-
const
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
});
|
|
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
|
+
}));
|
|
1776
2108
|
jsonResponse(res, 200, { components });
|
|
1777
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
|
+
}
|
|
1778
2115
|
async function handleGetComponentActive({ params, res }) {
|
|
1779
2116
|
const [comp] = params;
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
const cfg = readComponentConfig(comp, activeFile);
|
|
1783
|
-
if (!cfg) {
|
|
2117
|
+
const resolved = resolveLiveComponentConfig(comp, activeTheme());
|
|
2118
|
+
if (!resolved) {
|
|
1784
2119
|
jsonResponse(res, 404, { error: "Active config not found" });
|
|
1785
2120
|
return;
|
|
1786
2121
|
}
|
|
1787
|
-
jsonResponse(res, 200, {
|
|
2122
|
+
jsonResponse(res, 200, {
|
|
2123
|
+
...resolved.data,
|
|
2124
|
+
_fileName: themesResource.getActiveName(),
|
|
2125
|
+
_source: resolved.source
|
|
2126
|
+
});
|
|
1788
2127
|
}
|
|
1789
|
-
async function
|
|
2128
|
+
async function handleGetComponentWorking({ params, res }) {
|
|
1790
2129
|
const [comp] = params;
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
if (r.existingPath(fileName) === null) {
|
|
1795
|
-
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" });
|
|
1796
2133
|
return;
|
|
1797
2134
|
}
|
|
1798
|
-
|
|
1799
|
-
r.setActiveName(fileName);
|
|
1800
|
-
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
2135
|
+
jsonResponse(res, 200, raw);
|
|
1801
2136
|
}
|
|
1802
|
-
async function
|
|
2137
|
+
async function handleSetComponentWorking({ params, req, res }) {
|
|
1803
2138
|
const [comp] = params;
|
|
1804
|
-
|
|
1805
|
-
const
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
name: cfg?.name || prodFile,
|
|
1810
|
-
aliases: cfg?.aliases || {}
|
|
1811
|
-
});
|
|
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 });
|
|
1812
2144
|
}
|
|
1813
|
-
async function
|
|
2145
|
+
async function handleClearComponentWorking({ params, res }) {
|
|
1814
2146
|
const [comp] = params;
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
const r = componentResource(comp);
|
|
1818
|
-
if (r.existingPath(fileName) === null) {
|
|
1819
|
-
jsonResponse(res, 404, { error: "Config not found" });
|
|
1820
|
-
return;
|
|
1821
|
-
}
|
|
1822
|
-
if (manifestsResource.getActiveName() === "default") {
|
|
1823
|
-
jsonResponse(res, 409, {
|
|
1824
|
-
error: "Active manifest is protected. Save As first.",
|
|
1825
|
-
code: "ACTIVE_IS_PROTECTED"
|
|
1826
|
-
});
|
|
1827
|
-
return;
|
|
1828
|
-
}
|
|
1829
|
-
r.ensureDir();
|
|
1830
|
-
r.setProductionName(fileName);
|
|
1831
|
-
syncComponentsToCss();
|
|
1832
|
-
patchActiveManifest("component", comp, fileName);
|
|
1833
|
-
const cfg = readComponentConfig(comp, fileName);
|
|
1834
|
-
jsonResponse(res, 200, {
|
|
1835
|
-
ok: true,
|
|
1836
|
-
fileName,
|
|
1837
|
-
name: cfg?.name || fileName,
|
|
1838
|
-
aliases: cfg?.aliases || {}
|
|
1839
|
-
});
|
|
2147
|
+
componentResource(comp).clearWorking();
|
|
2148
|
+
jsonResponse(res, 200, { ok: true });
|
|
1840
2149
|
}
|
|
1841
2150
|
async function handleComponentConfigByName({ params, req, res }) {
|
|
1842
2151
|
const [comp, name] = params;
|
|
2152
|
+
if (rejectReservedName(res, name)) return;
|
|
1843
2153
|
const r = componentResource(comp);
|
|
1844
2154
|
const configPath = r.filePath(name);
|
|
1845
2155
|
if (req.method === "GET") {
|
|
@@ -1862,19 +2172,16 @@ ${lines.join("\n")}
|
|
|
1862
2172
|
body.component = comp;
|
|
1863
2173
|
body.name = body.name || name;
|
|
1864
2174
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1865
|
-
if (
|
|
2175
|
+
if (import_fs4.default.existsSync(configPath)) {
|
|
1866
2176
|
try {
|
|
1867
|
-
const existing = JSON.parse(
|
|
2177
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(configPath, "utf-8"));
|
|
1868
2178
|
if (existing.createdAt) body.createdAt = existing.createdAt;
|
|
1869
2179
|
} catch {
|
|
1870
2180
|
}
|
|
1871
2181
|
}
|
|
1872
2182
|
if (!body.createdAt) body.createdAt = body.updatedAt;
|
|
1873
2183
|
r.ensureDir();
|
|
1874
|
-
|
|
1875
|
-
if (r.getProductionName() === name) {
|
|
1876
|
-
syncComponentsToCss();
|
|
1877
|
-
}
|
|
2184
|
+
import_fs4.default.writeFileSync(configPath, JSON.stringify(body, null, 2));
|
|
1878
2185
|
jsonResponse(res, 200, { ok: true, fileName: name });
|
|
1879
2186
|
return;
|
|
1880
2187
|
}
|
|
@@ -1883,16 +2190,7 @@ ${lines.join("\n")}
|
|
|
1883
2190
|
jsonResponse(res, 403, { error: "Cannot delete default config" });
|
|
1884
2191
|
return;
|
|
1885
2192
|
}
|
|
1886
|
-
if (
|
|
1887
|
-
import_fs3.default.unlinkSync(configPath);
|
|
1888
|
-
if (r.getActiveName() === name) {
|
|
1889
|
-
r.setActiveName("default");
|
|
1890
|
-
}
|
|
1891
|
-
if (r.getProductionName() === name) {
|
|
1892
|
-
r.setProductionName("default");
|
|
1893
|
-
syncComponentsToCss();
|
|
1894
|
-
}
|
|
1895
|
-
}
|
|
2193
|
+
if (import_fs4.default.existsSync(configPath)) import_fs4.default.unlinkSync(configPath);
|
|
1896
2194
|
jsonResponse(res, 200, { ok: true });
|
|
1897
2195
|
return;
|
|
1898
2196
|
}
|
|
@@ -1900,199 +2198,190 @@ ${lines.join("\n")}
|
|
|
1900
2198
|
async function handleListComponentConfigs({ params, res }) {
|
|
1901
2199
|
const [comp] = params;
|
|
1902
2200
|
const r = componentResource(comp);
|
|
1903
|
-
if (!
|
|
2201
|
+
if (!import_fs4.default.existsSync(r.dir)) {
|
|
1904
2202
|
jsonResponse(res, 404, { error: "Component not found" });
|
|
1905
2203
|
return;
|
|
1906
2204
|
}
|
|
1907
|
-
const activeFile = r.getActiveName();
|
|
1908
|
-
const productionFile = r.getProductionName();
|
|
1909
2205
|
const files = r.listNames().map((fileName) => {
|
|
1910
2206
|
const data = r.readJson(fileName);
|
|
1911
2207
|
return {
|
|
1912
2208
|
name: data?.name || fileName,
|
|
1913
2209
|
fileName,
|
|
1914
|
-
updatedAt: data?.updatedAt || ""
|
|
1915
|
-
isActive: fileName === activeFile,
|
|
1916
|
-
isProduction: fileName === productionFile
|
|
2210
|
+
updatedAt: data?.updatedAt || ""
|
|
1917
2211
|
};
|
|
1918
2212
|
});
|
|
1919
|
-
jsonResponse(res, 200, { component: comp, files
|
|
2213
|
+
jsonResponse(res, 200, { component: comp, files });
|
|
1920
2214
|
}
|
|
1921
|
-
async function
|
|
1922
|
-
const activeFile =
|
|
1923
|
-
const
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
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,
|
|
1927
2229
|
fileName,
|
|
1928
|
-
updatedAt:
|
|
2230
|
+
updatedAt: read.theme.updatedAt,
|
|
1929
2231
|
isActive: fileName === activeFile,
|
|
2232
|
+
isProduction: fileName === productionFile,
|
|
1930
2233
|
isProtected: fileName === "default"
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
|
-
jsonResponse(res, 200, { files, activeFile });
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
jsonResponse(res, 200, { files, activeFile, productionFile });
|
|
2237
|
+
}
|
|
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");
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: fileName });
|
|
1934
2246
|
}
|
|
1935
|
-
async function
|
|
1936
|
-
const activeFile =
|
|
1937
|
-
const
|
|
1938
|
-
if (!
|
|
1939
|
-
|
|
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");
|
|
1940
2252
|
return;
|
|
1941
2253
|
}
|
|
1942
|
-
|
|
1943
|
-
data._fileName = activeFile;
|
|
1944
|
-
jsonResponse(res, 200, data);
|
|
2254
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: activeFile });
|
|
1945
2255
|
}
|
|
1946
|
-
async function
|
|
2256
|
+
async function handleSetActiveTheme({ req, res }) {
|
|
1947
2257
|
const body = JSON.parse(await readBody(req));
|
|
1948
2258
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1949
|
-
if (
|
|
1950
|
-
jsonResponse(res, 404, { error: "
|
|
2259
|
+
if (themesResource.existingPath(fileName) === null) {
|
|
2260
|
+
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1951
2261
|
return;
|
|
1952
2262
|
}
|
|
1953
|
-
|
|
2263
|
+
themesResource.setActiveName(fileName);
|
|
1954
2264
|
jsonResponse(res, 200, { ok: true, activeFile: fileName });
|
|
1955
2265
|
}
|
|
1956
|
-
async function
|
|
2266
|
+
async function handleThemeByName({ params, req, res }) {
|
|
1957
2267
|
const [fileName] = params;
|
|
1958
|
-
|
|
2268
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2269
|
+
const filePath = themesResource.filePath(fileName);
|
|
1959
2270
|
if (req.method === "GET") {
|
|
1960
|
-
const
|
|
1961
|
-
if (!
|
|
1962
|
-
|
|
2271
|
+
const read = readTheme(fileName);
|
|
2272
|
+
if (!read) {
|
|
2273
|
+
respondUnreadableTheme(res, fileName, "Not found");
|
|
1963
2274
|
return;
|
|
1964
2275
|
}
|
|
1965
|
-
|
|
1966
|
-
data._fileName = fileName;
|
|
1967
|
-
jsonResponse(res, 200, data);
|
|
2276
|
+
jsonResponse(res, 200, { ...read.theme, _fileName: fileName });
|
|
1968
2277
|
return;
|
|
1969
2278
|
}
|
|
1970
2279
|
if (req.method === "PUT") {
|
|
1971
2280
|
if (fileName === "default") {
|
|
1972
|
-
jsonResponse(res, 403, { error: "Cannot overwrite the default
|
|
2281
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default theme" });
|
|
1973
2282
|
return;
|
|
1974
2283
|
}
|
|
1975
2284
|
const body = JSON.parse(await readBody(req));
|
|
1976
|
-
|
|
1977
|
-
|
|
2285
|
+
const { theme } = normalizeTheme(body, diskThemeResolvers);
|
|
2286
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2287
|
+
if (import_fs4.default.existsSync(filePath)) {
|
|
1978
2288
|
try {
|
|
1979
|
-
const existing = JSON.parse(
|
|
1980
|
-
if (existing.createdAt)
|
|
2289
|
+
const existing = JSON.parse(import_fs4.default.readFileSync(filePath, "utf-8"));
|
|
2290
|
+
if (existing.createdAt) theme.createdAt = existing.createdAt;
|
|
1981
2291
|
} catch {
|
|
1982
2292
|
}
|
|
1983
2293
|
}
|
|
1984
|
-
|
|
1985
|
-
import_fs3.default.writeFileSync(filePath, JSON.stringify(body, null, 2));
|
|
2294
|
+
writeTheme(fileName, theme);
|
|
1986
2295
|
jsonResponse(res, 200, { ok: true, fileName });
|
|
1987
2296
|
return;
|
|
1988
2297
|
}
|
|
1989
2298
|
if (req.method === "DELETE") {
|
|
1990
2299
|
if (fileName === "default") {
|
|
1991
|
-
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
|
+
});
|
|
1992
2308
|
return;
|
|
1993
2309
|
}
|
|
1994
|
-
if (
|
|
1995
|
-
|
|
1996
|
-
if (
|
|
1997
|
-
|
|
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");
|
|
1998
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;
|
|
1999
2321
|
}
|
|
2000
2322
|
jsonResponse(res, 200, { ok: true });
|
|
2001
2323
|
return;
|
|
2002
2324
|
}
|
|
2003
2325
|
}
|
|
2004
|
-
async function
|
|
2326
|
+
async function handleApplyTheme({ params, res }) {
|
|
2005
2327
|
const [fileName] = params;
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2328
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2329
|
+
const read = readTheme(fileName);
|
|
2330
|
+
if (!read) {
|
|
2331
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
2009
2332
|
return;
|
|
2010
2333
|
}
|
|
2011
|
-
const
|
|
2012
|
-
|
|
2013
|
-
|
|
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" });
|
|
2014
2338
|
return;
|
|
2015
2339
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
const
|
|
2019
|
-
const
|
|
2020
|
-
|
|
2021
|
-
if (!knownComponents.has(comp)) continue;
|
|
2022
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
2023
|
-
const r = componentResource(comp);
|
|
2024
|
-
if (r.existingPath(sanitized) === null) {
|
|
2025
|
-
jsonResponse(res, 422, {
|
|
2026
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
2027
|
-
});
|
|
2028
|
-
return;
|
|
2029
|
-
}
|
|
2030
|
-
apply.push([comp, sanitized]);
|
|
2031
|
-
}
|
|
2032
|
-
themesResource.setActiveName(themeName);
|
|
2033
|
-
themesResource.setProductionName(themeName);
|
|
2034
|
-
syncTokensToCss(themeName);
|
|
2035
|
-
syncFontsToCss(themeName);
|
|
2036
|
-
const themeData = normalizeTheme(themesResource.readJson(themeName));
|
|
2037
|
-
themeData._fileName = themeName;
|
|
2038
|
-
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];
|
|
2039
2345
|
const r = componentResource(comp);
|
|
2040
|
-
r.
|
|
2041
|
-
r.
|
|
2042
|
-
const cfg = readComponentConfig(comp, configFile);
|
|
2043
|
-
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
|
|
2346
|
+
if (embedded) r.writeWorking({ ...embedded, component: comp });
|
|
2347
|
+
else r.clearWorking();
|
|
2044
2348
|
}
|
|
2349
|
+
themesResource.setActiveName(fileName);
|
|
2350
|
+
const resolvedConfigs = {};
|
|
2045
2351
|
for (const comp of knownComponents) {
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
const cfg = readComponentConfig(comp, activeName);
|
|
2049
|
-
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 };
|
|
2050
2354
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2355
|
+
const resolvedColorsAndType = resolveLiveColorsAndType(theme);
|
|
2356
|
+
const skippedComponents = Object.keys(theme.componentConfigs).filter(
|
|
2357
|
+
(comp) => !knownComponents.includes(comp)
|
|
2358
|
+
);
|
|
2053
2359
|
jsonResponse(res, 200, {
|
|
2054
2360
|
ok: true,
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2361
|
+
theme: { ...theme, _fileName: fileName },
|
|
2362
|
+
colorsAndType: resolvedColorsAndType && {
|
|
2363
|
+
...resolvedColorsAndType.data,
|
|
2364
|
+
_fileName: fileName,
|
|
2365
|
+
_source: resolvedColorsAndType.source
|
|
2366
|
+
},
|
|
2367
|
+
componentConfigs: resolvedConfigs,
|
|
2368
|
+
skippedComponents
|
|
2058
2369
|
});
|
|
2059
2370
|
}
|
|
2060
|
-
async function
|
|
2371
|
+
async function handleExportTheme({ params, res }) {
|
|
2061
2372
|
const [fileName] = params;
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
}
|
|
2067
|
-
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
2068
|
-
const theme = themesResource.readJson(themeName);
|
|
2069
|
-
if (!theme) {
|
|
2070
|
-
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
2373
|
+
if (rejectReservedName(res, fileName)) return;
|
|
2374
|
+
const read = readTheme(fileName);
|
|
2375
|
+
if (!read) {
|
|
2376
|
+
respondUnreadableTheme(res, fileName, "Theme not found");
|
|
2071
2377
|
return;
|
|
2072
2378
|
}
|
|
2073
|
-
const knownComponents = new Set(listComponentNames());
|
|
2074
|
-
const componentConfigs = {};
|
|
2075
|
-
for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
|
|
2076
|
-
if (!knownComponents.has(comp)) continue;
|
|
2077
|
-
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
2078
|
-
if (sanitized === "default") continue;
|
|
2079
|
-
const cfg = componentResource(comp).readJson(sanitized);
|
|
2080
|
-
if (!cfg) {
|
|
2081
|
-
jsonResponse(res, 422, {
|
|
2082
|
-
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
2083
|
-
});
|
|
2084
|
-
return;
|
|
2085
|
-
}
|
|
2086
|
-
componentConfigs[`${comp}/${sanitized}`] = cfg;
|
|
2087
|
-
}
|
|
2088
2379
|
const bundle = {
|
|
2089
|
-
kind: "
|
|
2090
|
-
schemaVersion:
|
|
2380
|
+
kind: "theme-bundle",
|
|
2381
|
+
schemaVersion: THEME_SCHEMA_VERSION,
|
|
2091
2382
|
liveTokensVersion: PKG_VERSION,
|
|
2092
2383
|
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2093
|
-
manifest
|
|
2094
|
-
theme,
|
|
2095
|
-
componentConfigs
|
|
2384
|
+
manifest: read.theme
|
|
2096
2385
|
};
|
|
2097
2386
|
res.statusCode = 200;
|
|
2098
2387
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -2105,7 +2394,7 @@ ${lines.join("\n")}
|
|
|
2105
2394
|
function nextAvailableName2(exists, baseName) {
|
|
2106
2395
|
return nextAvailableName(exists, sanitizeFileName(baseName));
|
|
2107
2396
|
}
|
|
2108
|
-
async function
|
|
2397
|
+
async function handleImportTheme({ req, res }) {
|
|
2109
2398
|
let bundle;
|
|
2110
2399
|
try {
|
|
2111
2400
|
bundle = JSON.parse(await readBody(req));
|
|
@@ -2113,117 +2402,112 @@ ${lines.join("\n")}
|
|
|
2113
2402
|
jsonResponse(res, 400, { error: "Body is not valid JSON" });
|
|
2114
2403
|
return;
|
|
2115
2404
|
}
|
|
2116
|
-
if (!bundle || bundle.kind
|
|
2405
|
+
if (!bundle || !Object.values(BUNDLE_KIND_BY_VERSION).includes(bundle.kind)) {
|
|
2117
2406
|
jsonResponse(res, 400, {
|
|
2118
|
-
error: "Not a
|
|
2407
|
+
error: "Not a theme bundle (kind discriminator missing or wrong)"
|
|
2119
2408
|
});
|
|
2120
2409
|
return;
|
|
2121
2410
|
}
|
|
2122
|
-
if (bundle.
|
|
2411
|
+
if (bundle.kind !== BUNDLE_KIND_BY_VERSION[bundle.schemaVersion]) {
|
|
2123
2412
|
jsonResponse(res, 400, {
|
|
2124
|
-
error: `Unsupported bundle schemaVersion
|
|
2413
|
+
error: `Unsupported bundle: kind "${bundle.kind}" with schemaVersion ${bundle.schemaVersion}`
|
|
2125
2414
|
});
|
|
2126
2415
|
return;
|
|
2127
2416
|
}
|
|
2128
|
-
if (!bundle.manifest
|
|
2129
|
-
jsonResponse(res, 400, { error: "Bundle missing
|
|
2417
|
+
if (!bundle.manifest) {
|
|
2418
|
+
jsonResponse(res, 400, { error: "Bundle missing theme" });
|
|
2419
|
+
return;
|
|
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" });
|
|
2130
2430
|
return;
|
|
2131
2431
|
}
|
|
2432
|
+
theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2132
2433
|
const renames = {};
|
|
2133
|
-
const
|
|
2134
|
-
const
|
|
2135
|
-
const finalThemeName = nextAvailableName2(
|
|
2434
|
+
const originalName = sanitizeFileName(theme.name || "imported");
|
|
2435
|
+
const finalName = nextAvailableName2(
|
|
2136
2436
|
(n) => themesResource.existingPath(n) !== null,
|
|
2137
|
-
|
|
2437
|
+
originalName
|
|
2138
2438
|
);
|
|
2139
|
-
if (
|
|
2140
|
-
renames[`theme:${
|
|
2439
|
+
if (finalName !== originalName) {
|
|
2440
|
+
renames[`theme:${originalName}`] = finalName;
|
|
2141
2441
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
if (finalName !== originalName) {
|
|
2159
|
-
renames[`componentConfig:${comp}/${originalName}`] = finalName;
|
|
2160
|
-
}
|
|
2161
|
-
componentRenames[`${comp}/${originalName}`] = finalName;
|
|
2162
|
-
const cfgBody = { ...cfgValue };
|
|
2163
|
-
cfgBody.component = comp;
|
|
2164
|
-
cfgBody.name = finalName;
|
|
2165
|
-
cfgBody.updatedAt = now;
|
|
2166
|
-
if (!cfgBody.createdAt) cfgBody.createdAt = now;
|
|
2167
|
-
r.ensureDir();
|
|
2168
|
-
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;
|
|
2169
2458
|
}
|
|
2170
|
-
const
|
|
2171
|
-
|
|
2172
|
-
theme
|
|
2173
|
-
|
|
2174
|
-
};
|
|
2175
|
-
for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
|
|
2176
|
-
const original = String(configName);
|
|
2177
|
-
if (original === "default") {
|
|
2178
|
-
rewrittenManifest.componentConfigs[comp] = "default";
|
|
2179
|
-
continue;
|
|
2180
|
-
}
|
|
2181
|
-
const finalName = componentRenames[`${comp}/${original}`] ?? original;
|
|
2182
|
-
rewrittenManifest.componentConfigs[comp] = finalName;
|
|
2183
|
-
}
|
|
2184
|
-
rewrittenManifest.updatedAt = now;
|
|
2185
|
-
if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
|
|
2186
|
-
const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
|
|
2187
|
-
const finalManifestName = nextAvailableName2(
|
|
2188
|
-
(n) => manifestsResource.existingPath(n) !== null,
|
|
2189
|
-
originalManifestName
|
|
2190
|
-
);
|
|
2191
|
-
if (finalManifestName !== originalManifestName) {
|
|
2192
|
-
renames[`manifest:${originalManifestName}`] = finalManifestName;
|
|
2459
|
+
const read = readTheme(activeName);
|
|
2460
|
+
if (!read) {
|
|
2461
|
+
respondUnreadableTheme(res, activeName, "Active theme not found");
|
|
2462
|
+
return;
|
|
2193
2463
|
}
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
JSON.stringify(rewrittenManifest, null, 2)
|
|
2198
|
-
);
|
|
2464
|
+
themesResource.setProductionName(activeName);
|
|
2465
|
+
regenerateTokensCss();
|
|
2466
|
+
syncFontsToCss(read.theme.colorsAndType);
|
|
2199
2467
|
jsonResponse(res, 200, {
|
|
2200
2468
|
ok: true,
|
|
2201
|
-
|
|
2202
|
-
|
|
2469
|
+
productionTheme: {
|
|
2470
|
+
fileName: activeName,
|
|
2471
|
+
name: read.theme.name,
|
|
2472
|
+
updatedAt: read.theme.updatedAt
|
|
2473
|
+
}
|
|
2203
2474
|
});
|
|
2204
2475
|
}
|
|
2205
2476
|
function methodNotAllowed({ res }) {
|
|
2206
2477
|
jsonResponse(res, 405, { error: "Method not allowed" });
|
|
2207
2478
|
}
|
|
2479
|
+
let legacyLayout = null;
|
|
2208
2480
|
const routes = [
|
|
2209
|
-
//
|
|
2210
|
-
|
|
2211
|
-
{ method: "GET", pattern:
|
|
2212
|
-
{ method: "
|
|
2213
|
-
{ method: "
|
|
2214
|
-
{ 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 },
|
|
2215
2494
|
// Component configs — list of components
|
|
2216
2495
|
{ method: "GET", pattern: COMPONENT_CONFIGS_ROUTE, handler: handleListComponents },
|
|
2217
2496
|
// Component configs — :comp/active (must precede :comp/:name)
|
|
2218
2497
|
{ method: "GET", pattern: COMP_ACTIVE_REGEX, handler: handleGetComponentActive },
|
|
2219
|
-
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler:
|
|
2498
|
+
{ method: "PUT", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
2220
2499
|
{ method: "POST", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
2221
2500
|
{ method: "DELETE", pattern: COMP_ACTIVE_REGEX, handler: methodNotAllowed },
|
|
2222
|
-
// Component configs — :comp/production
|
|
2223
|
-
{ method: "GET", pattern: COMP_PRODUCTION_REGEX, handler:
|
|
2224
|
-
{ 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 },
|
|
2225
2504
|
{ method: "POST", pattern: COMP_PRODUCTION_REGEX, handler: methodNotAllowed },
|
|
2226
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 },
|
|
2227
2511
|
// Component configs — :comp/:name (must precede :comp listing)
|
|
2228
2512
|
{ method: "GET", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
2229
2513
|
{ method: "PUT", pattern: COMP_BY_NAME_REGEX, handler: handleComponentConfigByName },
|
|
@@ -2231,33 +2515,44 @@ ${lines.join("\n")}
|
|
|
2231
2515
|
{ method: "POST", pattern: COMP_BY_NAME_REGEX, handler: methodNotAllowed },
|
|
2232
2516
|
// Component configs — list configs for :comp (broadest, runs last among comp routes)
|
|
2233
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 },
|
|
2234
2547
|
// Themes — :name CRUD (broadest theme route, runs last)
|
|
2235
2548
|
{ method: "GET", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
2236
2549
|
{ method: "PUT", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
2237
2550
|
{ method: "DELETE", pattern: THEME_BY_NAME_REGEX, handler: handleThemeByName },
|
|
2238
|
-
//
|
|
2239
|
-
{ method: "
|
|
2240
|
-
{ method: "GET", pattern:
|
|
2241
|
-
{ method: "
|
|
2242
|
-
|
|
2243
|
-
{ method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
|
|
2244
|
-
{ method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2245
|
-
{ method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2246
|
-
{ method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
2247
|
-
// Manifests — :name/apply (more specific than :name)
|
|
2248
|
-
{ method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
|
|
2249
|
-
{ method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
2250
|
-
{ method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
2251
|
-
{ method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
2252
|
-
// Manifests — :name/export (more specific than :name)
|
|
2253
|
-
{ method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
|
|
2254
|
-
{ method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2255
|
-
{ method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2256
|
-
{ method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
2257
|
-
// Manifests — :name CRUD (broadest manifest route, runs last)
|
|
2258
|
-
{ method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
2259
|
-
{ method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
2260
|
-
{ 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 }
|
|
2261
2556
|
];
|
|
2262
2557
|
return {
|
|
2263
2558
|
name: "theme-file-api",
|
|
@@ -2271,19 +2566,42 @@ ${lines.join("\n")}
|
|
|
2271
2566
|
};
|
|
2272
2567
|
},
|
|
2273
2568
|
configureServer(server) {
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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
|
+
}
|
|
2280
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
|
+
}
|
|
2281
2598
|
const handled = await dispatch(req, res, routes);
|
|
2282
2599
|
if (!handled) next();
|
|
2283
2600
|
});
|
|
2284
2601
|
},
|
|
2285
2602
|
handleHotUpdate(ctx) {
|
|
2286
|
-
|
|
2603
|
+
if (legacyLayout) return;
|
|
2604
|
+
const normalized = import_path4.default.resolve(ctx.file);
|
|
2287
2605
|
if (!COMPONENTS_SCAN_DIRS.some((d) => normalized.startsWith(d))) return;
|
|
2288
2606
|
if (!normalized.endsWith(".svelte")) return;
|
|
2289
2607
|
if (!isThemeAwareComponent(normalized)) return;
|