@atomixstudio/mcp 1.0.29 → 1.0.31
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/README.md +4 -4
- package/dist/{chunk-BXWOCXYT.js → chunk-CE6J5MJX.js} +5 -1
- package/dist/chunk-CE6J5MJX.js.map +1 -0
- package/dist/figma-bridge-protocol.d.ts +1 -1
- package/dist/figma-bridge-protocol.js +1 -1
- package/dist/index.js +802 -62
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/dist/chunk-BXWOCXYT.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
isAllowedMethod,
|
|
4
4
|
normalizeBridgeMethod
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-CE6J5MJX.js";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
@@ -1288,10 +1288,710 @@ function getTokenStats(data) {
|
|
|
1288
1288
|
};
|
|
1289
1289
|
}
|
|
1290
1290
|
|
|
1291
|
+
// ../figma-tools/dist/index.js
|
|
1292
|
+
var LEGACY_GROUP_PREFIX = {
|
|
1293
|
+
background: "bg",
|
|
1294
|
+
text: "text",
|
|
1295
|
+
icon: "icon",
|
|
1296
|
+
border: "border",
|
|
1297
|
+
brand: "brand",
|
|
1298
|
+
action: "action",
|
|
1299
|
+
feedback: "feedback"
|
|
1300
|
+
};
|
|
1301
|
+
var RESERVED_COLOR_KEYS = /* @__PURE__ */ new Set(["customScales", "neutral"]);
|
|
1302
|
+
function getColorGroupOrder(storedColors) {
|
|
1303
|
+
if (Array.isArray(storedColors._groupOrder) && storedColors._groupOrder.length > 0) {
|
|
1304
|
+
return storedColors._groupOrder;
|
|
1305
|
+
}
|
|
1306
|
+
return Object.keys(storedColors).filter(
|
|
1307
|
+
(k) => !k.startsWith("_") && !RESERVED_COLOR_KEYS.has(k) && typeof storedColors[k] === "object"
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
function getGroupPrefix(storedColors, groupName) {
|
|
1311
|
+
const custom = storedColors._groupPrefix;
|
|
1312
|
+
if (custom && typeof custom[groupName] === "string") return custom[groupName];
|
|
1313
|
+
return LEGACY_GROUP_PREFIX[groupName] ?? groupName;
|
|
1314
|
+
}
|
|
1315
|
+
function refScaleToFigmaDisplayName(scaleFromRef) {
|
|
1316
|
+
const s = scaleFromRef.trim();
|
|
1317
|
+
if (!s) return s;
|
|
1318
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
1319
|
+
}
|
|
1320
|
+
function referenceToFigmaPrimitiveName(ref, primitiveNames) {
|
|
1321
|
+
if (!ref || typeof ref !== "string") return null;
|
|
1322
|
+
const r = ref.trim();
|
|
1323
|
+
const scaleStep = /^([a-zA-Z]+)\.(\d+|[a-z]+)$/.exec(r);
|
|
1324
|
+
if (scaleStep) {
|
|
1325
|
+
const [, scale, step] = scaleStep;
|
|
1326
|
+
const scaleDisplay = refScaleToFigmaDisplayName(scale);
|
|
1327
|
+
const name = `${scaleDisplay} / ${step}`;
|
|
1328
|
+
return primitiveNames.has(name) ? name : null;
|
|
1329
|
+
}
|
|
1330
|
+
const radixMatch = /^radix\.([a-zA-Z]+)\.(\d+)$/.exec(r);
|
|
1331
|
+
if (radixMatch) {
|
|
1332
|
+
const [, family, step] = radixMatch;
|
|
1333
|
+
const scaleName = refScaleToFigmaDisplayName(family);
|
|
1334
|
+
const name = `${scaleName} / ${step}`;
|
|
1335
|
+
return primitiveNames.has(name) ? name : null;
|
|
1336
|
+
}
|
|
1337
|
+
const oneOffMatch = /^(?:new|oneOff)\.(.+)$/.exec(r);
|
|
1338
|
+
if (oneOffMatch) {
|
|
1339
|
+
const name = `One-off / ${oneOffMatch[1]}`;
|
|
1340
|
+
return primitiveNames.has(name) ? name : null;
|
|
1341
|
+
}
|
|
1342
|
+
const whiteAlpha = /^whiteAlpha\.(.+)$/.exec(r);
|
|
1343
|
+
if (whiteAlpha) {
|
|
1344
|
+
const name = `WhiteAlpha / ${whiteAlpha[1]}`;
|
|
1345
|
+
return primitiveNames.has(name) ? name : null;
|
|
1346
|
+
}
|
|
1347
|
+
const blackAlpha = /^blackAlpha\.(.+)$/.exec(r);
|
|
1348
|
+
if (blackAlpha) {
|
|
1349
|
+
const name = `BlackAlpha / ${blackAlpha[1]}`;
|
|
1350
|
+
return primitiveNames.has(name) ? name : null;
|
|
1351
|
+
}
|
|
1352
|
+
return null;
|
|
1353
|
+
}
|
|
1354
|
+
function referenceToScaleName(ref) {
|
|
1355
|
+
if (!ref || typeof ref !== "string") return null;
|
|
1356
|
+
const r = ref.trim();
|
|
1357
|
+
const scaleStep = /^([a-zA-Z]+)\.(\d+|[a-z]+)$/.exec(r);
|
|
1358
|
+
if (scaleStep) return refScaleToFigmaDisplayName(scaleStep[1]);
|
|
1359
|
+
const radixMatch = /^radix\.([a-zA-Z]+)\.(\d+)$/.exec(r);
|
|
1360
|
+
if (radixMatch) return refScaleToFigmaDisplayName(radixMatch[1]);
|
|
1361
|
+
return null;
|
|
1362
|
+
}
|
|
1363
|
+
function getReferencedScaleNames(storedColors) {
|
|
1364
|
+
const names = /* @__PURE__ */ new Set();
|
|
1365
|
+
const groupOrder = getColorGroupOrder(storedColors);
|
|
1366
|
+
for (const groupName of groupOrder) {
|
|
1367
|
+
const group = storedColors[groupName];
|
|
1368
|
+
if (!group || typeof group !== "object") continue;
|
|
1369
|
+
const keys = Object.keys(group).filter((k) => !k.startsWith("_") && k !== "governance");
|
|
1370
|
+
for (const key of keys) {
|
|
1371
|
+
const value = group[key];
|
|
1372
|
+
if (value === void 0 || typeof value !== "object" || value === null) continue;
|
|
1373
|
+
const theme = value;
|
|
1374
|
+
const lightScale = theme.light?.reference ? referenceToScaleName(theme.light.reference) : null;
|
|
1375
|
+
const darkScale = theme.dark?.reference ? referenceToScaleName(theme.dark.reference) : null;
|
|
1376
|
+
if (lightScale) names.add(lightScale);
|
|
1377
|
+
if (darkScale) names.add(darkScale);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return names;
|
|
1381
|
+
}
|
|
1382
|
+
function getFullScaleFromStored(storedColors, scaleName) {
|
|
1383
|
+
const key = scaleName.toLowerCase();
|
|
1384
|
+
if (key === "neutral" || key === "gray") {
|
|
1385
|
+
const neutral = storedColors.neutral;
|
|
1386
|
+
if (!neutral?.steps || typeof neutral.steps !== "object") return null;
|
|
1387
|
+
const out = {};
|
|
1388
|
+
for (const [step, stepValue] of Object.entries(neutral.steps)) {
|
|
1389
|
+
const hex = stepValue?.hex;
|
|
1390
|
+
if (typeof hex === "string" && /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/i.test(hex)) {
|
|
1391
|
+
out[step] = hex.startsWith("#") ? hex : `#${hex}`;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
1395
|
+
}
|
|
1396
|
+
const customScales = storedColors.customScales;
|
|
1397
|
+
if (Array.isArray(customScales)) {
|
|
1398
|
+
const keyNorm = key.replace(/\s+/g, "");
|
|
1399
|
+
const scale = customScales.find(
|
|
1400
|
+
(s) => s.name?.toLowerCase() === key || s.name?.toLowerCase().replace(/\s+/g, "") === keyNorm
|
|
1401
|
+
);
|
|
1402
|
+
if (scale?.steps && typeof scale.steps === "object") {
|
|
1403
|
+
const out = {};
|
|
1404
|
+
for (const [step, stepValue] of Object.entries(scale.steps)) {
|
|
1405
|
+
const hex = stepValue?.hex;
|
|
1406
|
+
if (typeof hex === "string" && /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/i.test(hex)) {
|
|
1407
|
+
out[step] = hex.startsWith("#") ? hex : `#${hex}`;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
if (Object.keys(out).length > 0) return out;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1415
|
+
function buildSemanticRefMap(storedColors, primitiveNames) {
|
|
1416
|
+
const out = {};
|
|
1417
|
+
const groupOrder = getColorGroupOrder(storedColors);
|
|
1418
|
+
for (const groupName of groupOrder) {
|
|
1419
|
+
const group = storedColors[groupName];
|
|
1420
|
+
if (!group || typeof group !== "object") continue;
|
|
1421
|
+
const prefix = getGroupPrefix(storedColors, groupName);
|
|
1422
|
+
const rowOrder = Array.isArray(group._rowOrder) ? group._rowOrder : void 0;
|
|
1423
|
+
const keys = Array.isArray(rowOrder) ? rowOrder : Object.keys(group).filter((k) => !k.startsWith("_") && k !== "governance");
|
|
1424
|
+
const toKebab = prefix === "action" || prefix === "brand" || prefix === "feedback";
|
|
1425
|
+
for (const key of keys) {
|
|
1426
|
+
const value = group[key];
|
|
1427
|
+
if (value === void 0 || typeof value !== "object" || value === null) continue;
|
|
1428
|
+
const theme = value;
|
|
1429
|
+
let cssKey = key === "app" ? "page" : key;
|
|
1430
|
+
if (toKebab) {
|
|
1431
|
+
cssKey = String(key).replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1432
|
+
}
|
|
1433
|
+
const fullKey = `${prefix}-${cssKey}`;
|
|
1434
|
+
const lightRef = theme.light?.reference ? referenceToFigmaPrimitiveName(theme.light.reference, primitiveNames) : null;
|
|
1435
|
+
const darkRef = theme.dark?.reference ? referenceToFigmaPrimitiveName(theme.dark.reference, primitiveNames) : null;
|
|
1436
|
+
if (lightRef || darkRef) {
|
|
1437
|
+
out[fullKey] = {};
|
|
1438
|
+
if (lightRef) out[fullKey].Light = lightRef;
|
|
1439
|
+
if (darkRef) out[fullKey].Dark = darkRef;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
return out;
|
|
1444
|
+
}
|
|
1445
|
+
function figmaColorNameWithGroup(key) {
|
|
1446
|
+
if (key.includes("/")) {
|
|
1447
|
+
const [group2, ...rest] = key.split("/");
|
|
1448
|
+
const name2 = rest.join("/").trim();
|
|
1449
|
+
if (!name2) return key;
|
|
1450
|
+
const groupDisplay2 = group2.charAt(0).toUpperCase() + group2.slice(1).toLowerCase();
|
|
1451
|
+
return `${groupDisplay2} / ${name2}`;
|
|
1452
|
+
}
|
|
1453
|
+
const firstDash = key.indexOf("-");
|
|
1454
|
+
if (firstDash <= 0) return key;
|
|
1455
|
+
const group = key.slice(0, firstDash);
|
|
1456
|
+
const name = key.slice(firstDash + 1);
|
|
1457
|
+
const groupDisplay = group.charAt(0).toUpperCase() + group.slice(1).toLowerCase();
|
|
1458
|
+
return `${groupDisplay} / ${name}`;
|
|
1459
|
+
}
|
|
1460
|
+
var FIGMA_SHADOW_ORDER = {
|
|
1461
|
+
none: 0,
|
|
1462
|
+
xs: 1,
|
|
1463
|
+
sm: 2,
|
|
1464
|
+
md: 3,
|
|
1465
|
+
lg: 4,
|
|
1466
|
+
xl: 5,
|
|
1467
|
+
"2xl": 6,
|
|
1468
|
+
focus: 7
|
|
1469
|
+
};
|
|
1470
|
+
function tokenValueToNumber(s) {
|
|
1471
|
+
if (typeof s !== "string" || !s.trim()) return 0;
|
|
1472
|
+
const t = s.trim();
|
|
1473
|
+
if (t.endsWith("rem")) {
|
|
1474
|
+
const n2 = parseFloat(t.replace(/rem$/, ""));
|
|
1475
|
+
return Number.isFinite(n2) ? Math.round(n2 * 16) : 0;
|
|
1476
|
+
}
|
|
1477
|
+
if (t.endsWith("px")) {
|
|
1478
|
+
const n2 = parseFloat(t.replace(/px$/, ""));
|
|
1479
|
+
return Number.isFinite(n2) ? Math.round(n2) : 0;
|
|
1480
|
+
}
|
|
1481
|
+
const n = parseFloat(t);
|
|
1482
|
+
return Number.isFinite(n) ? n : 0;
|
|
1483
|
+
}
|
|
1484
|
+
function parseBoxShadowToFigmaEffect(shadowStr) {
|
|
1485
|
+
const s = shadowStr.trim();
|
|
1486
|
+
if (!s || s.toLowerCase() === "none") return null;
|
|
1487
|
+
const parsePx = (x) => typeof x === "string" ? parseFloat(x.replace(/px$/i, "")) : NaN;
|
|
1488
|
+
const colorMatch = s.match(/(rgba?\s*\([^)]+\)|#[0-9A-Fa-f]{3,8})\s*$/i);
|
|
1489
|
+
const colorStr = colorMatch ? colorMatch[1].trim() : void 0;
|
|
1490
|
+
const rest = (colorMatch ? s.slice(0, colorMatch.index) : s).trim();
|
|
1491
|
+
const parts = rest ? rest.split(/\s+/) : [];
|
|
1492
|
+
if (parts.length < 3) return null;
|
|
1493
|
+
const offsetX = parsePx(parts[0]);
|
|
1494
|
+
const offsetY = parsePx(parts[1]);
|
|
1495
|
+
const blur = parsePx(parts[2]);
|
|
1496
|
+
let spread = 0;
|
|
1497
|
+
if (parts.length >= 4) spread = parsePx(parts[3]);
|
|
1498
|
+
let r = 0, g = 0, b = 0, a = 0.1;
|
|
1499
|
+
if (colorStr) {
|
|
1500
|
+
const rgbaMatch = colorStr.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)/i);
|
|
1501
|
+
if (rgbaMatch) {
|
|
1502
|
+
r = Number(rgbaMatch[1]) / 255;
|
|
1503
|
+
g = Number(rgbaMatch[2]) / 255;
|
|
1504
|
+
b = Number(rgbaMatch[3]) / 255;
|
|
1505
|
+
a = rgbaMatch[4] != null ? parseFloat(rgbaMatch[4]) : 1;
|
|
1506
|
+
} else {
|
|
1507
|
+
const hexMatch = colorStr.match(/^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/);
|
|
1508
|
+
if (hexMatch) {
|
|
1509
|
+
r = parseInt(hexMatch[1].slice(0, 2), 16) / 255;
|
|
1510
|
+
g = parseInt(hexMatch[1].slice(2, 4), 16) / 255;
|
|
1511
|
+
b = parseInt(hexMatch[1].slice(4, 6), 16) / 255;
|
|
1512
|
+
a = hexMatch[2] ? parseInt(hexMatch[2], 16) / 255 : 0.1;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
if (!Number.isFinite(offsetX) || !Number.isFinite(offsetY) || !Number.isFinite(blur)) return null;
|
|
1517
|
+
return {
|
|
1518
|
+
type: "DROP_SHADOW",
|
|
1519
|
+
offset: { x: offsetX, y: offsetY },
|
|
1520
|
+
radius: Math.max(0, blur),
|
|
1521
|
+
spread: Number.isFinite(spread) ? spread : 0,
|
|
1522
|
+
color: { r, g, b, a },
|
|
1523
|
+
visible: true,
|
|
1524
|
+
blendMode: "NORMAL"
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
function parseBoxShadowToFigmaEffects(shadowStr) {
|
|
1528
|
+
const s = (shadowStr || "").trim();
|
|
1529
|
+
if (!s || s.toLowerCase() === "none") return [];
|
|
1530
|
+
const out = [];
|
|
1531
|
+
const segments = s.split(/\s*,\s*/);
|
|
1532
|
+
for (const seg of segments) {
|
|
1533
|
+
const effect = parseBoxShadowToFigmaEffect(seg.trim());
|
|
1534
|
+
if (effect) out.push(effect);
|
|
1535
|
+
}
|
|
1536
|
+
return out;
|
|
1537
|
+
}
|
|
1538
|
+
function colorTo8DigitHex(color) {
|
|
1539
|
+
if (!color || typeof color !== "string") return null;
|
|
1540
|
+
const s = color.trim();
|
|
1541
|
+
const rgbaMatch = s.match(/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+)\s*)?\)/i);
|
|
1542
|
+
if (rgbaMatch) {
|
|
1543
|
+
const r = Math.max(0, Math.min(255, parseInt(rgbaMatch[1], 10)));
|
|
1544
|
+
const g = Math.max(0, Math.min(255, parseInt(rgbaMatch[2], 10)));
|
|
1545
|
+
const b = Math.max(0, Math.min(255, parseInt(rgbaMatch[3], 10)));
|
|
1546
|
+
const a = rgbaMatch[4] != null ? Math.max(0, Math.min(1, parseFloat(rgbaMatch[4]))) : 1;
|
|
1547
|
+
const aByte = Math.round(a * 255);
|
|
1548
|
+
const hex = "#" + [r, g, b, aByte].map((n) => n.toString(16).padStart(2, "0")).join("").toUpperCase();
|
|
1549
|
+
return hex;
|
|
1550
|
+
}
|
|
1551
|
+
const hexMatch = s.match(/^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/i);
|
|
1552
|
+
if (hexMatch) {
|
|
1553
|
+
const rgb = hexMatch[1];
|
|
1554
|
+
const aa = hexMatch[2] ?? "FF";
|
|
1555
|
+
return `#${rgb}${aa}`.toUpperCase();
|
|
1556
|
+
}
|
|
1557
|
+
return null;
|
|
1558
|
+
}
|
|
1559
|
+
function typesetKeyToFontFamilyRole(key) {
|
|
1560
|
+
const prefix = (key.split("-")[0] ?? key).toLowerCase();
|
|
1561
|
+
if (prefix === "display" || prefix.startsWith("display")) return "display";
|
|
1562
|
+
if (prefix === "heading" || prefix.startsWith("heading")) return "heading";
|
|
1563
|
+
if (prefix === "mono" || prefix.startsWith("mono")) return "mono";
|
|
1564
|
+
if (prefix.startsWith("body")) return "body";
|
|
1565
|
+
return "body";
|
|
1566
|
+
}
|
|
1567
|
+
function buildFigmaPayloadsFromDS(data) {
|
|
1568
|
+
const tokens = data.tokens;
|
|
1569
|
+
const colors = tokens?.colors;
|
|
1570
|
+
const typography = tokens?.typography;
|
|
1571
|
+
const hexRe = /^#?[0-9A-Fa-f]{6}([0-9A-Fa-f]{2})?$/;
|
|
1572
|
+
const modes = [];
|
|
1573
|
+
if (colors?.modes) {
|
|
1574
|
+
const light = colors.modes.light ?? {};
|
|
1575
|
+
const dark = colors.modes.dark ?? {};
|
|
1576
|
+
if (Object.keys(light).length > 0) modes.push("Light");
|
|
1577
|
+
if (Object.keys(dark).length > 0 && !modes.includes("Dark")) modes.push("Dark");
|
|
1578
|
+
}
|
|
1579
|
+
if (modes.length === 0) modes.push("Light");
|
|
1580
|
+
const primitiveModes = ["Value"];
|
|
1581
|
+
const dsName = data.meta?.name;
|
|
1582
|
+
const collectionPrefix = dsName ? `${dsName} ` : "";
|
|
1583
|
+
const referencedScaleNames = data.storedColors ? getReferencedScaleNames(data.storedColors) : /* @__PURE__ */ new Set();
|
|
1584
|
+
const primitiveVariables = [];
|
|
1585
|
+
const primitiveNames = /* @__PURE__ */ new Set();
|
|
1586
|
+
const oneOffHexToFigmaName = /* @__PURE__ */ new Map();
|
|
1587
|
+
const alphaScales = colors?.alphaScales;
|
|
1588
|
+
if (alphaScales?.whiteAlpha && typeof alphaScales.whiteAlpha === "object") {
|
|
1589
|
+
for (const [step, value] of Object.entries(alphaScales.whiteAlpha)) {
|
|
1590
|
+
if (typeof value !== "string") continue;
|
|
1591
|
+
const hex = colorTo8DigitHex(value);
|
|
1592
|
+
if (!hex || !hexRe.test(hex)) continue;
|
|
1593
|
+
const figmaName = `WhiteAlpha / ${step}`;
|
|
1594
|
+
if (primitiveNames.has(figmaName)) continue;
|
|
1595
|
+
primitiveNames.add(figmaName);
|
|
1596
|
+
const values = {};
|
|
1597
|
+
for (const m of primitiveModes) values[m] = hex;
|
|
1598
|
+
primitiveVariables.push({ name: figmaName, values });
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
if (alphaScales?.blackAlpha && typeof alphaScales.blackAlpha === "object") {
|
|
1602
|
+
for (const [step, value] of Object.entries(alphaScales.blackAlpha)) {
|
|
1603
|
+
if (typeof value !== "string") continue;
|
|
1604
|
+
const hex = colorTo8DigitHex(value);
|
|
1605
|
+
if (!hex || !hexRe.test(hex)) continue;
|
|
1606
|
+
const figmaName = `BlackAlpha / ${step}`;
|
|
1607
|
+
if (primitiveNames.has(figmaName)) continue;
|
|
1608
|
+
primitiveNames.add(figmaName);
|
|
1609
|
+
const values = {};
|
|
1610
|
+
for (const m of primitiveModes) values[m] = hex;
|
|
1611
|
+
primitiveVariables.push({ name: figmaName, values });
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
if (colors?.scales && typeof colors.scales === "object") {
|
|
1615
|
+
for (const [scaleName, steps] of Object.entries(colors.scales)) {
|
|
1616
|
+
if (!steps || typeof steps !== "object") continue;
|
|
1617
|
+
let groupDisplay = scaleName.charAt(0).toUpperCase() + scaleName.slice(1);
|
|
1618
|
+
if (scaleName.toLowerCase() === "neutral" && data.storedColors) {
|
|
1619
|
+
const neutral = data.storedColors.neutral;
|
|
1620
|
+
if (neutral?.baseHueFamily) {
|
|
1621
|
+
groupDisplay = refScaleToFigmaDisplayName(neutral.baseHueFamily);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
for (const [step, hexVal] of Object.entries(steps)) {
|
|
1625
|
+
if (typeof hexVal !== "string") continue;
|
|
1626
|
+
const hex = colorTo8DigitHex(hexVal) ?? (hexRe.test(hexVal) ? hexVal : null);
|
|
1627
|
+
if (!hex || !hexRe.test(hex)) continue;
|
|
1628
|
+
const figmaName = `${groupDisplay} / ${step}`;
|
|
1629
|
+
if (primitiveNames.has(figmaName)) continue;
|
|
1630
|
+
primitiveNames.add(figmaName);
|
|
1631
|
+
const values = {};
|
|
1632
|
+
for (const m of primitiveModes) values[m] = hex;
|
|
1633
|
+
primitiveVariables.push({ name: figmaName, values });
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
const radixVariables = [];
|
|
1638
|
+
const radixCollectionName = `${collectionPrefix}Colors Radix`;
|
|
1639
|
+
for (const scaleName of referencedScaleNames) {
|
|
1640
|
+
if (primitiveNames.has(`${scaleName} / 50`) || primitiveNames.has(`${scaleName} / 100`)) continue;
|
|
1641
|
+
const fromStored = data.storedColors ? getFullScaleFromStored(data.storedColors, scaleName) : null;
|
|
1642
|
+
const scaleData = fromStored ?? data.getRadixScale?.(scaleName) ?? null;
|
|
1643
|
+
if (!scaleData) continue;
|
|
1644
|
+
for (const [step, hexVal] of Object.entries(scaleData)) {
|
|
1645
|
+
const figmaName = `${scaleName} / ${step}`;
|
|
1646
|
+
if (primitiveNames.has(figmaName)) continue;
|
|
1647
|
+
const hex = colorTo8DigitHex(hexVal) ?? (hexRe.test(hexVal) ? hexVal : null);
|
|
1648
|
+
if (!hex || !hexRe.test(hex)) continue;
|
|
1649
|
+
primitiveNames.add(figmaName);
|
|
1650
|
+
const values = {};
|
|
1651
|
+
for (const m of primitiveModes) values[m] = hex;
|
|
1652
|
+
primitiveVariables.push({ name: figmaName, values });
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (colors?.oneOffs && Array.isArray(colors.oneOffs)) {
|
|
1656
|
+
for (const oneOff of colors.oneOffs) {
|
|
1657
|
+
if (!oneOff || typeof oneOff !== "object" || typeof oneOff.hex !== "string") continue;
|
|
1658
|
+
const hex = colorTo8DigitHex(oneOff.hex) ?? (hexRe.test(oneOff.hex) ? oneOff.hex : null);
|
|
1659
|
+
if (!hex || !hexRe.test(hex)) continue;
|
|
1660
|
+
const name = typeof oneOff.name === "string" && oneOff.name ? oneOff.name : oneOff.id ?? "unnamed";
|
|
1661
|
+
const figmaName = `One-off / ${name}`;
|
|
1662
|
+
if (primitiveNames.has(figmaName)) continue;
|
|
1663
|
+
primitiveNames.add(figmaName);
|
|
1664
|
+
const values = {};
|
|
1665
|
+
for (const m of primitiveModes) values[m] = hex;
|
|
1666
|
+
primitiveVariables.push({ name: figmaName, values });
|
|
1667
|
+
const normalizedHex = hex.replace(/^#/, "").toUpperCase();
|
|
1668
|
+
const key8 = normalizedHex.length === 6 ? normalizedHex + "FF" : normalizedHex;
|
|
1669
|
+
oneOffHexToFigmaName.set(key8, figmaName);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
const semanticRefMap = data.storedColors && primitiveNames.size > 0 ? buildSemanticRefMap(data.storedColors, primitiveNames) : {};
|
|
1673
|
+
const semanticVariables = [];
|
|
1674
|
+
const semanticNames = /* @__PURE__ */ new Set();
|
|
1675
|
+
const primitivesCollectionName = `${collectionPrefix}Colors Primitives`;
|
|
1676
|
+
if (colors?.modes) {
|
|
1677
|
+
const light = colors.modes.light ?? {};
|
|
1678
|
+
const dark = colors.modes.dark ?? {};
|
|
1679
|
+
const orderedKeys = [...Object.keys(light)];
|
|
1680
|
+
for (const k of Object.keys(dark)) {
|
|
1681
|
+
if (!orderedKeys.includes(k)) orderedKeys.push(k);
|
|
1682
|
+
}
|
|
1683
|
+
for (const key of orderedKeys) {
|
|
1684
|
+
const lightVal = light[key];
|
|
1685
|
+
const darkVal = dark[key];
|
|
1686
|
+
const lightHex = typeof lightVal === "string" ? colorTo8DigitHex(lightVal) ?? (hexRe.test(lightVal) ? lightVal : null) : null;
|
|
1687
|
+
if (lightHex && hexRe.test(lightHex)) {
|
|
1688
|
+
const figmaName = figmaColorNameWithGroup(key);
|
|
1689
|
+
if (semanticNames.has(figmaName)) continue;
|
|
1690
|
+
semanticNames.add(figmaName);
|
|
1691
|
+
const darkHex = typeof darkVal === "string" ? colorTo8DigitHex(darkVal) ?? (hexRe.test(darkVal) ? darkVal : null) : null;
|
|
1692
|
+
const refs = semanticRefMap[key];
|
|
1693
|
+
const values = {
|
|
1694
|
+
...modes.includes("Light") && { Light: lightHex },
|
|
1695
|
+
...modes.includes("Dark") && { Dark: darkHex && hexRe.test(darkHex) ? darkHex : lightHex }
|
|
1696
|
+
};
|
|
1697
|
+
const aliasByMode = {};
|
|
1698
|
+
for (const m of modes) {
|
|
1699
|
+
const aliasFromRef = m === "Light" ? refs?.Light : refs?.Dark;
|
|
1700
|
+
if (aliasFromRef && primitiveNames.has(aliasFromRef)) {
|
|
1701
|
+
aliasByMode[m] = aliasFromRef;
|
|
1702
|
+
continue;
|
|
1703
|
+
}
|
|
1704
|
+
const hexForMode = m === "Light" ? lightHex : darkHex && hexRe.test(darkHex) ? darkHex : lightHex;
|
|
1705
|
+
const norm = hexForMode.replace(/^#/, "").toUpperCase();
|
|
1706
|
+
const key8 = norm.length === 6 ? norm + "FF" : norm;
|
|
1707
|
+
const oneOffAlias = oneOffHexToFigmaName.get(key8);
|
|
1708
|
+
if (oneOffAlias) {
|
|
1709
|
+
aliasByMode[m] = oneOffAlias;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
semanticVariables.push({
|
|
1713
|
+
name: figmaName,
|
|
1714
|
+
values,
|
|
1715
|
+
...Object.keys(aliasByMode).length > 0 ? { aliasByMode } : {}
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
if (colors?.static?.brand && typeof colors.static.brand === "object") {
|
|
1721
|
+
for (const [key, hex] of Object.entries(colors.static.brand)) {
|
|
1722
|
+
if (typeof hex !== "string" || !hexRe.test(hex)) continue;
|
|
1723
|
+
const figmaName = figmaColorNameWithGroup(`brand/${key}`);
|
|
1724
|
+
if (semanticNames.has(figmaName)) continue;
|
|
1725
|
+
semanticNames.add(figmaName);
|
|
1726
|
+
const values = {};
|
|
1727
|
+
for (const m of modes) values[m] = hex;
|
|
1728
|
+
semanticVariables.push({ name: figmaName, values });
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
const colorVariableCollections = [];
|
|
1732
|
+
if (primitiveVariables.length > 0) {
|
|
1733
|
+
colorVariableCollections.push({
|
|
1734
|
+
collectionName: primitivesCollectionName,
|
|
1735
|
+
modes: primitiveModes,
|
|
1736
|
+
variables: primitiveVariables,
|
|
1737
|
+
applyScopes: false
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
if (radixVariables.length > 0) {
|
|
1741
|
+
colorVariableCollections.push({
|
|
1742
|
+
collectionName: radixCollectionName,
|
|
1743
|
+
modes,
|
|
1744
|
+
variables: radixVariables,
|
|
1745
|
+
applyScopes: false
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
if (semanticVariables.length > 0) {
|
|
1749
|
+
const primitiveCollections = [];
|
|
1750
|
+
if (primitiveVariables.length > 0) primitiveCollections.push(primitivesCollectionName);
|
|
1751
|
+
if (radixVariables.length > 0) primitiveCollections.push(radixCollectionName);
|
|
1752
|
+
colorVariableCollections.push({
|
|
1753
|
+
collectionName: `${collectionPrefix}Colors Semantic`,
|
|
1754
|
+
modes,
|
|
1755
|
+
variables: semanticVariables,
|
|
1756
|
+
applyScopes: true,
|
|
1757
|
+
...primitiveCollections.length > 1 ? { primitiveCollectionNames: primitiveCollections } : primitiveCollections.length === 1 ? { primitiveCollectionName: primitiveCollections[0] } : {}
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
const paintStyles = [];
|
|
1761
|
+
const textStyles = [];
|
|
1762
|
+
const sizeToPx = (val, basePx = 16) => {
|
|
1763
|
+
if (typeof val === "number") return Math.round(val);
|
|
1764
|
+
const s = String(val).trim();
|
|
1765
|
+
const pxMatch = s.match(/^([\d.]+)\s*px$/i);
|
|
1766
|
+
if (pxMatch) return Math.round(parseFloat(pxMatch[1]));
|
|
1767
|
+
const remMatch = s.match(/^([\d.]+)\s*rem$/i);
|
|
1768
|
+
if (remMatch) return Math.round(parseFloat(remMatch[1]) * basePx);
|
|
1769
|
+
const n = parseFloat(s);
|
|
1770
|
+
if (Number.isFinite(n)) return n <= 0 ? basePx : n < 50 ? Math.round(n * basePx) : Math.round(n);
|
|
1771
|
+
return basePx;
|
|
1772
|
+
};
|
|
1773
|
+
const letterSpacingToPx = (val, fontSizePx) => {
|
|
1774
|
+
if (val === void 0 || val === null) return void 0;
|
|
1775
|
+
if (typeof val === "number") return Math.round(val);
|
|
1776
|
+
const s = String(val).trim();
|
|
1777
|
+
const pxMatch = s.match(/^([-\d.]+)\s*px$/i);
|
|
1778
|
+
if (pxMatch) return Math.round(parseFloat(pxMatch[1]));
|
|
1779
|
+
const emMatch = s.match(/^([-\d.]+)\s*em$/i);
|
|
1780
|
+
if (emMatch) return Math.round(parseFloat(emMatch[1]) * fontSizePx);
|
|
1781
|
+
const n = parseFloat(s);
|
|
1782
|
+
return Number.isFinite(n) ? Math.round(n) : void 0;
|
|
1783
|
+
};
|
|
1784
|
+
const firstFont = (obj) => {
|
|
1785
|
+
if (typeof obj === "string") {
|
|
1786
|
+
const primary = obj.split(",")[0].trim().replace(/^['"]|['"]$/g, "");
|
|
1787
|
+
return primary || "Inter";
|
|
1788
|
+
}
|
|
1789
|
+
if (obj && typeof obj === "object" && !Array.isArray(obj)) {
|
|
1790
|
+
const v = obj.body ?? obj.heading ?? obj.display ?? Object.values(obj)[0];
|
|
1791
|
+
return firstFont(v);
|
|
1792
|
+
}
|
|
1793
|
+
return "Inter";
|
|
1794
|
+
};
|
|
1795
|
+
const toFontFamilyString = (val) => {
|
|
1796
|
+
if (typeof val === "string") {
|
|
1797
|
+
const s = val.trim().replace(/^["']|["']$/g, "");
|
|
1798
|
+
return s || "Inter";
|
|
1799
|
+
}
|
|
1800
|
+
return firstFont(val);
|
|
1801
|
+
};
|
|
1802
|
+
const fontFamilyMap = typography?.fontFamily ?? {};
|
|
1803
|
+
const defaultFontFamily = typography ? firstFont(typography.fontFamily ?? "Inter") : "Inter";
|
|
1804
|
+
const fontSizeMap = typography?.fontSize;
|
|
1805
|
+
const fontWeightMap = typography?.fontWeight;
|
|
1806
|
+
const lineHeightMap = typography?.lineHeight;
|
|
1807
|
+
const letterSpacingMap = typography?.letterSpacing;
|
|
1808
|
+
const textTransformMap = typography?.textTransform;
|
|
1809
|
+
const textDecorationMap = typography?.textDecoration;
|
|
1810
|
+
if (fontSizeMap && typeof fontSizeMap === "object" && Object.keys(fontSizeMap).length > 0) {
|
|
1811
|
+
for (const [key, sizeVal] of Object.entries(fontSizeMap)) {
|
|
1812
|
+
const fontSize = sizeToPx(sizeVal);
|
|
1813
|
+
if (fontSize <= 0) continue;
|
|
1814
|
+
const role = typesetKeyToFontFamilyRole(key);
|
|
1815
|
+
const fontFamily = toFontFamilyString(
|
|
1816
|
+
fontFamilyMap[role] ?? fontFamilyMap.body ?? fontFamilyMap.heading ?? fontFamilyMap.display ?? defaultFontFamily
|
|
1817
|
+
);
|
|
1818
|
+
const lh = lineHeightMap && typeof lineHeightMap === "object" ? lineHeightMap[key] : void 0;
|
|
1819
|
+
const weight = fontWeightMap && typeof fontWeightMap === "object" ? fontWeightMap[key] : void 0;
|
|
1820
|
+
const fontWeight = weight != null ? String(weight) : "400";
|
|
1821
|
+
const letterSpacingPx = letterSpacingToPx(
|
|
1822
|
+
letterSpacingMap && typeof letterSpacingMap === "object" ? letterSpacingMap[key] : void 0,
|
|
1823
|
+
fontSize
|
|
1824
|
+
);
|
|
1825
|
+
const textTransform = textTransformMap && typeof textTransformMap === "object" ? textTransformMap[key] : void 0;
|
|
1826
|
+
const textDecoration = textDecorationMap && typeof textDecorationMap === "object" ? textDecorationMap[key] : void 0;
|
|
1827
|
+
const namePart = key.replace(/-/g, " / ");
|
|
1828
|
+
const style = {
|
|
1829
|
+
name: namePart.startsWith("Typography") ? namePart : `Typography / ${namePart}`,
|
|
1830
|
+
fontFamily,
|
|
1831
|
+
fontWeight,
|
|
1832
|
+
fontSize,
|
|
1833
|
+
lineHeightUnit: "PERCENT",
|
|
1834
|
+
letterSpacingUnit: "PIXELS",
|
|
1835
|
+
...letterSpacingPx !== void 0 && letterSpacingPx !== 0 ? { letterSpacingValue: letterSpacingPx } : {}
|
|
1836
|
+
};
|
|
1837
|
+
if (lh != null && typeof lh === "number" && lh > 0) {
|
|
1838
|
+
style.lineHeightValue = lh >= 10 ? Math.round(lh / fontSize * 100) : Math.round(lh * 100);
|
|
1839
|
+
} else {
|
|
1840
|
+
style.lineHeightValue = 150;
|
|
1841
|
+
}
|
|
1842
|
+
if (textTransform === "uppercase") style.textCase = "UPPER";
|
|
1843
|
+
else if (textTransform === "lowercase") style.textCase = "LOWER";
|
|
1844
|
+
else if (textTransform === "capitalize") style.textCase = "TITLE";
|
|
1845
|
+
else style.textCase = "ORIGINAL";
|
|
1846
|
+
if (textDecoration === "underline") style.textDecoration = "UNDERLINE";
|
|
1847
|
+
else style.textDecoration = "NONE";
|
|
1848
|
+
textStyles.push(style);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
const textStylesMap = typography?.textStyles;
|
|
1852
|
+
if (textStyles.length === 0 && textStylesMap && typeof textStylesMap === "object") {
|
|
1853
|
+
for (const [styleName, style] of Object.entries(textStylesMap)) {
|
|
1854
|
+
if (!style || typeof style !== "object") continue;
|
|
1855
|
+
const fontSize = sizeToPx(style.fontSize ?? "1rem");
|
|
1856
|
+
const lhStr = style.lineHeight;
|
|
1857
|
+
const lineHeightUnitless = lhStr != null ? lhStr.endsWith("%") ? parseFloat(lhStr) / 100 : sizeToPx(lhStr) / fontSize : 1.5;
|
|
1858
|
+
const payload = {
|
|
1859
|
+
name: styleName.startsWith("Typography") ? styleName : `Typography / ${styleName.replace(/\//g, " / ")}`,
|
|
1860
|
+
fontFamily: defaultFontFamily,
|
|
1861
|
+
fontWeight: String(style.fontWeight ?? "400"),
|
|
1862
|
+
fontSize,
|
|
1863
|
+
lineHeightUnit: "PERCENT",
|
|
1864
|
+
lineHeightValue: Math.round((Number.isFinite(lineHeightUnitless) ? lineHeightUnitless : 1.5) * 100),
|
|
1865
|
+
letterSpacingUnit: "PIXELS",
|
|
1866
|
+
textCase: "ORIGINAL",
|
|
1867
|
+
textDecoration: "NONE"
|
|
1868
|
+
};
|
|
1869
|
+
textStyles.push(payload);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
textStyles.sort((a, b) => {
|
|
1873
|
+
if (a.fontSize !== b.fontSize) return a.fontSize - b.fontSize;
|
|
1874
|
+
return (a.name || "").localeCompare(b.name || "");
|
|
1875
|
+
});
|
|
1876
|
+
const numberVariableCollections = [];
|
|
1877
|
+
const spacing = tokens?.spacing;
|
|
1878
|
+
if (spacing?.scale && typeof spacing.scale === "object") {
|
|
1879
|
+
const vars = [];
|
|
1880
|
+
for (const [key, val] of Object.entries(spacing.scale)) {
|
|
1881
|
+
const n = tokenValueToNumber(val);
|
|
1882
|
+
if (n >= 0) vars.push({ name: `Spacing / ${key}`, value: n });
|
|
1883
|
+
}
|
|
1884
|
+
vars.sort((a, b) => a.value - b.value);
|
|
1885
|
+
if (vars.length > 0)
|
|
1886
|
+
numberVariableCollections.push({
|
|
1887
|
+
collectionName: `${collectionPrefix}Spacing`,
|
|
1888
|
+
categoryKey: "Spacing",
|
|
1889
|
+
variables: vars,
|
|
1890
|
+
scopes: ["GAP"]
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
const radius = tokens?.radius;
|
|
1894
|
+
if (radius?.scale && typeof radius.scale === "object") {
|
|
1895
|
+
const vars = [];
|
|
1896
|
+
for (const [key, val] of Object.entries(radius.scale)) {
|
|
1897
|
+
const n = tokenValueToNumber(val);
|
|
1898
|
+
if (n >= 0) vars.push({ name: `Radius / ${key}`, value: n });
|
|
1899
|
+
}
|
|
1900
|
+
vars.sort((a, b) => a.value - b.value);
|
|
1901
|
+
if (vars.length > 0)
|
|
1902
|
+
numberVariableCollections.push({
|
|
1903
|
+
collectionName: `${collectionPrefix}Radius`,
|
|
1904
|
+
categoryKey: "Radius",
|
|
1905
|
+
variables: vars,
|
|
1906
|
+
scopes: ["CORNER_RADIUS"]
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
const borders = tokens?.borders;
|
|
1910
|
+
if (borders?.width && typeof borders.width === "object") {
|
|
1911
|
+
const vars = [];
|
|
1912
|
+
for (const [key, val] of Object.entries(borders.width)) {
|
|
1913
|
+
const n = tokenValueToNumber(val);
|
|
1914
|
+
if (n >= 0) vars.push({ name: `Borders / ${key}`, value: n });
|
|
1915
|
+
}
|
|
1916
|
+
vars.sort((a, b) => a.value - b.value);
|
|
1917
|
+
if (vars.length > 0)
|
|
1918
|
+
numberVariableCollections.push({
|
|
1919
|
+
collectionName: `${collectionPrefix}Borders`,
|
|
1920
|
+
categoryKey: "Borders",
|
|
1921
|
+
variables: vars,
|
|
1922
|
+
scopes: ["STROKE_FLOAT"]
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
const sizing = tokens?.sizing;
|
|
1926
|
+
const sizingVariables = [];
|
|
1927
|
+
if (sizing?.height && typeof sizing.height === "object") {
|
|
1928
|
+
for (const [key, val] of Object.entries(sizing.height)) {
|
|
1929
|
+
const n = tokenValueToNumber(val);
|
|
1930
|
+
if (n >= 0) sizingVariables.push({ name: `Height / ${key}`, value: n });
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (sizing?.icon && typeof sizing.icon === "object") {
|
|
1934
|
+
for (const [key, val] of Object.entries(sizing.icon)) {
|
|
1935
|
+
const n = tokenValueToNumber(val);
|
|
1936
|
+
if (n >= 0) sizingVariables.push({ name: `Icon / ${key}`, value: n });
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
sizingVariables.sort((a, b) => a.value - b.value);
|
|
1940
|
+
if (sizingVariables.length > 0) {
|
|
1941
|
+
numberVariableCollections.push({
|
|
1942
|
+
collectionName: `${collectionPrefix}Sizing`,
|
|
1943
|
+
categoryKey: "Sizing",
|
|
1944
|
+
variables: sizingVariables,
|
|
1945
|
+
scopes: ["WIDTH_HEIGHT"]
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
const layout = tokens?.layout;
|
|
1949
|
+
if (layout?.breakpoint && typeof layout.breakpoint === "object") {
|
|
1950
|
+
const vars = [];
|
|
1951
|
+
for (const [key, val] of Object.entries(layout.breakpoint)) {
|
|
1952
|
+
const n = tokenValueToNumber(val);
|
|
1953
|
+
if (n >= 0) vars.push({ name: `Breakpoint / ${key}`, value: n });
|
|
1954
|
+
}
|
|
1955
|
+
vars.sort((a, b) => a.value - b.value);
|
|
1956
|
+
if (vars.length > 0)
|
|
1957
|
+
numberVariableCollections.push({
|
|
1958
|
+
collectionName: `${collectionPrefix}Layout`,
|
|
1959
|
+
categoryKey: "Layout",
|
|
1960
|
+
variables: vars,
|
|
1961
|
+
scopes: ["WIDTH_HEIGHT"]
|
|
1962
|
+
});
|
|
1963
|
+
}
|
|
1964
|
+
const effectStyles = [];
|
|
1965
|
+
const shadows = tokens?.shadows;
|
|
1966
|
+
if (shadows?.elevation && typeof shadows.elevation === "object") {
|
|
1967
|
+
for (const [key, val] of Object.entries(shadows.elevation)) {
|
|
1968
|
+
if (typeof val !== "string") continue;
|
|
1969
|
+
const effects = parseBoxShadowToFigmaEffects(val);
|
|
1970
|
+
if (effects.length > 0) effectStyles.push({ name: `Shadow / ${key}`, effects });
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
if (shadows?.focus && typeof shadows.focus === "string") {
|
|
1974
|
+
const effects = parseBoxShadowToFigmaEffects(shadows.focus);
|
|
1975
|
+
if (effects.length > 0) effectStyles.push({ name: "Shadow / focus", effects });
|
|
1976
|
+
}
|
|
1977
|
+
effectStyles.sort((a, b) => {
|
|
1978
|
+
const nameA = a.name.startsWith("Shadow / ") ? a.name.slice(9) : a.name;
|
|
1979
|
+
const nameB = b.name.startsWith("Shadow / ") ? b.name.slice(9) : b.name;
|
|
1980
|
+
const orderA = FIGMA_SHADOW_ORDER[nameA] ?? 100;
|
|
1981
|
+
const orderB = FIGMA_SHADOW_ORDER[nameB] ?? 100;
|
|
1982
|
+
if (orderA !== orderB) return orderA - orderB;
|
|
1983
|
+
return nameA.localeCompare(nameB);
|
|
1984
|
+
});
|
|
1985
|
+
return {
|
|
1986
|
+
colorVariableCollections,
|
|
1987
|
+
paintStyles,
|
|
1988
|
+
textStyles,
|
|
1989
|
+
numberVariableCollections,
|
|
1990
|
+
effectStyles
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1291
1994
|
// src/index.ts
|
|
1292
|
-
import {
|
|
1293
|
-
buildFigmaPayloadsFromDS
|
|
1294
|
-
} from "@atomix/figma_tools";
|
|
1295
1995
|
import * as path2 from "path";
|
|
1296
1996
|
import * as fs2 from "fs";
|
|
1297
1997
|
import { execSync } from "child_process";
|
|
@@ -1493,7 +2193,7 @@ function parseArgs() {
|
|
|
1493
2193
|
}
|
|
1494
2194
|
var cliArgs = parseArgs();
|
|
1495
2195
|
var { dsId, apiKey, accessToken } = cliArgs;
|
|
1496
|
-
var apiBase = cliArgs.apiBase || "https://
|
|
2196
|
+
var apiBase = cliArgs.apiBase || "https://atomix.studio";
|
|
1497
2197
|
var cachedData = null;
|
|
1498
2198
|
var cachedETag = null;
|
|
1499
2199
|
var cachedMcpTier = null;
|
|
@@ -1557,6 +2257,10 @@ function formatValidationBlock(entries) {
|
|
|
1557
2257
|
async function fetchDesignSystemForMCP(forceRefresh = false) {
|
|
1558
2258
|
if (!dsId) throw new Error("Missing --ds-id. Usage: npx @atomixstudio/mcp --ds-id <id> --atomix-token <token>");
|
|
1559
2259
|
if (!accessToken) throw new Error("Missing --atomix-token. Get your token from the Export modal or Settings.");
|
|
2260
|
+
if (forceRefresh) {
|
|
2261
|
+
cachedData = null;
|
|
2262
|
+
cachedETag = null;
|
|
2263
|
+
}
|
|
1560
2264
|
const result = await fetchDesignSystem({
|
|
1561
2265
|
dsId,
|
|
1562
2266
|
accessToken,
|
|
@@ -1573,7 +2277,7 @@ async function fetchDesignSystemForMCP(forceRefresh = false) {
|
|
|
1573
2277
|
return result.data;
|
|
1574
2278
|
}
|
|
1575
2279
|
var TOKEN_CATEGORIES = ["colors", "typography", "spacing", "sizing", "shadows", "radius", "borders", "motion", "zIndex"];
|
|
1576
|
-
function
|
|
2280
|
+
function typesetKeyToFontFamilyRole2(key) {
|
|
1577
2281
|
const prefix = (key.split("-")[0] ?? key).toLowerCase();
|
|
1578
2282
|
if (prefix === "display" || prefix.startsWith("display")) return "display";
|
|
1579
2283
|
if (prefix === "heading" || prefix.startsWith("heading")) return "heading";
|
|
@@ -1593,7 +2297,7 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
|
|
|
1593
2297
|
const p = cssPrefix ? `${cssPrefix}-` : "";
|
|
1594
2298
|
const typesets = [];
|
|
1595
2299
|
for (const key of Object.keys(fontSize)) {
|
|
1596
|
-
const role =
|
|
2300
|
+
const role = typesetKeyToFontFamilyRole2(key);
|
|
1597
2301
|
const familyName = fontFamily[role] ?? fontFamily.body;
|
|
1598
2302
|
const fontFamilyVarName = familyName ? `--${p}typography-font-family-${role}` : void 0;
|
|
1599
2303
|
const fontFamilyVar = familyName ? `var(${fontFamilyVarName})` : "";
|
|
@@ -1619,7 +2323,7 @@ function buildTypesetsList(typography, cssPrefix = "atmx") {
|
|
|
1619
2323
|
var server = new Server(
|
|
1620
2324
|
{
|
|
1621
2325
|
name: "atomix-mcp-user",
|
|
1622
|
-
version: "1.0.
|
|
2326
|
+
version: "1.0.30"
|
|
1623
2327
|
},
|
|
1624
2328
|
{
|
|
1625
2329
|
capabilities: {
|
|
@@ -1918,8 +2622,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1918
2622
|
const totalChanges = lightChanges + darkChanges + deprecatedCount;
|
|
1919
2623
|
if (totalChanges === 0 && !dryRun) {
|
|
1920
2624
|
const lastUpdated = designSystemData.meta.exportedAt ? new Date(designSystemData.meta.exportedAt).toLocaleString() : "N/A";
|
|
2625
|
+
lastSyncAffectedTokens = { modified: [], removed: [], added: [], format, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1921
2626
|
return {
|
|
1922
|
-
responseText: `\u2713 Already up to date
|
|
2627
|
+
responseText: `\u2713 Already up to date (checked DB v${designSystemData.meta.version}, exported ${lastUpdated}).
|
|
1923
2628
|
|
|
1924
2629
|
File: ${output}
|
|
1925
2630
|
Tokens: ${tokenCount}
|
|
@@ -1930,8 +2635,9 @@ Last updated: ${lastUpdated}`,
|
|
|
1930
2635
|
};
|
|
1931
2636
|
}
|
|
1932
2637
|
if (totalChanges === 0 && dryRun) {
|
|
2638
|
+
lastSyncAffectedTokens = { modified: [], removed: [], added: [], format, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1933
2639
|
return {
|
|
1934
|
-
responseText: `[DRY RUN] Already up to date. No changes would be written.
|
|
2640
|
+
responseText: `[DRY RUN] Already up to date (checked DB v${designSystemData.meta.version}). No changes would be written.
|
|
1935
2641
|
|
|
1936
2642
|
File: ${output}
|
|
1937
2643
|
Tokens: ${tokenCount}
|
|
@@ -2572,9 +3278,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
2572
3278
|
out.agentInstruction = agentStartBridge;
|
|
2573
3279
|
out.userInstruction = `If the bridge still does not connect: ${userSteps}`;
|
|
2574
3280
|
out.figmaPayload = {
|
|
2575
|
-
|
|
2576
|
-
modes: payloads.colorVariables.modes,
|
|
2577
|
-
variables: payloads.colorVariables.variables,
|
|
3281
|
+
colorVariableCollections: payloads.colorVariableCollections,
|
|
2578
3282
|
paintStyles: payloads.paintStyles,
|
|
2579
3283
|
textStyles: payloads.textStyles,
|
|
2580
3284
|
numberVariableCollections: payloads.numberVariableCollections,
|
|
@@ -2584,13 +3288,21 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
2584
3288
|
content: [{ type: "text", text: JSON.stringify(out, null, 2) }]
|
|
2585
3289
|
};
|
|
2586
3290
|
}
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
3291
|
+
const colorResults = [];
|
|
3292
|
+
for (const coll of payloads.colorVariableCollections) {
|
|
3293
|
+
if (coll.variables.length > 0) {
|
|
3294
|
+
const result = await sendBridgeRequest("create_color_variables", {
|
|
3295
|
+
collectionName: coll.collectionName,
|
|
3296
|
+
modes: coll.modes,
|
|
3297
|
+
variables: coll.variables,
|
|
3298
|
+
removeVariablesNotInPayload: true,
|
|
3299
|
+
applyScopes: coll.applyScopes
|
|
3300
|
+
});
|
|
3301
|
+
colorResults.push({ collectionName: coll.collectionName, result });
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
if (colorResults.length > 0) {
|
|
3305
|
+
out.colorVariables = colorResults.length === 1 ? colorResults[0].result : colorResults;
|
|
2594
3306
|
}
|
|
2595
3307
|
if (payloads.paintStyles.length > 0) {
|
|
2596
3308
|
out.paintStyles = await sendBridgeRequest("create_paint_styles", {
|
|
@@ -2632,9 +3344,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
2632
3344
|
});
|
|
2633
3345
|
}
|
|
2634
3346
|
out.figmaPayload = {
|
|
2635
|
-
|
|
2636
|
-
modes: payloads.colorVariables.modes,
|
|
2637
|
-
variables: payloads.colorVariables.variables,
|
|
3347
|
+
colorVariableCollections: payloads.colorVariableCollections,
|
|
2638
3348
|
paintStyles: payloads.paintStyles,
|
|
2639
3349
|
textStyles: payloads.textStyles,
|
|
2640
3350
|
numberVariableCollections: payloads.numberVariableCollections,
|
|
@@ -2643,9 +3353,7 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
2643
3353
|
} catch (e) {
|
|
2644
3354
|
out.error = e instanceof Error ? e.message : String(e);
|
|
2645
3355
|
out.figmaPayload = {
|
|
2646
|
-
|
|
2647
|
-
modes: payloads.colorVariables.modes,
|
|
2648
|
-
variables: payloads.colorVariables.variables,
|
|
3356
|
+
colorVariableCollections: payloads.colorVariableCollections,
|
|
2649
3357
|
paintStyles: payloads.paintStyles,
|
|
2650
3358
|
textStyles: payloads.textStyles,
|
|
2651
3359
|
numberVariableCollections: payloads.numberVariableCollections,
|
|
@@ -2671,11 +3379,21 @@ ${tokenResponseText}${validationBlock}` : `${summary}${validationBlock}`);
|
|
|
2671
3379
|
}
|
|
2672
3380
|
const summaryParts = [];
|
|
2673
3381
|
const colorResult = out.colorVariables;
|
|
2674
|
-
if (colorResult
|
|
2675
|
-
const
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
3382
|
+
if (colorResult) {
|
|
3383
|
+
const results = Array.isArray(colorResult) ? colorResult : [{ result: colorResult }];
|
|
3384
|
+
let totalSynced = 0;
|
|
3385
|
+
let totalRemoved = 0;
|
|
3386
|
+
for (const r of results) {
|
|
3387
|
+
const res = r.result;
|
|
3388
|
+
if (res?.variableNames?.length) totalSynced += res.variableNames.length;
|
|
3389
|
+
if ((res?.removed ?? 0) > 0) totalRemoved += res.removed ?? 0;
|
|
3390
|
+
}
|
|
3391
|
+
if (totalSynced > 0 || totalRemoved > 0) {
|
|
3392
|
+
const parts = [];
|
|
3393
|
+
if (totalSynced > 0) parts.push(`${totalSynced} synced`);
|
|
3394
|
+
if (totalRemoved > 0) parts.push(`${totalRemoved} removed`);
|
|
3395
|
+
summaryParts.push(`Colors: ${parts.join(", ")}.`);
|
|
3396
|
+
}
|
|
2679
3397
|
}
|
|
2680
3398
|
const paintResult = out.paintStyles;
|
|
2681
3399
|
if (paintResult) {
|
|
@@ -2903,7 +3621,7 @@ var SHOWCASE_HTML_TEMPLATE = `<!DOCTYPE html>
|
|
|
2903
3621
|
<li>Run <code>/--rules</code> to load governance rules; run <code>/--sync</code> and <code>/--refactor</code> after you change tokens in Atomix Studio</li>
|
|
2904
3622
|
</ul>
|
|
2905
3623
|
</div>
|
|
2906
|
-
<p class="tips">Keep the source of truth at <a href="https://
|
|
3624
|
+
<p class="tips">Keep the source of truth at <a href="https://atomix.studio" target="_blank" rel="noopener">atomix.studio</a> \u2014 avoid editing token values in this repo.</p>
|
|
2907
3625
|
</div>
|
|
2908
3626
|
</body>
|
|
2909
3627
|
</html>
|
|
@@ -3027,7 +3745,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3027
3745
|
};
|
|
3028
3746
|
}
|
|
3029
3747
|
const canonicalName = name === "--hello" ? "hello" : name === "--get-started" ? "atomix-setup" : name === "--rules" ? "design-system-rules" : name === "--sync" ? "sync" : name === "--refactor" ? "refactor" : name === "--sync-to-figma" || name === "syncToFigma" ? "sync-to-figma" : name;
|
|
3030
|
-
const shouldForceRefresh = canonicalName === "sync";
|
|
3748
|
+
const shouldForceRefresh = canonicalName === "sync" || canonicalName === "refactor";
|
|
3031
3749
|
let data = null;
|
|
3032
3750
|
let stats = null;
|
|
3033
3751
|
try {
|
|
@@ -3044,7 +3762,7 @@ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
|
3044
3762
|
content: {
|
|
3045
3763
|
type: "text",
|
|
3046
3764
|
text: `The MCP server isn't configured. To use design system prompts, you need to configure the server with:
|
|
3047
|
-
- \`--ds-id\`: Your design system ID (get it from https://
|
|
3765
|
+
- \`--ds-id\`: Your design system ID (get it from https://atomix.studio/ds/[your-ds-id])
|
|
3048
3766
|
- \`--atomix-token\`: Your access token (get it from the Export modal or Settings \u2192 Regenerate Atomix access token)
|
|
3049
3767
|
|
|
3050
3768
|
Both are required. Configure the MCP server in your AI tool's MCP settings, then restart your AI tool.`
|
|
@@ -3358,42 +4076,59 @@ Format the response as a markdown table with columns: Token Name | Value | CSS V
|
|
|
3358
4076
|
};
|
|
3359
4077
|
}
|
|
3360
4078
|
case "refactor": {
|
|
3361
|
-
|
|
4079
|
+
const refactorOutput = args?.output || "./tokens.css";
|
|
4080
|
+
const refactorFormat = args?.format || "css";
|
|
4081
|
+
const refactorOutputPath = path2.resolve(process.cwd(), refactorOutput);
|
|
4082
|
+
const refactorFileExists = fs2.existsSync(refactorOutputPath);
|
|
4083
|
+
if (!data) {
|
|
3362
4084
|
return {
|
|
3363
4085
|
description: "Refactor codebase for deprecated tokens",
|
|
3364
|
-
messages: [
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
type: "text",
|
|
3369
|
-
text: `No recent sync data available. Please run \`/sync\` first to update your token file, then run \`/refactor\` to scan your codebase for deprecated token usage.`
|
|
3370
|
-
}
|
|
3371
|
-
}
|
|
3372
|
-
]
|
|
4086
|
+
messages: [{
|
|
4087
|
+
role: "user",
|
|
4088
|
+
content: { type: "text", text: `Failed to fetch design system from DB. Check your --ds-id and --atomix-token configuration.` }
|
|
4089
|
+
}]
|
|
3373
4090
|
};
|
|
3374
4091
|
}
|
|
3375
|
-
if (
|
|
4092
|
+
if (!refactorFileExists) {
|
|
3376
4093
|
return {
|
|
3377
4094
|
description: "Refactor codebase for deprecated tokens",
|
|
3378
|
-
messages: [
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
4095
|
+
messages: [{
|
|
4096
|
+
role: "user",
|
|
4097
|
+
content: { type: "text", text: `No token file found at \`${refactorOutput}\`. Please run \`/--sync\` first to create your token file, then run \`/--refactor\` to scan your codebase for deprecated token usage.` }
|
|
4098
|
+
}]
|
|
4099
|
+
};
|
|
4100
|
+
}
|
|
4101
|
+
const deprecatedTokens = /* @__PURE__ */ new Map();
|
|
4102
|
+
if (["css", "scss", "less"].includes(refactorFormat)) {
|
|
4103
|
+
const oldContent = fs2.readFileSync(refactorOutputPath, "utf-8");
|
|
4104
|
+
const oldVarPattern = /(?:^|\n)\s*(?:\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)?(--[a-zA-Z0-9-]+):\s*([^;]+);/gm;
|
|
4105
|
+
let match;
|
|
4106
|
+
while ((match = oldVarPattern.exec(oldContent)) !== null) {
|
|
4107
|
+
const varName = match[1];
|
|
4108
|
+
const varValue = match[2].trim();
|
|
4109
|
+
if (!(varName in data.cssVariables)) deprecatedTokens.set(varName, varValue);
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
const dsVersion = data.meta.version ?? "?";
|
|
4113
|
+
const dsExportedAt = data.meta.exportedAt ? new Date(data.meta.exportedAt).toLocaleString() : "N/A";
|
|
4114
|
+
if (deprecatedTokens.size === 0) {
|
|
4115
|
+
return {
|
|
4116
|
+
description: "Refactor codebase for deprecated tokens",
|
|
4117
|
+
messages: [{
|
|
4118
|
+
role: "user",
|
|
4119
|
+
content: { type: "text", text: `\u2713 No deprecated tokens found.
|
|
4120
|
+
|
|
4121
|
+
Your token file \`${refactorOutput}\` is aligned with the design system (v${dsVersion}, exported ${dsExportedAt}). No tokens need migration.` }
|
|
4122
|
+
}]
|
|
3389
4123
|
};
|
|
3390
4124
|
}
|
|
3391
|
-
const format =
|
|
4125
|
+
const format = refactorFormat;
|
|
3392
4126
|
const isNativeFormat = ["swift", "kotlin", "dart"].includes(format);
|
|
4127
|
+
const removed = Array.from(deprecatedTokens.entries()).map(([token, lastValue]) => ({ token, lastValue }));
|
|
3393
4128
|
let searchPatterns = "";
|
|
3394
4129
|
let fileExtensions = "";
|
|
3395
4130
|
if (isNativeFormat) {
|
|
3396
|
-
const nativeTokens =
|
|
4131
|
+
const nativeTokens = removed.map((r) => {
|
|
3397
4132
|
const withoutPrefix = r.token.replace(/^--atmx-/, "");
|
|
3398
4133
|
const camelCase = withoutPrefix.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
3399
4134
|
const pascalCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
|
@@ -3411,15 +4146,20 @@ Your codebase is up to date! The last sync on ${new Date(lastSyncAffectedTokens.
|
|
|
3411
4146
|
}
|
|
3412
4147
|
} else {
|
|
3413
4148
|
fileExtensions = ".tsx, .ts, .jsx, .js, .css, .scss, .less, .vue, .svelte files";
|
|
3414
|
-
searchPatterns =
|
|
4149
|
+
searchPatterns = removed.map(
|
|
3415
4150
|
(r) => ` \u2022 \`var(${r.token})\` or \`"${r.token}"\` (was: ${r.lastValue})`
|
|
3416
4151
|
).join("\n");
|
|
3417
4152
|
}
|
|
3418
4153
|
const instructions = `Scan the codebase for deprecated design tokens and help update them.
|
|
3419
4154
|
|
|
3420
|
-
##
|
|
4155
|
+
## Design System (fresh from DB)
|
|
4156
|
+
- **Version:** ${dsVersion}
|
|
4157
|
+
- **Last exported:** ${dsExportedAt}
|
|
4158
|
+
- **Token file:** ${refactorOutput}
|
|
4159
|
+
|
|
4160
|
+
## Deprecated Tokens (${removed.length})
|
|
3421
4161
|
|
|
3422
|
-
The following tokens have been removed from the design system:
|
|
4162
|
+
The following tokens exist in your local token file but have been removed from the design system:
|
|
3423
4163
|
|
|
3424
4164
|
${searchPatterns}
|
|
3425
4165
|
|
|
@@ -3588,7 +4328,7 @@ ${tokenSummary}
|
|
|
3588
4328
|
|
|
3589
4329
|
---
|
|
3590
4330
|
|
|
3591
|
-
*Atomix Studio \u2014 https://
|
|
4331
|
+
*Atomix Studio \u2014 https://atomix.studio | DS: https://atomix.studio/ds/${dsId}*
|
|
3592
4332
|
`;
|
|
3593
4333
|
}
|
|
3594
4334
|
async function startServer() {
|