@bridge-ds/core 0.2.0 → 0.3.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/bridge.config.schema.json +74 -5
- package/dist/change-drafts.d.ts +29 -0
- package/dist/{component-spec-B0V_D3gM.js → component-spec-IRFGJ7XK.js} +607 -119
- package/dist/component-spec-IRFGJ7XK.js.map +1 -0
- package/dist/config.d.ts +16 -2
- package/dist/docs-ai.d.ts +116 -0
- package/dist/docs-draft.d.ts +30 -4
- package/dist/draft-exchange.d.ts +135 -14
- package/dist/explorations-github.d.ts +40 -5
- package/dist/explorations.d.ts +34 -28
- package/dist/github-client.d.ts +120 -2
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1470 -446
- package/dist/index.js.map +1 -1
- package/dist/node/index.js +1 -1
- package/dist/previews-github.d.ts +22 -0
- package/dist/previews.d.ts +102 -0
- package/dist/protection-github.d.ts +25 -0
- package/dist/protection.d.ts +139 -0
- package/dist/requests-github.d.ts +21 -0
- package/dist/requests.d.ts +16 -1
- package/dist/rights.d.ts +63 -0
- package/dist/teams.d.ts +149 -0
- package/package.json +1 -1
- package/dist/component-spec-B0V_D3gM.js.map +0 -1
|
@@ -100,12 +100,12 @@ function previewComment(input) {
|
|
|
100
100
|
}
|
|
101
101
|
lines.push("");
|
|
102
102
|
const ctx = { branch: input.branch, pr: input.pr, sha: input.sha };
|
|
103
|
-
const
|
|
103
|
+
const previews = input.previews ?? resolveBranchPreviews(input.apps, ctx);
|
|
104
104
|
lines.push("### Previews", "");
|
|
105
|
-
if (
|
|
105
|
+
if (previews.length) {
|
|
106
106
|
lines.push("| App | Preview |", "| --- | --- |");
|
|
107
|
-
for (const
|
|
108
|
-
const docs =
|
|
107
|
+
for (const p of previews) lines.push(`| ${p.label} | ${p.url ?? "Link in the Vercel comment on this pull request"} |`);
|
|
108
|
+
const docs = previews.find((p) => p.app === "docs")?.url;
|
|
109
109
|
if (docs) {
|
|
110
110
|
lines.push("");
|
|
111
111
|
lines.push(`Tokens only, no reinstall: open your staging with \`?bridge-tokens=${new URL("tokens.css", docs).href}\` (the staging app must allow this origin, see docs/PREVIEWS.md).`);
|
|
@@ -115,6 +115,449 @@ function previewComment(input) {
|
|
|
115
115
|
}
|
|
116
116
|
return lines.join("\n") + "\n";
|
|
117
117
|
}
|
|
118
|
+
function installCommandFromPreviewComment(body) {
|
|
119
|
+
if (!body || !body.trimStart().startsWith(PREVIEW_COMMENT_MARKER)) return null;
|
|
120
|
+
const m = /```(?:sh|bash|shell)[^\n]*\n([\s\S]*?)```/.exec(body);
|
|
121
|
+
if (!m) return null;
|
|
122
|
+
const command = m[1].split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#")).join(" ").replace(/\s*\\\s+/g, " ").replace(/\s+/g, " ").trim();
|
|
123
|
+
return command || null;
|
|
124
|
+
}
|
|
125
|
+
function readPreviewComment(body) {
|
|
126
|
+
if (!body || !body.trimStart().startsWith(PREVIEW_COMMENT_MARKER)) return null;
|
|
127
|
+
const command = installCommandFromPreviewComment(body);
|
|
128
|
+
const sha7 = /Snapshot of `([0-9a-f]{7,40})`/.exec(body)?.[1]?.slice(0, 7) ?? null;
|
|
129
|
+
const source = !command ? "none" : command.includes("https://pkg.pr.new/") ? "pkg.pr.new" : /\/releases\/download\/preview-pr-\d+\//.test(command) ? "release" : "other";
|
|
130
|
+
return { command, sha7, source };
|
|
131
|
+
}
|
|
132
|
+
function findPreviewComment(comments) {
|
|
133
|
+
return [...comments].reverse().find((c) => !!c.body && c.body.trimStart().startsWith(PREVIEW_COMMENT_MARKER));
|
|
134
|
+
}
|
|
135
|
+
const httpsUrl = (raw) => {
|
|
136
|
+
if (!raw || typeof raw !== "string") return null;
|
|
137
|
+
try {
|
|
138
|
+
const u = new URL(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`);
|
|
139
|
+
return u.protocol === "https:" || u.protocol === "http:" ? u.href : null;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
const decodeBase64 = (b64) => {
|
|
145
|
+
const bin = atob(b64.replace(/\s/g, ""));
|
|
146
|
+
const bytes = new Uint8Array(bin.length);
|
|
147
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
148
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
149
|
+
};
|
|
150
|
+
const isVercelComment = (body) => !!body && /^\[vc\]: #[^:\s]*:[A-Za-z0-9+/=]+/m.test(body);
|
|
151
|
+
function parseVercelComment(body) {
|
|
152
|
+
if (!body) return null;
|
|
153
|
+
const m = /^\[vc\]: #[^:\s]*:([A-Za-z0-9+/=]+)/m.exec(body);
|
|
154
|
+
if (!m) return null;
|
|
155
|
+
let data;
|
|
156
|
+
try {
|
|
157
|
+
data = JSON.parse(decodeBase64(m[1]));
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (!data || !Array.isArray(data.projects)) return null;
|
|
162
|
+
return data.projects.filter((p) => p && typeof p.name === "string").map((p) => ({ name: p.name, previewUrl: httpsUrl(typeof p.previewUrl === "string" ? p.previewUrl : null), status: typeof p.nextCommitStatus === "string" ? p.nextCommitStatus : null }));
|
|
163
|
+
}
|
|
164
|
+
function findVercelComment(comments) {
|
|
165
|
+
return [...comments].reverse().find((c) => isVercelComment(c.body));
|
|
166
|
+
}
|
|
167
|
+
function templateHostPrefix(template) {
|
|
168
|
+
if (!template) return null;
|
|
169
|
+
const host = /^https?:\/\/([^/?#]+)/i.exec(template)?.[1];
|
|
170
|
+
if (!host) return null;
|
|
171
|
+
return host.split(".")[0].split("{")[0].toLowerCase();
|
|
172
|
+
}
|
|
173
|
+
function appForProject(project, apps) {
|
|
174
|
+
const name = project.trim().toLowerCase();
|
|
175
|
+
if (!name) return null;
|
|
176
|
+
const keys = Object.keys(PREVIEW_APP_LABELS);
|
|
177
|
+
const prefixes = keys.map((app) => [app, templateHostPrefix(apps[app])]);
|
|
178
|
+
const exact = prefixes.find(([, p]) => p === `${name}-git-` || p === name);
|
|
179
|
+
if (exact) return exact[0];
|
|
180
|
+
const loose = prefixes.filter(([, p]) => p !== null && p.startsWith(`${name}-`));
|
|
181
|
+
return loose.length === 1 ? loose[0][0] : null;
|
|
182
|
+
}
|
|
183
|
+
function projectOfLabel(label) {
|
|
184
|
+
const m = /^\s*(?:preview|vercel)\s*[\u2013\u2014-]\s*(.+?)\s*$/i.exec(label);
|
|
185
|
+
return m ? m[1] : null;
|
|
186
|
+
}
|
|
187
|
+
const STATE_OF = {
|
|
188
|
+
success: "ready",
|
|
189
|
+
inactive: "ready",
|
|
190
|
+
deployed: "ready",
|
|
191
|
+
ready: "ready",
|
|
192
|
+
ignored: "ready",
|
|
193
|
+
// Vercel skipped the build (nothing changed for this app): the branch alias serves the last one.
|
|
194
|
+
pending: "building",
|
|
195
|
+
queued: "building",
|
|
196
|
+
in_progress: "building",
|
|
197
|
+
building: "building",
|
|
198
|
+
initializing: "building",
|
|
199
|
+
failure: "failed",
|
|
200
|
+
error: "failed",
|
|
201
|
+
failed: "failed",
|
|
202
|
+
canceled: "failed"
|
|
203
|
+
};
|
|
204
|
+
const stateOf = (s) => STATE_OF[(s ?? "").toLowerCase()] ?? "unknown";
|
|
205
|
+
const isInspectorUrl = (u) => /^https:\/\/(?:www\.)?vercel\.com\//i.test(u);
|
|
206
|
+
function resolveBranchPreviews(apps, ctx, sources = {}) {
|
|
207
|
+
const seen = /* @__PURE__ */ new Map();
|
|
208
|
+
const at = (app) => seen.get(app) ?? (seen.set(app, {}), seen.get(app));
|
|
209
|
+
const offer = (app, url, urlSource, state, stateRank) => {
|
|
210
|
+
if (!app) return;
|
|
211
|
+
const s = at(app);
|
|
212
|
+
if (url && !s.url) Object.assign(s, { url, urlSource });
|
|
213
|
+
if (state !== "unknown" && (s.stateRank === void 0 || stateRank < s.stateRank)) Object.assign(s, { state, stateRank });
|
|
214
|
+
};
|
|
215
|
+
const contexts = /* @__PURE__ */ new Set();
|
|
216
|
+
for (const st of sources.statuses ?? []) {
|
|
217
|
+
if (contexts.has(st.context)) continue;
|
|
218
|
+
contexts.add(st.context);
|
|
219
|
+
const project = projectOfLabel(st.context);
|
|
220
|
+
if (!project) continue;
|
|
221
|
+
const url = httpsUrl(st.target_url);
|
|
222
|
+
offer(appForProject(project, apps), url && !isInspectorUrl(url) ? url : null, "status", stateOf(st.state), 0);
|
|
223
|
+
}
|
|
224
|
+
for (const d of sources.deployments ?? []) {
|
|
225
|
+
const project = projectOfLabel(d.environment);
|
|
226
|
+
const app = project ? appForProject(project, apps) : null;
|
|
227
|
+
if (!app) continue;
|
|
228
|
+
const url = httpsUrl(d.environmentUrl);
|
|
229
|
+
const s = at(app);
|
|
230
|
+
if (url && s.urlSource !== "deployment") Object.assign(s, { url, urlSource: "deployment" });
|
|
231
|
+
const state = stateOf(d.state);
|
|
232
|
+
if (state !== "unknown" && (s.stateRank === void 0 || 1 < s.stateRank)) Object.assign(s, { state, stateRank: 1 });
|
|
233
|
+
}
|
|
234
|
+
for (const p of parseVercelComment(findVercelComment(sources.comments ?? [])?.body) ?? []) offer(appForProject(p.name, apps), p.previewUrl, "vercel-comment", stateOf(p.status), 2);
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const app of Object.keys(PREVIEW_APP_LABELS)) {
|
|
237
|
+
if (!apps[app]) continue;
|
|
238
|
+
const s = seen.get(app) ?? {};
|
|
239
|
+
const url = s.url ?? previewUrl(apps[app], ctx);
|
|
240
|
+
const source = s.url ? s.urlSource : url ? "template" : null;
|
|
241
|
+
out.push({ app, label: PREVIEW_APP_LABELS[app], url, state: s.state ?? "unknown", source });
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
const previewLinksOf = (previews) => previews.filter((p) => !!p.url).map((p) => ({ app: p.app, label: p.label, url: p.url }));
|
|
246
|
+
const previewsSettled = (previews) => previews.every((p) => !!p.url && (p.state === "ready" || p.state === "failed"));
|
|
247
|
+
const LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
|
|
248
|
+
const SLUG = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
249
|
+
function parseOwnerEntry(raw) {
|
|
250
|
+
if (typeof raw !== "string") return null;
|
|
251
|
+
const s = raw.trim().replace(/^@/, "");
|
|
252
|
+
const slash = s.indexOf("/");
|
|
253
|
+
if (slash < 0) return LOGIN.test(s) ? { kind: "person", login: s } : null;
|
|
254
|
+
const org = s.slice(0, slash);
|
|
255
|
+
const slug = s.slice(slash + 1);
|
|
256
|
+
return LOGIN.test(org) && SLUG.test(slug) ? { kind: "team", org, slug } : null;
|
|
257
|
+
}
|
|
258
|
+
function formatOwnerEntry(e) {
|
|
259
|
+
return e.kind === "team" ? `@${e.org}/${e.slug}` : e.login;
|
|
260
|
+
}
|
|
261
|
+
const isTeamEntry = (raw) => parseOwnerEntry(raw)?.kind === "team";
|
|
262
|
+
const teamKey = (org, slug) => `@${org.toLowerCase()}/${slug.toLowerCase()}`;
|
|
263
|
+
function teamKeyOf(raw) {
|
|
264
|
+
const e = parseOwnerEntry(raw);
|
|
265
|
+
return e?.kind === "team" ? teamKey(e.org, e.slug) : void 0;
|
|
266
|
+
}
|
|
267
|
+
function ownerEntryProblems(raw) {
|
|
268
|
+
if (raw === void 0) return [];
|
|
269
|
+
const isObj2 = (x) => Boolean(x) && typeof x === "object" && !Array.isArray(x);
|
|
270
|
+
if (!isObj2(raw)) return ['"governance.owners" must be { "design": [people or teams], "tech": [people or teams] }'];
|
|
271
|
+
const out = [];
|
|
272
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
273
|
+
if (k !== "design" && k !== "tech") {
|
|
274
|
+
out.push(`"governance.owners.${k}" is not an owner group (design, tech)`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (!Array.isArray(v)) {
|
|
278
|
+
out.push(`"governance.owners.${k}" must be a list of people or teams`);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
v.forEach((x, i) => {
|
|
282
|
+
if (typeof x !== "string" || !parseOwnerEntry(x))
|
|
283
|
+
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".`);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
287
|
+
}
|
|
288
|
+
function teamUnreadableMessage(team, reason) {
|
|
289
|
+
const name = `@${team.org}/${team.slug}`;
|
|
290
|
+
switch (reason) {
|
|
291
|
+
case "personal-account":
|
|
292
|
+
return `${name} cannot be used: ${team.org} is a personal GitHub account, and teams only exist in organizations. List people instead, or move the repository to an organization.`;
|
|
293
|
+
case "missing-permission":
|
|
294
|
+
return `The members of ${name} cannot be read: the GitHub token needs read:org (classic token) or Members: Read on the ${team.org} organization (fine-grained token).`;
|
|
295
|
+
case "no-token":
|
|
296
|
+
return `The members of ${name} cannot be read without a working GitHub token. Connect GitHub with a token that has read:org.`;
|
|
297
|
+
case "not-found":
|
|
298
|
+
return `${name} was not found. Check the team slug on GitHub, or give the token read:org so it can see the team.`;
|
|
299
|
+
case "unreachable":
|
|
300
|
+
return `The members of ${name} cannot be read right now: GitHub did not answer. Try again in a moment.`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function teamsIn(owners) {
|
|
304
|
+
const all = Array.isArray(owners) ? owners : [...owners.design ?? [], ...owners.tech ?? []];
|
|
305
|
+
const out = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const raw of all) {
|
|
307
|
+
const e = parseOwnerEntry(raw);
|
|
308
|
+
if (e?.kind === "team") out.set(teamKey(e.org, e.slug), { key: teamKey(e.org, e.slug), org: e.org, slug: e.slug });
|
|
309
|
+
}
|
|
310
|
+
return [...out.values()];
|
|
311
|
+
}
|
|
312
|
+
const norm$1 = (x) => x.trim().toLowerCase().replace(/^@/, "");
|
|
313
|
+
function expandEntries(entries, teams) {
|
|
314
|
+
const out = [];
|
|
315
|
+
const seen = /* @__PURE__ */ new Set();
|
|
316
|
+
const add = (x) => {
|
|
317
|
+
const k = norm$1(x);
|
|
318
|
+
if (k && !seen.has(k)) {
|
|
319
|
+
seen.add(k);
|
|
320
|
+
out.push(x.replace(/^@(?=[^/]+$)/, ""));
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
for (const raw of entries) {
|
|
324
|
+
const e = parseOwnerEntry(raw);
|
|
325
|
+
if (!e) continue;
|
|
326
|
+
if (e.kind === "person") add(e.login);
|
|
327
|
+
else {
|
|
328
|
+
const state = teams?.[teamKey(e.org, e.slug)];
|
|
329
|
+
if (state?.status === "ok") state.members.forEach(add);
|
|
330
|
+
else add(teamKey(e.org, e.slug));
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
function expandOwners(owners, teams) {
|
|
336
|
+
return { design: expandEntries(owners.design, teams), tech: expandEntries(owners.tech, teams) };
|
|
337
|
+
}
|
|
338
|
+
const people = (n) => `${n} ${n === 1 ? "person" : "people"}`;
|
|
339
|
+
function entryLabel(raw, teams) {
|
|
340
|
+
const e = parseOwnerEntry(raw);
|
|
341
|
+
if (!e) return raw;
|
|
342
|
+
if (e.kind === "person") return `@${e.login}`;
|
|
343
|
+
const state = teams?.[teamKey(e.org, e.slug)];
|
|
344
|
+
const name = `@${e.org}/${e.slug}`;
|
|
345
|
+
return state?.status === "ok" ? `${name} (${people(state.members.length)})` : name;
|
|
346
|
+
}
|
|
347
|
+
function entriesFor(entries, logins, teams) {
|
|
348
|
+
const want = new Set(logins.map(norm$1));
|
|
349
|
+
const out = [];
|
|
350
|
+
for (const raw of entries) {
|
|
351
|
+
const e = parseOwnerEntry(raw);
|
|
352
|
+
if (!e) continue;
|
|
353
|
+
if (e.kind === "person") {
|
|
354
|
+
if (want.has(norm$1(e.login))) out.push(entryLabel(raw, teams));
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
const state = teams?.[teamKey(e.org, e.slug)];
|
|
358
|
+
if (state?.status !== "ok" || state.members.some((m) => want.has(norm$1(m)))) out.push(entryLabel(raw, teams));
|
|
359
|
+
}
|
|
360
|
+
return [...new Set(out)];
|
|
361
|
+
}
|
|
362
|
+
function unreadableTeams(teams) {
|
|
363
|
+
return Object.values(teams ?? {}).filter((t) => t.status === "unreadable");
|
|
364
|
+
}
|
|
365
|
+
class TeamDirectory {
|
|
366
|
+
constructor(reader, opts = {}) {
|
|
367
|
+
this.reader = reader;
|
|
368
|
+
this.ttl = opts.ttlMs ?? 5 * 6e4;
|
|
369
|
+
this.now = opts.now ?? Date.now;
|
|
370
|
+
}
|
|
371
|
+
reader;
|
|
372
|
+
teams = /* @__PURE__ */ new Map();
|
|
373
|
+
accounts = /* @__PURE__ */ new Map();
|
|
374
|
+
memberships = /* @__PURE__ */ new Map();
|
|
375
|
+
ttl;
|
|
376
|
+
now;
|
|
377
|
+
accountType(org) {
|
|
378
|
+
const k = org.toLowerCase();
|
|
379
|
+
let p = this.accounts.get(k);
|
|
380
|
+
if (!p) {
|
|
381
|
+
p = this.reader.getAccountType(org).catch(() => null);
|
|
382
|
+
this.accounts.set(k, p);
|
|
383
|
+
}
|
|
384
|
+
return p;
|
|
385
|
+
}
|
|
386
|
+
/** The reason a read of `org` failed, as a state. A 404 on a personal account says so; a 403 is a missing permission. */
|
|
387
|
+
async failure(ref, err) {
|
|
388
|
+
const e = err ?? {};
|
|
389
|
+
let reason;
|
|
390
|
+
if (!this.reader.hasToken) reason = await this.accountType(ref.org) === "User" ? "personal-account" : "no-token";
|
|
391
|
+
else if (e.status === 401) reason = "no-token";
|
|
392
|
+
else if (e.status === 403 || e.auth === "missing-permission") reason = "missing-permission";
|
|
393
|
+
else if (e.status === 404) reason = await this.accountType(ref.org) === "User" ? "personal-account" : "not-found";
|
|
394
|
+
else reason = "unreachable";
|
|
395
|
+
return { ...ref, status: "unreadable", reason, message: teamUnreadableMessage(ref, reason) };
|
|
396
|
+
}
|
|
397
|
+
/** Members of one team, or why they cannot be read. Cached. */
|
|
398
|
+
team(org, slug) {
|
|
399
|
+
const ref = { key: teamKey(org, slug), org, slug };
|
|
400
|
+
const hit = this.teams.get(ref.key);
|
|
401
|
+
if (hit && this.now() - hit.at < this.ttl) return hit.value;
|
|
402
|
+
const value = (async () => {
|
|
403
|
+
if (!this.reader.hasToken) return this.failure(ref, void 0);
|
|
404
|
+
try {
|
|
405
|
+
const members = await this.reader.listTeamMembers(org, slug);
|
|
406
|
+
return { ...ref, status: "ok", members };
|
|
407
|
+
} catch (err) {
|
|
408
|
+
return this.failure(ref, err);
|
|
409
|
+
}
|
|
410
|
+
})();
|
|
411
|
+
this.teams.set(ref.key, { at: this.now(), value });
|
|
412
|
+
void value.then((s) => {
|
|
413
|
+
if (s.status === "unreadable" && s.reason === "unreachable") this.teams.delete(ref.key);
|
|
414
|
+
});
|
|
415
|
+
return value;
|
|
416
|
+
}
|
|
417
|
+
/** Every team named in the owner lists, read in parallel. Empty object when no team is named (no request). */
|
|
418
|
+
async resolve(owners) {
|
|
419
|
+
const refs = teamsIn(owners);
|
|
420
|
+
const states = await Promise.all(refs.map((r) => this.team(r.org, r.slug)));
|
|
421
|
+
return Object.fromEntries(states.map((s) => [s.key, s]));
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Whether `login` is an active member of one team: from the cached member list when there is one, else one
|
|
425
|
+
* membership read (GET /orgs/{org}/teams/{slug}/memberships/{user}). An unreadable team returns its state.
|
|
426
|
+
*/
|
|
427
|
+
async isMember(org, slug, login) {
|
|
428
|
+
const ref = { key: teamKey(org, slug), org, slug };
|
|
429
|
+
const cached = this.teams.get(ref.key);
|
|
430
|
+
if (cached && this.now() - cached.at < this.ttl) {
|
|
431
|
+
const s = await cached.value;
|
|
432
|
+
if (s.status === "ok") return s.members.some((m) => norm$1(m) === norm$1(login));
|
|
433
|
+
}
|
|
434
|
+
const k = `${ref.key}#${norm$1(login)}`;
|
|
435
|
+
const hit = this.memberships.get(k);
|
|
436
|
+
if (hit && this.now() - hit.at < this.ttl) return hit.value;
|
|
437
|
+
const value = (async () => {
|
|
438
|
+
if (!this.reader.hasToken) return this.failure(ref, void 0);
|
|
439
|
+
try {
|
|
440
|
+
return await this.reader.getTeamMembership(org, slug, login) === "active";
|
|
441
|
+
} catch (err) {
|
|
442
|
+
return this.failure(ref, err);
|
|
443
|
+
}
|
|
444
|
+
})();
|
|
445
|
+
this.memberships.set(k, { at: this.now(), value });
|
|
446
|
+
return value;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Whether `login` holds a right given to `entries` (people and teams): a person entry matches directly, a team
|
|
450
|
+
* through its membership. Teams are only read when no person entry matches.
|
|
451
|
+
*/
|
|
452
|
+
async memberOfAny(entries, login) {
|
|
453
|
+
const unreadable = [];
|
|
454
|
+
const parsed = entries.map(parseOwnerEntry).filter((e) => Boolean(e));
|
|
455
|
+
if (parsed.some((e) => e.kind === "person" && norm$1(e.login) === norm$1(login))) return { member: true, unreadable };
|
|
456
|
+
for (const e of parsed) {
|
|
457
|
+
if (e.kind !== "team") continue;
|
|
458
|
+
const r = await this.isMember(e.org, e.slug, login);
|
|
459
|
+
if (r === true) return { member: true, unreadable };
|
|
460
|
+
if (typeof r === "object" && r.status === "unreadable") unreadable.push(r);
|
|
461
|
+
}
|
|
462
|
+
return { member: false, unreadable };
|
|
463
|
+
}
|
|
464
|
+
/** The organization's teams for a picker, or why they cannot be listed (a personal account has none). */
|
|
465
|
+
async orgTeams(org) {
|
|
466
|
+
if (await this.accountType(org) === "User") return { status: "unreadable", reason: "personal-account" };
|
|
467
|
+
if (!this.reader.hasToken) return { status: "unreadable", reason: "no-token" };
|
|
468
|
+
try {
|
|
469
|
+
return { status: "ok", teams: await this.reader.listOrgTeams(org) };
|
|
470
|
+
} catch (err) {
|
|
471
|
+
const s = await this.failure({ key: teamKey(org, "*"), org, slug: "*" }, err);
|
|
472
|
+
return { status: "unreadable", reason: s.status === "unreadable" ? s.reason : "unreachable" };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/** Drops every cached read (after the token changes). */
|
|
476
|
+
clear() {
|
|
477
|
+
this.teams.clear();
|
|
478
|
+
this.accounts.clear();
|
|
479
|
+
this.memberships.clear();
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const OWNER_GROUPS = ["design", "tech"];
|
|
483
|
+
const RIGHTS = ["approveDesign", "approveTech", "ship"];
|
|
484
|
+
const RIGHT_LABEL = {
|
|
485
|
+
approveDesign: "Approve design",
|
|
486
|
+
approveTech: "Approve tech",
|
|
487
|
+
ship: "Ship"
|
|
488
|
+
};
|
|
489
|
+
const DEFAULT_RIGHTS = { approveDesign: ["design"], approveTech: ["tech"], ship: ["tech"] };
|
|
490
|
+
const norm = (x) => x.trim().toLowerCase().replace(/^@/, "");
|
|
491
|
+
const uniq = (xs) => [...new Set(xs)];
|
|
492
|
+
function normalizeRights(input) {
|
|
493
|
+
const pick = (r) => {
|
|
494
|
+
const v = input?.[r];
|
|
495
|
+
if (!Array.isArray(v)) return [...DEFAULT_RIGHTS[r]];
|
|
496
|
+
return OWNER_GROUPS.filter((g) => v.includes(g));
|
|
497
|
+
};
|
|
498
|
+
return { approveDesign: pick("approveDesign"), approveTech: pick("approveTech"), ship: pick("ship") };
|
|
499
|
+
}
|
|
500
|
+
function rightsProblems(raw) {
|
|
501
|
+
if (raw === void 0) return [];
|
|
502
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return ['"governance.rights" must be { "approveDesign": [groups], "approveTech": [groups], "ship": [groups] }'];
|
|
503
|
+
const out = [];
|
|
504
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
505
|
+
if (!RIGHTS.includes(k)) out.push(`"governance.rights.${k}" is not a right (${RIGHTS.join(", ")})`);
|
|
506
|
+
else if (!Array.isArray(v) || v.length === 0 || !v.every((g) => OWNER_GROUPS.includes(g)))
|
|
507
|
+
out.push(`"governance.rights.${k}" must list one or more of "design", "tech"`);
|
|
508
|
+
}
|
|
509
|
+
return out;
|
|
510
|
+
}
|
|
511
|
+
function holdersOf(right, owners, rights) {
|
|
512
|
+
const groups = (rights ?? DEFAULT_RIGHTS)[right];
|
|
513
|
+
return uniq(groups.flatMap((g) => owners[g] ?? []));
|
|
514
|
+
}
|
|
515
|
+
function approverLists(owners, rights, teams) {
|
|
516
|
+
const entries = approverEntries(owners, rights);
|
|
517
|
+
return teams === void 0 ? entries : expandOwners(entries, teams);
|
|
518
|
+
}
|
|
519
|
+
function approverEntries(owners, rights) {
|
|
520
|
+
if (!rights) return { design: [...owners.design], tech: [...owners.tech] };
|
|
521
|
+
return { design: holdersOf("approveDesign", owners, rights), tech: holdersOf("approveTech", owners, rights) };
|
|
522
|
+
}
|
|
523
|
+
function shipPolicy(owners, rights, teams) {
|
|
524
|
+
if (!rights) return null;
|
|
525
|
+
const entries = holdersOf("ship", owners, rights);
|
|
526
|
+
if (!entries.length) return null;
|
|
527
|
+
const logins = expandEntries(entries, teams);
|
|
528
|
+
return { logins, groups: [...rights.ship], entries, ...teams ? { teams } : {} };
|
|
529
|
+
}
|
|
530
|
+
function unreadableShipTeams(policy) {
|
|
531
|
+
if (!policy?.teams) return [];
|
|
532
|
+
const keys = new Set((policy.entries ?? []).map((e) => e.trim().toLowerCase().replace(/^@?/, "@")));
|
|
533
|
+
return unreadableTeams(policy.teams).filter((t) => keys.has(t.key));
|
|
534
|
+
}
|
|
535
|
+
function canShip(login, policy) {
|
|
536
|
+
if (!policy) return true;
|
|
537
|
+
if (!login || login.includes("/")) return false;
|
|
538
|
+
return policy.logins.map(norm).includes(norm(login));
|
|
539
|
+
}
|
|
540
|
+
function groupsPhrase(groups) {
|
|
541
|
+
const g = OWNER_GROUPS.filter((x) => groups.includes(x));
|
|
542
|
+
if (g.length === 1) return `the ${g[0]} owner`;
|
|
543
|
+
return `a ${g.join(" or ")} owner`;
|
|
544
|
+
}
|
|
545
|
+
function whoCanShipSentence(policy) {
|
|
546
|
+
const names = (policy.entries ?? policy.logins).map((l) => entryLabel(l, policy.teams)).join(", ");
|
|
547
|
+
return `Only ${groupsPhrase(policy.groups)} can ship this${names ? ` (${names})` : ""}.`;
|
|
548
|
+
}
|
|
549
|
+
const DEFAULT_VISUAL_IMPACT = { mode: "auto" };
|
|
550
|
+
const VISUAL_CHECK_LABEL = "visual-check";
|
|
551
|
+
function visualImpactProblems(raw) {
|
|
552
|
+
if (raw === void 0) return [];
|
|
553
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return ['"visualImpact" must be { "mode": "auto" | "on-demand" }'];
|
|
554
|
+
const out = [];
|
|
555
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
556
|
+
if (k !== "mode") out.push(`"visualImpact.${k}" is not a setting (mode)`);
|
|
557
|
+
else if (v !== "auto" && v !== "on-demand") out.push('"visualImpact.mode" must be "auto" or "on-demand"');
|
|
558
|
+
}
|
|
559
|
+
return out;
|
|
560
|
+
}
|
|
118
561
|
const CONFIG_FILE = "bridge.config.json";
|
|
119
562
|
const DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
|
|
120
563
|
const DEFAULT_TOKEN_PATHS = {
|
|
@@ -251,9 +694,11 @@ function defineConfig(input) {
|
|
|
251
694
|
requireReason: g.override.requireReason ?? DEFAULT_OVERRIDE.requireReason,
|
|
252
695
|
minReasonLength: g.override.minReasonLength ?? DEFAULT_OVERRIDE.minReasonLength
|
|
253
696
|
}
|
|
254
|
-
} : {}
|
|
697
|
+
} : {},
|
|
698
|
+
...g.rights ? { rights: normalizeRights(g.rights) } : {}
|
|
255
699
|
},
|
|
256
700
|
...input.audit ? { audit: { ...input.audit } } : {},
|
|
701
|
+
...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
|
|
257
702
|
...input.integrations ? { integrations: { ...input.integrations } } : {},
|
|
258
703
|
...input.previews ? {
|
|
259
704
|
previews: {
|
|
@@ -360,9 +805,7 @@ function loadConfig(source) {
|
|
|
360
805
|
else {
|
|
361
806
|
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`);
|
|
362
807
|
if (gov.branchPrefix !== void 0 && !isStr(gov.branchPrefix)) problems.push('"governance.branchPrefix" must be a non-empty string');
|
|
363
|
-
|
|
364
|
-
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")))))
|
|
365
|
-
problems.push('"governance.owners" must be { "design": [logins], "tech": [logins] }');
|
|
808
|
+
problems.push(...ownerEntryProblems(gov.owners));
|
|
366
809
|
const ov = gov.override;
|
|
367
810
|
if (ov !== void 0) {
|
|
368
811
|
if (!isObj(ov)) problems.push('"governance.override" must be an object');
|
|
@@ -372,8 +815,10 @@ function loadConfig(source) {
|
|
|
372
815
|
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');
|
|
373
816
|
}
|
|
374
817
|
}
|
|
818
|
+
problems.push(...rightsProblems(gov.rights));
|
|
375
819
|
}
|
|
376
820
|
}
|
|
821
|
+
problems.push(...visualImpactProblems(d.visualImpact));
|
|
377
822
|
const audit = d.audit;
|
|
378
823
|
if (audit !== void 0) {
|
|
379
824
|
if (!isObj(audit)) problems.push('"audit" must be an object');
|
|
@@ -419,7 +864,8 @@ function toConfigJson(config, schema = "./packages/core/bridge.config.schema.jso
|
|
|
419
864
|
cssVar: config.cssVar,
|
|
420
865
|
governance: config.governance,
|
|
421
866
|
...config.audit ? { audit: config.audit } : {},
|
|
422
|
-
...config.previews ? { previews: config.previews } : {}
|
|
867
|
+
...config.previews ? { previews: config.previews } : {},
|
|
868
|
+
...config.visualImpact ? { visualImpact: config.visualImpact } : {}
|
|
423
869
|
};
|
|
424
870
|
}
|
|
425
871
|
class JsonParseError extends Error {
|
|
@@ -2480,7 +2926,7 @@ function parseComponentSpecs(text) {
|
|
|
2480
2926
|
}
|
|
2481
2927
|
const tokenCssVar = (path, prefix = "--", separator = "-") => `${prefix}${path.split(".").join(separator)}`;
|
|
2482
2928
|
export {
|
|
2483
|
-
|
|
2929
|
+
parseContrastChecks as $,
|
|
2484
2930
|
removeToken as A,
|
|
2485
2931
|
moveToken as B,
|
|
2486
2932
|
COMPONENT_SPECS_VERSION as C,
|
|
@@ -2504,117 +2950,159 @@ export {
|
|
|
2504
2950
|
DEFAULT_TOKEN_PATHS as U,
|
|
2505
2951
|
applyTokenEdits as V,
|
|
2506
2952
|
buildHistoryEntry as W,
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2953
|
+
resolveBranchPreviews as X,
|
|
2954
|
+
tokensPath as Y,
|
|
2955
|
+
coerceValue as Z,
|
|
2956
|
+
toConfigJson as _,
|
|
2511
2957
|
assertSafeBranch as a,
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2958
|
+
contrastLevel as a$,
|
|
2959
|
+
checkLayeredPair as a0,
|
|
2960
|
+
classifyRisk as a1,
|
|
2961
|
+
formatChangeset as a2,
|
|
2962
|
+
RISK_LABELS as a3,
|
|
2963
|
+
parseJson as a4,
|
|
2964
|
+
serializeJson as a5,
|
|
2965
|
+
plainEqual as a6,
|
|
2966
|
+
toPlain as a7,
|
|
2967
|
+
getEntry as a8,
|
|
2968
|
+
fromPlain as a9,
|
|
2969
|
+
TeamDirectory as aA,
|
|
2970
|
+
UNRELEASED as aB,
|
|
2971
|
+
VISUAL_CHECK_LABEL as aC,
|
|
2972
|
+
affectedContrast as aD,
|
|
2973
|
+
appForProject as aE,
|
|
2974
|
+
appUrl as aF,
|
|
2975
|
+
approverEntries as aG,
|
|
2976
|
+
approverLists as aH,
|
|
2977
|
+
assignVersions as aI,
|
|
2978
|
+
baseBranchOf as aJ,
|
|
2979
|
+
branchName as aK,
|
|
2980
|
+
branchSlug as aL,
|
|
2981
|
+
canShip as aM,
|
|
2982
|
+
changeKind as aN,
|
|
2983
|
+
changeTable as aO,
|
|
2984
|
+
changedModes as aP,
|
|
2985
|
+
changesetBump as aQ,
|
|
2986
|
+
classifyLayered as aR,
|
|
2987
|
+
clearRawDeep as aS,
|
|
2988
|
+
commitMessage as aT,
|
|
2989
|
+
composeChangeset as aU,
|
|
2990
|
+
composePrBody as aV,
|
|
2991
|
+
composeProposalBody as aW,
|
|
2992
|
+
composite as aX,
|
|
2993
|
+
contextFor as aY,
|
|
2994
|
+
contrastDelta as aZ,
|
|
2995
|
+
contrastDeltaTable as a_,
|
|
2996
|
+
previewLinks as aa,
|
|
2997
|
+
slugify as ab,
|
|
2998
|
+
readPreviewComment as ac,
|
|
2999
|
+
findPreviewComment as ad,
|
|
3000
|
+
previewsSettled as ae,
|
|
3001
|
+
AAA_MIN as af,
|
|
3002
|
+
AA_MIN as ag,
|
|
3003
|
+
BULK_LIMIT as ah,
|
|
3004
|
+
CONTRAST_CELL_MAX as ai,
|
|
3005
|
+
CONTRAST_CHECKS_FILE as aj,
|
|
3006
|
+
DEFAULT_COMPONENT_SPECS_FILE as ak,
|
|
3007
|
+
DEFAULT_GOVERNANCE as al,
|
|
3008
|
+
DEFAULT_OVERRIDE as am,
|
|
3009
|
+
DEFAULT_RIGHTS as an,
|
|
3010
|
+
DEFAULT_VISUAL_IMPACT as ao,
|
|
3011
|
+
JsonParseError as ap,
|
|
3012
|
+
OWNER_GROUPS as aq,
|
|
3013
|
+
PREVIEW_APP_LABELS as ar,
|
|
3014
|
+
PREVIEW_COMMENT_MARKER as as,
|
|
3015
|
+
PREVIEW_PLACEHOLDERS as at,
|
|
3016
|
+
PRIMITIVE_MODE as au,
|
|
3017
|
+
RIGHTS as av,
|
|
3018
|
+
RIGHT_LABEL as aw,
|
|
3019
|
+
RISK_TIER as ax,
|
|
3020
|
+
SPEC_FIELDS as ay,
|
|
3021
|
+
TIER_ORDER as az,
|
|
2576
3022
|
parseChangelog as b,
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
previewUrl as
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
3023
|
+
tarballUrl as b$,
|
|
3024
|
+
contrastPair as b0,
|
|
3025
|
+
contrastRatio as b1,
|
|
3026
|
+
countChanges as b2,
|
|
3027
|
+
defaultContext as b3,
|
|
3028
|
+
defaultTitle as b4,
|
|
3029
|
+
defineConfig as b5,
|
|
3030
|
+
detectLayout as b6,
|
|
3031
|
+
diffLayered as b7,
|
|
3032
|
+
entriesFor as b8,
|
|
3033
|
+
entryLabel as b9,
|
|
3034
|
+
nodesEqual as bA,
|
|
3035
|
+
normalizeRights as bB,
|
|
3036
|
+
overrideKey as bC,
|
|
3037
|
+
ownerEntryProblems as bD,
|
|
3038
|
+
pairKey as bE,
|
|
3039
|
+
parseChangesetText as bF,
|
|
3040
|
+
parseComponentSpecs as bG,
|
|
3041
|
+
parseOwnerEntry as bH,
|
|
3042
|
+
parseVercelComment as bI,
|
|
3043
|
+
planRestore as bJ,
|
|
3044
|
+
planRevert as bK,
|
|
3045
|
+
planToJson as bL,
|
|
3046
|
+
previewComment as bM,
|
|
3047
|
+
previewLinksOf as bN,
|
|
3048
|
+
previewUrl as bO,
|
|
3049
|
+
projectOfLabel as bP,
|
|
3050
|
+
protectedBranchesOf as bQ,
|
|
3051
|
+
renderedColor as bR,
|
|
3052
|
+
resolve as bS,
|
|
3053
|
+
rightsProblems as bT,
|
|
3054
|
+
runLayeredContrast as bU,
|
|
3055
|
+
sameToken as bV,
|
|
3056
|
+
shipPolicy as bW,
|
|
3057
|
+
snapshotUrl as bX,
|
|
3058
|
+
specHash as bY,
|
|
3059
|
+
summarizeTokenChange as bZ,
|
|
3060
|
+
tarballAssetName as b_,
|
|
3061
|
+
expandEntries as ba,
|
|
3062
|
+
expandOwners as bb,
|
|
3063
|
+
fieldChanges as bc,
|
|
3064
|
+
fileModes as bd,
|
|
3065
|
+
filterHistory as be,
|
|
3066
|
+
findToken as bf,
|
|
3067
|
+
findVercelComment as bg,
|
|
3068
|
+
firstParagraph as bh,
|
|
3069
|
+
flattenTokens as bi,
|
|
3070
|
+
formatNumber as bj,
|
|
3071
|
+
formatOwnerEntry as bk,
|
|
3072
|
+
getChild as bl,
|
|
3073
|
+
groupsPhrase as bm,
|
|
3074
|
+
holdersOf as bn,
|
|
3075
|
+
indexByPath as bo,
|
|
3076
|
+
inheritedTypeAt as bp,
|
|
3077
|
+
installCommand as bq,
|
|
3078
|
+
installCommandFromPreviewComment as br,
|
|
3079
|
+
isGuardrail as bs,
|
|
3080
|
+
isTeamEntry as bt,
|
|
3081
|
+
isTokenFile as bu,
|
|
3082
|
+
isVercelComment as bv,
|
|
3083
|
+
kindForMin as bw,
|
|
3084
|
+
layeredImpact as bx,
|
|
3085
|
+
luminance as by,
|
|
3086
|
+
modeLabel as bz,
|
|
2617
3087
|
collectHistory as c,
|
|
3088
|
+
teamKey as c0,
|
|
3089
|
+
teamKeyOf as c1,
|
|
3090
|
+
teamUnreadableMessage as c2,
|
|
3091
|
+
teamsIn as c3,
|
|
3092
|
+
threeWayDiff as c4,
|
|
3093
|
+
titleOf as c5,
|
|
3094
|
+
toChangeSummary as c6,
|
|
3095
|
+
toMap as c7,
|
|
3096
|
+
tokenBump as c8,
|
|
3097
|
+
tokenHistory as c9,
|
|
3098
|
+
tokenNode as ca,
|
|
3099
|
+
unreadableShipTeams as cb,
|
|
3100
|
+
unreadableTeams as cc,
|
|
3101
|
+
valueIn as cd,
|
|
3102
|
+
variantName as ce,
|
|
3103
|
+
versionTimeline as cf,
|
|
3104
|
+
visualImpactProblems as cg,
|
|
3105
|
+
whoCanShipSentence as ch,
|
|
2618
3106
|
buildReport as d,
|
|
2619
3107
|
prNumberFromSubject as e,
|
|
2620
3108
|
CONFIG_FILE as f,
|
|
@@ -2639,4 +3127,4 @@ export {
|
|
|
2639
3127
|
contextLabel as y,
|
|
2640
3128
|
pathId as z
|
|
2641
3129
|
};
|
|
2642
|
-
//# sourceMappingURL=component-spec-
|
|
3130
|
+
//# sourceMappingURL=component-spec-IRFGJ7XK.js.map
|