@bridge-ds/cli 0.8.0 → 0.9.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 +95 -15
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1813,10 +1813,10 @@ ${" ".repeat(column - 1)}${expr}${suffix}` : `${prefix}${expr}${suffix}`;
1813
1813
  out.themeRefs += r.themeRefs;
1814
1814
  return;
1815
1815
  }
1816
- const str = /^(\s*)([`'"])([\s\S]*)\2\s*$/.exec(a.value);
1817
- if (str) {
1818
- const body = str[3].replace(/\$\{[^}]*\}/g, (m) => " ".repeat(m.length));
1819
- const r = auditCssText(body, "css", ds, { pos, offset: a.valueStart + str[1].length + 1, source: "style" });
1816
+ const str2 = /^(\s*)([`'"])([\s\S]*)\2\s*$/.exec(a.value);
1817
+ if (str2) {
1818
+ const body = str2[3].replace(/\$\{[^}]*\}/g, (m) => " ".repeat(m.length));
1819
+ const r = auditCssText(body, "css", ds, { pos, offset: a.valueStart + str2[1].length + 1, source: "style" });
1820
1820
  out.findings.push(...r.findings);
1821
1821
  out.tokenUsages += r.tokenUsages;
1822
1822
  out.themeRefs += r.themeRefs;
@@ -2176,8 +2176,8 @@ function parseOwnerEntry(raw) {
2176
2176
  var teamKey = (org, slug) => `@${org.toLowerCase()}/${slug.toLowerCase()}`;
2177
2177
  function ownerEntryProblems(raw) {
2178
2178
  if (raw === void 0) return [];
2179
- const isObj2 = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
2180
- if (!isObj2(raw)) return ['"governance.owners" must be { "design": [people or teams], "tech": [people or teams] }'];
2179
+ const isObj3 = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
2180
+ if (!isObj3(raw)) return ['"governance.owners" must be { "design": [people or teams], "tech": [people or teams] }'];
2181
2181
  const out = [];
2182
2182
  for (const [k, v] of Object.entries(raw)) {
2183
2183
  if (k !== "design" && k !== "tech") {
@@ -2562,7 +2562,8 @@ function defineConfig(input) {
2562
2562
  minReasonLength: g.override.minReasonLength ?? DEFAULT_OVERRIDE.minReasonLength
2563
2563
  }
2564
2564
  } : {},
2565
- ...g.rights ? { rights: normalizeRights(g.rights) } : {}
2565
+ ...g.rights ? { rights: normalizeRights(g.rights) } : {},
2566
+ ...g.soloOwnerCanApproveBoth === true ? { soloOwnerCanApproveBoth: true } : {}
2566
2567
  },
2567
2568
  products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
2568
2569
  changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
@@ -2686,6 +2687,7 @@ function loadConfig(source) {
2686
2687
  }
2687
2688
  }
2688
2689
  problems.push(...rightsProblems(gov.rights));
2690
+ if (gov.soloOwnerCanApproveBoth !== void 0 && typeof gov.soloOwnerCanApproveBoth !== "boolean") problems.push('"governance.soloOwnerCanApproveBoth" must be true or false');
2689
2691
  }
2690
2692
  }
2691
2693
  problems.push(...visualImpactProblems(d.visualImpact));
@@ -4251,6 +4253,81 @@ function planReferences(file) {
4251
4253
  );
4252
4254
  }
4253
4255
 
4256
+ // ../core/src/storybook.ts
4257
+ var StorybookIndexError = class extends Error {
4258
+ constructor(code, message, status) {
4259
+ super(message);
4260
+ this.code = code;
4261
+ this.status = status;
4262
+ this.name = "StorybookIndexError";
4263
+ }
4264
+ code;
4265
+ status;
4266
+ };
4267
+ var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
4268
+ var str = (v) => typeof v === "string" && v.trim() ? v : void 0;
4269
+ function entryFrom(key, raw, v3) {
4270
+ if (!isObj2(raw)) return null;
4271
+ const id = str(raw.id) ?? key;
4272
+ const title = str(raw.title) ?? str(raw.kind);
4273
+ const name = str(raw.name) ?? str(raw.story);
4274
+ if (!id || !title || !name) return null;
4275
+ const params = isObj2(raw.parameters) ? raw.parameters : {};
4276
+ const type = raw.type === "docs" || raw.type === "story" ? raw.type : v3 && params.docsOnly === true ? "docs" : "story";
4277
+ const e = {
4278
+ id,
4279
+ title,
4280
+ name,
4281
+ type,
4282
+ importPath: str(raw.importPath) ?? str(params.fileName) ?? "",
4283
+ tags: Array.isArray(raw.tags) ? raw.tags.filter((t) => typeof t === "string") : []
4284
+ };
4285
+ const subtype = str(raw.subtype);
4286
+ if (subtype) e.subtype = subtype;
4287
+ const exportName2 = str(raw.exportName);
4288
+ if (exportName2) e.exportName = exportName2;
4289
+ const componentPath = str(raw.componentPath);
4290
+ if (componentPath) e.componentPath = componentPath;
4291
+ return e;
4292
+ }
4293
+ function groupStorybookComponents(entries) {
4294
+ const byTitle = /* @__PURE__ */ new Map();
4295
+ for (const e of entries) {
4296
+ let c = byTitle.get(e.title);
4297
+ if (!c) {
4298
+ const parts = e.title.split("/").map((p) => p.trim()).filter(Boolean);
4299
+ c = { title: e.title, name: parts[parts.length - 1] ?? e.title, group: parts.slice(0, -1), stories: [], docs: [], tags: [] };
4300
+ byTitle.set(e.title, c);
4301
+ }
4302
+ if (e.type === "docs") c.docs.push(e);
4303
+ else if (e.subtype !== "test") c.stories.push(e);
4304
+ for (const t of e.tags) if (!c.tags.includes(t)) c.tags.push(t);
4305
+ }
4306
+ const out = [...byTitle.values()].filter((c) => c.stories.length || c.docs.length);
4307
+ for (const c of out) c.tags.sort();
4308
+ return out.sort((a, b) => a.title.localeCompare(b.title));
4309
+ }
4310
+ function parseStorybookIndex(json, source) {
4311
+ if (!isObj2(json)) throw new StorybookIndexError("not-json", "The Storybook index is not a JSON object.");
4312
+ if (typeof json.loginUrl === "string" && !("entries" in json) && !("stories" in json)) {
4313
+ throw new StorybookIndexError("private", "This Storybook is private: it asks for a sign-in before showing its index.");
4314
+ }
4315
+ const map = isObj2(json.entries) ? json.entries : isObj2(json.stories) ? json.stories : null;
4316
+ if (!map) throw new StorybookIndexError("empty", "This file has no Storybook entries.");
4317
+ const v3 = !isObj2(json.entries);
4318
+ const entries = Object.entries(map).map(([k, raw]) => entryFrom(k, raw, v3)).filter((e) => e !== null);
4319
+ if (!entries.length) throw new StorybookIndexError("empty", "This Storybook index lists no stories or docs.");
4320
+ const version = typeof json.v === "number" ? json.v : v3 ? 3 : 4;
4321
+ return { version, source: source ?? (v3 ? "stories.json" : "index.json"), entries, components: groupStorybookComponents(entries) };
4322
+ }
4323
+ var pairs = (o) => Object.entries(o).map(([k, v]) => `${encodeURIComponent(k)}:${encodeURIComponent(v)}`).join(";");
4324
+ function storybookIframeUrl(base, id, o = {}) {
4325
+ let url = `${base.replace(/\/+$/, "")}/iframe.html?id=${encodeURIComponent(id)}&viewMode=${o.viewMode ?? "story"}`;
4326
+ if (o.globals && Object.keys(o.globals).length) url += `&globals=${pairs(o.globals)}`;
4327
+ if (o.args && Object.keys(o.args).length) url += `&args=${pairs(o.args)}`;
4328
+ return url;
4329
+ }
4330
+
4254
4331
  // ../visual/src/capture.ts
4255
4332
  import { mkdir, writeFile } from "node:fs/promises";
4256
4333
  import { dirname as dirname2 } from "node:path";
@@ -4645,7 +4722,7 @@ function routeUrl(base, route, tenant, theme) {
4645
4722
  return `${base.replace(/\/$/, "")}/?tenant=${encodeURIComponent(tenant)}&theme=${encodeURIComponent(theme)}&density=comfortable&lang=en#${route.pathByTenant?.[tenant] ?? route.path}`;
4646
4723
  }
4647
4724
  function storyUrl(base, id, brand2, theme) {
4648
- return `${base.replace(/\/$/, "")}/iframe.html?id=${encodeURIComponent(id)}&viewMode=story&globals=brand:${brand2};theme:${theme};density:comfortable`;
4725
+ return storybookIframeUrl(base, id, { globals: { brand: brand2, theme, density: "comfortable" } });
4649
4726
  }
4650
4727
  var shotKey = (...parts) => parts.map((p) => p.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "")).join("__");
