@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.
@@ -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({ dir: path2.join(COMPONENT_CONFIGS_DIR, comp) });
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({ dir: MANIFESTS_DIR });
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 themePath = path2.join(THEMES_DIR, `${productionThemeName}.json`);
704
+ const themeData = themesResource.readJson(productionThemeName);
663
705
  let themeVarCount = 0;
664
- if (fs2.existsSync(themePath)) {
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 themePath = path2.join(THEMES_DIR, `${fileName}.json`);
731
- if (!fs2.existsSync(themePath)) return;
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 (!fs2.existsSync(manifestsResource.filePath(activeName))) {
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 JSON.parse(fs2.readFileSync(filePath, "utf-8"));
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 = fs2.readdirSync(THEMES_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
1007
- const filePath = path2.join(THEMES_DIR, f);
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.name || fileName,
1024
+ name: data?.name || fileName,
1012
1025
  fileName,
1013
- updatedAt: data.updatedAt || "",
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 filePath = themesResource.filePath(activeFile);
1022
- if (!fs2.existsSync(filePath)) {
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(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
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 (!fs2.existsSync(themesResource.filePath(fileName))) {
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 filePath = themesResource.filePath(prodFile);
1043
- if (!fs2.existsSync(filePath)) {
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(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
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 (!fs2.existsSync(themesResource.filePath(fileName))) {
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 = JSON.parse(fs2.readFileSync(themesResource.filePath(fileName), "utf-8"));
1087
+ const data = themesResource.readJson(fileName);
1075
1088
  jsonResponse(res, 200, {
1076
1089
  ok: true,
1077
1090
  fileName,
1078
- name: data.name || fileName,
1079
- updatedAt: data.updatedAt || ""
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
- if (!fs2.existsSync(filePath)) {
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(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
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
- const configPath = r.filePath(fileName);
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
- const configPath = r.filePath(fileName);
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
- if (!fs2.existsSync(configPath)) {
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 = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
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 = fs2.readdirSync(r.dir).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
1281
- const filePath = path2.join(r.dir, f);
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.name || fileName,
1300
+ name: data?.name || fileName,
1286
1301
  fileName,
1287
- updatedAt: data.updatedAt || "",
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 = fs2.readdirSync(MANIFESTS_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
1297
- const filePath = path2.join(MANIFESTS_DIR, f);
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.name || fileName,
1314
+ name: data?.name || fileName,
1302
1315
  fileName,
1303
- updatedAt: data.updatedAt || "",
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 filePath = manifestsResource.filePath(activeFile);
1313
- if (!fs2.existsSync(filePath)) {
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 = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
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 (!fs2.existsSync(manifestsResource.filePath(fileName))) {
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
- if (!fs2.existsSync(filePath)) {
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 = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
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 manifestPath = manifestsResource.filePath(fileName);
1381
- if (!fs2.existsSync(manifestPath)) {
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
- const themePath = themesResource.filePath(themeName);
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
- const cfgPath = r.filePath(sanitized);
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(JSON.parse(fs2.readFileSync(themePath, "utf-8")));
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 manifestPath = manifestsResource.filePath(fileName);
1440
- if (!fs2.existsSync(manifestPath)) {
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 themePath = themesResource.filePath(themeName);
1447
- if (!fs2.existsSync(themePath)) {
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 cfgPath = componentResource(comp).filePath(sanitized);
1459
- if (!fs2.existsSync(cfgPath)) {
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(resourceFilePath, baseName) {
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.filePath(n),
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.filePath(n),
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.filePath(n),
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.25.1",
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