@motion-proto/live-tokens 0.25.1 → 0.28.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-build-page/SKILL.md +6 -4
- package/README.md +28 -3
- package/dist-plugin/index.cjs +120 -115
- package/dist-plugin/index.js +120 -115
- package/package.json +14 -5
- package/src/editor/core/store/editorPersistence.ts +23 -1
- package/src/editor/docs/CodeBlock.svelte +92 -0
- package/src/editor/docs/Docs.svelte +658 -0
- package/src/editor/docs/Docs.svelte.d.ts +2 -0
- package/src/editor/docs/chapters.ts +44 -0
- package/src/editor/docs/content/01-overview.md +31 -0
- package/src/editor/docs/content/creating-components.md +40 -0
- package/src/editor/docs/content/editing-tokens.md +74 -0
- package/src/editor/docs/content/getting-started.md +67 -0
- package/src/editor/docs/content/themes-workflow.md +60 -0
- package/src/editor/overlay/LiveTokensRouter.svelte +71 -13
- package/src/editor/pages/ComponentEditorPage.svelte +0 -11
- package/src/editor/ui/ManifestFileManager.svelte +15 -5
- package/src/editor/ui/ThemeFileManager.svelte +6 -2
- package/src/live-tokens/data/manifests/default.json +35 -0
- package/src/live-tokens/data/themes/default.json +2295 -0
- package/src/live-tokens/data/tokens.generated.css +9 -9
- package/src/system/components/CodeSnippet.svelte +4 -0
- package/src/system/components/SideNavigation.svelte +13 -13
- package/template/README.md +2 -1
package/dist-plugin/index.js
CHANGED
|
@@ -435,6 +435,8 @@ function versionedFileResourceServer(opts) {
|
|
|
435
435
|
const activePath = path.join(dir, "_active.json");
|
|
436
436
|
const productionPath = path.join(dir, "_production.json");
|
|
437
437
|
const defaultName = opts.defaultName ?? "default";
|
|
438
|
+
const resolvedDir = path.resolve(dir);
|
|
439
|
+
const resolvedPackageDir = opts.packageDir ? path.resolve(opts.packageDir) : null;
|
|
438
440
|
function ensureDir() {
|
|
439
441
|
if (!fs.existsSync(dir)) {
|
|
440
442
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -451,6 +453,37 @@ function versionedFileResourceServer(opts) {
|
|
|
451
453
|
function filePath(name) {
|
|
452
454
|
return path.join(dir, `${name}.json`);
|
|
453
455
|
}
|
|
456
|
+
function existingPath(name) {
|
|
457
|
+
const local = filePath(name);
|
|
458
|
+
if (fs.existsSync(local)) return local;
|
|
459
|
+
if (resolvedPackageDir) {
|
|
460
|
+
const pkg = path.join(resolvedPackageDir, `${name}.json`);
|
|
461
|
+
if (fs.existsSync(pkg)) return pkg;
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
function readJson(name) {
|
|
466
|
+
const resolved = existingPath(name);
|
|
467
|
+
if (!resolved) return null;
|
|
468
|
+
return JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
|
469
|
+
}
|
|
470
|
+
function listNames() {
|
|
471
|
+
const names = [];
|
|
472
|
+
const seen = /* @__PURE__ */ new Set();
|
|
473
|
+
const collect = (d) => {
|
|
474
|
+
if (!d || !fs.existsSync(d)) return;
|
|
475
|
+
for (const f of fs.readdirSync(d)) {
|
|
476
|
+
if (!f.endsWith(".json") || f.startsWith("_")) continue;
|
|
477
|
+
const name = f.slice(0, -".json".length);
|
|
478
|
+
if (seen.has(name)) continue;
|
|
479
|
+
seen.add(name);
|
|
480
|
+
names.push(name);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
collect(resolvedDir);
|
|
484
|
+
if (resolvedPackageDir && resolvedPackageDir !== resolvedDir) collect(resolvedPackageDir);
|
|
485
|
+
return names;
|
|
486
|
+
}
|
|
454
487
|
function getActiveName() {
|
|
455
488
|
try {
|
|
456
489
|
const data = JSON.parse(fs.readFileSync(activePath, "utf-8"));
|
|
@@ -480,6 +513,9 @@ function versionedFileResourceServer(opts) {
|
|
|
480
513
|
ensureDir,
|
|
481
514
|
ensureMeta,
|
|
482
515
|
filePath,
|
|
516
|
+
existingPath,
|
|
517
|
+
readJson,
|
|
518
|
+
listNames,
|
|
483
519
|
getActiveName,
|
|
484
520
|
getProductionName,
|
|
485
521
|
setActiveName,
|
|
@@ -566,36 +602,42 @@ function themeFileApi(opts) {
|
|
|
566
602
|
"system",
|
|
567
603
|
"components"
|
|
568
604
|
);
|
|
605
|
+
const packageDataDir = path2.resolve(
|
|
606
|
+
path2.dirname(fileURLToPath(import.meta.url)),
|
|
607
|
+
"..",
|
|
608
|
+
"src",
|
|
609
|
+
"live-tokens",
|
|
610
|
+
"data"
|
|
611
|
+
);
|
|
612
|
+
const packageThemesDir = path2.join(packageDataDir, "themes");
|
|
613
|
+
const packageManifestsDir = path2.join(packageDataDir, "manifests");
|
|
614
|
+
const packageComponentConfigsDir = path2.join(packageDataDir, "component-configs");
|
|
569
615
|
const COMPONENTS_SCAN_DIRS = [...consumerComponentDirs];
|
|
570
616
|
if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs2.existsSync(packageComponentsDir)) {
|
|
571
617
|
COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
|
|
572
618
|
}
|
|
573
|
-
const LEGACY_PRESETS_DIR = path2.resolve("presets");
|
|
574
619
|
const themesResource = versionedFileResourceServer({
|
|
575
|
-
dir: THEMES_DIR
|
|
620
|
+
dir: THEMES_DIR,
|
|
621
|
+
packageDir: packageThemesDir
|
|
576
622
|
});
|
|
577
623
|
const componentResourceCache = /* @__PURE__ */ new Map();
|
|
578
624
|
function componentResource(comp) {
|
|
579
625
|
let r = componentResourceCache.get(comp);
|
|
580
626
|
if (!r) {
|
|
581
|
-
r = versionedFileResourceServer({
|
|
627
|
+
r = versionedFileResourceServer({
|
|
628
|
+
dir: path2.join(COMPONENT_CONFIGS_DIR, comp),
|
|
629
|
+
packageDir: path2.join(packageComponentConfigsDir, comp)
|
|
630
|
+
});
|
|
582
631
|
componentResourceCache.set(comp, r);
|
|
583
632
|
}
|
|
584
633
|
return r;
|
|
585
634
|
}
|
|
586
|
-
const manifestsResource = versionedFileResourceServer({
|
|
635
|
+
const manifestsResource = versionedFileResourceServer({
|
|
636
|
+
dir: MANIFESTS_DIR,
|
|
637
|
+
packageDir: packageManifestsDir
|
|
638
|
+
});
|
|
587
639
|
function ensureThemesDir() {
|
|
588
640
|
themesResource.ensureDir();
|
|
589
|
-
if (!fs2.existsSync(path2.join(THEMES_DIR, "default.json"))) {
|
|
590
|
-
const defaultTheme = {
|
|
591
|
-
name: "Default Theme",
|
|
592
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
593
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
594
|
-
editorConfigs: {},
|
|
595
|
-
cssVariables: {}
|
|
596
|
-
};
|
|
597
|
-
fs2.writeFileSync(path2.join(THEMES_DIR, "default.json"), JSON.stringify(defaultTheme, null, 2));
|
|
598
|
-
}
|
|
599
641
|
themesResource.ensureMeta();
|
|
600
642
|
}
|
|
601
643
|
function readBody(req) {
|
|
@@ -659,10 +701,9 @@ function themeFileApi(opts) {
|
|
|
659
701
|
lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
|
|
660
702
|
lines.push("");
|
|
661
703
|
const productionThemeName = themesResource.getProductionName();
|
|
662
|
-
const
|
|
704
|
+
const themeData = themesResource.readJson(productionThemeName);
|
|
663
705
|
let themeVarCount = 0;
|
|
664
|
-
if (
|
|
665
|
-
const themeData = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
|
|
706
|
+
if (themeData) {
|
|
666
707
|
const cssVars = { ...themeData.cssVariables || {} };
|
|
667
708
|
Object.assign(cssVars, palettesToVars(themeData.editorConfigs ?? {}));
|
|
668
709
|
const resolvedFontVars = resolveFontStacks(themeData);
|
|
@@ -727,9 +768,8 @@ function themeFileApi(opts) {
|
|
|
727
768
|
regenerateTokensCss();
|
|
728
769
|
}
|
|
729
770
|
function syncFontsToCss(fileName) {
|
|
730
|
-
const
|
|
731
|
-
if (!
|
|
732
|
-
const themeData = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
|
|
771
|
+
const themeData = themesResource.readJson(fileName);
|
|
772
|
+
if (!themeData) return;
|
|
733
773
|
const sources = themeData.fontSources;
|
|
734
774
|
if (!sources) return;
|
|
735
775
|
const lines = [];
|
|
@@ -889,28 +929,7 @@ ${lines.join("\n")}
|
|
|
889
929
|
}
|
|
890
930
|
}
|
|
891
931
|
function ensureManifestsDir() {
|
|
892
|
-
if (!fs2.existsSync(MANIFESTS_DIR) && fs2.existsSync(LEGACY_PRESETS_DIR)) {
|
|
893
|
-
fs2.renameSync(LEGACY_PRESETS_DIR, MANIFESTS_DIR);
|
|
894
|
-
const legacyProd = path2.join(MANIFESTS_DIR, "_production.json");
|
|
895
|
-
if (fs2.existsSync(legacyProd)) fs2.unlinkSync(legacyProd);
|
|
896
|
-
}
|
|
897
932
|
manifestsResource.ensureDir();
|
|
898
|
-
const defaultPath = path2.join(MANIFESTS_DIR, "default.json");
|
|
899
|
-
if (!fs2.existsSync(defaultPath)) {
|
|
900
|
-
const componentConfigs = {};
|
|
901
|
-
for (const comp of listComponentNames()) {
|
|
902
|
-
componentConfigs[comp] = componentResource(comp).getActiveName();
|
|
903
|
-
}
|
|
904
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
905
|
-
const defaultManifest = {
|
|
906
|
-
name: "Default",
|
|
907
|
-
createdAt: now,
|
|
908
|
-
updatedAt: now,
|
|
909
|
-
theme: themesResource.getActiveName(),
|
|
910
|
-
componentConfigs
|
|
911
|
-
};
|
|
912
|
-
fs2.writeFileSync(defaultPath, JSON.stringify(defaultManifest, null, 2));
|
|
913
|
-
}
|
|
914
933
|
if (!fs2.existsSync(manifestsResource.activePath)) {
|
|
915
934
|
fs2.writeFileSync(
|
|
916
935
|
manifestsResource.activePath,
|
|
@@ -918,12 +937,10 @@ ${lines.join("\n")}
|
|
|
918
937
|
);
|
|
919
938
|
} else {
|
|
920
939
|
const activeName = manifestsResource.getActiveName();
|
|
921
|
-
if (
|
|
940
|
+
if (manifestsResource.existingPath(activeName) === null) {
|
|
922
941
|
manifestsResource.setActiveName("default");
|
|
923
942
|
}
|
|
924
943
|
}
|
|
925
|
-
const stragglerProd = path2.join(MANIFESTS_DIR, "_production.json");
|
|
926
|
-
if (fs2.existsSync(stragglerProd)) fs2.unlinkSync(stragglerProd);
|
|
927
944
|
}
|
|
928
945
|
function patchActiveManifest(field, comp, fileName) {
|
|
929
946
|
const activeFile = manifestsResource.getActiveName();
|
|
@@ -974,10 +991,8 @@ ${lines.join("\n")}
|
|
|
974
991
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
975
992
|
}
|
|
976
993
|
function readComponentConfig(comp, name) {
|
|
977
|
-
const filePath = componentResource(comp).filePath(name);
|
|
978
|
-
if (!fs2.existsSync(filePath)) return null;
|
|
979
994
|
try {
|
|
980
|
-
return
|
|
995
|
+
return componentResource(comp).readJson(name);
|
|
981
996
|
} catch {
|
|
982
997
|
return null;
|
|
983
998
|
}
|
|
@@ -1003,14 +1018,12 @@ ${lines.join("\n")}
|
|
|
1003
1018
|
const MANIFEST_BY_NAME_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)$`);
|
|
1004
1019
|
async function handleListThemes(_ctx) {
|
|
1005
1020
|
const activeFile = themesResource.getActiveName();
|
|
1006
|
-
const files =
|
|
1007
|
-
const
|
|
1008
|
-
const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
1009
|
-
const fileName = f.replace(".json", "");
|
|
1021
|
+
const files = themesResource.listNames().map((fileName) => {
|
|
1022
|
+
const data = themesResource.readJson(fileName);
|
|
1010
1023
|
return {
|
|
1011
|
-
name: data
|
|
1024
|
+
name: data?.name || fileName,
|
|
1012
1025
|
fileName,
|
|
1013
|
-
updatedAt: data
|
|
1026
|
+
updatedAt: data?.updatedAt || "",
|
|
1014
1027
|
isActive: fileName === activeFile
|
|
1015
1028
|
};
|
|
1016
1029
|
});
|
|
@@ -1018,19 +1031,19 @@ ${lines.join("\n")}
|
|
|
1018
1031
|
}
|
|
1019
1032
|
async function handleGetActiveTheme({ res }) {
|
|
1020
1033
|
const activeFile = themesResource.getActiveName();
|
|
1021
|
-
const
|
|
1022
|
-
if (!
|
|
1034
|
+
const raw = themesResource.readJson(activeFile);
|
|
1035
|
+
if (!raw) {
|
|
1023
1036
|
jsonResponse(res, 404, { error: "Active theme not found" });
|
|
1024
1037
|
return;
|
|
1025
1038
|
}
|
|
1026
|
-
const data = normalizeTheme(
|
|
1039
|
+
const data = normalizeTheme(raw);
|
|
1027
1040
|
data._fileName = activeFile;
|
|
1028
1041
|
jsonResponse(res, 200, data);
|
|
1029
1042
|
}
|
|
1030
1043
|
async function handleSetActiveTheme({ req, res }) {
|
|
1031
1044
|
const body = JSON.parse(await readBody(req));
|
|
1032
1045
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1033
|
-
if (
|
|
1046
|
+
if (themesResource.existingPath(fileName) === null) {
|
|
1034
1047
|
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1035
1048
|
return;
|
|
1036
1049
|
}
|
|
@@ -1039,12 +1052,12 @@ ${lines.join("\n")}
|
|
|
1039
1052
|
}
|
|
1040
1053
|
async function handleGetProductionTheme({ res }) {
|
|
1041
1054
|
const prodFile = themesResource.getProductionName();
|
|
1042
|
-
const
|
|
1043
|
-
if (!
|
|
1055
|
+
const raw = themesResource.readJson(prodFile);
|
|
1056
|
+
if (!raw) {
|
|
1044
1057
|
jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
|
|
1045
1058
|
return;
|
|
1046
1059
|
}
|
|
1047
|
-
const data = normalizeTheme(
|
|
1060
|
+
const data = normalizeTheme(raw);
|
|
1048
1061
|
jsonResponse(res, 200, {
|
|
1049
1062
|
fileName: prodFile,
|
|
1050
1063
|
name: data.name || prodFile,
|
|
@@ -1055,7 +1068,7 @@ ${lines.join("\n")}
|
|
|
1055
1068
|
async function handleSetProductionTheme({ req, res }) {
|
|
1056
1069
|
const body = JSON.parse(await readBody(req));
|
|
1057
1070
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1058
|
-
if (
|
|
1071
|
+
if (themesResource.existingPath(fileName) === null) {
|
|
1059
1072
|
jsonResponse(res, 404, { error: "Theme not found" });
|
|
1060
1073
|
return;
|
|
1061
1074
|
}
|
|
@@ -1071,28 +1084,33 @@ ${lines.join("\n")}
|
|
|
1071
1084
|
syncFontsToCss(fileName);
|
|
1072
1085
|
syncComponentsToCss();
|
|
1073
1086
|
patchActiveManifest("theme", null, fileName);
|
|
1074
|
-
const data =
|
|
1087
|
+
const data = themesResource.readJson(fileName);
|
|
1075
1088
|
jsonResponse(res, 200, {
|
|
1076
1089
|
ok: true,
|
|
1077
1090
|
fileName,
|
|
1078
|
-
name: data
|
|
1079
|
-
updatedAt: data
|
|
1091
|
+
name: data?.name || fileName,
|
|
1092
|
+
updatedAt: data?.updatedAt || ""
|
|
1080
1093
|
});
|
|
1081
1094
|
}
|
|
1082
1095
|
async function handleThemeByName({ params, req, res }) {
|
|
1083
1096
|
const [fileName] = params;
|
|
1084
1097
|
const filePath = themesResource.filePath(fileName);
|
|
1085
1098
|
if (req.method === "GET") {
|
|
1086
|
-
|
|
1099
|
+
const raw = themesResource.readJson(fileName);
|
|
1100
|
+
if (!raw) {
|
|
1087
1101
|
jsonResponse(res, 404, { error: "Not found" });
|
|
1088
1102
|
return;
|
|
1089
1103
|
}
|
|
1090
|
-
const data = normalizeTheme(
|
|
1104
|
+
const data = normalizeTheme(raw);
|
|
1091
1105
|
data._fileName = fileName;
|
|
1092
1106
|
jsonResponse(res, 200, data);
|
|
1093
1107
|
return;
|
|
1094
1108
|
}
|
|
1095
1109
|
if (req.method === "PUT") {
|
|
1110
|
+
if (fileName === "default") {
|
|
1111
|
+
jsonResponse(res, 403, { error: "Cannot overwrite the default theme (live from package)" });
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1096
1114
|
const body = JSON.parse(await readBody(req));
|
|
1097
1115
|
body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1098
1116
|
if (fs2.existsSync(filePath)) {
|
|
@@ -1161,8 +1179,7 @@ ${lines.join("\n")}
|
|
|
1161
1179
|
const body = JSON.parse(await readBody(req));
|
|
1162
1180
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1163
1181
|
const r = componentResource(comp);
|
|
1164
|
-
|
|
1165
|
-
if (!fs2.existsSync(configPath)) {
|
|
1182
|
+
if (r.existingPath(fileName) === null) {
|
|
1166
1183
|
jsonResponse(res, 404, { error: "Config not found" });
|
|
1167
1184
|
return;
|
|
1168
1185
|
}
|
|
@@ -1186,8 +1203,7 @@ ${lines.join("\n")}
|
|
|
1186
1203
|
const body = JSON.parse(await readBody(req));
|
|
1187
1204
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1188
1205
|
const r = componentResource(comp);
|
|
1189
|
-
|
|
1190
|
-
if (!fs2.existsSync(configPath)) {
|
|
1206
|
+
if (r.existingPath(fileName) === null) {
|
|
1191
1207
|
jsonResponse(res, 404, { error: "Config not found" });
|
|
1192
1208
|
return;
|
|
1193
1209
|
}
|
|
@@ -1215,11 +1231,12 @@ ${lines.join("\n")}
|
|
|
1215
1231
|
const r = componentResource(comp);
|
|
1216
1232
|
const configPath = r.filePath(name);
|
|
1217
1233
|
if (req.method === "GET") {
|
|
1218
|
-
|
|
1234
|
+
const raw = r.readJson(name);
|
|
1235
|
+
if (!raw) {
|
|
1219
1236
|
jsonResponse(res, 404, { error: "Not found" });
|
|
1220
1237
|
return;
|
|
1221
1238
|
}
|
|
1222
|
-
const data =
|
|
1239
|
+
const data = raw;
|
|
1223
1240
|
data._fileName = name;
|
|
1224
1241
|
jsonResponse(res, 200, data);
|
|
1225
1242
|
return;
|
|
@@ -1277,14 +1294,12 @@ ${lines.join("\n")}
|
|
|
1277
1294
|
}
|
|
1278
1295
|
const activeFile = r.getActiveName();
|
|
1279
1296
|
const productionFile = r.getProductionName();
|
|
1280
|
-
const files =
|
|
1281
|
-
const
|
|
1282
|
-
const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
1283
|
-
const fileName = f.replace(".json", "");
|
|
1297
|
+
const files = r.listNames().map((fileName) => {
|
|
1298
|
+
const data = r.readJson(fileName);
|
|
1284
1299
|
return {
|
|
1285
|
-
name: data
|
|
1300
|
+
name: data?.name || fileName,
|
|
1286
1301
|
fileName,
|
|
1287
|
-
updatedAt: data
|
|
1302
|
+
updatedAt: data?.updatedAt || "",
|
|
1288
1303
|
isActive: fileName === activeFile,
|
|
1289
1304
|
isProduction: fileName === productionFile
|
|
1290
1305
|
};
|
|
@@ -1293,14 +1308,12 @@ ${lines.join("\n")}
|
|
|
1293
1308
|
}
|
|
1294
1309
|
async function handleListManifests({ res }) {
|
|
1295
1310
|
const activeFile = manifestsResource.getActiveName();
|
|
1296
|
-
const files =
|
|
1297
|
-
const
|
|
1298
|
-
const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
1299
|
-
const fileName = f.replace(".json", "");
|
|
1311
|
+
const files = manifestsResource.listNames().map((fileName) => {
|
|
1312
|
+
const data = manifestsResource.readJson(fileName);
|
|
1300
1313
|
return {
|
|
1301
|
-
name: data
|
|
1314
|
+
name: data?.name || fileName,
|
|
1302
1315
|
fileName,
|
|
1303
|
-
updatedAt: data
|
|
1316
|
+
updatedAt: data?.updatedAt || "",
|
|
1304
1317
|
isActive: fileName === activeFile,
|
|
1305
1318
|
isProtected: fileName === "default"
|
|
1306
1319
|
};
|
|
@@ -1309,19 +1322,19 @@ ${lines.join("\n")}
|
|
|
1309
1322
|
}
|
|
1310
1323
|
async function handleGetActiveManifest({ res }) {
|
|
1311
1324
|
const activeFile = manifestsResource.getActiveName();
|
|
1312
|
-
const
|
|
1313
|
-
if (!
|
|
1325
|
+
const raw = manifestsResource.readJson(activeFile);
|
|
1326
|
+
if (!raw) {
|
|
1314
1327
|
jsonResponse(res, 404, { error: "Active manifest not found" });
|
|
1315
1328
|
return;
|
|
1316
1329
|
}
|
|
1317
|
-
const data =
|
|
1330
|
+
const data = raw;
|
|
1318
1331
|
data._fileName = activeFile;
|
|
1319
1332
|
jsonResponse(res, 200, data);
|
|
1320
1333
|
}
|
|
1321
1334
|
async function handleSetActiveManifest({ req, res }) {
|
|
1322
1335
|
const body = JSON.parse(await readBody(req));
|
|
1323
1336
|
const fileName = sanitizeFileName(body.name || "default");
|
|
1324
|
-
if (
|
|
1337
|
+
if (manifestsResource.existingPath(fileName) === null) {
|
|
1325
1338
|
jsonResponse(res, 404, { error: "Manifest not found" });
|
|
1326
1339
|
return;
|
|
1327
1340
|
}
|
|
@@ -1332,11 +1345,12 @@ ${lines.join("\n")}
|
|
|
1332
1345
|
const [fileName] = params;
|
|
1333
1346
|
const filePath = manifestsResource.filePath(fileName);
|
|
1334
1347
|
if (req.method === "GET") {
|
|
1335
|
-
|
|
1348
|
+
const raw = manifestsResource.readJson(fileName);
|
|
1349
|
+
if (!raw) {
|
|
1336
1350
|
jsonResponse(res, 404, { error: "Not found" });
|
|
1337
1351
|
return;
|
|
1338
1352
|
}
|
|
1339
|
-
const data =
|
|
1353
|
+
const data = raw;
|
|
1340
1354
|
data._fileName = fileName;
|
|
1341
1355
|
jsonResponse(res, 200, data);
|
|
1342
1356
|
return;
|
|
@@ -1377,15 +1391,13 @@ ${lines.join("\n")}
|
|
|
1377
1391
|
}
|
|
1378
1392
|
async function handleApplyManifest({ params, res }) {
|
|
1379
1393
|
const [fileName] = params;
|
|
1380
|
-
const
|
|
1381
|
-
if (!
|
|
1394
|
+
const manifest = manifestsResource.readJson(fileName);
|
|
1395
|
+
if (!manifest) {
|
|
1382
1396
|
jsonResponse(res, 404, { error: "Manifest not found" });
|
|
1383
1397
|
return;
|
|
1384
1398
|
}
|
|
1385
|
-
const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
|
|
1386
1399
|
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
1387
|
-
|
|
1388
|
-
if (!fs2.existsSync(themePath)) {
|
|
1400
|
+
if (themesResource.existingPath(themeName) === null) {
|
|
1389
1401
|
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
1390
1402
|
return;
|
|
1391
1403
|
}
|
|
@@ -1397,8 +1409,7 @@ ${lines.join("\n")}
|
|
|
1397
1409
|
if (!knownComponents.has(comp)) continue;
|
|
1398
1410
|
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1399
1411
|
const r = componentResource(comp);
|
|
1400
|
-
|
|
1401
|
-
if (!fs2.existsSync(cfgPath)) {
|
|
1412
|
+
if (r.existingPath(sanitized) === null) {
|
|
1402
1413
|
jsonResponse(res, 422, {
|
|
1403
1414
|
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1404
1415
|
});
|
|
@@ -1410,7 +1421,7 @@ ${lines.join("\n")}
|
|
|
1410
1421
|
themesResource.setProductionName(themeName);
|
|
1411
1422
|
syncTokensToCss(themeName);
|
|
1412
1423
|
syncFontsToCss(themeName);
|
|
1413
|
-
const themeData = normalizeTheme(
|
|
1424
|
+
const themeData = normalizeTheme(themesResource.readJson(themeName));
|
|
1414
1425
|
themeData._fileName = themeName;
|
|
1415
1426
|
for (const [comp, configFile] of apply) {
|
|
1416
1427
|
const r = componentResource(comp);
|
|
@@ -1436,33 +1447,30 @@ ${lines.join("\n")}
|
|
|
1436
1447
|
}
|
|
1437
1448
|
async function handleExportManifest({ params, res }) {
|
|
1438
1449
|
const [fileName] = params;
|
|
1439
|
-
const
|
|
1440
|
-
if (!
|
|
1450
|
+
const manifest = manifestsResource.readJson(fileName);
|
|
1451
|
+
if (!manifest) {
|
|
1441
1452
|
jsonResponse(res, 404, { error: "Manifest not found" });
|
|
1442
1453
|
return;
|
|
1443
1454
|
}
|
|
1444
|
-
const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
|
|
1445
1455
|
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
1446
|
-
const
|
|
1447
|
-
if (!
|
|
1456
|
+
const theme = themesResource.readJson(themeName);
|
|
1457
|
+
if (!theme) {
|
|
1448
1458
|
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
1449
1459
|
return;
|
|
1450
1460
|
}
|
|
1451
|
-
const theme = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
|
|
1452
1461
|
const knownComponents = new Set(listComponentNames());
|
|
1453
1462
|
const componentConfigs = {};
|
|
1454
1463
|
for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
|
|
1455
1464
|
if (!knownComponents.has(comp)) continue;
|
|
1456
1465
|
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1457
1466
|
if (sanitized === "default") continue;
|
|
1458
|
-
const
|
|
1459
|
-
if (!
|
|
1467
|
+
const cfg = componentResource(comp).readJson(sanitized);
|
|
1468
|
+
if (!cfg) {
|
|
1460
1469
|
jsonResponse(res, 422, {
|
|
1461
1470
|
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1462
1471
|
});
|
|
1463
1472
|
return;
|
|
1464
1473
|
}
|
|
1465
|
-
const cfg = JSON.parse(fs2.readFileSync(cfgPath, "utf-8"));
|
|
1466
1474
|
componentConfigs[`${comp}/${sanitized}`] = cfg;
|
|
1467
1475
|
}
|
|
1468
1476
|
const bundle = {
|
|
@@ -1482,11 +1490,8 @@ ${lines.join("\n")}
|
|
|
1482
1490
|
);
|
|
1483
1491
|
res.end(JSON.stringify(bundle, null, 2));
|
|
1484
1492
|
}
|
|
1485
|
-
function nextAvailableName2(
|
|
1486
|
-
return nextAvailableName(
|
|
1487
|
-
(n) => fs2.existsSync(resourceFilePath(n)),
|
|
1488
|
-
sanitizeFileName(baseName)
|
|
1489
|
-
);
|
|
1493
|
+
function nextAvailableName2(exists, baseName) {
|
|
1494
|
+
return nextAvailableName(exists, sanitizeFileName(baseName));
|
|
1490
1495
|
}
|
|
1491
1496
|
async function handleImportManifest({ req, res }) {
|
|
1492
1497
|
let bundle;
|
|
@@ -1516,7 +1521,7 @@ ${lines.join("\n")}
|
|
|
1516
1521
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1517
1522
|
const originalThemeName = sanitizeFileName(bundle.manifest.theme || "default");
|
|
1518
1523
|
const finalThemeName = nextAvailableName2(
|
|
1519
|
-
(n) => themesResource.
|
|
1524
|
+
(n) => themesResource.existingPath(n) !== null,
|
|
1520
1525
|
originalThemeName
|
|
1521
1526
|
);
|
|
1522
1527
|
if (finalThemeName !== originalThemeName) {
|
|
@@ -1535,7 +1540,7 @@ ${lines.join("\n")}
|
|
|
1535
1540
|
if (!knownComponents.has(comp)) continue;
|
|
1536
1541
|
const r = componentResource(comp);
|
|
1537
1542
|
const finalName = nextAvailableName2(
|
|
1538
|
-
(n) => r.
|
|
1543
|
+
(n) => r.existingPath(n) !== null,
|
|
1539
1544
|
originalName
|
|
1540
1545
|
);
|
|
1541
1546
|
if (finalName !== originalName) {
|
|
@@ -1568,7 +1573,7 @@ ${lines.join("\n")}
|
|
|
1568
1573
|
if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
|
|
1569
1574
|
const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
|
|
1570
1575
|
const finalManifestName = nextAvailableName2(
|
|
1571
|
-
(n) => manifestsResource.
|
|
1576
|
+
(n) => manifestsResource.existingPath(n) !== null,
|
|
1572
1577
|
originalManifestName
|
|
1573
1578
|
);
|
|
1574
1579
|
if (finalManifestName !== originalManifestName) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
|
|
6
6
|
"keywords": [
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"src/system",
|
|
24
24
|
"src/app/site.css",
|
|
25
25
|
"src/live-tokens/data/tokens.generated.css",
|
|
26
|
+
"src/live-tokens/data/themes/default.json",
|
|
27
|
+
"src/live-tokens/data/manifests/default.json",
|
|
26
28
|
"dist-plugin",
|
|
27
29
|
".claude/skills",
|
|
28
30
|
"bin",
|
|
@@ -63,6 +65,11 @@
|
|
|
63
65
|
"svelte": "./src/editor/pages/ComponentEditorPage.svelte",
|
|
64
66
|
"default": "./src/editor/pages/ComponentEditorPage.svelte"
|
|
65
67
|
},
|
|
68
|
+
"./docs": {
|
|
69
|
+
"types": "./src/editor/docs/Docs.svelte.d.ts",
|
|
70
|
+
"svelte": "./src/editor/docs/Docs.svelte",
|
|
71
|
+
"default": "./src/editor/docs/Docs.svelte"
|
|
72
|
+
},
|
|
66
73
|
"./components/*": {
|
|
67
74
|
"svelte": "./src/system/components/*",
|
|
68
75
|
"default": "./src/system/components/*"
|
|
@@ -91,10 +98,12 @@
|
|
|
91
98
|
"check:no-style-imports": "node scripts/check-no-style-imports.mjs",
|
|
92
99
|
"check:editor-font-isolation": "node scripts/check-editor-font-isolation.mjs",
|
|
93
100
|
"check:component-defaults": "node scripts/sync-component-defaults.mjs --check",
|
|
101
|
+
"check:production-is-default": "node scripts/check-production-is-default.mjs",
|
|
94
102
|
"sync:component-defaults": "node scripts/sync-component-defaults.mjs --write",
|
|
103
|
+
"collapse:manifest": "node scripts/collapse-manifest-to-default.mjs",
|
|
95
104
|
"check:smoke-install": "bash scripts/smoke-install.sh",
|
|
96
105
|
"check:smoke-create": "bash scripts/smoke-create.sh",
|
|
97
|
-
"prepublishOnly": "npm run check:no-style-imports && npm run check:editor-font-isolation && npm run check:component-defaults && npm run build:lib && npm run check:smoke-install && npm run check:smoke-create"
|
|
106
|
+
"prepublishOnly": "npm run check:no-style-imports && npm run check:editor-font-isolation && npm run check:component-defaults && npm run check:production-is-default && npm run build:lib && npm run check:smoke-install && npm run check:smoke-create"
|
|
98
107
|
},
|
|
99
108
|
"peerDependencies": {
|
|
100
109
|
"@sveltejs/vite-plugin-svelte": "^7.0",
|
|
@@ -106,8 +115,6 @@
|
|
|
106
115
|
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
107
116
|
"@types/node": "^25.9.1",
|
|
108
117
|
"happy-dom": "^20.9.0",
|
|
109
|
-
"highlight.js": "^11.11.1",
|
|
110
|
-
"marked": "^18.0.4",
|
|
111
118
|
"sass": "^1.98.0",
|
|
112
119
|
"svelte": "^5.55.5",
|
|
113
120
|
"svelte-check": "^4.4.8",
|
|
@@ -117,6 +124,8 @@
|
|
|
117
124
|
"vitest": "^4.1.4"
|
|
118
125
|
},
|
|
119
126
|
"dependencies": {
|
|
120
|
-
"@fortawesome/fontawesome-free": "^7.2.0"
|
|
127
|
+
"@fortawesome/fontawesome-free": "^7.2.0",
|
|
128
|
+
"highlight.js": "^11.11.1",
|
|
129
|
+
"marked": "^18.0.4"
|
|
121
130
|
}
|
|
122
131
|
}
|
|
@@ -65,6 +65,28 @@ function migrateGradients(state: EditorState): EditorState {
|
|
|
65
65
|
return { ...state, gradients: { tokens: makeDefaultGradients() } };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// `hydrate` shallow-merges the persisted `components` bag over the default, so
|
|
69
|
+
// a slice serialized by an older build can lack fields added since (e.g.
|
|
70
|
+
// `config`, added with the two-field alias/config split). `componentsToVars`
|
|
71
|
+
// calls `Object.entries(slice.config)` unconditionally, so backfill the
|
|
72
|
+
// required fields and drop any non-object slice before the state reaches the
|
|
73
|
+
// renderer. Spread preserves optional fields like `unlinked`.
|
|
74
|
+
export function normalizeComponents(state: EditorState): EditorState {
|
|
75
|
+
const raw = state.components;
|
|
76
|
+
if (!raw || typeof raw !== 'object') return { ...state, components: {} };
|
|
77
|
+
const components: EditorState['components'] = {};
|
|
78
|
+
for (const [name, slice] of Object.entries(raw)) {
|
|
79
|
+
if (!slice || typeof slice !== 'object') continue;
|
|
80
|
+
components[name] = {
|
|
81
|
+
...slice,
|
|
82
|
+
activeFile: typeof slice.activeFile === 'string' ? slice.activeFile : 'default',
|
|
83
|
+
aliases: slice.aliases && typeof slice.aliases === 'object' ? slice.aliases : {},
|
|
84
|
+
config: slice.config && typeof slice.config === 'object' ? slice.config : {},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { ...state, components };
|
|
88
|
+
}
|
|
89
|
+
|
|
68
90
|
export function hydrate(): void {
|
|
69
91
|
// Corrupt state, missing key, or unavailable storage all return null;
|
|
70
92
|
// the editor falls through to the empty default in that case.
|
|
@@ -73,7 +95,7 @@ export function hydrate(): void {
|
|
|
73
95
|
// Shallow-merge onto default shape so older persisted state missing
|
|
74
96
|
// newly-added domain fields still loads.
|
|
75
97
|
const merged = { ...emptyStateFactory(), ...(parsed as object) } as EditorState;
|
|
76
|
-
store.set(migrateGradients(merged));
|
|
98
|
+
store.set(normalizeComponents(migrateGradients(merged)));
|
|
77
99
|
}
|
|
78
100
|
// m13 fix: seed shadows from the DOM at hydrate time so the editor
|
|
79
101
|
// captures the tokens.css baseline regardless of whether the user opens
|