4651
4728
 
@@ -4936,8 +5013,8 @@ function changedTokens(before, after) {
4936
5013
  return [...modes.entries()].map(([path, m]) => ({ path, kind: direct.has(path) ? "direct" : "alias", modes: m })).sort((a, b) => Number(a.kind === "alias") - Number(b.kind === "alias") || a.path.localeCompare(b.path));
4937
5014
  }
4938
5015
  async function readIndexJson(dir) {
4939
- const raw = JSON.parse(await readFile3(join4(dir, "index.json"), "utf8"));
4940
- return Object.values(raw.entries).filter((e) => e.type === "story").map(({ id, title, name, importPath }) => ({ id, title, name, importPath }));
5016
+ const index = parseStorybookIndex(JSON.parse(await readFile3(join4(dir, "index.json"), "utf8")), "index.json");
5017
+ return index.entries.filter((e) => e.type === "story").map(({ id, title, name, importPath }) => ({ id, title, name, importPath }));
4941
5018
  }
4942
5019
  async function planImpact(o) {
4943
5020
  const root = resolve2(o.root);
@@ -5824,6 +5901,7 @@ ${r.summary}`);
5824
5901
  Version ${r.version}${r.edited ? ` (${r.edited})` : ""}`);
5825
5902
  io.print(`Approved by: ${r.approvals.approvedBy.map((l) => `@${l}`).join(", ") || "nobody yet"}`);
