@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.
- package/dist/index.js +74 -4
- 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
|
-
|
|
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.
|
|
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.
|
|
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"
|