@bridge-ds/cli 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.
Files changed (2) hide show
  1. package/dist/index.js +291 -24
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { existsSync as existsSync5 } from "node:fs";
5
- import { readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
6
- import { isAbsolute, join as join5, relative as relative4, resolve as resolve3 } from "node:path";
4
+ import { existsSync as existsSync6 } from "node:fs";
5
+ import { readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
6
+ import { isAbsolute, join as join6, relative as relative4, resolve as resolve3 } from "node:path";
7
7
 
8
8
  // ../core/src/audit/types.ts
9
9
  var AUDIT_SCHEMA_VERSION = 1;
@@ -1212,7 +1212,8 @@ function classifyTag(t, imports, ds) {
1212
1212
  if (name === "a" && !t.hasHref) return {};
1213
1213
  if (t.inRenderProp) return {};
1214
1214
  const suggestion = nativeEquivalent(name, t.type, ds.components);
1215
- return { component: { ...at, name, kind: "native", library: "native", ...suggestion ? { suggestion } : {} } };
1215
+ if (!suggestion) return {};
1216
+ return { component: { ...at, name, kind: "native", library: "native", suggestion } };
1216
1217
  }
1217
1218
  const root = name.split(".")[0];
1218
1219
  const imp = imports.get(root);
@@ -2898,6 +2899,22 @@ function fixCss(target, ds, write) {
2898
2899
  return out;
2899
2900
  }
2900
2901
 
2902
+ // ../core/src/component-spec.ts
2903
+ var COMPONENT_SPECS_VERSION = 1;
2904
+ var variantKeyOf = (props) => Object.keys(props).sort().map((k) => `${k}=${props[k]}`).join(", ") || "Default";
2905
+ function parseComponentSpecs(text) {
2906
+ let data;
2907
+ try {
2908
+ data = JSON.parse(text);
2909
+ } catch {
2910
+ throw new Error("component-specs.json is not valid JSON.");
2911
+ }
2912
+ const f = data;
2913
+ if (!f || typeof f !== "object" || !Array.isArray(f.components)) throw new Error('component-specs.json has no "components" list.');
2914
+ if (f.version !== COMPONENT_SPECS_VERSION) throw new Error(`component-specs.json is version ${String(f.version)}; this tool reads version ${COMPONENT_SPECS_VERSION}.`);
2915
+ return f;
2916
+ }
2917
+
2901
2918
  // src/index.ts
2902
2919
  import { fileURLToPath } from "node:url";
2903
2920
 
@@ -3922,6 +3939,132 @@ function jiraIssueFields(r, jira, issueUrl) {
3922
3939
  };
3923
3940
  }
3924
3941
 
3942
+ // ../core/src/fidelity-references.ts
3943
+ var FIGMA_REFERENCES_DIR = "figma-references";
3944
+ var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
3945
+ var FIGMA_REFERENCE_STORY = "internal-figma-reference--variant";
3946
+ var FIGMA_FILL_WIDTH_TOKEN = "size.80";
3947
+ var STATE_PSEUDO = {
3948
+ default: "rest",
3949
+ rest: "rest",
3950
+ enabled: "rest",
3951
+ hover: "hover",
3952
+ hovered: "hover",
3953
+ pressed: "active",
3954
+ active: "active",
3955
+ focus: "focus-visible",
3956
+ focused: "focus-visible",
3957
+ "focus visible": "focus-visible",
3958
+ disabled: "disabled"
3959
+ };
3960
+ var iconName = (component) => component.replace(/^Icon\//, "");
3961
+ var exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
3962
+ function planReference(spec, variant, file) {
3963
+ if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
3964
+ const props = { ...spec.fixed ?? {} };
3965
+ let pseudo;
3966
+ for (const axis of spec.axes) {
3967
+ const value = variant.props[axis.name];
3968
+ if (value === void 0) return { ok: false, reason: `the variant has no ${axis.name} value` };
3969
+ if (axis.prop) {
3970
+ props[axis.prop] = value === "true" ? true : value === "false" ? false : value;
3971
+ continue;
3972
+ }
3973
+ if (axis.name === "Checked") {
3974
+ props.checked = value === "true";
3975
+ if (value === "mixed") props.indeterminate = true;
3976
+ continue;
3977
+ }
3978
+ if (axis.name.toLowerCase() !== "state") return { ok: false, reason: `${axis.name} has no matching option in code` };
3979
+ const s = STATE_PSEUDO[value.toLowerCase()];
3980
+ if (!s) return { ok: false, reason: `the ${value} state has no code equivalent to render` };
3981
+ if (s === "disabled") props.disabled = true;
3982
+ else if (s !== "rest") pseudo = s;
3983
+ }
3984
+ const root = spec.parts.find((p) => !p.parent);
3985
+ if (!root) return { ok: false, reason: "the spec has no root layer" };
3986
+ const children = (id) => spec.parts.filter((p) => p.parent === id);
3987
+ const textOf = (part) => {
3988
+ const swapTo = variant.slots?.[part.id] ?? part.swap?.default;
3989
+ if (part.swap && swapTo) {
3990
+ const slot = file?.slots?.find((s) => s.name === swapTo);
3991
+ if (!slot) return { reason: `${swapTo} is not in the specs` };
3992
+ if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
3993
+ return slot.text.default;
3994
+ }
3995
+ const prop = spec.properties.find((p) => p.name === part.text && p.type === "TEXT");
3996
+ return typeof prop?.default === "string" ? prop.default : part.name;
3997
+ };
3998
+ let text;
3999
+ const icons = {};
4000
+ for (const prop of spec.properties) {
4001
+ if (prop.type === "TEXT") {
4002
+ const hidden = spec.properties.some((b) => b.type === "BOOLEAN" && b.prop === prop.prop && b.default === false);
4003
+ if (hidden && prop.prop) continue;
4004
+ if (prop.prop === "children") text = String(prop.default);
4005
+ else if (prop.prop) props[prop.prop] = String(prop.default);
4006
+ continue;
4007
+ }
4008
+ const iconPart = spec.parts.find((p) => p.kind === "icon" && p.visible === prop.name);
4009
+ if (prop.default === true && !iconPart && prop.prop && props[prop.prop] === void 0 && spec.example?.p?.[prop.prop] === void 0) props[prop.prop] = true;
4010
+ }
4011
+ for (const part of spec.parts) {
4012
+ if (part.kind !== "icon" || !part.swap) continue;
4013
+ const shown = part.visible ? spec.properties.find((p) => p.name === part.visible)?.default === true : true;
4014
+ if (!shown) continue;
4015
+ if (!part.swap.prop) return { ok: false, reason: `the ${part.name} icon has no matching option in code` };
4016
+ icons[part.swap.prop] = iconName(variant.slots?.[part.id] ?? part.swap.default);
4017
+ }
4018
+ let parts;
4019
+ const loose = spec.properties.filter((p) => p.type === "TEXT" && !p.prop);
4020
+ const frames = children(root.id).filter((p) => p.kind === "frame");
4021
+ 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)));
4022
+ if (compound) {
4023
+ parts = [];
4024
+ for (const f of frames) {
4025
+ const t = spec.parts.find((p) => p.parent === f.id && p.kind === "text");
4026
+ const value = textOf(t);
4027
+ if (typeof value !== "string") return { ok: false, reason: value.reason };
4028
+ parts.push({ component: `${exportName(spec.name)}${exportName(f.name)}`, text: value });
4029
+ }
4030
+ }
4031
+ for (const p of loose) {
4032
+ if (p.name === "Value") continue;
4033
+ const part = spec.parts.find((x) => x.text === p.name);
4034
+ if (compound && part && part.parent !== root.id) continue;
4035
+ if (part && /placeholder$/.test(part.id)) {
4036
+ props.placeholder = String(p.default);
4037
+ continue;
4038
+ }
4039
+ return { ok: false, reason: `the ${p.name} text has no matching option in code` };
4040
+ }
4041
+ const example = spec.example;
4042
+ const exampleChildren = example?.children?.length ? example.children : void 0;
4043
+ return {
4044
+ ok: true,
4045
+ render: {
4046
+ component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
4047
+ props: { ...example?.p ?? {}, ...props },
4048
+ ...exampleChildren ? { children: exampleChildren } : {},
4049
+ ...text !== void 0 && !exampleChildren ? { text } : {},
4050
+ ...parts && !exampleChildren ? { parts } : {},
4051
+ ...Object.keys(icons).length ? { icons } : {},
4052
+ ...pseudo ? { pseudo } : {},
4053
+ ...root.fill ? { width: FIGMA_FILL_WIDTH_TOKEN } : {}
4054
+ }
4055
+ };
4056
+ }
4057
+ function referenceImagePath(component, key) {
4058
+ const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4059
+ const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
4060
+ return `${slug(component)}/${file || "default"}.png`;
4061
+ }
4062
+ function planReferences(file) {
4063
+ return file.components.flatMap(
4064
+ (spec) => spec.variants.map((v) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v.props), plan: planReference(spec, v, file) }))
4065
+ );
4066
+ }
4067
+
3925
4068
  // ../visual/src/capture.ts
