@bridge-ds/core 0.11.0 → 0.12.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.
- package/README.md +16 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +181 -30
- package/dist/index.js.map +1 -1
- package/dist/storybook.d.ts +94 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -283,6 +283,22 @@ reviewSuggestionOnGitHub(gh, n, { event, role?, message?, payloadSha? }, gov); l
|
|
|
283
283
|
declineSuggestionOnGitHub(gh, n, reason, by?); syncSuggestionStateLabel(gh, n, labels, status);
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
## storybook (storybook.ts)
|
|
287
|
+
|
|
288
|
+
Reads a deployed or built Storybook without the repo: `index.json` (v4 for Storybook 7, v5 for 8 and later), else
|
|
289
|
+
`stories.json` (v3). Entries are grouped by title into components (name = last title segment); test entries stay out
|
|
290
|
+
of `stories`. A 404, an HTML answer from a host that falls back to `index.html`, or a body with no entries moves on to
|
|
291
|
+
the next file. Errors carry a code: `invalid-url`, `not-found`, `not-json`, `private` (sign-in wall, Chromatic login
|
|
292
|
+
answer, 401/403), `empty`, `http`, `network`. Pure, with an injected `fetch`; `@bridge-ds/visual` uses the parser for
|
|
293
|
+
`storybook-static/index.json` and the iframe URL builder for its captures.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
fetchStorybookIndex(base, { fetch?, signal? }): Promise<{ version; source: 'index.json' | 'stories.json'; entries; components }>;
|
|
297
|
+
parseStorybookIndex(json, source?); groupStorybookComponents(entries): { title; name; group; stories; docs; tags }[];
|
|
298
|
+
normalizeStorybookUrl(pasted): string; // https only (http on localhost), drops ?path=, /index.html, /iframe.html
|
|
299
|
+
storybookIframeUrl(base, id, { viewMode?, globals?, args? }); storybookManagerUrl(base, id, viewMode?); storybookIndexUrls(base);
|
|
300
|
+
```
|
|
301
|
+
|
|
286
302
|
## history (history.ts, history-github.ts, history-demo.ts)
|
|
287
303
|
|
|
288
304
|
What changed in the tokens, when, who changed it and why, and how to undo it safely.
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ function checkValue(set, owner, value, mode, where, issues) {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
function validateSet(set,
|
|
55
|
+
function validateSet(set, pairs2) {
|
|
56
56
|
const issues = [];
|
|
57
57
|
for (const p2 of set.problems) issues.push({ code: "file-problem", severity: "error", message: p2 });
|
|
58
58
|
for (const t2 of set.tokens.values()) {
|
|
@@ -119,7 +119,7 @@ function validateSet(set, pairs) {
|
|
|
119
119
|
}
|
|
120
120
|
);
|
|
121
121
|
}
|
|
122
|
-
const contrast = evaluatePairs(set.byPath,
|
|
122
|
+
const contrast = evaluatePairs(set.byPath, pairs2, contrastScope(set));
|
|
123
123
|
for (const r of contrast) {
|
|
124
124
|
const path = `${r.pair.foreground} on ${r.pair.background}`;
|
|
125
125
|
const mode = `${r.brand ?? ""}.${r.theme}`;
|
|
@@ -154,9 +154,9 @@ function shadowedEdits(set, edits) {
|
|
|
154
154
|
}
|
|
155
155
|
return out;
|
|
156
156
|
}
|
|
157
|
-
function validateProposal(proposed, head,
|
|
158
|
-
const after = validateSet(proposed,
|
|
159
|
-
const before = validateSet(head,
|
|
157
|
+
function validateProposal(proposed, head, pairs2, typeChanges = []) {
|
|
158
|
+
const after = validateSet(proposed, pairs2);
|
|
159
|
+
const before = validateSet(head, pairs2);
|
|
160
160
|
const existing = new Set(before.issues.map(issueKey$1));
|
|
161
161
|
const blocking = [];
|
|
162
162
|
const warnings = [];
|
|
@@ -1112,7 +1112,7 @@ const RADIUS_ROLES = ["inset", "control", "container", "overlay"];
|
|
|
1112
1112
|
const RESERVED = /* @__PURE__ */ new Set(["default", "base", "all", "none", "new", "blank", "primitives", "semantic", "components", "density"]);
|
|
1113
1113
|
const BRAND_SLUG_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1114
1114
|
const DEFAULT_PRIMARY_CHROMA = 0.14;
|
|
1115
|
-
const isObj = (x) => !!x && typeof x === "object" && !Array.isArray(x);
|
|
1115
|
+
const isObj$1 = (x) => !!x && typeof x === "object" && !Array.isArray(x);
|
|
1116
1116
|
const fileMap = (f) => f instanceof Map ? new Map(f) : new Map(Object.entries(f));
|
|
1117
1117
|
const brandDisplayName = (slug) => slug.split("-").filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
1118
1118
|
function brandLayers(config) {
|
|
@@ -1133,27 +1133,27 @@ function setAt(doc, path, node) {
|
|
|
1133
1133
|
const keys = path.split(".");
|
|
1134
1134
|
let cur = doc;
|
|
1135
1135
|
for (const k of keys.slice(0, -1)) {
|
|
1136
|
-
if (!isObj(cur[k])) cur[k] = {};
|
|
1136
|
+
if (!isObj$1(cur[k])) cur[k] = {};
|
|
1137
1137
|
cur = cur[k];
|
|
1138
1138
|
}
|
|
1139
1139
|
const last = keys[keys.length - 1];
|
|
1140
|
-
const prev = isObj(cur[last]) ? cur[last] : {};
|
|
1140
|
+
const prev = isObj$1(cur[last]) ? cur[last] : {};
|
|
1141
1141
|
cur[last] = { ...prev, ...node };
|
|
1142
1142
|
}
|
|
1143
1143
|
function getAt(doc, path) {
|
|
1144
1144
|
let cur = doc;
|
|
1145
|
-
for (const k of path.split(".")) cur = isObj(cur) ? cur[k] : void 0;
|
|
1146
|
-
return isObj(cur) ? cur : void 0;
|
|
1145
|
+
for (const k of path.split(".")) cur = isObj$1(cur) ? cur[k] : void 0;
|
|
1146
|
+
return isObj$1(cur) ? cur : void 0;
|
|
1147
1147
|
}
|
|
1148
1148
|
function typeAt(doc, path) {
|
|
1149
1149
|
let cur = doc;
|
|
1150
1150
|
let type;
|
|
1151
1151
|
for (const k of path.split(".")) {
|
|
1152
|
-
if (!isObj(cur)) return type;
|
|
1152
|
+
if (!isObj$1(cur)) return type;
|
|
1153
1153
|
if (typeof cur.$type === "string") type = cur.$type;
|
|
1154
1154
|
cur = cur[k];
|
|
1155
1155
|
}
|
|
1156
|
-
return isObj(cur) && typeof cur.$type === "string" ? cur.$type : type;
|
|
1156
|
+
return isObj$1(cur) && typeof cur.$type === "string" ? cur.$type : type;
|
|
1157
1157
|
}
|
|
1158
1158
|
function rampFor(input, pinStep, kind) {
|
|
1159
1159
|
if (typeof input === "number" || typeof input === "string" && /^\d+(\.\d+)?$/.test(input.trim())) {
|
|
@@ -1210,7 +1210,7 @@ function listBrands(source) {
|
|
|
1210
1210
|
doc = JSON.parse(files.get(file) ?? "{}");
|
|
1211
1211
|
} catch {
|
|
1212
1212
|
}
|
|
1213
|
-
const meta = isObj(doc.$extensions) && isObj(doc.$extensions["dev.bridge.brand"]) ? doc.$extensions["dev.bridge.brand"] : {};
|
|
1213
|
+
const meta = isObj$1(doc.$extensions) && isObj$1(doc.$extensions["dev.bridge.brand"]) ? doc.$extensions["dev.bridge.brand"] : {};
|
|
1214
1214
|
const combo = set.normalizeCombo({ brand: slug });
|
|
1215
1215
|
const primary = set.resolve("color.action.primary.bg", combo);
|
|
1216
1216
|
const overrides = layers.override ? (config.axes[layers.overrideAxis]?.values ?? []).map((v) => fill(layers.override, { brand: slug, [layers.overrideAxis]: v })).filter((f) => files.has(f)) : [];
|
|
@@ -1300,7 +1300,7 @@ function planNewBrand(source, input) {
|
|
|
1300
1300
|
for (const [path, defs] of before.view(before.defaultCombo)) {
|
|
1301
1301
|
const m = /^radius\.([\w-]+)$/.exec(path);
|
|
1302
1302
|
const v = defs[defs.length - 1].value;
|
|
1303
|
-
if (m && isObj(v) && typeof v.value === "number" && v.unit === "px" && !getAt(doc, path)) scale.set(v.value, m[1]);
|
|
1303
|
+
if (m && isObj$1(v) && typeof v.value === "number" && v.unit === "px" && !getAt(doc, path)) scale.set(v.value, m[1]);
|
|
1304
1304
|
}
|
|
1305
1305
|
const radius = input.radius ?? (blank ? RADIUS_PRESETS[0].radius : void 0);
|
|
1306
1306
|
if (radius) {
|
|
@@ -1339,9 +1339,9 @@ function planNewBrand(source, input) {
|
|
|
1339
1339
|
}
|
|
1340
1340
|
const palette = getAt(doc, "palette");
|
|
1341
1341
|
for (const [role, group] of Object.entries(palette ?? {})) {
|
|
1342
|
-
if (role.startsWith("$") || !isObj(group)) continue;
|
|
1342
|
+
if (role.startsWith("$") || !isObj$1(group)) continue;
|
|
1343
1343
|
for (const [step, node] of Object.entries(group)) {
|
|
1344
|
-
if (step.startsWith("$") || !isObj(node) || !("$value" in node) || typeof node.$value === "string") continue;
|
|
1344
|
+
if (step.startsWith("$") || !isObj$1(node) || !("$value" in node) || typeof node.$value === "string") continue;
|
|
1345
1345
|
const ramp = `${slug}-${role}`;
|
|
1346
1346
|
const entry = prims.get(ramp) ?? { description: `${name} ${role}. Exact values set when the brand was created.`, steps: /* @__PURE__ */ new Map() };
|
|
1347
1347
|
entry.steps.set(Number(step), node.$value);
|
|
@@ -1354,7 +1354,7 @@ function planNewBrand(source, input) {
|
|
|
1354
1354
|
if (prims.size && files.has(primFile)) issues.push({ level: "error", code: "slug-file-exists", message: `${tokensPath(config, primFile)} already exists. Pick another name.` });
|
|
1355
1355
|
for (const r of prims.keys()) if (before.allPaths().some((p2) => p2.startsWith(`color.${r}.`))) issues.push({ level: "error", code: "ramp-exists", message: `A primitive ramp color.${r} already exists. Pick another name.` });
|
|
1356
1356
|
doc.$description = `${name}: created with Bridge, started from ${blank ? "a blank brand" : brandDisplayName(from)}.`;
|
|
1357
|
-
doc.$extensions = { ...isObj(doc.$extensions) ? doc.$extensions : {}, "dev.bridge.brand": { name, from, ...Object.keys(anchors).length ? { anchors } : {} } };
|
|
1357
|
+
doc.$extensions = { ...isObj$1(doc.$extensions) ? doc.$extensions : {}, "dev.bridge.brand": { name, from, ...Object.keys(anchors).length ? { anchors } : {} } };
|
|
1358
1358
|
const { $description, $extensions, ...rest } = doc;
|
|
1359
1359
|
const brandFile = fill(layers.brand, { brand: slug });
|
|
1360
1360
|
const nextFiles = new Map(files);
|
|
@@ -1406,10 +1406,10 @@ function planNewBrand(source, input) {
|
|
|
1406
1406
|
if (i.modes && !i.modes.some((m) => m.split(",").includes(brandKey)) && !created.includes(i.file ?? "")) continue;
|
|
1407
1407
|
issues.push({ level: i.level, code: i.code, message: i.message, ...i.path ? { path: i.path } : {}, ...i.file ? { file: tokensPath(config, i.file) } : {} });
|
|
1408
1408
|
}
|
|
1409
|
-
const
|
|
1409
|
+
const pairs2 = parseContrastChecks(source.contrastText);
|
|
1410
1410
|
const inGate = nextConfig.axes.brand?.values.includes(slug);
|
|
1411
1411
|
const combos = inGate ? after.combos(nextConfig.contrast?.modes).filter((c) => c.brand === slug) : [];
|
|
1412
|
-
const results = combos.flatMap((c) =>
|
|
1412
|
+
const results = combos.flatMap((c) => pairs2.map((p2) => checkLayeredPair(after, p2, c)));
|
|
1413
1413
|
const failing = results.filter((r) => !r.pass);
|
|
1414
1414
|
const themes = [...new Set(combos.map((c) => c.theme).filter((t2) => !!t2))];
|
|
1415
1415
|
const ramps = {};
|
|
@@ -1780,7 +1780,7 @@ function composePendingDrafts(input) {
|
|
|
1780
1780
|
return p2;
|
|
1781
1781
|
}
|
|
1782
1782
|
const KINDS = new Set(REQUEST_KINDS.map((k) => k.value));
|
|
1783
|
-
const str = (x) => typeof x === "string";
|
|
1783
|
+
const str$1 = (x) => typeof x === "string";
|
|
1784
1784
|
const optStr = (x) => x === void 0 || typeof x === "string";
|
|
1785
1785
|
function validatePendingDrafts(x) {
|
|
1786
1786
|
const out = [];
|
|
@@ -1788,7 +1788,7 @@ function validatePendingDrafts(x) {
|
|
|
1788
1788
|
const p2 = x;
|
|
1789
1789
|
if (p2.version !== DRAFT_EXCHANGE_VERSION) out.push(`Unsupported draft session version ${String(p2.version)}: update Bridge.`);
|
|
1790
1790
|
if (!isDraftSessionId(p2.sessionId)) out.push("The session id is missing or invalid.");
|
|
1791
|
-
if (!str(p2.createdAt) || Number.isNaN(Date.parse(p2.createdAt))) out.push("createdAt must be an ISO date.");
|
|
1791
|
+
if (!str$1(p2.createdAt) || Number.isNaN(Date.parse(p2.createdAt))) out.push("createdAt must be an ISO date.");
|
|
1792
1792
|
if (p2.status !== "pending" && p2.status !== "drafted") out.push('status must be "pending" or "drafted".');
|
|
1793
1793
|
if (p2.source !== "figma" && p2.source !== "studio") out.push('source must be "figma" or "studio".');
|
|
1794
1794
|
if (!Array.isArray(p2.items) || !p2.items.length) out.push("A draft session needs at least one item.");
|
|
@@ -1799,32 +1799,32 @@ function validatePendingDrafts(x) {
|
|
|
1799
1799
|
out.push(`${at3} is not an object.`);
|
|
1800
1800
|
continue;
|
|
1801
1801
|
}
|
|
1802
|
-
if (!str(i.id) || !i.id) out.push(`${at3} has no id.`);
|
|
1802
|
+
if (!str$1(i.id) || !i.id) out.push(`${at3} has no id.`);
|
|
1803
1803
|
else if (ids.has(i.id)) out.push(`${at3} repeats the id "${i.id}".`);
|
|
1804
1804
|
else ids.add(i.id);
|
|
1805
1805
|
if (i.scope !== "request" && i.scope !== "push" && i.scope !== "docs") out.push(`${at3}: scope must be "request", "push" or "docs".`);
|
|
1806
1806
|
if (i.scope === "docs") {
|
|
1807
1807
|
const d = i.docs;
|
|
1808
|
-
if (!d || typeof d !== "object" || !str(d.slug) || !d.slug || !str(d.prompt) || !d.prompt || !optStr(d.path)) out.push(`${at3}: a docs item needs docs {slug, task, prompt}.`);
|
|
1808
|
+
if (!d || typeof d !== "object" || !str$1(d.slug) || !d.slug || !str$1(d.prompt) || !d.prompt || !optStr(d.path)) out.push(`${at3}: a docs item needs docs {slug, task, prompt}.`);
|
|
1809
1809
|
else out.push(...validateDocsTask(d.task).map((m) => `${at3}: ${m}`));
|
|
1810
1810
|
} else if (i.docs !== void 0) out.push(`${at3}: only a docs item has docs.`);
|
|
1811
1811
|
if (!optStr(i.component)) out.push(`${at3}: component must be text.`);
|
|
1812
1812
|
if (i.kind !== void 0 && !KINDS.has(i.kind)) out.push(`${at3}: unknown kind "${String(i.kind)}".`);
|
|
1813
|
-
if (!Array.isArray(i.changes) || i.changes.some((c) => !c || !str(c.field) || !optStr(c.before) || !optStr(c.after))) out.push(`${at3}: changes must be a list of {field, before, after}.`);
|
|
1814
|
-
if (i.variables !== void 0 && (!Array.isArray(i.variables) || i.variables.some((v) => !v || !str(v.path)))) out.push(`${at3}: variables must be a list of {path, before, after}.`);
|
|
1813
|
+
if (!Array.isArray(i.changes) || i.changes.some((c) => !c || !str$1(c.field) || !optStr(c.before) || !optStr(c.after))) out.push(`${at3}: changes must be a list of {field, before, after}.`);
|
|
1814
|
+
if (i.variables !== void 0 && (!Array.isArray(i.variables) || i.variables.some((v) => !v || !str$1(v.path)))) out.push(`${at3}: variables must be a list of {path, before, after}.`);
|
|
1815
1815
|
if (i.variables !== void 0 && i.scope !== "push") out.push(`${at3}: only a push item has variables.`);
|
|
1816
|
-
if (i.images !== void 0 && (!Array.isArray(i.images) || i.images.some((m) => !m || m.label !== "before" && m.label !== "after" || !str(m.url)))) out.push(`${at3}: images must be a list of {label: before or after, url}.`);
|
|
1817
|
-
if (!i.draft || !str(i.draft.title) || !str(i.draft.summary)) out.push(`${at3}: draft must hold a title and a summary.`);
|
|
1818
|
-
if (i.lines !== void 0 && (!Array.isArray(i.lines) || i.lines.some((l) => !l || !str(l.id) || !l.id || !str(l.line) || !optStr(l.title) || !Array.isArray(l.changes)))) out.push(`${at3}: lines must be a list of {id, changes, line, title}.`);
|
|
1816
|
+
if (i.images !== void 0 && (!Array.isArray(i.images) || i.images.some((m) => !m || m.label !== "before" && m.label !== "after" || !str$1(m.url)))) out.push(`${at3}: images must be a list of {label: before or after, url}.`);
|
|
1817
|
+
if (!i.draft || !str$1(i.draft.title) || !str$1(i.draft.summary)) out.push(`${at3}: draft must hold a title and a summary.`);
|
|
1818
|
+
if (i.lines !== void 0 && (!Array.isArray(i.lines) || i.lines.some((l) => !l || !str$1(l.id) || !l.id || !str$1(l.line) || !optStr(l.title) || !Array.isArray(l.changes)))) out.push(`${at3}: lines must be a list of {id, changes, line, title}.`);
|
|
1819
1819
|
}
|
|
1820
1820
|
if (p2.results !== void 0) {
|
|
1821
1821
|
if (!Array.isArray(p2.results)) out.push("results must be a list.");
|
|
1822
1822
|
else
|
|
1823
1823
|
for (const r of p2.results) {
|
|
1824
|
-
if (!r || !str(r.id) || !str(r.title) || !str(r.summary)) out.push("Each result needs an id, a title and a summary.");
|
|
1824
|
+
if (!r || !str$1(r.id) || !str$1(r.title) || !str$1(r.summary)) out.push("Each result needs an id, a title and a summary.");
|
|
1825
1825
|
else if (r.docs !== void 0 && (!r.docs || typeof r.docs !== "object" || Array.isArray(r.docs))) out.push("Result docs must be an object of sections.");
|
|
1826
1826
|
else if (!optStr(r.replacement)) out.push("A result replacement must be text.");
|
|
1827
|
-
else if (r.lines !== void 0 && (!Array.isArray(r.lines) || r.lines.some((l) => !l || !str(l.id) || !str(l.line) || !optStr(l.title)))) out.push("Result lines must be a list of {id, line, title}.");
|
|
1827
|
+
else if (r.lines !== void 0 && (!Array.isArray(r.lines) || r.lines.some((l) => !l || !str$1(l.id) || !str$1(l.line) || !optStr(l.title)))) out.push("Result lines must be a list of {id, line, title}.");
|
|
1828
1828
|
}
|
|
1829
1829
|
}
|
|
1830
1830
|
return out;
|
|
@@ -3210,6 +3210,149 @@ async function figmaPushStatusesOnGitHub(gh, numbers) {
|
|
|
3210
3210
|
);
|
|
3211
3211
|
return out;
|
|
3212
3212
|
}
|
|
3213
|
+
class StorybookIndexError extends Error {
|
|
3214
|
+
constructor(code2, message, status) {
|
|
3215
|
+
super(message);
|
|
3216
|
+
this.code = code2;
|
|
3217
|
+
this.status = status;
|
|
3218
|
+
this.name = "StorybookIndexError";
|
|
3219
|
+
}
|
|
3220
|
+
code;
|
|
3221
|
+
status;
|
|
3222
|
+
}
|
|
3223
|
+
const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3224
|
+
const str = (v) => typeof v === "string" && v.trim() ? v : void 0;
|
|
3225
|
+
function entryFrom(key, raw, v3) {
|
|
3226
|
+
if (!isObj(raw)) return null;
|
|
3227
|
+
const id = str(raw.id) ?? key;
|
|
3228
|
+
const title = str(raw.title) ?? str(raw.kind);
|
|
3229
|
+
const name = str(raw.name) ?? str(raw.story);
|
|
3230
|
+
if (!id || !title || !name) return null;
|
|
3231
|
+
const params = isObj(raw.parameters) ? raw.parameters : {};
|
|
3232
|
+
const type = raw.type === "docs" || raw.type === "story" ? raw.type : v3 && params.docsOnly === true ? "docs" : "story";
|
|
3233
|
+
const e = {
|
|
3234
|
+
id,
|
|
3235
|
+
title,
|
|
3236
|
+
name,
|
|
3237
|
+
type,
|
|
3238
|
+
importPath: str(raw.importPath) ?? str(params.fileName) ?? "",
|
|
3239
|
+
tags: Array.isArray(raw.tags) ? raw.tags.filter((t2) => typeof t2 === "string") : []
|
|
3240
|
+
};
|
|
3241
|
+
const subtype = str(raw.subtype);
|
|
3242
|
+
if (subtype) e.subtype = subtype;
|
|
3243
|
+
const exportName2 = str(raw.exportName);
|
|
3244
|
+
if (exportName2) e.exportName = exportName2;
|
|
3245
|
+
const componentPath = str(raw.componentPath);
|
|
3246
|
+
if (componentPath) e.componentPath = componentPath;
|
|
3247
|
+
return e;
|
|
3248
|
+
}
|
|
3249
|
+
function groupStorybookComponents(entries) {
|
|
3250
|
+
const byTitle = /* @__PURE__ */ new Map();
|
|
3251
|
+
for (const e of entries) {
|
|
3252
|
+
let c = byTitle.get(e.title);
|
|
3253
|
+
if (!c) {
|
|
3254
|
+
const parts = e.title.split("/").map((p2) => p2.trim()).filter(Boolean);
|
|
3255
|
+
c = { title: e.title, name: parts[parts.length - 1] ?? e.title, group: parts.slice(0, -1), stories: [], docs: [], tags: [] };
|
|
3256
|
+
byTitle.set(e.title, c);
|
|
3257
|
+
}
|
|
3258
|
+
if (e.type === "docs") c.docs.push(e);
|
|
3259
|
+
else if (e.subtype !== "test") c.stories.push(e);
|
|
3260
|
+
for (const t2 of e.tags) if (!c.tags.includes(t2)) c.tags.push(t2);
|
|
3261
|
+
}
|
|
3262
|
+
const out = [...byTitle.values()].filter((c) => c.stories.length || c.docs.length);
|
|
3263
|
+
for (const c of out) c.tags.sort();
|
|
3264
|
+
return out.sort((a2, b) => a2.title.localeCompare(b.title));
|
|
3265
|
+
}
|
|
3266
|
+
function parseStorybookIndex(json, source) {
|
|
3267
|
+
if (!isObj(json)) throw new StorybookIndexError("not-json", "The Storybook index is not a JSON object.");
|
|
3268
|
+
if (typeof json.loginUrl === "string" && !("entries" in json) && !("stories" in json)) {
|
|
3269
|
+
throw new StorybookIndexError("private", "This Storybook is private: it asks for a sign-in before showing its index.");
|
|
3270
|
+
}
|
|
3271
|
+
const map = isObj(json.entries) ? json.entries : isObj(json.stories) ? json.stories : null;
|
|
3272
|
+
if (!map) throw new StorybookIndexError("empty", "This file has no Storybook entries.");
|
|
3273
|
+
const v3 = !isObj(json.entries);
|
|
3274
|
+
const entries = Object.entries(map).map(([k, raw]) => entryFrom(k, raw, v3)).filter((e) => e !== null);
|
|
3275
|
+
if (!entries.length) throw new StorybookIndexError("empty", "This Storybook index lists no stories or docs.");
|
|
3276
|
+
const version = typeof json.v === "number" ? json.v : v3 ? 3 : 4;
|
|
3277
|
+
return { version, source: source ?? (v3 ? "stories.json" : "index.json"), entries, components: groupStorybookComponents(entries) };
|
|
3278
|
+
}
|
|
3279
|
+
function normalizeStorybookUrl(input) {
|
|
3280
|
+
let u;
|
|
3281
|
+
try {
|
|
3282
|
+
u = new URL(input.trim());
|
|
3283
|
+
} catch {
|
|
3284
|
+
throw new StorybookIndexError("invalid-url", "Enter the full Storybook address, for example https://storybook.example.com");
|
|
3285
|
+
}
|
|
3286
|
+
const local = u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]";
|
|
3287
|
+
if (u.protocol !== "https:" && !(u.protocol === "http:" && local)) {
|
|
3288
|
+
throw new StorybookIndexError("invalid-url", "The Storybook address must start with https://");
|
|
3289
|
+
}
|
|
3290
|
+
if (u.username || u.password) throw new StorybookIndexError("invalid-url", "Remove the user name and password from the address.");
|
|
3291
|
+
const path = u.pathname.replace(/\/(index\.html|iframe\.html|index\.json|stories\.json)$/i, "/").replace(/\/+$/, "");
|
|
3292
|
+
return `${u.protocol}//${u.host}${path}`;
|
|
3293
|
+
}
|
|
3294
|
+
const storybookIndexUrls = (base) => {
|
|
3295
|
+
const b = base.replace(/\/+$/, "");
|
|
3296
|
+
return [
|
|
3297
|
+
{ url: `${b}/index.json`, source: "index.json" },
|
|
3298
|
+
{ url: `${b}/stories.json`, source: "stories.json" }
|
|
3299
|
+
];
|
|
3300
|
+
};
|
|
3301
|
+
const pairs = (o) => Object.entries(o).map(([k, v]) => `${encodeURIComponent(k)}:${encodeURIComponent(v)}`).join(";");
|
|
3302
|
+
function storybookIframeUrl(base, id, o = {}) {
|
|
3303
|
+
let url = `${base.replace(/\/+$/, "")}/iframe.html?id=${encodeURIComponent(id)}&viewMode=${o.viewMode ?? "story"}`;
|
|
3304
|
+
if (o.globals && Object.keys(o.globals).length) url += `&globals=${pairs(o.globals)}`;
|
|
3305
|
+
if (o.args && Object.keys(o.args).length) url += `&args=${pairs(o.args)}`;
|
|
3306
|
+
return url;
|
|
3307
|
+
}
|
|
3308
|
+
function storybookManagerUrl(base, id, viewMode = "story") {
|
|
3309
|
+
return `${base.replace(/\/+$/, "")}/?path=/${viewMode}/${encodeURIComponent(id)}`;
|
|
3310
|
+
}
|
|
3311
|
+
async function fetchStorybookIndex(base, o = {}) {
|
|
3312
|
+
const doFetch = o.fetch ?? ((url, init) => fetch(url, init));
|
|
3313
|
+
const rank = { private: 6, http: 5, network: 4, empty: 3, "not-json": 2, "not-found": 1, "invalid-url": 0 };
|
|
3314
|
+
let worst = null;
|
|
3315
|
+
const keep = (e) => {
|
|
3316
|
+
if (!worst || rank[e.code] > rank[worst.code]) worst = e;
|
|
3317
|
+
};
|
|
3318
|
+
for (const { url, source } of storybookIndexUrls(base)) {
|
|
3319
|
+
let res;
|
|
3320
|
+
try {
|
|
3321
|
+
res = await doFetch(url, { headers: { accept: "application/json" }, ...o.signal ? { signal: o.signal } : {} });
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
if (o.signal?.aborted) throw err;
|
|
3324
|
+
keep(new StorybookIndexError("network", `Could not reach ${url}. The host may be down or may not allow reading from another site.`));
|
|
3325
|
+
continue;
|
|
3326
|
+
}
|
|
3327
|
+
if (res.status === 404 || res.status === 410) {
|
|
3328
|
+
keep(new StorybookIndexError("not-found", `No Storybook index at ${base}: neither index.json nor stories.json was found.`, res.status));
|
|
3329
|
+
continue;
|
|
3330
|
+
}
|
|
3331
|
+
if (res.status === 401 || res.status === 403) {
|
|
3332
|
+
keep(new StorybookIndexError("private", "This Storybook is private: it asks for a sign-in before showing its index.", res.status));
|
|
3333
|
+
continue;
|
|
3334
|
+
}
|
|
3335
|
+
if (!res.ok) {
|
|
3336
|
+
keep(new StorybookIndexError("http", `The Storybook host answered ${res.status} for ${url}.`, res.status));
|
|
3337
|
+
continue;
|
|
3338
|
+
}
|
|
3339
|
+
const text = await res.text();
|
|
3340
|
+
let json;
|
|
3341
|
+
try {
|
|
3342
|
+
json = JSON.parse(text);
|
|
3343
|
+
} catch {
|
|
3344
|
+
keep(new StorybookIndexError("not-json", `${url} is not a Storybook index (the host answered with a web page).`));
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
try {
|
|
3348
|
+
return parseStorybookIndex(json, source);
|
|
3349
|
+
} catch (err) {
|
|
3350
|
+
if (err instanceof StorybookIndexError) keep(err);
|
|
3351
|
+
else throw err;
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
throw worst ?? new StorybookIndexError("not-found", `No Storybook index at ${base}.`);
|
|
3355
|
+
}
|
|
3213
3356
|
export {
|
|
3214
3357
|
af as AAA_MIN,
|
|
3215
3358
|
ag as AA_MIN,
|
|
@@ -3326,6 +3469,7 @@ export {
|
|
|
3326
3469
|
aI2 as SUGGESTION_STATE_LABELS,
|
|
3327
3470
|
aJ2 as SUGGESTION_STATUS_LABEL,
|
|
3328
3471
|
aK2 as SUGGESTION_TOO_LARGE,
|
|
3472
|
+
StorybookIndexError,
|
|
3329
3473
|
aL2 as SuggestionError,
|
|
3330
3474
|
TIER_AXIS,
|
|
3331
3475
|
TIER_NAMES,
|
|
@@ -3515,6 +3659,7 @@ export {
|
|
|
3515
3659
|
explorationTestBuildOnGitHub,
|
|
3516
3660
|
explorationTryIt,
|
|
3517
3661
|
explorationTryItOnGitHub,
|
|
3662
|
+
fetchStorybookIndex,
|
|
3518
3663
|
fidelityComponentLine,
|
|
3519
3664
|
fidelityVerdict,
|
|
3520
3665
|
bb as fieldChanges,
|
|
@@ -3560,6 +3705,7 @@ export {
|
|
|
3560
3705
|
gitBlobSha,
|
|
3561
3706
|
gitHubHistoryBackend,
|
|
3562
3707
|
bl as globToRegExp,
|
|
3708
|
+
groupStorybookComponents,
|
|
3563
3709
|
bm as groupsPhrase,
|
|
3564
3710
|
bL2 as hasSuggestionBlock,
|
|
3565
3711
|
bM2 as hasWriteAccess,
|
|
@@ -3647,6 +3793,7 @@ export {
|
|
|
3647
3793
|
newDraftSessionId,
|
|
3648
3794
|
bC as nodesEqual,
|
|
3649
3795
|
bD as normalizeRights,
|
|
3796
|
+
normalizeStorybookUrl,
|
|
3650
3797
|
oklch,
|
|
3651
3798
|
oklchToLinear,
|
|
3652
3799
|
onlyTokens,
|
|
@@ -3683,6 +3830,7 @@ export {
|
|
|
3683
3830
|
parsePiecesRecord,
|
|
3684
3831
|
cd2 as parseRepo,
|
|
3685
3832
|
ce2 as parseRequestIssue,
|
|
3833
|
+
parseStorybookIndex,
|
|
3686
3834
|
cf2 as parseStructureChange,
|
|
3687
3835
|
cg2 as parseSuggestionAppliedComment,
|
|
3688
3836
|
ch2 as parseSuggestionIssue,
|
|
@@ -3813,6 +3961,9 @@ export {
|
|
|
3813
3961
|
stableCommand,
|
|
3814
3962
|
startExplorationOnGitHub,
|
|
3815
3963
|
stateSentence,
|
|
3964
|
+
storybookIframeUrl,
|
|
3965
|
+
storybookIndexUrls,
|
|
3966
|
+
storybookManagerUrl,
|
|
3816
3967
|
c4 as storybookUrl,
|
|
3817
3968
|
c5 as suggestNearestComment,
|
|
3818
3969
|
suggestTokenProposalOnGitHub,
|