@bridge-ds/mcp 0.3.2 → 0.3.3
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.
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
unreadableTeams,
|
|
55
55
|
validateLayered,
|
|
56
56
|
whoCanShipSentence
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-K3HY24MZ.js";
|
|
58
58
|
|
|
59
59
|
// src/lib.ts
|
|
60
60
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -3048,6 +3048,8 @@ function explorationStatus(p) {
|
|
|
3048
3048
|
}
|
|
3049
3049
|
function promotionPlan(input) {
|
|
3050
3050
|
const exploration = parseExplorationBody(input.body);
|
|
3051
|
+
const jira = /^([A-Z][A-Z0-9_]*-\d+)\s+/.exec(input.title)?.[1] ?? null;
|
|
3052
|
+
const title = jira ? input.title.slice(jira.length).trim() : input.title;
|
|
3051
3053
|
const removeFiles = input.files.filter((f) => f.path.startsWith(`${EXPLORATION_NOTES_DIR}/`) && f.status !== "removed").map((f) => f.path);
|
|
3052
3054
|
const files = input.files.filter((f) => !f.path.startsWith(`${EXPLORATION_NOTES_DIR}/`));
|
|
3053
3055
|
const risk = classifyRisk({ files, ...input.config ? { config: input.config } : {} });
|
|
@@ -3060,13 +3062,13 @@ function promotionPlan(input) {
|
|
|
3060
3062
|
const body = [
|
|
3061
3063
|
`**Promoted from an exploration on ${date}.** Review it like any change: the tier below comes from the files it touches.`,
|
|
3062
3064
|
"",
|
|
3063
|
-
kept || `## ${
|
|
3065
|
+
kept || `## ${title.replace(/^Exploration:\s*/i, "")}`,
|
|
3064
3066
|
"",
|
|
3065
3067
|
`**Risk tier:** ${risk.tier}.`,
|
|
3066
3068
|
...risk.reasons.slice(0, 8).map((r) => `- ${r}`),
|
|
3067
3069
|
...closes.length ? ["", ...closes.map((n) => `Closes #${n}`)] : []
|
|
3068
3070
|
].join("\n");
|
|
3069
|
-
const t =
|
|
3071
|
+
const t = title.replace(/^Exploration:\s*/i, "").trim() || exploration?.name || title;
|
|
3070
3072
|
return {
|
|
3071
3073
|
tier: risk.tier,
|
|
3072
3074
|
reasons: risk.reasons,
|
|
@@ -3074,7 +3076,7 @@ function promotionPlan(input) {
|
|
|
3074
3076
|
addLabels,
|
|
3075
3077
|
removeFiles,
|
|
3076
3078
|
body,
|
|
3077
|
-
title: t.charAt(0).toUpperCase()
|
|
3079
|
+
title: `${jira ? `${jira} ` : ""}${t.charAt(0).toUpperCase()}${t.slice(1)}`,
|
|
3078
3080
|
markReady: true
|
|
3079
3081
|
};
|
|
3080
3082
|
}
|
|
@@ -3435,7 +3437,7 @@ function explorationSummary(p, _owner, _repo, s) {
|
|
|
3435
3437
|
url: p.url,
|
|
3436
3438
|
branch: p.headRef,
|
|
3437
3439
|
slug: slug2,
|
|
3438
|
-
name: e?.name ?? p.title.replace(/^Exploration:\s*/i, ""),
|
|
3440
|
+
name: e?.name ?? p.title.replace(/^[A-Z][A-Z0-9_]*-\d+\s+/, "").replace(/^Exploration:\s*/i, ""),
|
|
3439
3441
|
purpose: e?.purpose ?? "",
|
|
3440
3442
|
...e?.useCase ? { useCase: e.useCase } : {},
|
|
3441
3443
|
status: explorationStatus({ state: p.state, labels: p.labels, headRef: p.headRef, body: p.body }),
|
|
@@ -3472,7 +3474,8 @@ Exploration: never merged as is. Promote it to review or close it.`,
|
|
|
3472
3474
|
files: [{ path: explorationNotePath(slug2), content: explorationNote(full) }]
|
|
3473
3475
|
});
|
|
3474
3476
|
const ctx = { owner: gh.owner, repo: gh.name, packages: s.packages, apps: s.apps, ...s.requests?.length ? { requests: s.requests } : {} };
|
|
3475
|
-
const
|
|
3477
|
+
const title = full.jira ? `${full.jira.trim().toUpperCase()} ${explorationPrTitle(full)}` : explorationPrTitle(full);
|
|
3478
|
+
const pr = await x.createPullRequest({ title, head: branch, base: s.base, body: explorationPrBody(full, ctx), draft: true });
|
|
3476
3479
|
await x.addLabels(pr.number, [EXPLORATION_LABEL]);
|
|
3477
3480
|
await x.updatePullRequest(pr.number, { body: explorationPrBody(full, { ...ctx, pr: pr.number }) }).catch(() => void 0);
|
|
3478
3481
|
return {
|
|
@@ -4344,7 +4347,8 @@ function classifyTag(t, imports, ds) {
|
|
|
4344
4347
|
if (name === "a" && !t.hasHref) return {};
|
|
4345
4348
|
if (t.inRenderProp) return {};
|
|
4346
4349
|
const suggestion = nativeEquivalent(name, t.type, ds.components);
|
|
4347
|
-
|
|
4350
|
+
if (!suggestion) return {};
|
|
4351
|
+
return { component: { ...at, name, kind: "native", library: "native", suggestion } };
|
|
4348
4352
|
}
|
|
4349
4353
|
const root = name.split(".")[0];
|
|
4350
4354
|
const imp = imports.get(root);
|
|
@@ -7022,7 +7026,7 @@ async function visualImpact(ctx, a, run2) {
|
|
|
7022
7026
|
if (ctx.mode !== "local" || !ctx.root) throw new Error("Visual impact needs a local checkout (start Bridge with --root): it builds Storybook and the app at both refs.");
|
|
7023
7027
|
if (!ctx.isGit) throw new Error(`${ctx.root} is not a git repository.`);
|
|
7024
7028
|
const out = a.out ? resolve3(ctx.root, a.out) : join4(ctx.root, "visual-impact");
|
|
7025
|
-
const runImpact = run2 ?? (await import("./src-
|
|
7029
|
+
const runImpact = run2 ?? (await import("./src-V653Z7JC.js")).runImpact;
|
|
7026
7030
|
const r = await runImpact({
|
|
7027
7031
|
root: ctx.root,
|
|
7028
7032
|
base: a.base ?? "main",
|
|
@@ -7652,7 +7656,7 @@ async function writeDrafts(ctx, a) {
|
|
|
7652
7656
|
}
|
|
7653
7657
|
|
|
7654
7658
|
// src/server.ts
|
|
7655
|
-
var VERSION = true ? "0.3.
|
|
7659
|
+
var VERSION = true ? "0.3.3" : "dev";
|
|
7656
7660
|
var ok = (data) => ({ content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }] });
|
|
7657
7661
|
var fail = (e) => ({ content: [{ type: "text", text: `Error: ${e.message ?? String(e)}` }], isError: true });
|
|
7658
7662
|
var run = (fn) => async (a) => {
|
|
@@ -433,7 +433,7 @@ function groupsPhrase(groups) {
|
|
|
433
433
|
}
|
|
434
434
|
function whoCanShipSentence(policy) {
|
|
435
435
|
const names = (policy.entries ?? policy.logins).map((l) => entryLabel(l, policy.teams)).join(", ");
|
|
436
|
-
return `Only ${groupsPhrase(policy.groups)} can
|
|
436
|
+
return `Only ${groupsPhrase(policy.groups)} can merge this${names ? ` (${names})` : ""}.`;
|
|
437
437
|
}
|
|
438
438
|
function visualImpactProblems(raw) {
|
|
439
439
|
if (raw === void 0) return [];
|
package/dist/index.js
CHANGED
package/dist/lib.js
CHANGED
|
@@ -23,12 +23,12 @@ import {
|
|
|
23
23
|
parseDocsReply,
|
|
24
24
|
requests_exports,
|
|
25
25
|
serveStdio
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-7HYTZVRW.js";
|
|
27
27
|
import {
|
|
28
28
|
CONFIG_FILE,
|
|
29
29
|
loadConfig,
|
|
30
30
|
toConfigJson
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-K3HY24MZ.js";
|
|
32
32
|
export {
|
|
33
33
|
BridgeContext,
|
|
34
34
|
CONFIG_FILE,
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
loadConfig,
|
|
8
8
|
selectTokenFiles,
|
|
9
9
|
showFile
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-K3HY24MZ.js";
|
|
11
11
|
|
|
12
12
|
// ../visual/src/run.ts
|
|
13
13
|
import { existsSync as existsSync3 } from "node:fs";
|
|
@@ -123,6 +123,148 @@ function impactedModules(index, input) {
|
|
|
123
123
|
return { modules, globalHits };
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// ../core/src/component-spec.ts
|
|
127
|
+
var COMPONENT_SPECS_VERSION = 1;
|
|
128
|
+
var variantKeyOf = (props) => Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join(", ") || "Default";
|
|
129
|
+
function parseComponentSpecs(text) {
|
|
130
|
+
let data;
|
|
131
|
+
try {
|
|
132
|
+
data = JSON.parse(text);
|
|
133
|
+
} catch {
|
|
134
|
+
throw new Error("component-specs.json is not valid JSON.");
|
|
135
|
+
}
|
|
136
|
+
const f = data;
|
|
137
|
+
if (!f || typeof f !== "object" || !Array.isArray(f.components)) throw new Error('component-specs.json has no "components" list.');
|
|
138
|
+
if (f.version !== COMPONENT_SPECS_VERSION) throw new Error(`component-specs.json is version ${String(f.version)}; this tool reads version ${COMPONENT_SPECS_VERSION}.`);
|
|
139
|
+
return f;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ../core/src/fidelity-references.ts
|
|
143
|
+
var FIGMA_REFERENCES_DIR = "figma-references";
|
|
144
|
+
var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
|
|
145
|
+
var FIGMA_REFERENCE_STORY = "internal-figma-reference--variant";
|
|
146
|
+
var FIGMA_FILL_WIDTH_TOKEN = "size.80";
|
|
147
|
+
var STATE_PSEUDO = {
|
|
148
|
+
default: "rest",
|
|
149
|
+
rest: "rest",
|
|
150
|
+
enabled: "rest",
|
|
151
|
+
hover: "hover",
|
|
152
|
+
hovered: "hover",
|
|
153
|
+
pressed: "active",
|
|
154
|
+
active: "active",
|
|
155
|
+
focus: "focus-visible",
|
|
156
|
+
focused: "focus-visible",
|
|
157
|
+
"focus visible": "focus-visible",
|
|
158
|
+
disabled: "disabled"
|
|
159
|
+
};
|
|
160
|
+
var iconName = (component) => component.replace(/^Icon\//, "");
|
|
161
|
+
var exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
|
|
162
|
+
function planReference(spec, variant, file) {
|
|
163
|
+
if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
|
|
164
|
+
const props = { ...spec.fixed ?? {} };
|
|
165
|
+
let pseudo;
|
|
166
|
+
for (const axis of spec.axes) {
|
|
167
|
+
const value = variant.props[axis.name];
|
|
168
|
+
if (value === void 0) return { ok: false, reason: `the variant has no ${axis.name} value` };
|
|
169
|
+
if (axis.prop) {
|
|
170
|
+
props[axis.prop] = value === "true" ? true : value === "false" ? false : value;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (axis.name === "Checked") {
|
|
174
|
+
props.checked = value === "true";
|
|
175
|
+
if (value === "mixed") props.indeterminate = true;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (axis.name.toLowerCase() !== "state") return { ok: false, reason: `${axis.name} has no matching option in code` };
|
|
179
|
+
const s = STATE_PSEUDO[value.toLowerCase()];
|
|
180
|
+
if (!s) return { ok: false, reason: `the ${value} state has no code equivalent to render` };
|
|
181
|
+
if (s === "disabled") props.disabled = true;
|
|
182
|
+
else if (s !== "rest") pseudo = s;
|
|
183
|
+
}
|
|
184
|
+
const root = spec.parts.find((p) => !p.parent);
|
|
185
|
+
if (!root) return { ok: false, reason: "the spec has no root layer" };
|
|
186
|
+
const children = (id) => spec.parts.filter((p) => p.parent === id);
|
|
187
|
+
const textOf = (part) => {
|
|
188
|
+
const swapTo = variant.slots?.[part.id] ?? part.swap?.default;
|
|
189
|
+
if (part.swap && swapTo) {
|
|
190
|
+
const slot = file?.slots?.find((s) => s.name === swapTo);
|
|
191
|
+
if (!slot) return { reason: `${swapTo} is not in the specs` };
|
|
192
|
+
if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
|
|
193
|
+
return slot.text.default;
|
|
194
|
+
}
|
|
195
|
+
const prop = spec.properties.find((p) => p.name === part.text && p.type === "TEXT");
|
|
196
|
+
return typeof prop?.default === "string" ? prop.default : part.name;
|
|
197
|
+
};
|
|
198
|
+
let text;
|
|
199
|
+
const icons = {};
|
|
200
|
+
for (const prop of spec.properties) {
|
|
201
|
+
if (prop.type === "TEXT") {
|
|
202
|
+
const hidden = spec.properties.some((b) => b.type === "BOOLEAN" && b.prop === prop.prop && b.default === false);
|
|
203
|
+
if (hidden && prop.prop) continue;
|
|
204
|
+
if (prop.prop === "children") text = String(prop.default);
|
|
205
|
+
else if (prop.prop) props[prop.prop] = String(prop.default);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const iconPart = spec.parts.find((p) => p.kind === "icon" && p.visible === prop.name);
|
|
209
|
+
if (prop.default === true && !iconPart && prop.prop && props[prop.prop] === void 0 && spec.example?.p?.[prop.prop] === void 0) props[prop.prop] = true;
|
|
210
|
+
}
|
|
211
|
+
for (const part of spec.parts) {
|
|
212
|
+
if (part.kind !== "icon" || !part.swap) continue;
|
|
213
|
+
const shown = part.visible ? spec.properties.find((p) => p.name === part.visible)?.default === true : true;
|
|
214
|
+
if (!shown) continue;
|
|
215
|
+
if (!part.swap.prop) return { ok: false, reason: `the ${part.name} icon has no matching option in code` };
|
|
216
|
+
icons[part.swap.prop] = iconName(variant.slots?.[part.id] ?? part.swap.default);
|
|
217
|
+
}
|
|
218
|
+
let parts;
|
|
219
|
+
const loose = spec.properties.filter((p) => p.type === "TEXT" && !p.prop);
|
|
220
|
+
const frames = children(root.id).filter((p) => p.kind === "frame");
|
|
221
|
+
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)));
|
|
222
|
+
if (compound) {
|
|
223
|
+
parts = [];
|
|
224
|
+
for (const f of frames) {
|
|
225
|
+
const t = spec.parts.find((p) => p.parent === f.id && p.kind === "text");
|
|
226
|
+
const value = textOf(t);
|
|
227
|
+
if (typeof value !== "string") return { ok: false, reason: value.reason };
|
|
228
|
+
parts.push({ component: `${exportName(spec.name)}${exportName(f.name)}`, text: value });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const p of loose) {
|
|
232
|
+
if (p.name === "Value") continue;
|
|
233
|
+
const part = spec.parts.find((x) => x.text === p.name);
|
|
234
|
+
if (compound && part && part.parent !== root.id) continue;
|
|
235
|
+
if (part && /placeholder$/.test(part.id)) {
|
|
236
|
+
props.placeholder = String(p.default);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
return { ok: false, reason: `the ${p.name} text has no matching option in code` };
|
|
240
|
+
}
|
|
241
|
+
const example = spec.example;
|
|
242
|
+
const exampleChildren = example?.children?.length ? example.children : void 0;
|
|
243
|
+
return {
|
|
244
|
+
ok: true,
|
|
245
|
+
render: {
|
|
246
|
+
component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
|
|
247
|
+
props: { ...example?.p ?? {}, ...props },
|
|
248
|
+
...exampleChildren ? { children: exampleChildren } : {},
|
|
249
|
+
...text !== void 0 && !exampleChildren ? { text } : {},
|
|
250
|
+
...parts && !exampleChildren ? { parts } : {},
|
|
251
|
+
...Object.keys(icons).length ? { icons } : {},
|
|
252
|
+
...pseudo ? { pseudo } : {},
|
|
253
|
+
...root.fill ? { width: FIGMA_FILL_WIDTH_TOKEN } : {}
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
function referenceImagePath(component, key) {
|
|
258
|
+
const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
259
|
+
const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
|
|
260
|
+
return `${slug(component)}/${file || "default"}.png`;
|
|
261
|
+
}
|
|
262
|
+
function planReferences(file) {
|
|
263
|
+
return file.components.flatMap(
|
|
264
|
+
(spec) => spec.variants.map((v) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v.props), plan: planReference(spec, v, file) }))
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
126
268
|
// ../visual/src/capture.ts
|
|
127
269
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
128
270
|
import { dirname } from "node:path";
|
|
@@ -146,8 +288,8 @@ async function launch(o = {}) {
|
|
|
146
288
|
args: ["--lang=en-US", "--disable-gpu", "--font-render-hinting=none", "--disable-lcd-text", "--force-color-profile=srgb", "--hide-scrollbars", "--disable-skia-runtime-opts", "--disable-renderer-backgrounding"]
|
|
147
289
|
});
|
|
148
290
|
}
|
|
149
|
-
async function newContext(browser, viewport) {
|
|
150
|
-
const ctx = await browser.newContext({ viewport, deviceScaleFactor
|
|
291
|
+
async function newContext(browser, viewport, deviceScaleFactor = 1) {
|
|
292
|
+
const ctx = await browser.newContext({ viewport, deviceScaleFactor, reducedMotion: "reduce", colorScheme: "light", locale: "en-US", timezoneId: "UTC", serviceWorkers: "block" });
|
|
151
293
|
await ctx.addInitScript(INIT_SCRIPT);
|
|
152
294
|
await ctx.route(
|
|
153
295
|
/^https?:\/\/(?!127\.0\.0\.1[:/]|localhost[:/])/,
|
|
@@ -580,9 +722,9 @@ function selectImpact(input) {
|
|
|
580
722
|
app
|
|
581
723
|
};
|
|
582
724
|
}
|
|
583
|
-
function storyId(title,
|
|
725
|
+
function storyId(title, exportName2) {
|
|
584
726
|
const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
|
|
585
|
-
const startCase =
|
|
727
|
+
const startCase = exportName2.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
|
|
586
728
|
return `${sanitize(title)}--${sanitize(startCase)}`;
|
|
587
729
|
}
|
|
588
730
|
function storiesFromSource(files) {
|
|
@@ -1096,12 +1238,106 @@ async function runImpact(o) {
|
|
|
1096
1238
|
log(`${result.summary} Manifest: ${relative2(process.cwd(), result.manifestPath) || result.manifestPath}`);
|
|
1097
1239
|
return result;
|
|
1098
1240
|
}
|
|
1241
|
+
|
|
1242
|
+
// ../visual/src/references.ts
|
|
1243
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
1244
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
|
|
1245
|
+
import { dirname as dirname3, join as join4 } from "node:path";
|
|
1246
|
+
async function captureFigmaReferences(o) {
|
|
1247
|
+
const log = o.log ?? (() => void 0);
|
|
1248
|
+
const scale = o.scale ?? 2;
|
|
1249
|
+
const brand = o.brand ?? "bridge";
|
|
1250
|
+
const theme = o.theme ?? "light";
|
|
1251
|
+
if (!existsSync4(join4(o.storybook, "index.json"))) throw new Error(`No built Storybook in ${o.storybook}: run npm run build-storybook first.`);
|
|
1252
|
+
const index = JSON.parse(await readFile4(join4(o.storybook, "index.json"), "utf8"));
|
|
1253
|
+
if (!index.entries[FIGMA_REFERENCE_STORY]) throw new Error(`The Storybook build has no ${FIGMA_REFERENCE_STORY} story (packages/react/src/stories/FigmaReference.stories.tsx).`);
|
|
1254
|
+
const specs = parseComponentSpecs(await readFile4(o.specs, "utf8"));
|
|
1255
|
+
const jobs = planReferences(specs).filter((j) => !o.only?.length || o.only.includes(j.component));
|
|
1256
|
+
const manifest = {
|
|
1257
|
+
version: 1,
|
|
1258
|
+
tool: "@bridge-ds/visual",
|
|
1259
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1260
|
+
sha: o.sha,
|
|
1261
|
+
scale,
|
|
1262
|
+
mode: { brand, theme, density: "comfortable" },
|
|
1263
|
+
storyId: FIGMA_REFERENCE_STORY,
|
|
1264
|
+
components: {}
|
|
1265
|
+
};
|
|
1266
|
+
for (const j of jobs) manifest.components[j.component] ??= { specHash: j.specHash, variants: {} };
|
|
1267
|
+
let captured = 0;
|
|
1268
|
+
let skipped = 0;
|
|
1269
|
+
const served = await serveDir(o.storybook);
|
|
1270
|
+
const browser = await launch();
|
|
1271
|
+
try {
|
|
1272
|
+
const ctx = await newContext(browser, { width: 1200, height: 800 }, scale);
|
|
1273
|
+
const page = await newPage(ctx);
|
|
1274
|
+
await page.goto(storyUrl(served.url, FIGMA_REFERENCE_STORY, brand, theme), { waitUntil: "load", timeout: 3e4 });
|
|
1275
|
+
await page.waitForSelector("[data-fidelity-root], [data-fidelity-error]", { state: "attached", timeout: 2e4 });
|
|
1276
|
+
await settle(page, { busy: false });
|
|
1277
|
+
log(`Capturing ${jobs.length} variants at ${scale}x (${brand} ${theme})`);
|
|
1278
|
+
for (const j of jobs) {
|
|
1279
|
+
const variants = manifest.components[j.component].variants;
|
|
1280
|
+
if (!j.plan.ok) {
|
|
1281
|
+
variants[j.key] = { skipped: j.plan.reason };
|
|
1282
|
+
skipped++;
|
|
1283
|
+
continue;
|
|
1284
|
+
}
|
|
1285
|
+
const plan = JSON.stringify(j.plan.render);
|
|
1286
|
+
await page.evaluate(
|
|
1287
|
+
({ id, plan: plan2 }) => new Promise((resolve2) => {
|
|
1288
|
+
const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
|
|
1289
|
+
const done = () => {
|
|
1290
|
+
clearTimeout(timer);
|
|
1291
|
+
ch.off("storyRendered", done);
|
|
1292
|
+
resolve2();
|
|
1293
|
+
};
|
|
1294
|
+
const timer = setTimeout(done, 1e4);
|
|
1295
|
+
ch.on("storyRendered", done);
|
|
1296
|
+
ch.emit("updateStoryArgs", { storyId: id, updatedArgs: { plan: plan2 } });
|
|
1297
|
+
}),
|
|
1298
|
+
{ id: FIGMA_REFERENCE_STORY, plan }
|
|
1299
|
+
);
|
|
1300
|
+
await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
|
|
1301
|
+
const error = await page.locator("[data-fidelity-error]").first().textContent({ timeout: 200 }).catch(() => null);
|
|
1302
|
+
let target = page.locator("[data-fidelity-root] > *").first();
|
|
1303
|
+
if (!error && !await target.count()) {
|
|
1304
|
+
const marked = page.locator("[data-fidelity-target]").first();
|
|
1305
|
+
await marked.waitFor({ state: "visible", timeout: 3e3 }).catch(() => void 0);
|
|
1306
|
+
await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
|
|
1307
|
+
target = marked;
|
|
1308
|
+
}
|
|
1309
|
+
if (error || !await target.count()) {
|
|
1310
|
+
variants[j.key] = { skipped: error ?? "the code example did not render" };
|
|
1311
|
+
skipped++;
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
const file = referenceImagePath(j.component, j.key);
|
|
1315
|
+
const path = join4(o.out, file);
|
|
1316
|
+
await mkdir4(dirname3(path), { recursive: true });
|
|
1317
|
+
await target.screenshot({ path, omitBackground: true, animations: "disabled", caret: "hide", scale: "device" });
|
|
1318
|
+
const box = await target.boundingBox();
|
|
1319
|
+
variants[j.key] = { file, width: Math.round(box?.width ?? 0), height: Math.round(box?.height ?? 0) };
|
|
1320
|
+
captured++;
|
|
1321
|
+
}
|
|
1322
|
+
await ctx.close();
|
|
1323
|
+
} finally {
|
|
1324
|
+
await browser.close().catch(() => void 0);
|
|
1325
|
+
await served.close();
|
|
1326
|
+
}
|
|
1327
|
+
await mkdir4(o.out, { recursive: true });
|
|
1328
|
+
const manifestPath = join4(o.out, "manifest.json");
|
|
1329
|
+
await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
1330
|
+
`);
|
|
1331
|
+
log(`${captured} images, ${skipped} variants skipped. ${manifestPath}`);
|
|
1332
|
+
return { manifest, manifestPath, captured, skipped };
|
|
1333
|
+
}
|
|
1099
1334
|
export {
|
|
1100
1335
|
COMMENT_MARKER,
|
|
1101
1336
|
DEFAULT_ROUTES,
|
|
1102
1337
|
DEFAULT_TENANTS,
|
|
1103
1338
|
boxesFromCells,
|
|
1104
1339
|
boxesFromDiff,
|
|
1340
|
+
captureFigmaReferences,
|
|
1105
1341
|
changedTokens,
|
|
1106
1342
|
decodePng,
|
|
1107
1343
|
diffPngs,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Bridge MCP server: lets Claude (Claude Code, Claude Desktop or any MCP client) read, check and propose changes to a DTCG design system repo through pull requests.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"build:package": "node ../../scripts/build-packages.mjs mcp"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@bridge-ds/core": "^0.
|
|
58
|
-
"@bridge-ds/visual": "^0.
|
|
57
|
+
"@bridge-ds/core": "^0.6.0",
|
|
58
|
+
"@bridge-ds/visual": "^0.3.0",
|
|
59
59
|
"@modelcontextprotocol/sdk": "1.31.0",
|
|
60
60
|
"typescript": "5.9.3",
|
|
61
61
|
"zod": "3.25.76"
|