@bridge-ds/visual 0.3.5 → 0.3.7

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 +83 -6
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -21,8 +21,8 @@ function parseOwnerEntry(raw) {
21
21
  }
22
22
  function ownerEntryProblems(raw) {
23
23
  if (raw === void 0) return [];
24
- const isObj2 = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
25
- if (!isObj2(raw)) return ['"governance.owners" must be { "design": [people or teams], "tech": [people or teams] }'];
24
+ const isObj3 = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
25
+ if (!isObj3(raw)) return ['"governance.owners" must be { "design": [people or teams], "tech": [people or teams] }'];
26
26
  const out = [];
27
27
  for (const [k, v] of Object.entries(raw)) {
28
28
  if (k !== "design" && k !== "tech") {
@@ -256,7 +256,8 @@ function defineConfig(input) {
256
256
  minReasonLength: g.override.minReasonLength ?? DEFAULT_OVERRIDE.minReasonLength
257
257
  }
258
258
  } : {},
259
- ...g.rights ? { rights: normalizeRights(g.rights) } : {}
259
+ ...g.rights ? { rights: normalizeRights(g.rights) } : {},
260
+ ...g.soloOwnerCanApproveBoth === true ? { soloOwnerCanApproveBoth: true } : {}
260
261
  },
261
262
  products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
262
263
  changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
@@ -380,6 +381,7 @@ function loadConfig(source) {
380
381
  }
381
382
  }
382
383
  problems.push(...rightsProblems(gov.rights));
384
+ if (gov.soloOwnerCanApproveBoth !== void 0 && typeof gov.soloOwnerCanApproveBoth !== "boolean") problems.push('"governance.soloOwnerCanApproveBoth" must be true or false');
383
385
  }
384
386
  }
385
387
  problems.push(...visualImpactProblems(d.visualImpact));
@@ -989,6 +991,81 @@ function planReferences(file) {
989
991
  );
990
992
  }
991
993
 
994
+ // ../core/src/storybook.ts
995
+ var StorybookIndexError = class extends Error {
996
+ constructor(code, message, status) {
997
+ super(message);
998
+ this.code = code;
999
+ this.status = status;
1000
+ this.name = "StorybookIndexError";
1001
+ }
1002
+ code;
1003
+ status;
1004
+ };
1005
+ var isObj2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1006
+ var str = (v) => typeof v === "string" && v.trim() ? v : void 0;
1007
+ function entryFrom(key, raw, v3) {
1008
+ if (!isObj2(raw)) return null;
1009
+ const id = str(raw.id) ?? key;
1010
+ const title = str(raw.title) ?? str(raw.kind);
1011
+ const name = str(raw.name) ?? str(raw.story);
1012
+ if (!id || !title || !name) return null;
1013
+ const params = isObj2(raw.parameters) ? raw.parameters : {};
1014
+ const type = raw.type === "docs" || raw.type === "story" ? raw.type : v3 && params.docsOnly === true ? "docs" : "story";
1015
+ const e = {
1016
+ id,
1017
+ title,
1018
+ name,
1019
+ type,
1020
+ importPath: str(raw.importPath) ?? str(params.fileName) ?? "",
1021
+ tags: Array.isArray(raw.tags) ? raw.tags.filter((t) => typeof t === "string") : []
1022
+ };
1023
+ const subtype = str(raw.subtype);
1024
+ if (subtype) e.subtype = subtype;
1025
+ const exportName2 = str(raw.exportName);
1026
+ if (exportName2) e.exportName = exportName2;
1027
+ const componentPath = str(raw.componentPath);
1028
+ if (componentPath) e.componentPath = componentPath;
1029
+ return e;
1030
+ }
1031
+ function groupStorybookComponents(entries) {
1032
+ const byTitle = /* @__PURE__ */ new Map();
1033
+ for (const e of entries) {
1034
+ let c = byTitle.get(e.title);
1035
+ if (!c) {
1036
+ const parts = e.title.split("/").map((p) => p.trim()).filter(Boolean);
1037
+ c = { title: e.title, name: parts[parts.length - 1] ?? e.title, group: parts.slice(0, -1), stories: [], docs: [], tags: [] };
1038
+ byTitle.set(e.title, c);
1039
+ }
1040
+ if (e.type === "docs") c.docs.push(e);
1041
+ else if (e.subtype !== "test") c.stories.push(e);
1042
+ for (const t of e.tags) if (!c.tags.includes(t)) c.tags.push(t);
1043
+ }
1044
+ const out = [...byTitle.values()].filter((c) => c.stories.length || c.docs.length);
1045
+ for (const c of out) c.tags.sort();
1046
+ return out.sort((a, b) => a.title.localeCompare(b.title));
1047
+ }
1048
+ function parseStorybookIndex(json, source) {
1049
+ if (!isObj2(json)) throw new StorybookIndexError("not-json", "The Storybook index is not a JSON object.");
1050
+ if (typeof json.loginUrl === "string" && !("entries" in json) && !("stories" in json)) {
1051
+ throw new StorybookIndexError("private", "This Storybook is private: it asks for a sign-in before showing its index.");
1052
+ }
1053
+ const map = isObj2(json.entries) ? json.entries : isObj2(json.stories) ? json.stories : null;
1054
+ if (!map) throw new StorybookIndexError("empty", "This file has no Storybook entries.");
1055
+ const v3 = !isObj2(json.entries);
1056
+ const entries = Object.entries(map).map(([k, raw]) => entryFrom(k, raw, v3)).filter((e) => e !== null);
1057
+ if (!entries.length) throw new StorybookIndexError("empty", "This Storybook index lists no stories or docs.");
1058
+ const version = typeof json.v === "number" ? json.v : v3 ? 3 : 4;
1059
+ return { version, source: source ?? (v3 ? "stories.json" : "index.json"), entries, components: groupStorybookComponents(entries) };
1060
+ }
1061
+ var pairs = (o) => Object.entries(o).map(([k, v]) => `${encodeURIComponent(k)}:${encodeURIComponent(v)}`).join(";");
1062
+ function storybookIframeUrl(base, id, o = {}) {
1063
+ let url = `${base.replace(/\/+$/, "")}/iframe.html?id=${encodeURIComponent(id)}&viewMode=${o.viewMode ?? "story"}`;
1064
+ if (o.globals && Object.keys(o.globals).length) url += `&globals=${pairs(o.globals)}`;
1065
+ if (o.args && Object.keys(o.args).length) url += `&args=${pairs(o.args)}`;
1066
+ return url;
1067
+ }
1068
+
992
1069
  // ../core/src/node/git-local.ts