5826
5903
  if (r.approvals.missing.length) io.print(`Missing: ${r.approvals.missing.join(" and ")}`);
5904
+ if (r.approvals.soloOwner) io.print(r.approvals.soloOwner);
5827
5905
  if (r.approvals.changesRequestedBy.length) io.print(`Changes requested by: ${r.approvals.changesRequestedBy.map((l) => `@${l}`).join(", ")}`);
5828
5906
  for (const p of r.linkedPrs ?? []) io.print(`Pull request: #${p.number} ${p.merged ? "merged" : p.state} ${p.url}`);
5829
5907
  io.print(`
@@ -5960,7 +6038,7 @@ function number(a, at, usage) {
5960
6038
  if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
5961
6039
  return branches.branchNumber(raw);
5962
6040
  }
5963
- function pairs(list2, what) {
6041
+ function pairs2(list2, what) {
5964
6042
  const out = {};
5965
6043
  for (const s of list2 ?? []) {
5966
6044
  const i = s.lastIndexOf("=");
@@ -5993,6 +6071,7 @@ async function runBranch(a, root, io = defaultIO()) {
5993
6071
  return out(r, () => {
5994
6072
  io.print(`#${r.pr} ${r.title} (${r.product.name})`);
5995
6073
  io.print(`${r.status.label}. ${r.approvalNeeded} needed; approved by ${r.approvals.approvedBy.map((x) => `@${x}`).join(", ") || "nobody yet"}.`);
6074
+ if (r.approvals.soloOwner) io.print(r.approvals.soloOwner);
5996
6075
  if (r.merge.blockedBy?.length) for (const x of r.merge.blockedBy) io.print(` ${x}`);
5997
6076
  io.print(`Next: ${r.next}`);
5998
6077
  io.print(r.url);
@@ -6049,7 +6128,7 @@ async function runBranch(a, root, io = defaultIO()) {
6049
6128
  }
6050
6129
  case "update": {
6051
6130
  const n = number(a, 2, "bridge branch update <n> [--choice key=mine|theirs] [--dry-run]");
6052
- const choices = pairs(a.flags.choice, "choice");
6131
+ const choices = pairs2(a.flags.choice, "choice");
6053
6132
  for (const [k, v] of Object.entries(choices)) if (v !== "mine" && v !== "theirs") throw new RequestUsageError(`--choice ${k}= takes mine or theirs, got "${v}".`);
6054
6133
  const r = await branches.updateFromMain(ctx, { pr: n, ...Object.keys(choices).length ? { choices } : {}, dryRun: has4(a, "dry-run") });
6055
6134
  return out(r, () => {
@@ -6067,7 +6146,7 @@ Share with a product team: ${r.share.install ? r.share.install : r.share.sentenc
6067
6146
  }
6068
6147
  case "combine": {
6069
6148
  const prs = a._.slice(2);
6070
- const picks = Object.fromEntries(Object.entries(pairs(a.flags.pick, "pick")).map(([k, v]) => [k, branches.branchNumber(v)]));
6149
+ const picks = Object.fromEntries(Object.entries(pairs2(a.flags.pick, "pick")).map(([k, v]) => [k, branches.branchNumber(v)]));
6071
6150
  const r = await branches.combineBranches(ctx, { prs, ...Object.keys(picks).length ? { picks } : {} });
6072
6151
  return out(r, () => {
6073
6152
  io.print(`${r.tokens} tokens change together.`);
@@ -6098,6 +6177,7 @@ This folder: ${r2.current.name}. Pick another with --product <key>.`);
6098
6177
  if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
6099
6178
  io.print(`Design approval: ${r2.roles.designApproval.join(", ") || "any reviewer"}`);
6100
6179
  io.print(`Tech approval: ${r2.roles.techApproval.join(", ") || "any reviewer"}`);
6180
+ if (r2.roles.soloOwner) io.print(r2.roles.soloOwner);
6101
6181
  io.print(`Merge: ${r2.roles.ship}`);
6102
6182
  io.print(`Owner override: ${r2.ownerOverride.on ? `${r2.ownerOverride.who.join(", ")}. ${r2.ownerOverride.reason}` : r2.ownerOverride.reason}`);
6103
6183
  io.print(`Protection: ${r2.protection}`);
@@ -6590,8 +6670,8 @@ async function diff(a, root) {
6590
6670
  const before = await core.loadTokenSet(new GitRefSource(root, base), config);
6591
6671
  const after = await ctx.tokens();
6592
6672
  const changes = core.diff(before, after);
6593
- const pairs2 = await core.readContrastPairs(ctx.source, config).catch(() => []);
6594
- const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
6673
+ const pairs3 = await core.readContrastPairs(ctx.source, config).catch(() => []);
6674
+ const delta2 = pairs3.length ? core.contrastDelta(core.runContrast(before, pairs3, config.contrast?.modes), core.runContrast(after, pairs3, config.contrast?.modes)) : [];
6595
6675
  const tier = core.classify(changes, [], config);
6596
6676
  const imp = core.impact(before, after, changes, 30);
6597
6677
  if (has7(a, "json")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.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.7.0",
47
- "@bridge-ds/visual": "^0.3.4",
46
+ "@bridge-ds/mcp": "^0.8.0",
47
+ "@bridge-ds/visual": "^0.3.7",
48
48
  "typescript": "5.9.3"
49
49
  },
50
50
  "devDependencies": {