@bridge-ds/mcp 0.3.2 → 0.4.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/README.md +17 -1
- package/dist/branches.d.ts +443 -0
- package/dist/{chunk-X455XY4E.js → chunk-7DBLDJIL.js} +333 -2
- package/dist/{chunk-MTQCDV65.js → chunk-NYFH322K.js} +2202 -41
- package/dist/explorations.d.ts +5 -0
- package/dist/index.js +2 -2
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +7 -3
- package/dist/ops.d.ts +70 -1
- package/dist/requests.d.ts +2 -0
- package/dist/{src-XSBCZV4W.js → src-2YRRAUUM.js} +101 -3
- package/dist/studio.d.ts +107 -0
- package/package.json +3 -3
|
@@ -391,6 +391,7 @@ var TeamDirectory = class {
|
|
|
391
391
|
var OWNER_GROUPS = ["design", "tech"];
|
|
392
392
|
var RIGHTS = ["approveDesign", "approveTech", "ship"];
|
|
393
393
|
var DEFAULT_RIGHTS = { approveDesign: ["design"], approveTech: ["tech"], ship: ["tech"] };
|
|
394
|
+
var norm2 = (x) => x.trim().toLowerCase().replace(/^@/, "");
|
|
394
395
|
var uniq = (xs) => [...new Set(xs)];
|
|
395
396
|
function normalizeRights(input) {
|
|
396
397
|
const pick = (r) => {
|
|
@@ -426,6 +427,16 @@ function shipPolicy(owners, rights, teams) {
|
|
|
426
427
|
const logins = expandEntries(entries, teams);
|
|
427
428
|
return { logins, groups: [...rights.ship], entries, ...teams ? { teams } : {} };
|
|
428
429
|
}
|
|
430
|
+
function unreadableShipTeams(policy) {
|
|
431
|
+
if (!policy?.teams) return [];
|
|
432
|
+
const keys = new Set((policy.entries ?? []).map((e) => e.trim().toLowerCase().replace(/^@?/, "@")));
|
|
433
|
+
return unreadableTeams(policy.teams).filter((t) => keys.has(t.key));
|
|
434
|
+
}
|
|
435
|
+
function canShip(login, policy) {
|
|
436
|
+
if (!policy) return true;
|
|
437
|
+
if (!login || login.includes("/")) return false;
|
|
438
|
+
return policy.logins.map(norm2).includes(norm2(login));
|
|
439
|
+
}
|
|
429
440
|
function groupsPhrase(groups) {
|
|
430
441
|
const g = OWNER_GROUPS.filter((x) => groups.includes(x));
|
|
431
442
|
if (g.length === 1) return `the ${g[0]} owner`;
|
|
@@ -433,7 +444,7 @@ function groupsPhrase(groups) {
|
|
|
433
444
|
}
|
|
434
445
|
function whoCanShipSentence(policy) {
|
|
435
446
|
const names = (policy.entries ?? policy.logins).map((l) => entryLabel(l, policy.teams)).join(", ");
|
|
436
|
-
return `Only ${groupsPhrase(policy.groups)} can
|
|
447
|
+
return `Only ${groupsPhrase(policy.groups)} can merge this${names ? ` (${names})` : ""}.`;
|
|
437
448
|
}
|
|
438
449
|
function visualImpactProblems(raw) {
|
|
439
450
|
if (raw === void 0) return [];
|
|
@@ -446,6 +457,101 @@ function visualImpactProblems(raw) {
|
|
|
446
457
|
return out;
|
|
447
458
|
}
|
|
448
459
|
|
|
460
|
+
// ../core/src/products.ts
|
|
461
|
+
var DEFAULT_PRODUCTS = [
|
|
462
|
+
{ key: "admin", name: "Admin demo", path: "apps/admin", framework: "react" },
|
|
463
|
+
{ key: "library", name: "Library", default: true }
|
|
464
|
+
];
|
|
465
|
+
var PRODUCT_LABEL_PREFIX = "product: ";
|
|
466
|
+
var productLabel = (key) => `${PRODUCT_LABEL_PREFIX}${key}`;
|
|
467
|
+
function productsOf(config) {
|
|
468
|
+
const list2 = config?.products;
|
|
469
|
+
return Array.isArray(list2) && list2.length ? list2 : DEFAULT_PRODUCTS;
|
|
470
|
+
}
|
|
471
|
+
function defaultProduct(products) {
|
|
472
|
+
const list2 = products.length ? products : DEFAULT_PRODUCTS;
|
|
473
|
+
return list2.find((p) => p.default) ?? list2[list2.length - 1];
|
|
474
|
+
}
|
|
475
|
+
function findProduct(products, keyOrName) {
|
|
476
|
+
const k = (keyOrName ?? "").trim().toLowerCase();
|
|
477
|
+
if (!k) return void 0;
|
|
478
|
+
return products.find((p) => p.key.toLowerCase() === k) ?? products.find((p) => p.name.toLowerCase() === k);
|
|
479
|
+
}
|
|
480
|
+
var cleanPath = (p) => p.trim().replace(/^\.?\/+|\/+$/g, "");
|
|
481
|
+
function productOfBranch(products, branch) {
|
|
482
|
+
const fallback = defaultProduct(products);
|
|
483
|
+
for (const l of branch.labels ?? []) {
|
|
484
|
+
const name = typeof l === "string" ? l : l?.name;
|
|
485
|
+
if (typeof name !== "string" || !name.toLowerCase().startsWith(PRODUCT_LABEL_PREFIX)) continue;
|
|
486
|
+
const hit = findProduct(products, name.slice(PRODUCT_LABEL_PREFIX.length));
|
|
487
|
+
if (hit) return hit;
|
|
488
|
+
}
|
|
489
|
+
const files = branch.files ?? [];
|
|
490
|
+
for (const p of products) {
|
|
491
|
+
if (p === fallback || !p.path) continue;
|
|
492
|
+
const prefix = `${cleanPath(p.path)}/`;
|
|
493
|
+
if (prefix !== "/" && files.some((f) => f.startsWith(prefix))) return p;
|
|
494
|
+
}
|
|
495
|
+
return fallback;
|
|
496
|
+
}
|
|
497
|
+
function productForPlace(products, place) {
|
|
498
|
+
const repo = place.repo?.trim().toLowerCase();
|
|
499
|
+
if (repo && repo !== place.designSystemRepo?.trim().toLowerCase()) {
|
|
500
|
+
const hit = products.find((p) => p.repo?.toLowerCase() === repo);
|
|
501
|
+
if (hit) return { product: hit, how: "repo" };
|
|
502
|
+
}
|
|
503
|
+
const rel = cleanPath(place.relPath ?? "");
|
|
504
|
+
if (rel && rel !== ".") {
|
|
505
|
+
let best;
|
|
506
|
+
for (const p of products) {
|
|
507
|
+
if (!p.path) continue;
|
|
508
|
+
const dir = cleanPath(p.path);
|
|
509
|
+
if (dir && (rel === dir || rel.startsWith(`${dir}/`)) && (!best || dir.length > cleanPath(best.path).length)) best = p;
|
|
510
|
+
}
|
|
511
|
+
if (best) return { product: best, how: "path" };
|
|
512
|
+
}
|
|
513
|
+
return { product: defaultProduct(products), how: "default" };
|
|
514
|
+
}
|
|
515
|
+
var KEY = /^[a-z0-9][a-z0-9-]*$/;
|
|
516
|
+
var REPO = /^[\w.-]+\/[\w.-]+$/;
|
|
517
|
+
var FIELDS = ["key", "name", "repo", "path", "preview", "framework", "default"];
|
|
518
|
+
function productProblems(raw) {
|
|
519
|
+
if (raw === void 0) return [];
|
|
520
|
+
if (!Array.isArray(raw)) return ['"products" must be a list of { "key", "name" } objects'];
|
|
521
|
+
if (!raw.length) return ['"products" must list at least one product, or be left out to use the defaults'];
|
|
522
|
+
const problems = [];
|
|
523
|
+
const seen = /* @__PURE__ */ new Set();
|
|
524
|
+
let defaults = 0;
|
|
525
|
+
raw.forEach((p, i) => {
|
|
526
|
+
const at = `"products[${i}]`;
|
|
527
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) {
|
|
528
|
+
problems.push(`${at}" must be an object with a "key" and a "name"`);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const o = p;
|
|
532
|
+
for (const k of Object.keys(o)) if (!FIELDS.includes(k)) problems.push(`${at}.${k}" is not a known field (${FIELDS.join(", ")})`);
|
|
533
|
+
if (typeof o.key !== "string" || !KEY.test(o.key)) problems.push(`${at}.key" must be lowercase letters, digits and dashes, such as "admin"`);
|
|
534
|
+
else if (seen.has(o.key)) problems.push(`${at}.key" ("${o.key}") is used twice: give each product its own key`);
|
|
535
|
+
else seen.add(o.key);
|
|
536
|
+
if (typeof o.name !== "string" || !o.name.trim()) problems.push(`${at}.name" must be the name people read, such as "Admin demo"`);
|
|
537
|
+
if (o.repo !== void 0 && (typeof o.repo !== "string" || !REPO.test(o.repo))) problems.push(`${at}.repo" must be "owner/name"`);
|
|
538
|
+
if (o.path !== void 0) {
|
|
539
|
+
const bad = typeof o.path !== "string" || !o.path.trim() || o.path.startsWith("/") || o.path.split("/").includes("..");
|
|
540
|
+
if (bad) problems.push(`${at}.path" must be a folder relative to the repository root, without a leading "/" or "..", such as "apps/admin"`);
|
|
541
|
+
}
|
|
542
|
+
if (o.preview !== void 0 && (typeof o.preview !== "string" || !/^https?:\/\//.test(o.preview))) problems.push(`${at}.preview" must be an http(s) URL template, such as "https://app-git-{branch}.vercel.app"`);
|
|
543
|
+
if (o.framework !== void 0 && (typeof o.framework !== "string" || !o.framework.trim())) problems.push(`${at}.framework" must be a name such as "react", "vue" or "other"`);
|
|
544
|
+
if (o.default !== void 0 && typeof o.default !== "boolean") problems.push(`${at}.default" must be true or false`);
|
|
545
|
+
if (o.default === true) defaults++;
|
|
546
|
+
});
|
|
547
|
+
if (defaults > 1) problems.push('"products" marks more than one product as default: keep "default": true on the library only');
|
|
548
|
+
return problems;
|
|
549
|
+
}
|
|
550
|
+
function sameProducts(a, b) {
|
|
551
|
+
const norm3 = (l) => JSON.stringify(l.map((p) => FIELDS.map((f) => p[f] ?? null)));
|
|
552
|
+
return norm3(a) === norm3(b);
|
|
553
|
+
}
|
|
554
|
+
|
|
449
555
|
// ../core/src/config.ts
|
|
450
556
|
var CONFIG_FILE = "bridge.config.json";
|
|
451
557
|
var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
|
|
@@ -586,6 +692,7 @@ function defineConfig(input) {
|
|
|
586
692
|
} : {},
|
|
587
693
|
...g.rights ? { rights: normalizeRights(g.rights) } : {}
|
|
588
694
|
},
|
|
695
|
+
products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
|
|
589
696
|
...input.audit ? { audit: { ...input.audit } } : {},
|
|
590
697
|
...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
|
|
591
698
|
...input.integrations ? { integrations: { ...input.integrations } } : {},
|
|
@@ -708,6 +815,7 @@ function loadConfig(source) {
|
|
|
708
815
|
}
|
|
709
816
|
}
|
|
710
817
|
problems.push(...visualImpactProblems(d.visualImpact));
|
|
818
|
+
problems.push(...productProblems(d.products));
|
|
711
819
|
const audit = d.audit;
|
|
712
820
|
if (audit !== void 0) {
|
|
713
821
|
if (!isObj(audit)) problems.push('"audit" must be an object');
|
|
@@ -754,7 +862,8 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
|
|
|
754
862
|
governance: config.governance,
|
|
755
863
|
...config.audit ? { audit: config.audit } : {},
|
|
756
864
|
...config.previews ? { previews: config.previews } : {},
|
|
757
|
-
...config.visualImpact ? { visualImpact: config.visualImpact } : {}
|
|
865
|
+
...config.visualImpact ? { visualImpact: config.visualImpact } : {},
|
|
866
|
+
...config.products && !sameProducts(config.products, DEFAULT_PRODUCTS) ? { products: config.products } : {}
|
|
758
867
|
};
|
|
759
868
|
}
|
|
760
869
|
|
|
@@ -1317,6 +1426,49 @@ function fieldChanges(a, b) {
|
|
|
1317
1426
|
if (!sameScopes(sa, sb)) out.push({ field: "scopes", before: sa, after: sb });
|
|
1318
1427
|
return out;
|
|
1319
1428
|
}
|
|
1429
|
+
function sameToken(a, b) {
|
|
1430
|
+
if (!a || !b) return a === b;
|
|
1431
|
+
return fieldChanges(a, b).length === 0;
|
|
1432
|
+
}
|
|
1433
|
+
function sideChange(from, to) {
|
|
1434
|
+
if (!from && !to) return void 0;
|
|
1435
|
+
if (!from) return { kind: "added", fields: [] };
|
|
1436
|
+
if (!to) return { kind: "removed", fields: [] };
|
|
1437
|
+
const fields = fieldChanges(from, to);
|
|
1438
|
+
if (!fields.length) return void 0;
|
|
1439
|
+
return { kind: fields.some((f) => f.field === "path") ? "renamed" : "changed", fields };
|
|
1440
|
+
}
|
|
1441
|
+
function threeWayDiff(base, figma, head) {
|
|
1442
|
+
const ids = /* @__PURE__ */ new Set([...base?.keys() ?? [], ...figma.keys(), ...head.keys()]);
|
|
1443
|
+
const result = { local: [], remote: [], conflicts: [], unchanged: 0 };
|
|
1444
|
+
for (const id of [...ids].sort()) {
|
|
1445
|
+
const b = base?.get(id);
|
|
1446
|
+
const f = figma.get(id);
|
|
1447
|
+
const h = head.get(id);
|
|
1448
|
+
const local = sideChange(b, f);
|
|
1449
|
+
const remote = sideChange(b, h);
|
|
1450
|
+
if (!local && !remote || f && h && sameToken(f, h) || !f && !h) {
|
|
1451
|
+
result.unchanged++;
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
const entry = { id, path: (f ?? h ?? b).path, base: b, figma: f, head: h, local, remote, conflict: false };
|
|
1455
|
+
if (local && !remote) {
|
|
1456
|
+
entry.path = (f ?? b).path;
|
|
1457
|
+
result.local.push(entry);
|
|
1458
|
+
} else if (remote && !local) {
|
|
1459
|
+
entry.path = (h ?? b).path;
|
|
1460
|
+
result.remote.push(entry);
|
|
1461
|
+
} else {
|
|
1462
|
+
entry.conflict = true;
|
|
1463
|
+
result.conflicts.push(entry);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
const byPath = (a, b) => a.path.localeCompare(b.path);
|
|
1467
|
+
result.local.sort(byPath);
|
|
1468
|
+
result.remote.sort(byPath);
|
|
1469
|
+
result.conflicts.sort(byPath);
|
|
1470
|
+
return result;
|
|
1471
|
+
}
|
|
1320
1472
|
|
|
1321
1473
|
// ../core/src/risk.ts
|
|
1322
1474
|
var RISK_LABELS = {
|
|
@@ -1551,6 +1703,164 @@ function composeProposalBody(args) {
|
|
|
1551
1703
|
].join("\n");
|
|
1552
1704
|
}
|
|
1553
1705
|
|
|
1706
|
+
// ../core/src/component-spec.ts
|
|
1707
|
+
var COMPONENT_SPECS_VERSION = 1;
|
|
1708
|
+
var DEFAULT_COMPONENT_SPECS_FILE = "packages/react/component-specs.json";
|
|
1709
|
+
function coverageSummary(file) {
|
|
1710
|
+
const list2 = file.coverage ?? file.components.map((c) => ({ name: c.name, status: "generated" }));
|
|
1711
|
+
const generated = list2.filter((c) => c.status === "generated").length;
|
|
1712
|
+
const drafted = list2.filter((c) => c.status === "drafted").length;
|
|
1713
|
+
const layout = list2.filter((c) => c.status === "layout").length;
|
|
1714
|
+
return { generated, drafted, inFigma: generated + drafted, visual: list2.length - layout, layout, total: list2.length, missing: list2.filter((c) => c.status === "unsupported") };
|
|
1715
|
+
}
|
|
1716
|
+
var variantKeyOf = (props) => Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join(", ") || "Default";
|
|
1717
|
+
function parseComponentSpecs(text) {
|
|
1718
|
+
let data;
|
|
1719
|
+
try {
|
|
1720
|
+
data = JSON.parse(text);
|
|
1721
|
+
} catch {
|
|
1722
|
+
throw new Error("component-specs.json is not valid JSON.");
|
|
1723
|
+
}
|
|
1724
|
+
const f = data;
|
|
1725
|
+
if (!f || typeof f !== "object" || !Array.isArray(f.components)) throw new Error('component-specs.json has no "components" list.');
|
|
1726
|
+
if (f.version !== COMPONENT_SPECS_VERSION) throw new Error(`component-specs.json is version ${String(f.version)}; this tool reads version ${COMPONENT_SPECS_VERSION}.`);
|
|
1727
|
+
return f;
|
|
1728
|
+
}
|
|
1729
|
+
function figmaCell(entry) {
|
|
1730
|
+
if (!entry) return void 0;
|
|
1731
|
+
if (entry.status === "generated") return { label: "Yes" };
|
|
1732
|
+
if (entry.status === "drafted") return { label: "Drafted", title: "Drafted with Claude where the code cannot be read on its own: in Figma, check it against the code." };
|
|
1733
|
+
if (entry.status === "layout") return { label: "Auto layout", title: "A layout helper: nothing to draw, use auto layout in Figma." };
|
|
1734
|
+
return { label: "Not yet", ...entry.reason ? { title: `Not supported yet: ${entry.reason}` } : {} };
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// ../core/src/fidelity-references.ts
|
|
1738
|
+
var FIGMA_REFERENCES_BRANCH = "bridge-visual";
|
|
1739
|
+
var FIGMA_REFERENCES_DIR = "figma-references";
|
|
1740
|
+
var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
|
|
1741
|
+
var FIGMA_REFERENCE_STORY = "internal-figma-reference--variant";
|
|
1742
|
+
var FIGMA_FILL_WIDTH_TOKEN = "size.80";
|
|
1743
|
+
var STATE_PSEUDO = {
|
|
1744
|
+
default: "rest",
|
|
1745
|
+
rest: "rest",
|
|
1746
|
+
enabled: "rest",
|
|
1747
|
+
hover: "hover",
|
|
1748
|
+
hovered: "hover",
|
|
1749
|
+
pressed: "active",
|
|
1750
|
+
active: "active",
|
|
1751
|
+
focus: "focus-visible",
|
|
1752
|
+
focused: "focus-visible",
|
|
1753
|
+
"focus visible": "focus-visible",
|
|
1754
|
+
disabled: "disabled"
|
|
1755
|
+
};
|
|
1756
|
+
var iconName = (component) => component.replace(/^Icon\//, "");
|
|
1757
|
+
var exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
|
|
1758
|
+
function planReference(spec, variant, file) {
|
|
1759
|
+
if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
|
|
1760
|
+
const props = { ...spec.fixed ?? {} };
|
|
1761
|
+
let pseudo;
|
|
1762
|
+
for (const axis of spec.axes) {
|
|
1763
|
+
const value = variant.props[axis.name];
|
|
1764
|
+
if (value === void 0) return { ok: false, reason: `the variant has no ${axis.name} value` };
|
|
1765
|
+
if (axis.prop) {
|
|
1766
|
+
props[axis.prop] = value === "true" ? true : value === "false" ? false : value;
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
if (axis.name === "Checked") {
|
|
1770
|
+
props.checked = value === "true";
|
|
1771
|
+
if (value === "mixed") props.indeterminate = true;
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
if (axis.name.toLowerCase() !== "state") return { ok: false, reason: `${axis.name} has no matching option in code` };
|
|
1775
|
+
const s = STATE_PSEUDO[value.toLowerCase()];
|
|
1776
|
+
if (!s) return { ok: false, reason: `the ${value} state has no code equivalent to render` };
|
|
1777
|
+
if (s === "disabled") props.disabled = true;
|
|
1778
|
+
else if (s !== "rest") pseudo = s;
|
|
1779
|
+
}
|
|
1780
|
+
const root = spec.parts.find((p) => !p.parent);
|
|
1781
|
+
if (!root) return { ok: false, reason: "the spec has no root layer" };
|
|
1782
|
+
const children = (id) => spec.parts.filter((p) => p.parent === id);
|
|
1783
|
+
const textOf = (part) => {
|
|
1784
|
+
const swapTo = variant.slots?.[part.id] ?? part.swap?.default;
|
|
1785
|
+
if (part.swap && swapTo) {
|
|
1786
|
+
const slot = file?.slots?.find((s) => s.name === swapTo);
|
|
1787
|
+
if (!slot) return { reason: `${swapTo} is not in the specs` };
|
|
1788
|
+
if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
|
|
1789
|
+
return slot.text.default;
|
|
1790
|
+
}
|
|
1791
|
+
const prop = spec.properties.find((p) => p.name === part.text && p.type === "TEXT");
|
|
1792
|
+
return typeof prop?.default === "string" ? prop.default : part.name;
|
|
1793
|
+
};
|
|
1794
|
+
let text;
|
|
1795
|
+
const icons = {};
|
|
1796
|
+
for (const prop of spec.properties) {
|
|
1797
|
+
if (prop.type === "TEXT") {
|
|
1798
|
+
const hidden = spec.properties.some((b) => b.type === "BOOLEAN" && b.prop === prop.prop && b.default === false);
|
|
1799
|
+
if (hidden && prop.prop) continue;
|
|
1800
|
+
if (prop.prop === "children") text = String(prop.default);
|
|
1801
|
+
else if (prop.prop) props[prop.prop] = String(prop.default);
|
|
1802
|
+
continue;
|
|
1803
|
+
}
|
|
1804
|
+
const iconPart = spec.parts.find((p) => p.kind === "icon" && p.visible === prop.name);
|
|
1805
|
+
if (prop.default === true && !iconPart && prop.prop && props[prop.prop] === void 0 && spec.example?.p?.[prop.prop] === void 0) props[prop.prop] = true;
|
|
1806
|
+
}
|
|
1807
|
+
for (const part of spec.parts) {
|
|
1808
|
+
if (part.kind !== "icon" || !part.swap) continue;
|
|
1809
|
+
const shown = part.visible ? spec.properties.find((p) => p.name === part.visible)?.default === true : true;
|
|
1810
|
+
if (!shown) continue;
|
|
1811
|
+
if (!part.swap.prop) return { ok: false, reason: `the ${part.name} icon has no matching option in code` };
|
|
1812
|
+
icons[part.swap.prop] = iconName(variant.slots?.[part.id] ?? part.swap.default);
|
|
1813
|
+
}
|
|
1814
|
+
let parts;
|
|
1815
|
+
const loose = spec.properties.filter((p) => p.type === "TEXT" && !p.prop);
|
|
1816
|
+
const frames = children(root.id).filter((p) => p.kind === "frame");
|
|
1817
|
+
const compound = text === void 0 && frames.length > 0 && frames.every((f) => spec.parts.some((p) => p.parent === f.id && p.kind === "text" && (p.swap || p.text === f.name)));
|
|
1818
|
+
if (compound) {
|
|
1819
|
+
parts = [];
|
|
1820
|
+
for (const f of frames) {
|
|
1821
|
+
const t = spec.parts.find((p) => p.parent === f.id && p.kind === "text");
|
|
1822
|
+
const value = textOf(t);
|
|
1823
|
+
if (typeof value !== "string") return { ok: false, reason: value.reason };
|
|
1824
|
+
parts.push({ component: `${exportName(spec.name)}${exportName(f.name)}`, text: value });
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
for (const p of loose) {
|
|
1828
|
+
if (p.name === "Value") continue;
|
|
1829
|
+
const part = spec.parts.find((x) => x.text === p.name);
|
|
1830
|
+
if (compound && part && part.parent !== root.id) continue;
|
|
1831
|
+
if (part && /placeholder$/.test(part.id)) {
|
|
1832
|
+
props.placeholder = String(p.default);
|
|
1833
|
+
continue;
|
|
1834
|
+
}
|
|
1835
|
+
return { ok: false, reason: `the ${p.name} text has no matching option in code` };
|
|
1836
|
+
}
|
|
1837
|
+
const example = spec.example;
|
|
1838
|
+
const exampleChildren = example?.children?.length ? example.children : void 0;
|
|
1839
|
+
return {
|
|
1840
|
+
ok: true,
|
|
1841
|
+
render: {
|
|
1842
|
+
component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
|
|
1843
|
+
props: { ...example?.p ?? {}, ...props },
|
|
1844
|
+
...exampleChildren ? { children: exampleChildren } : {},
|
|
1845
|
+
...text !== void 0 && !exampleChildren ? { text } : {},
|
|
1846
|
+
...parts && !exampleChildren ? { parts } : {},
|
|
1847
|
+
...Object.keys(icons).length ? { icons } : {},
|
|
1848
|
+
...pseudo ? { pseudo } : {},
|
|
1849
|
+
...root.fill ? { width: FIGMA_FILL_WIDTH_TOKEN } : {}
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
function referenceImagePath(component, key) {
|
|
1854
|
+
const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
1855
|
+
const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
|
|
1856
|
+
return `${slug(component)}/${file || "default"}.png`;
|
|
1857
|
+
}
|
|
1858
|
+
function planReferences(file) {
|
|
1859
|
+
return file.components.flatMap(
|
|
1860
|
+
(spec) => spec.variants.map((v) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v.props), plan: planReference(spec, v, file) }))
|
|
1861
|
+
);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1554
1864
|
// ../core/src/node/git-local.ts
|
|
1555
1865
|
import { execFile } from "node:child_process";
|
|
1556
1866
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
@@ -1661,6 +1971,7 @@ async function identityEnv(root) {
|
|
|
1661
1971
|
|
|
1662
1972
|
export {
|
|
1663
1973
|
__export,
|
|
1974
|
+
previewUrl,
|
|
1664
1975
|
previewLinks,
|
|
1665
1976
|
readPreviewComment,
|
|
1666
1977
|
findPreviewComment,
|
|
@@ -1673,8 +1984,18 @@ export {
|
|
|
1673
1984
|
DEFAULT_RIGHTS,
|
|
1674
1985
|
approverEntries,
|
|
1675
1986
|
shipPolicy,
|
|
1987
|
+
unreadableShipTeams,
|
|
1988
|
+
canShip,
|
|
1676
1989
|
whoCanShipSentence,
|
|
1990
|
+
productLabel,
|
|
1991
|
+
productsOf,
|
|
1992
|
+
defaultProduct,
|
|
1993
|
+
findProduct,
|
|
1994
|
+
productOfBranch,
|
|
1995
|
+
productForPlace,
|
|
1677
1996
|
CONFIG_FILE,
|
|
1997
|
+
DEFAULT_OVERRIDE,
|
|
1998
|
+
DEFAULT_TOKEN_PATHS,
|
|
1678
1999
|
loadConfig,
|
|
1679
2000
|
tokensPath,
|
|
1680
2001
|
protectedBranchesOf,
|
|
@@ -1686,6 +2007,7 @@ export {
|
|
|
1686
2007
|
renderedColor,
|
|
1687
2008
|
contrastPair,
|
|
1688
2009
|
parseContrastChecks,
|
|
2010
|
+
threeWayDiff,
|
|
1689
2011
|
RISK_LABELS,
|
|
1690
2012
|
classifyRisk,
|
|
1691
2013
|
aliasOf,
|
|
@@ -1707,6 +2029,15 @@ export {
|
|
|
1707
2029
|
DEFAULT_PROTECTED_BRANCHES,
|
|
1708
2030
|
ProtectedBranchError,
|
|
1709
2031
|
assertSafeBranch,
|
|
2032
|
+
DEFAULT_COMPONENT_SPECS_FILE,
|
|
2033
|
+
coverageSummary,
|
|
2034
|
+
parseComponentSpecs,
|
|
2035
|
+
figmaCell,
|
|
2036
|
+
FIGMA_REFERENCES_BRANCH,
|
|
2037
|
+
FIGMA_REFERENCES_MANIFEST,
|
|
2038
|
+
FIGMA_REFERENCE_STORY,
|
|
2039
|
+
referenceImagePath,
|
|
2040
|
+
planReferences,
|
|
1710
2041
|
git,
|
|
1711
2042
|
isGitRepo,
|
|
1712
2043
|
currentBranch,
|