@bridge-ds/visual 0.2.0 → 0.2.1

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 +74 -4
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -7,6 +7,74 @@ import { join as join3, relative as relative2, resolve } from "node:path";
7
7
  // ../core/src/previews.ts
8
8
  var PREVIEW_APP_LABELS = { docs: "Docs", storybook: "Storybook", admin: "Admin demo" };
9
9
 
10
+ // ../core/src/teams.ts
11
+ var LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
12
+ var SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
13
+ function parseOwnerEntry(raw) {
14
+ if (typeof raw !== "string") return null;
15
+ const s = raw.trim().replace(/^@/, "");
16
+ const slash = s.indexOf("/");
17
+ if (slash < 0) return LOGIN.test(s) ? { kind: "person", login: s } : null;
18
+ const org = s.slice(0, slash);
19
+ const slug = s.slice(slash + 1);
20
+ return LOGIN.test(org) && SLUG.test(slug) ? { kind: "team", org, slug } : null;
21
+ }
22
+ function ownerEntryProblems(raw) {
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] }'];
26
+ const out = [];
27
+ for (const [k, v] of Object.entries(raw)) {
28
+ if (k !== "design" && k !== "tech") {
29
+ out.push(`"governance.owners.${k}" is not an owner group (design, tech)`);
30
+ continue;
31
+ }
32
+ if (!Array.isArray(v)) {
33
+ out.push(`"governance.owners.${k}" must be a list of people or teams`);
34
+ continue;
35
+ }
36
+ v.forEach((x, i) => {
37
+ if (typeof x !== "string" || !parseOwnerEntry(x))
38
+ out.push(`"governance.owners.${k}[${i}]" (${JSON.stringify(x)}) is not a GitHub login or team. Write a person as "peterbono" or "@peterbono", a team as "@org/team-slug".`);
39
+ });
40
+ }
41
+ return out;
42
+ }
43
+
44
+ // ../core/src/rights.ts
45
+ var OWNER_GROUPS = ["design", "tech"];
46
+ var RIGHTS = ["approveDesign", "approveTech", "ship"];
47
+ var DEFAULT_RIGHTS = { approveDesign: ["design"], approveTech: ["tech"], ship: ["tech"] };
48
+ function normalizeRights(input) {
49
+ const pick = (r) => {
50
+ const v = input?.[r];
51
+ if (!Array.isArray(v)) return [...DEFAULT_RIGHTS[r]];
52
+ return OWNER_GROUPS.filter((g) => v.includes(g));
53
+ };
54
+ return { approveDesign: pick("approveDesign"), approveTech: pick("approveTech"), ship: pick("ship") };
55
+ }
56
+ function rightsProblems(raw) {
57
+ if (raw === void 0) return [];
58
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return ['"governance.rights" must be { "approveDesign": [groups], "approveTech": [groups], "ship": [groups] }'];
59
+ const out = [];
60
+ for (const [k, v] of Object.entries(raw)) {
61
+ if (!RIGHTS.includes(k)) out.push(`"governance.rights.${k}" is not a right (${RIGHTS.join(", ")})`);
62
+ else if (!Array.isArray(v) || v.length === 0 || !v.every((g) => OWNER_GROUPS.includes(g)))
63
+ out.push(`"governance.rights.${k}" must list one or more of "design", "tech"`);
64
+ }
65
+ return out;
66
+ }
67
+ function visualImpactProblems(raw) {
68
+ if (raw === void 0) return [];
69
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return ['"visualImpact" must be { "mode": "auto" | "on-demand" }'];
70
+ const out = [];
71
+ for (const [k, v] of Object.entries(raw)) {
72
+ if (k !== "mode") out.push(`"visualImpact.${k}" is not a setting (mode)`);
73
+ else if (v !== "auto" && v !== "on-demand") out.push('"visualImpact.mode" must be "auto" or "on-demand"');
74
+ }
75
+ return out;
76
+ }
77
+
10
78
  // ../core/src/config.ts
11
79
  var CONFIG_FILE = "bridge.config.json";
12
80
  var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
@@ -144,9 +212,11 @@ function defineConfig(input) {
144
212
  requireReason: g.override.requireReason ?? DEFAULT_OVERRIDE.requireReason,
145
213
  minReasonLength: g.override.minReasonLength ?? DEFAULT_OVERRIDE.minReasonLength
146
214
  }
147
- } : {}
215
+ } : {},
216
+ ...g.rights ? { rights: normalizeRights(g.rights) } : {}
148
217
  },
149
218
  ...input.audit ? { audit: { ...input.audit } } : {},
219
+ ...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
150
220
  ...input.integrations ? { integrations: { ...input.integrations } } : {},
151
221
  ...input.previews ? {
152
222
  previews: {
@@ -253,9 +323,7 @@ function loadConfig(source) {
253
323
  else {
254
324
  for (const k of ["protectedBranches", "labels", "guardrails"]) if (gov[k] !== void 0 && !(Array.isArray(gov[k]) && gov[k].every((x) => typeof x === "string"))) problems.push(`"governance.${k}" must be a list of strings`);
255
325
  if (gov.branchPrefix !== void 0 && !isStr(gov.branchPrefix)) problems.push('"governance.branchPrefix" must be a non-empty string');
256
- const o = gov.owners;
257
- if (o !== void 0 && (!isObj(o) || ["design", "tech"].some((k) => o[k] !== void 0 && !(Array.isArray(o[k]) && o[k].every((x) => typeof x === "string")))))
258
- problems.push('"governance.owners" must be { "design": [logins], "tech": [logins] }');
326
+ problems.push(...ownerEntryProblems(gov.owners));
259
327
  const ov = gov.override;
260
328
  if (ov !== void 0) {
261
329
  if (!isObj(ov)) problems.push('"governance.override" must be an object');
@@ -265,8 +333,10 @@ function loadConfig(source) {
265
333
  if (ov.minReasonLength !== void 0 && !(Number.isInteger(ov.minReasonLength) && ov.minReasonLength >= 0)) problems.push('"governance.override.minReasonLength" must be a whole number, 0 or more');
266
334
  }
267
335
  }
336
+ problems.push(...rightsProblems(gov.rights));
268
337
  }
269
338
  }
339
+ problems.push(...visualImpactProblems(d.visualImpact));
270
340
  const audit = d.audit;
271
341
  if (audit !== void 0) {
272
342
  if (!isObj(audit)) problems.push('"audit" must be an object');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bridge-ds/visual",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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.2.0",
50
+ "@bridge-ds/core": "^0.3.0",
51
51
  "pixelmatch": "7.1.0",
52
52
  "playwright-core": "1.63.0",
53
53
  "pngjs": "7.0.0"