993
1070
  import { execFile } from "node:child_process";
994
1071
  function git(root, args, opts = {}) {
@@ -1405,7 +1482,7 @@ function routeUrl(base, route, tenant, theme) {
1405
1482
  return `${base.replace(/\/$/, "")}/?tenant=${encodeURIComponent(tenant)}&theme=${encodeURIComponent(theme)}&density=comfortable&lang=en#${route.pathByTenant?.[tenant] ?? route.path}`;
1406
1483
  }
1407
1484
  function storyUrl(base, id, brand, theme) {
1408
- return `${base.replace(/\/$/, "")}/iframe.html?id=${encodeURIComponent(id)}&viewMode=story&globals=brand:${brand};theme:${theme};density:comfortable`;
1485
+ return storybookIframeUrl(base, id, { globals: { brand, theme, density: "comfortable" } });
1409
1486
  }
1410
1487
  var shotKey = (...parts) => parts.map((p) => p.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "")).join("__");
1411
1488
 
@@ -1696,8 +1773,8 @@ function changedTokens(before, after) {
1696
1773
  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));
1697
1774
  }
1698
1775
  async function readIndexJson(dir) {
1699
- const raw = JSON.parse(await readFile3(join3(dir, "index.json"), "utf8"));
1700
- return Object.values(raw.entries).filter((e) => e.type === "story").map(({ id, title, name, importPath }) => ({ id, title, name, importPath }));
1776
+ const index = parseStorybookIndex(JSON.parse(await readFile3(join3(dir, "index.json"), "utf8")), "index.json");
1777
+ return index.entries.filter((e) => e.type === "story").map(({ id, title, name, importPath }) => ({ id, title, name, importPath }));
1701
1778
  }
1702
1779
  async function planImpact(o) {
1703
1780
  const root = resolve(o.root);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/visual",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "description": "Bridge visual impact analysis: screenshots the stories and app screens a design system change touches, at the base and head refs, and pixel-diffs them into before/after/diff images and a JSON manifest.",
6
6
  "license": "MIT",
@@ -47,7 +47,7 @@
47
47
  "build:package": "node ../../scripts/build-packages.mjs visual"
48
48
  },
49
49
  "dependencies": {
50
- "@bridge-ds/core": "^0.11.0",
50
+ "@bridge-ds/core": "^0.13.0",
51
51
  "pixelmatch": "7.1.0",
52
52
  "playwright-core": "1.63.0",
53
53
  "pngjs": "7.0.0"