3926
4069
  import { mkdir, writeFile } from "node:fs/promises";
3927
4070
  import { dirname as dirname2 } from "node:path";
@@ -3945,8 +4088,8 @@ async function launch(o = {}) {
3945
4088
  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"]
3946
4089
  });
3947
4090
  }
3948
- async function newContext(browser, viewport) {
3949
- const ctx = await browser.newContext({ viewport, deviceScaleFactor: 1, reducedMotion: "reduce", colorScheme: "light", locale: "en-US", timezoneId: "UTC", serviceWorkers: "block" });
4091
+ async function newContext(browser, viewport, deviceScaleFactor = 1) {
4092
+ const ctx = await browser.newContext({ viewport, deviceScaleFactor, reducedMotion: "reduce", colorScheme: "light", locale: "en-US", timezoneId: "UTC", serviceWorkers: "block" });
3950
4093
  await ctx.addInitScript(INIT_SCRIPT);
3951
4094
  await ctx.route(
3952
4095
  /^https?:\/\/(?!127\.0\.0\.1[:/]|localhost[:/])/,
@@ -4379,9 +4522,9 @@ function selectImpact(input) {
4379
4522
  app
4380
4523
  };
4381
4524
  }
4382
- function storyId(title, exportName) {
4525
+ function storyId(title, exportName2) {
4383
4526
  const sanitize = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/-+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
4384
- const startCase = exportName.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").replace(/[_-]+/g, " ").trim();
4527
+ 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();
4385
4528
  return `${sanitize(title)}--${sanitize(startCase)}`;
4386
4529
  }
4387
4530
  function storiesFromSource(files) {
@@ -4896,9 +5039,105 @@ async function runImpact(o) {
4896
5039
  return result;
4897
5040
  }
4898
5041
 
5042
+ // ../visual/src/references.ts
5043
+ import { existsSync as existsSync5 } from "node:fs";
5044
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
5045
+ import { dirname as dirname4, join as join5 } from "node:path";
5046
+ async function captureFigmaReferences(o) {
5047
+ const log = o.log ?? (() => void 0);
5048
+ const scale = o.scale ?? 2;
5049
+ const brand2 = o.brand ?? "bridge";
5050
+ const theme = o.theme ?? "light";
5051
+ if (!existsSync5(join5(o.storybook, "index.json"))) throw new Error(`No built Storybook in ${o.storybook}: run npm run build-storybook first.`);
5052
+ const index = JSON.parse(await readFile4(join5(o.storybook, "index.json"), "utf8"));
5053
+ 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).`);
5054
+ const specs = parseComponentSpecs(await readFile4(o.specs, "utf8"));
5055
+ const jobs = planReferences(specs).filter((j) => !o.only?.length || o.only.includes(j.component));
5056
+ const manifest = {
5057
+ version: 1,
5058
+ tool: "@bridge-ds/visual",
5059
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5060
+ sha: o.sha,
5061
+ scale,
5062
+ mode: { brand: brand2, theme, density: "comfortable" },
5063
+ storyId: FIGMA_REFERENCE_STORY,
5064
+ components: {}
5065
+ };
5066
+ for (const j of jobs) manifest.components[j.component] ??= { specHash: j.specHash, variants: {} };
5067
+ let captured = 0;
5068
+ let skipped = 0;
5069
+ const served = await serveDir(o.storybook);
5070
+ const browser = await launch();
5071
+ try {
5072
+ const ctx = await newContext(browser, { width: 1200, height: 800 }, scale);
5073
+ const page = await newPage(ctx);
5074
+ await page.goto(storyUrl(served.url, FIGMA_REFERENCE_STORY, brand2, theme), { waitUntil: "load", timeout: 3e4 });
5075
+ await page.waitForSelector("[data-fidelity-root], [data-fidelity-error]", { state: "attached", timeout: 2e4 });
5076
+ await settle(page, { busy: false });
5077
+ log(`Capturing ${jobs.length} variants at ${scale}x (${brand2} ${theme})`);
5078
+ for (const j of jobs) {
5079
+ const variants = manifest.components[j.component].variants;
5080
+ if (!j.plan.ok) {
5081
+ variants[j.key] = { skipped: j.plan.reason };
5082
+ skipped++;
5083
+ continue;
5084
+ }
5085
+ const plan = JSON.stringify(j.plan.render);
5086
+ await page.evaluate(
5087
+ ({ id, plan: plan2 }) => new Promise((resolve4) => {
5088
+ const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
5089
+ const done = () => {
5090
+ clearTimeout(timer);
5091
+ ch.off("storyRendered", done);
5092
+ resolve4();
5093
+ };
5094
+ const timer = setTimeout(done, 1e4);
5095
+ ch.on("storyRendered", done);
5096
+ ch.emit("updateStoryArgs", { storyId: id, updatedArgs: { plan: plan2 } });
5097
+ }),
5098
+ { id: FIGMA_REFERENCE_STORY, plan }
5099
+ );
5100
+ await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
5101
+ const error = await page.locator("[data-fidelity-error]").first().textContent({ timeout: 200 }).catch(() => null);
5102
+ let target = page.locator("[data-fidelity-root] > *").first();
5103
+ if (!error && !await target.count()) {
5104
+ const marked = page.locator("[data-fidelity-target]").first();
5105
+ await marked.waitFor({ state: "visible", timeout: 3e3 }).catch(() => void 0);
5106
+ await settle(page, { network: false, quiet: 2, interval: 40, busy: false });
5107
+ target = marked;
5108
+ }
5109
+ if (error || !await target.count()) {
5110
+ variants[j.key] = { skipped: error ?? "the code example did not render" };
5111
+ skipped++;
5112
+ continue;
5113
+ }
5114
+ const file = referenceImagePath(j.component, j.key);
5115
+ const path = join5(o.out, file);
5116
+ await mkdir4(dirname4(path), { recursive: true });
5117
+ await target.screenshot({ path, omitBackground: true, animations: "disabled", caret: "hide", scale: "device" });
5118
+ const box = await target.boundingBox();
5119
+ variants[j.key] = { file, width: Math.round(box?.width ?? 0), height: Math.round(box?.height ?? 0) };
5120
+ captured++;
5121
+ }
5122
+ await ctx.close();
5123
+ } finally {
5124
+ await browser.close().catch(() => void 0);
5125
+ await served.close();
5126
+ }
5127
+ await mkdir4(o.out, { recursive: true });
5128
+ const manifestPath = join5(o.out, "manifest.json");
5129
+ await writeFile4(manifestPath, `${JSON.stringify(manifest, null, 2)}
5130
+ `);
5131
+ log(`${captured} images, ${skipped} variants skipped. ${manifestPath}`);
5132
+ return { manifest, manifestPath, captured, skipped };
5133
+ }
5134
+
5135
+ // src/index.ts
5136
+ import { execFileSync as execFileSync2 } from "node:child_process";
5137
+
4899
5138
  // src/request.ts
4900
5139
  import { execFileSync, spawnSync } from "node:child_process";
4901
- import { readFile as readFile4 } from "node:fs/promises";
5140
+ import { readFile as readFile5 } from "node:fs/promises";
4902
5141
  import { BridgeContext as BridgeContext2, requests as requests2 } from "@bridge-ds/mcp";
4903
5142
 
4904
5143
  // src/jira-sync.ts
@@ -5172,7 +5411,7 @@ ${r.issue.body}`);
5172
5411
  let event = {};
5173
5412
  if (eventPath) {
5174
5413
  try {
5175
- event = JSON.parse(await readFile4(eventPath, "utf8"));
5414
+ event = JSON.parse(await readFile5(eventPath, "utf8"));
5176
5415
  } catch {
5177
5416
  throw new RequestUsageError(`Could not read the GitHub event at ${eventPath}.`);
5178
5417
  }
@@ -5364,6 +5603,11 @@ Usage:
5364
5603
  visual impact: build Storybook and the app at both refs, screenshot
5365
5604
  what the change touches, pixel-diff, write manifest.json + images.
5366
5605
  Exit 0 even with diffs (2 on setup errors). Needs Chrome installed.
5606
+ bridge figma-references [--storybook storybook-static] [--specs packages/react/component-specs.json]
5607
+ [--out figma-references] [--sha <sha>] [--only Button,Card] [--json]
5608
+ code images for the Figma fidelity check: one PNG per generated
5609
+ Figma variant from a built Storybook, plus manifest.json.
5610
+ Needs Chrome installed
5367
5611
  bridge history [token] [--json] [--limit n] [--author a] [--tier n] [--version v]
5368
5612
  token history: versions, merged changes, who, when, why
5369
5613
  bridge revert <pr|commit|id> [--dry-run] [--restore-conflicts] [--use-before path@file] [--json]
@@ -5430,9 +5674,9 @@ var print = (s) => process.stdout.write(`${s}
5430
5674
  `);
5431
5675
  var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
5432
5676
  async function init(a, root) {
5433
- const cfgPath = join5(root, CONFIG_FILE2);
5677
+ const cfgPath = join6(root, CONFIG_FILE2);
5434
5678
  let config;
5435
- if (existsSync5(cfgPath) && !has3(a, "force")) {
5679
+ if (existsSync6(cfgPath) && !has3(a, "force")) {
5436
5680
  print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
5437
5681
  config = (await (await BridgeContext4.create({ root })).config()).config;
5438
5682
  } else {
@@ -5440,7 +5684,7 @@ async function init(a, root) {
5440
5684
  const files = await src.list("");
5441
5685
  const repo = await originRepo(root);
5442
5686
  config = await detectConfig(files, (p) => src.read(p), repo ? { repo } : {});
5443
- await writeFile4(cfgPath, `${JSON.stringify(toConfigJson(config, "https://raw.githubusercontent.com/peterbono/ai-design-system-demo/main/packages/core/bridge.config.schema.json"), null, 2)}
5687
+ await writeFile5(cfgPath, `${JSON.stringify(toConfigJson(config, "https://raw.githubusercontent.com/peterbono/ai-design-system-demo/main/packages/core/bridge.config.schema.json"), null, 2)}
5444
5688
  `);
5445
5689
  print(`Wrote ${CONFIG_FILE2}: tokens in ${config.tokens.root}, ${config.tokens.layers.length} layers, modes ${Object.keys(config.axes).join(", ") || "none"}.`);
5446
5690
  print("Check the layers order (lowest first) and the tiers; detection is a starting point.");
@@ -5449,10 +5693,10 @@ async function init(a, root) {
5449
5693
  const relServer = relative4(root, serverJs);
5450
5694
  const inRepo = !relServer.startsWith("..") && !isAbsolute(relServer);
5451
5695
  const server = (dir) => inRepo ? { command: "node", args: [relServer, "--root", dir] } : { command: "npx", args: ["-y", MCP_PACKAGE, "--root", dir] };
5452
- const mcpPath = join5(root, ".mcp.json");
5453
- const existing = existsSync5(mcpPath) ? JSON.parse(await readFile5(mcpPath, "utf8")) : {};
5696
+ const mcpPath = join6(root, ".mcp.json");
5697
+ const existing = existsSync6(mcpPath) ? JSON.parse(await readFile6(mcpPath, "utf8")) : {};
5454
5698
  existing.mcpServers = { ...existing.mcpServers ?? {}, bridge: server(".") };
5455
- await writeFile4(mcpPath, `${JSON.stringify(existing, null, 2)}
5699
+ await writeFile5(mcpPath, `${JSON.stringify(existing, null, 2)}
5456
5700
  `);
5457
5701
  print(`Wrote .mcp.json: Claude Code picks up the "bridge" server next time it starts in ${root}.`);
5458
5702
  const desktop = inRepo ? { command: "node", args: [serverJs, "--root", root] } : server(root);
@@ -5575,7 +5819,7 @@ async function propose(a, root) {
5575
5819
  edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
5576
5820
  }
5577
5821
  const editsFile = flag3(a, "edits");
5578
- if (editsFile) edits.push(...JSON.parse(await readFile5(resolve3(editsFile), "utf8")));
5822
+ if (editsFile) edits.push(...JSON.parse(await readFile6(resolve3(editsFile), "utf8")));
5579
5823
  if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
5580
5824
  const ctx = await BridgeContext4.create({ root });
5581
5825
  const summary = flag3(a, "summary");
@@ -5641,6 +5885,27 @@ async function brandCmd(a, root) {
5641
5885
  if (r.hint) print(String(r.hint));
5642
5886
  return r.status === "blocked" ? 1 : 0;
5643
5887
  }
5888
+ async function figmaReferences(a, root) {
5889
+ let sha = flag3(a, "sha");
5890
+ if (!sha) {
5891
+ try {
5892
+ sha = execFileSync2("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
5893
+ } catch {
5894
+ sha = "unknown";
5895
+ }
5896
+ }
5897
+ const only = (flag3(a, "only") ?? "").split(",").filter(Boolean);
5898
+ const r = await captureFigmaReferences({
5899
+ storybook: resolve3(root, flag3(a, "storybook") ?? "storybook-static"),
5900
+ specs: resolve3(root, flag3(a, "specs") ?? "packages/react/component-specs.json"),
5901
+ out: resolve3(root, flag3(a, "out") ?? "figma-references"),
5902
+ sha,
5903
+ ...only.length ? { only } : {},
5904
+ log: has3(a, "json") ? void 0 : (line) => print(line)
5905
+ });
5906
+ if (has3(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
5907
+ return 0;
5908
+ }
5644
5909
  async function impact(a, root) {
5645
5910
  const only = (flag3(a, "only") ?? "").split(",").filter(Boolean);
5646
5911
  for (const x of only) if (x !== "stories" && x !== "app") throw new UsageError(`--only takes stories or app, got "${x}"`);
@@ -5750,8 +6015,8 @@ ${r.prBody}`);
5750
6015
  async function audit(a, root) {
5751
6016
  const target = a._[1];
5752
6017
  if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
5753
- if (!existsSync5(target)) throw new UsageError(`No such file or folder: ${target}`);
5754
- const dsRoot = flag3(a, "ds") ? resolve3(flag3(a, "ds")) : existsSync5(join5(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
6018
+ if (!existsSync6(target)) throw new UsageError(`No such file or folder: ${target}`);
6019
+ const dsRoot = flag3(a, "ds") ? resolve3(flag3(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
5755
6020
  if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
5756
6021
  const ds = loadDesignSystem(dsRoot);
5757
6022
  if (has3(a, "fix")) {
@@ -5765,12 +6030,12 @@ async function audit(a, root) {
5765
6030
  const baselinePath = flag3(a, "baseline");
5766
6031
  let baseline;
5767
6032
  if (baselinePath) {
5768
- if (!existsSync5(baselinePath)) throw new UsageError(`Baseline not found: ${baselinePath}`);
5769
- baseline = JSON.parse(await readFile5(baselinePath, "utf8"));
6033
+ if (!existsSync6(baselinePath)) throw new UsageError(`Baseline not found: ${baselinePath}`);
6034
+ baseline = JSON.parse(await readFile6(baselinePath, "utf8"));
5770
6035
  }
5771
6036
  const label = flag3(a, "label");
5772
6037
  const allowlistPath = flag3(a, "allowlist");
5773
- if (allowlistPath && !existsSync5(allowlistPath)) throw new UsageError(`Allowlist not found: ${allowlistPath}`);
6038
+ if (allowlistPath && !existsSync6(allowlistPath)) throw new UsageError(`Allowlist not found: ${allowlistPath}`);
5774
6039
  let allowlist;
5775
6040
  try {
5776
6041
  allowlist = allowlistPath ? loadAllowlist(allowlistPath) : void 0;
@@ -5781,9 +6046,9 @@ async function audit(a, root) {
5781
6046
  const md = auditMarkdown(report);
5782
6047
  const jsonOut = flag3(a, "json");
5783
6048
  const mdOut = flag3(a, "md");
5784
- if (jsonOut && jsonOut !== "true") await writeFile4(jsonOut, `${JSON.stringify(report, null, 2)}
6049
+ if (jsonOut && jsonOut !== "true") await writeFile5(jsonOut, `${JSON.stringify(report, null, 2)}
5785
6050
  `);
5786
- if (mdOut && mdOut !== "true") await writeFile4(mdOut, `${md}
6051
+ if (mdOut && mdOut !== "true") await writeFile5(mdOut, `${md}
5787
6052
  `);
5788
6053
  if (jsonOut === "true") print(JSON.stringify(report, null, 2));
5789
6054
  else if (mdOut === "true") print(md);
@@ -5901,6 +6166,8 @@ async function main() {
5901
6166
  return brandCmd(a, root);
5902
6167
  case "impact":
5903
6168
  return impact(a, root);
6169
+ case "figma-references":
6170
+ return figmaReferences(a, root);
5904
6171
  case "history":
5905
6172
  return historyCmd(a, root);
5906
6173
  case "revert":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/cli",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "bridge CLI: set up Bridge in a design system repo, check tokens in CI, search and diff tokens, propose changes, start the MCP server.",
6
6
  "license": "MIT",
@@ -43,8 +43,8 @@
43
43
  "build:package": "node ../../scripts/build-packages.mjs cli"
44
44
  },
45
45
  "dependencies": {
46
- "@bridge-ds/mcp": "^0.3.2",
47
- "@bridge-ds/visual": "^0.2.3",
46
+ "@bridge-ds/mcp": "^0.3.3",
47
+ "@bridge-ds/visual": "^0.3.0",
48
48
  "typescript": "5.9.3"
49
49
  },
50
50
  "devDependencies": {