@bridge-ds/core 0.6.0 → 0.8.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 +69 -0
- package/dist/branch-actions.d.ts +70 -0
- package/dist/combine-branches.d.ts +41 -0
- package/dist/{component-spec-GFVEE6Fc.js → component-spec-SKuywZqr.js} +366 -184
- package/dist/component-spec-SKuywZqr.js.map +1 -0
- package/dist/component-spec.d.ts +114 -0
- package/dist/config.d.ts +4 -0
- package/dist/docs-proposal.d.ts +154 -0
- package/dist/explorations-github.d.ts +2 -0
- package/dist/github-client.d.ts +89 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1280 -298
- package/dist/index.js.map +1 -1
- package/dist/jira.d.ts +15 -0
- package/dist/library-update.d.ts +145 -0
- package/dist/node/component-specs.d.ts +2 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +301 -5
- package/dist/node/index.js.map +1 -1
- package/dist/node/spec-motion.d.ts +66 -0
- package/dist/products.d.ts +65 -0
- package/dist/review-gate.d.ts +251 -0
- package/dist/share-branch.d.ts +85 -0
- package/dist/try-in-product.d.ts +16 -0
- package/package.json +1 -1
- package/dist/component-spec-GFVEE6Fc.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { u as modeDisplayName, w as modesOf$1, x as isMultiMode, T as TIER_NAMES, y as TIER_AXIS, z as findReferenceProblems, A as evaluatePairs, B as contrastScope, D as contextLabel, S as SINGLE_MODE, E as pathId, F as flattenTokens, G as serializeTokenFile, H as removeToken, I as upsertToken, J as threeWayDiff, K as emptyTokenFile, L as parseTokenFile, M as EXT_ID, N as EXT_SCOPES, n as resolve, O as moveToken, P as effectiveScopes, Q as sameScopes, R as defaultScopes, U as readTokenAt, V as modeFile, W as defaultFile, X as DEFAULT_PROTECTED_BRANCHES, a as assertSafeBranch, Y as ProtectedBranchError, C as CONFIG_FILE, l as loadConfig, Z as ConfigError, _ as DEFAULT_TOKEN_PATHS, c as collectHistory, p as packageDirCandidates, b as parseChangelog, r as releasesFromTags, m as mergeReleases, d as buildReport, $ as applyTokenEdits, a0 as buildHistoryEntry, a1 as resolveBranchPreviews, a2 as tokensPath, a3 as coerceValue, a4 as toConfigJson, a5 as parseContrastChecks, a6 as checkLayeredPair, a7 as classifyRisk, a8 as formatChangeset, a9 as RISK_LABELS, aa as parseJson, ab as serializeJson, ac as plainEqual, ad as toPlain, ae as getEntry, af as fromPlain, ag as previewLinks, ah as slugify, ai as readPreviewComment, aj as findPreviewComment, ak as previewsSettled, f as variantKeyOf, al as canShip, am as whoCanShipSentence, an as unreadableShipTeams, g as layoutOptions, j as buildTokenSet } from "./component-spec-SKuywZqr.js";
|
|
2
|
+
import { ao, ap, aq, q, o, ar, as, at, au, av, aw, ax, ay, az, aA, aB, aC, aD, aE, aF, aG, aH, aI, aJ, aK, aL, aM, aN, aO, aP, aQ, aR, aS, aT, aU, aV, aW, aX, aY, aZ, a_, a$, b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, ba, bb, bc, bd, be, bf, bg, bh, k, bi, bj, bk, bl, bm, bn, bo, bp, bq, br, bs, bt, bu, bv, bw, bx, by, bz, bA, bB, bC, h, bD, i, bE, bF, bG, bH, bI, bJ, bK, bL, bM, bN, bO, bP, bQ, bR, bS, bT, bU, bV, bW, bX, bY, bZ, b_, b$, e, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf, cg, ch, s, ci, cj, ck, cl, cm, cn, co, cp, cq, cr, cs, t, ct, cu, cv, v, cw, cx, cy, cz } from "./component-spec-SKuywZqr.js";
|
|
3
3
|
import { a as collectRefs, i as isAlias, b as aliasTarget, v as valuesEqual, p as parseColor, e as rgbaToHex, f as formatValue, s as selectTokenFiles, L as LayeredTokenSet, j as validateLayered } from "./layered-BnbmToGs.js";
|
|
4
|
-
import { D, d, k as k2, l, c, m, n
|
|
4
|
+
import { D, d, k as k2, l, c, m, n, g, h as h2, o as o2, q as q2, r, t as t2, u } from "./layered-BnbmToGs.js";
|
|
5
5
|
const issueKey = (i2) => `${i2.code}|${i2.path ?? ""}|${i2.mode ?? ""}|${i2.code === "file-problem" ? i2.message : ""}`;
|
|
6
6
|
const modeName = (mode) => mode === SINGLE_MODE ? "all modes" : modeDisplayName(mode);
|
|
7
7
|
const isPalette = (t3) => t3.tier === "brand" && t3.path.startsWith("palette.");
|
|
@@ -82,23 +82,23 @@ function validateSet(set, pairs) {
|
|
|
82
82
|
checkValue(set, t3, v2, mode, modeName(mode), issues);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
for (const
|
|
86
|
-
const mode = `${
|
|
87
|
-
const where = `${modeDisplayName(
|
|
88
|
-
const base = set.byPath.get(
|
|
85
|
+
for (const o3 of set.overrides) {
|
|
86
|
+
const mode = `${o3.brand}.${o3.theme}`;
|
|
87
|
+
const where = `${modeDisplayName(o3.brand)} ${o3.theme} (${o3.file})`;
|
|
88
|
+
const base = set.byPath.get(o3.path);
|
|
89
89
|
if (!base || base.tier !== "semantic") {
|
|
90
90
|
issues.push({
|
|
91
91
|
code: "override-orphan",
|
|
92
92
|
severity: "error",
|
|
93
|
-
path:
|
|
93
|
+
path: o3.path,
|
|
94
94
|
mode,
|
|
95
|
-
message: `${
|
|
95
|
+
message: `${o3.file} overrides ${o3.path}, which is not a semantic token. Remove the override or restore the token.`
|
|
96
96
|
});
|
|
97
97
|
continue;
|
|
98
98
|
}
|
|
99
|
-
checkValue(set, { path:
|
|
100
|
-
for (const ref of collectRefs(
|
|
101
|
-
if (!set.byPath.has(ref)) issues.push({ code: "broken-reference", severity: "error", path:
|
|
99
|
+
checkValue(set, { path: o3.path, tier: "semantic", type: base.type }, o3.value, mode, where, issues);
|
|
100
|
+
for (const ref of collectRefs(o3.value)) {
|
|
101
|
+
if (!set.byPath.has(ref)) issues.push({ code: "broken-reference", severity: "error", path: o3.path, mode, message: `${o3.path} points to ${ref}, which does not exist (${where}).` });
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
const axes = { brand: modesOf$1(set.layout, "brand"), theme: modesOf$1(set.layout, "semantic"), density: modesOf$1(set.layout, "density") };
|
|
@@ -141,14 +141,14 @@ function shadowedEdits(set, edits) {
|
|
|
141
141
|
const out = [];
|
|
142
142
|
for (const e2 of edits) {
|
|
143
143
|
if (e2.tier !== "semantic") continue;
|
|
144
|
-
for (const
|
|
145
|
-
if (
|
|
144
|
+
for (const o3 of set.overrides) {
|
|
145
|
+
if (o3.path !== e2.path || !e2.modes.includes(o3.theme)) continue;
|
|
146
146
|
out.push({
|
|
147
147
|
code: "override-shadowed",
|
|
148
148
|
severity: "warning",
|
|
149
149
|
path: e2.path,
|
|
150
|
-
mode: `${
|
|
151
|
-
message: `${e2.path} is overridden for ${modeDisplayName(
|
|
150
|
+
mode: `${o3.brand}.${o3.theme}`,
|
|
151
|
+
message: `${e2.path} is overridden for ${modeDisplayName(o3.brand)} in ${o3.theme} (${o3.file}), so this edit does not change ${modeDisplayName(o3.brand)} ${o3.theme}. Brand overrides stay code-only.`
|
|
152
152
|
});
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -180,9 +180,9 @@ function levenshtein(a, b) {
|
|
|
180
180
|
for (let i2 = 1; i2 <= a.length; i2++) {
|
|
181
181
|
let diag = prev[0];
|
|
182
182
|
prev[0] = i2;
|
|
183
|
-
for (let
|
|
184
|
-
const tmp = prev[
|
|
185
|
-
prev[
|
|
183
|
+
for (let j = 1; j <= b.length; j++) {
|
|
184
|
+
const tmp = prev[j];
|
|
185
|
+
prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i2 - 1] === b[j - 1] ? 0 : 1));
|
|
186
186
|
diag = tmp;
|
|
187
187
|
}
|
|
188
188
|
}
|
|
@@ -221,16 +221,16 @@ function reconcileIds(figma, git) {
|
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
const suggestions = [];
|
|
224
|
-
const unclaimedGit = [...gitById.values()].filter((
|
|
224
|
+
const unclaimedGit = [...gitById.values()].filter((g2) => !claimed.has(g2.id));
|
|
225
225
|
for (const t3 of tokens) {
|
|
226
226
|
if (gitById.has(t3.id)) continue;
|
|
227
227
|
let best;
|
|
228
|
-
for (const
|
|
229
|
-
if (!sameContent(t3,
|
|
230
|
-
const similarity = pathSimilarity(t3.path,
|
|
228
|
+
for (const g2 of unclaimedGit) {
|
|
229
|
+
if (!sameContent(t3, g2)) continue;
|
|
230
|
+
const similarity = pathSimilarity(t3.path, g2.path);
|
|
231
231
|
if (similarity < SIMILARITY_THRESHOLD) continue;
|
|
232
232
|
if (!best || similarity > best.similarity) {
|
|
233
|
-
best = { figmaId: t3.id, gitId:
|
|
233
|
+
best = { figmaId: t3.id, gitId: g2.id, fromPath: g2.path, toPath: t3.path, similarity: Math.round(similarity * 100) / 100 };
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
if (best) suggestions.push(best);
|
|
@@ -389,11 +389,11 @@ function optionsOf(edits) {
|
|
|
389
389
|
const out = [];
|
|
390
390
|
for (const e2 of edits) {
|
|
391
391
|
const l2 = look(e2.token);
|
|
392
|
-
const hit = out.find((
|
|
392
|
+
const hit = out.find((o3) => o3.look === l2);
|
|
393
393
|
if (hit) hit.option.changes.push(e2.change);
|
|
394
394
|
else out.push({ look: l2, option: { changes: [e2.change], token: e2.token } });
|
|
395
395
|
}
|
|
396
|
-
return out.map((
|
|
396
|
+
return out.map((o3) => o3.option);
|
|
397
397
|
}
|
|
398
398
|
function combineConflicts(changes) {
|
|
399
399
|
const out = [];
|
|
@@ -404,8 +404,8 @@ function combineConflicts(changes) {
|
|
|
404
404
|
return out;
|
|
405
405
|
}
|
|
406
406
|
function pickedOption(c10, picks) {
|
|
407
|
-
const
|
|
408
|
-
return
|
|
407
|
+
const n2 = picks[c10.key];
|
|
408
|
+
return n2 === void 0 ? null : c10.options.find((o3) => o3.changes.includes(n2)) ?? null;
|
|
409
409
|
}
|
|
410
410
|
const unpicked = (conflicts, picks) => conflicts.filter((c10) => !pickedOption(c10, picks));
|
|
411
411
|
function combineChanges(latest, changes, picks = {}, opts = {}) {
|
|
@@ -458,16 +458,16 @@ function combineChanges(latest, changes, picks = {}, opts = {}) {
|
|
|
458
458
|
return { files, included: on.map((c10) => c10.number), edits: groups.size, tokens, conflicts, missing, from, problems };
|
|
459
459
|
}
|
|
460
460
|
function encodePicks(picks) {
|
|
461
|
-
return Object.entries(picks).filter(([,
|
|
461
|
+
return Object.entries(picks).filter(([, n2]) => Number.isSafeInteger(n2) && n2 > 0).sort(([a], [b]) => a.localeCompare(b)).map(([key, n2]) => `${n2}:${key}`).join("~");
|
|
462
462
|
}
|
|
463
463
|
function decodePicks(raw) {
|
|
464
464
|
const out = {};
|
|
465
465
|
for (const part of (raw ?? "").split("~")) {
|
|
466
466
|
const i2 = part.indexOf(":");
|
|
467
467
|
if (i2 < 1) continue;
|
|
468
|
-
const
|
|
468
|
+
const n2 = Number(part.slice(0, i2));
|
|
469
469
|
const key = part.slice(i2 + 1);
|
|
470
|
-
if (/^\d{1,7}$/.test(part.slice(0, i2)) &&
|
|
470
|
+
if (/^\d{1,7}$/.test(part.slice(0, i2)) && n2 > 0 && key.includes("#")) out[key] = n2;
|
|
471
471
|
}
|
|
472
472
|
return out;
|
|
473
473
|
}
|
|
@@ -595,7 +595,7 @@ function requiredCheckNames(defaultBranchRuns, pullRequestRuns) {
|
|
|
595
595
|
const names2 = [...new Set(defaultBranchRuns.filter((r2) => !isVercel(r2) && ran(r2)).map((r2) => r2.name))];
|
|
596
596
|
if (!pullRequestRuns?.length) return names2;
|
|
597
597
|
const onPulls = new Set(pullRequestRuns.filter((r2) => !isVercel(r2)).map((r2) => r2.name));
|
|
598
|
-
return names2.filter((
|
|
598
|
+
return names2.filter((n2) => onPulls.has(n2));
|
|
599
599
|
}
|
|
600
600
|
function refPattern(pattern) {
|
|
601
601
|
const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
|
|
@@ -903,12 +903,12 @@ class GitHubClient {
|
|
|
903
903
|
// --- History (read only) ----------------------------------------------------------------------
|
|
904
904
|
/** Commits on a ref, newest first, optionally only those touching `path` (a file or directory). */
|
|
905
905
|
async listCommits(opts = {}) {
|
|
906
|
-
const
|
|
907
|
-
if (opts.sha)
|
|
908
|
-
if (opts.path)
|
|
909
|
-
|
|
910
|
-
if (opts.page)
|
|
911
|
-
const r2 = await this.request("GET", `${this.repoPath}/commits?${
|
|
906
|
+
const q3 = new URLSearchParams();
|
|
907
|
+
if (opts.sha) q3.set("sha", opts.sha);
|
|
908
|
+
if (opts.path) q3.set("path", opts.path);
|
|
909
|
+
q3.set("per_page", String(Math.min(100, opts.perPage ?? 30)));
|
|
910
|
+
if (opts.page) q3.set("page", String(opts.page));
|
|
911
|
+
const r2 = await this.request("GET", `${this.repoPath}/commits?${q3}`);
|
|
912
912
|
return r2.map(toCommitSummary);
|
|
913
913
|
}
|
|
914
914
|
/** One commit: message, author, date and parents. */
|
|
@@ -989,37 +989,37 @@ class GitHubClient {
|
|
|
989
989
|
* Marks a draft pull request ready for review. REST cannot unset `draft`, so this is the GraphQL mutation
|
|
990
990
|
* markPullRequestReadyForReview, with the same token (pull requests: write).
|
|
991
991
|
*/
|
|
992
|
-
async markReadyForReview(
|
|
992
|
+
async markReadyForReview(n2) {
|
|
993
993
|
this.assertToken("Marking a pull request ready for review");
|
|
994
|
-
const pr = await this.request("GET", `${this.repoPath}/pulls/${
|
|
994
|
+
const pr = await this.request("GET", `${this.repoPath}/pulls/${n2}`);
|
|
995
995
|
if (pr.draft === false) return;
|
|
996
|
-
if (!pr.node_id) throw new GitHubError(422, `Pull request #${
|
|
996
|
+
if (!pr.node_id) throw new GitHubError(422, `Pull request #${n2} has no node id: mark it ready for review on GitHub.`);
|
|
997
997
|
const r2 = await this.request("POST", "/graphql", {
|
|
998
998
|
query: "mutation($id: ID!) { markPullRequestReadyForReview(input: { pullRequestId: $id }) { pullRequest { number isDraft } } }",
|
|
999
999
|
variables: { id: pr.node_id }
|
|
1000
1000
|
});
|
|
1001
1001
|
if (r2?.errors?.length) {
|
|
1002
1002
|
const detail = r2.errors.map((e2) => e2.message).filter(Boolean).join(" ");
|
|
1003
|
-
throw new GitHubError(422, `GitHub refused to mark #${
|
|
1003
|
+
throw new GitHubError(422, `GitHub refused to mark #${n2} ready for review${detail ? `: ${detail}` : "."} Use "Ready for review" on GitHub instead.`, detail);
|
|
1004
1004
|
}
|
|
1005
1005
|
}
|
|
1006
1006
|
/**
|
|
1007
1007
|
* Turns an open pull request back into a draft: the GraphQL mutation convertPullRequestToDraft (REST cannot set
|
|
1008
1008
|
* `draft` either), same token. No-op when it is already a draft.
|
|
1009
1009
|
*/
|
|
1010
|
-
async convertToDraft(
|
|
1010
|
+
async convertToDraft(n2) {
|
|
1011
1011
|
this.assertToken("Turning a pull request back into a draft");
|
|
1012
|
-
const pr = await this.request("GET", `${this.repoPath}/pulls/${
|
|
1012
|
+
const pr = await this.request("GET", `${this.repoPath}/pulls/${n2}`);
|
|
1013
1013
|
if (pr.draft === true) return;
|
|
1014
|
-
if (pr.state && pr.state !== "open") throw new GitHubError(422, `Pull request #${
|
|
1015
|
-
if (!pr.node_id) throw new GitHubError(422, `Pull request #${
|
|
1014
|
+
if (pr.state && pr.state !== "open") throw new GitHubError(422, `Pull request #${n2} is ${pr.state}: only an open one can go back to draft.`);
|
|
1015
|
+
if (!pr.node_id) throw new GitHubError(422, `Pull request #${n2} has no node id: convert it to draft on GitHub.`);
|
|
1016
1016
|
const r2 = await this.request("POST", "/graphql", {
|
|
1017
1017
|
query: "mutation($id: ID!) { convertPullRequestToDraft(input: { pullRequestId: $id }) { pullRequest { number isDraft } } }",
|
|
1018
1018
|
variables: { id: pr.node_id }
|
|
1019
1019
|
});
|
|
1020
1020
|
if (r2?.errors?.length) {
|
|
1021
1021
|
const detail = r2.errors.map((e2) => e2.message).filter(Boolean).join(" ");
|
|
1022
|
-
throw new GitHubError(422, `GitHub refused to turn #${
|
|
1022
|
+
throw new GitHubError(422, `GitHub refused to turn #${n2} back into a draft${detail ? `: ${detail}` : "."} Use "Convert to draft" on GitHub instead.`, detail);
|
|
1023
1023
|
}
|
|
1024
1024
|
}
|
|
1025
1025
|
async createPullRequest(input) {
|
|
@@ -1068,23 +1068,23 @@ class GitHubClient {
|
|
|
1068
1068
|
}
|
|
1069
1069
|
/** Issues (not pull requests) with a label, newest first. */
|
|
1070
1070
|
async listIssues(opts = {}) {
|
|
1071
|
-
const
|
|
1072
|
-
if (opts.label)
|
|
1073
|
-
const r2 = await this.request("GET", `${this.repoPath}/issues?${
|
|
1071
|
+
const q3 = new URLSearchParams({ state: opts.state ?? "all", per_page: String(opts.perPage ?? 50), sort: "created", direction: "desc" });
|
|
1072
|
+
if (opts.label) q3.set("labels", opts.label);
|
|
1073
|
+
const r2 = await this.request("GET", `${this.repoPath}/issues?${q3}`);
|
|
1074
1074
|
return r2.filter((i2) => !i2.pull_request).map(toIssue);
|
|
1075
1075
|
}
|
|
1076
|
-
async getIssue(
|
|
1077
|
-
return toIssue(await this.request("GET", `${this.repoPath}/issues/${
|
|
1076
|
+
async getIssue(n2) {
|
|
1077
|
+
return toIssue(await this.request("GET", `${this.repoPath}/issues/${n2}`));
|
|
1078
1078
|
}
|
|
1079
1079
|
/** Edits an issue: its body, or closes it with a reason ("not_planned" for a declined request). */
|
|
1080
|
-
async updateIssue(
|
|
1080
|
+
async updateIssue(n2, patch) {
|
|
1081
1081
|
this.assertToken("Editing an issue");
|
|
1082
|
-
return toIssue(await this.request("PATCH", `${this.repoPath}/issues/${
|
|
1082
|
+
return toIssue(await this.request("PATCH", `${this.repoPath}/issues/${n2}`, patch));
|
|
1083
1083
|
}
|
|
1084
1084
|
/** Adds people to an issue's assignees (GitHub ignores logins that cannot be assigned: the result says who is). */
|
|
1085
|
-
async addAssignees(
|
|
1085
|
+
async addAssignees(n2, logins) {
|
|
1086
1086
|
this.assertToken("Assigning an issue");
|
|
1087
|
-
const r2 = await this.request("POST", `${this.repoPath}/issues/${
|
|
1087
|
+
const r2 = await this.request("POST", `${this.repoPath}/issues/${n2}/assignees`, { assignees: logins });
|
|
1088
1088
|
return (r2.assignees ?? []).map((a) => a.login);
|
|
1089
1089
|
}
|
|
1090
1090
|
/** People with at least `permission` on the repository (reading the list needs push access). First 100. */
|
|
@@ -1151,8 +1151,8 @@ class GitHubClient {
|
|
|
1151
1151
|
/** Text of one file at a ref (branch, tag or sha), or undefined when it does not exist. */
|
|
1152
1152
|
async getFileText(path, ref) {
|
|
1153
1153
|
try {
|
|
1154
|
-
const
|
|
1155
|
-
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${
|
|
1154
|
+
const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
1155
|
+
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
|
|
1156
1156
|
if (r2.type !== "file") return void 0;
|
|
1157
1157
|
if (r2.encoding === "base64" && r2.content !== void 0) return base64ToUtf8(r2.content);
|
|
1158
1158
|
return r2.content;
|
|
@@ -1167,8 +1167,8 @@ class GitHubClient {
|
|
|
1167
1167
|
*/
|
|
1168
1168
|
async getFile(path, ref) {
|
|
1169
1169
|
try {
|
|
1170
|
-
const
|
|
1171
|
-
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${
|
|
1170
|
+
const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
1171
|
+
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
|
|
1172
1172
|
if (r2.type !== "file") return void 0;
|
|
1173
1173
|
const text = r2.encoding === "base64" && r2.content !== void 0 ? base64ToUtf8(r2.content) : r2.content ?? "";
|
|
1174
1174
|
return { text, sha: r2.sha };
|
|
@@ -1183,8 +1183,8 @@ class GitHubClient {
|
|
|
1183
1183
|
*/
|
|
1184
1184
|
async getFileBase64(path, ref) {
|
|
1185
1185
|
try {
|
|
1186
|
-
const
|
|
1187
|
-
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${
|
|
1186
|
+
const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
1187
|
+
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
|
|
1188
1188
|
if (r2.type !== "file" || r2.encoding !== "base64" || !r2.content) return void 0;
|
|
1189
1189
|
return { base64: r2.content.replace(/\s+/g, ""), size: r2.size ?? 0 };
|
|
1190
1190
|
} catch (err) {
|
|
@@ -1195,8 +1195,8 @@ class GitHubClient {
|
|
|
1195
1195
|
/** Entries of one folder at a ref (contents API), or an empty list when the folder does not exist. */
|
|
1196
1196
|
async listDirectory(path, ref) {
|
|
1197
1197
|
try {
|
|
1198
|
-
const
|
|
1199
|
-
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${
|
|
1198
|
+
const q3 = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
1199
|
+
const r2 = await this.request("GET", `${this.repoPath}/contents/${encodeRef(path)}${q3}`);
|
|
1200
1200
|
if (!Array.isArray(r2)) return [];
|
|
1201
1201
|
return r2.map((e2) => ({ name: e2.name, path: e2.path, type: e2.type, sha: e2.sha, size: e2.size ?? 0 }));
|
|
1202
1202
|
} catch (err) {
|
|
@@ -1293,25 +1293,25 @@ class GitHubClient {
|
|
|
1293
1293
|
})
|
|
1294
1294
|
);
|
|
1295
1295
|
}
|
|
1296
|
-
async getPullRequest(
|
|
1297
|
-
const r2 = await this.request("GET", `${this.repoPath}/pulls/${
|
|
1298
|
-
return { ...toSummary(r2), body: r2.body ?? "", mergeable: r2.mergeable ?? null };
|
|
1296
|
+
async getPullRequest(n2) {
|
|
1297
|
+
const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}`);
|
|
1298
|
+
return { ...toSummary(r2), body: r2.body ?? "", mergeable: r2.mergeable ?? null, requestedReviewers: (r2.requested_reviewers ?? []).map((x) => x.login) };
|
|
1299
1299
|
}
|
|
1300
|
-
async listPullRequestFiles(
|
|
1301
|
-
const r2 = await this.request("GET", `${this.repoPath}/pulls/${
|
|
1300
|
+
async listPullRequestFiles(n2) {
|
|
1301
|
+
const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}/files?per_page=100`);
|
|
1302
1302
|
return r2.map((f) => ({ filename: f.filename, status: f.status, additions: f.additions, deletions: f.deletions }));
|
|
1303
1303
|
}
|
|
1304
|
-
async updatePullRequest(
|
|
1305
|
-
await this.request("PATCH", `${this.repoPath}/pulls/${
|
|
1304
|
+
async updatePullRequest(n2, patch) {
|
|
1305
|
+
await this.request("PATCH", `${this.repoPath}/pulls/${n2}`, patch);
|
|
1306
1306
|
}
|
|
1307
|
-
async listReviews(
|
|
1308
|
-
const r2 = await this.request("GET", `${this.repoPath}/pulls/${
|
|
1307
|
+
async listReviews(n2) {
|
|
1308
|
+
const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}/reviews`);
|
|
1309
1309
|
return r2.map((x) => ({ id: x.id, user: x.user?.login, state: x.state, body: x.body ?? "", ...x.submitted_at ? { submittedAt: x.submitted_at } : {} }));
|
|
1310
1310
|
}
|
|
1311
1311
|
/** Submit a review. `COMMENT` works for any collaborator; approving your own PR is refused by GitHub. */
|
|
1312
|
-
async createReview(
|
|
1312
|
+
async createReview(n2, input) {
|
|
1313
1313
|
this.assertToken("Reviewing a pull request");
|
|
1314
|
-
const r2 = await this.request("POST", `${this.repoPath}/pulls/${
|
|
1314
|
+
const r2 = await this.request("POST", `${this.repoPath}/pulls/${n2}/reviews`, input);
|
|
1315
1315
|
return { id: r2.id, user: r2.user?.login, state: r2.state, body: r2.body ?? "" };
|
|
1316
1316
|
}
|
|
1317
1317
|
async listCheckRuns(ref) {
|
|
@@ -1372,6 +1372,100 @@ class GitHubClient {
|
|
|
1372
1372
|
async removeLabel(issueNumber, label) {
|
|
1373
1373
|
await this.request("DELETE", `${this.repoPath}/issues/${issueNumber}/labels/${encodeURIComponent(label)}`);
|
|
1374
1374
|
}
|
|
1375
|
+
// --- branch actions shared by Studio, the MCP server and the CLI (branch-actions.ts) ---
|
|
1376
|
+
/** The account the token belongs to (GET /user). Needs a token. */
|
|
1377
|
+
async getViewer() {
|
|
1378
|
+
this.assertToken("Reading who you are on GitHub");
|
|
1379
|
+
const r2 = await this.request("GET", "/user");
|
|
1380
|
+
return { login: r2.login };
|
|
1381
|
+
}
|
|
1382
|
+
/** The repository's merge settings and the viewer's permissions (unknown settings count as allowed). */
|
|
1383
|
+
async getMergeSettings() {
|
|
1384
|
+
return this.request("GET", this.repoPath);
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Merges a pull request, only at `sha` (GitHub refuses when the branch moved meanwhile). The repository's branch
|
|
1388
|
+
* rules still apply: GitHub refuses a merge they block, whatever Bridge says.
|
|
1389
|
+
*/
|
|
1390
|
+
async mergePullRequest(n2, input) {
|
|
1391
|
+
this.assertToken("Merging a branch");
|
|
1392
|
+
const r2 = await this.request("PUT", `${this.repoPath}/pulls/${n2}/merge`, {
|
|
1393
|
+
merge_method: input.method,
|
|
1394
|
+
sha: input.sha,
|
|
1395
|
+
...input.commitMessage !== void 0 && input.method !== "rebase" ? { commit_message: input.commitMessage } : {}
|
|
1396
|
+
});
|
|
1397
|
+
return { sha: r2?.sha ?? "", merged: r2?.merged ?? true };
|
|
1398
|
+
}
|
|
1399
|
+
/** GitHub's "Update branch": merges the base into the pull request, only when its head is still `expectedHeadSha`. */
|
|
1400
|
+
async updatePullRequestBranch(n2, expectedHeadSha) {
|
|
1401
|
+
this.assertToken("Updating a branch from main");
|
|
1402
|
+
await this.request("PUT", `${this.repoPath}/pulls/${n2}/update-branch`, { expected_head_sha: expectedHeadSha });
|
|
1403
|
+
}
|
|
1404
|
+
/** Posts a comment on an issue or pull request and returns its id and link. */
|
|
1405
|
+
async createIssueComment(n2, body) {
|
|
1406
|
+
this.assertToken("Commenting");
|
|
1407
|
+
const r2 = await this.request("POST", `${this.repoPath}/issues/${n2}/comments`, { body });
|
|
1408
|
+
return { id: r2.id, url: r2.html_url };
|
|
1409
|
+
}
|
|
1410
|
+
/** Replaces the text of a comment. */
|
|
1411
|
+
async updateIssueComment(id, body) {
|
|
1412
|
+
this.assertToken("Editing a comment");
|
|
1413
|
+
await this.request("PATCH", `${this.repoPath}/issues/comments/${id}`, { body });
|
|
1414
|
+
}
|
|
1415
|
+
/** Submits a review on a given commit (the head the reviewer saw); the body may be empty for an approval. */
|
|
1416
|
+
async submitReview(n2, input) {
|
|
1417
|
+
this.assertToken("Reviewing a branch");
|
|
1418
|
+
const r2 = await this.request("POST", `${this.repoPath}/pulls/${n2}/reviews`, {
|
|
1419
|
+
event: input.event,
|
|
1420
|
+
...input.body ? { body: input.body } : {},
|
|
1421
|
+
...input.commitId ? { commit_id: input.commitId } : {}
|
|
1422
|
+
});
|
|
1423
|
+
return { id: r2.id, user: r2.user?.login, state: r2.state, body: r2.body ?? "" };
|
|
1424
|
+
}
|
|
1425
|
+
/** Line comments of a pull request (review threads), oldest first. */
|
|
1426
|
+
async listReviewComments(n2) {
|
|
1427
|
+
const r2 = await this.request(
|
|
1428
|
+
"GET",
|
|
1429
|
+
`${this.repoPath}/pulls/${n2}/comments?per_page=100`
|
|
1430
|
+
);
|
|
1431
|
+
return r2.map((c10) => ({ id: c10.id, body: c10.body ?? "", author: c10.user?.login ?? "unknown", path: c10.path, line: c10.line ?? null, inReplyTo: c10.in_reply_to_id ?? null, url: c10.html_url, createdAt: c10.created_at }));
|
|
1432
|
+
}
|
|
1433
|
+
/** Asks these people to review a pull request. */
|
|
1434
|
+
async requestReviewers(n2, logins) {
|
|
1435
|
+
this.assertToken("Asking for a review");
|
|
1436
|
+
await this.request("POST", `${this.repoPath}/pulls/${n2}/requested_reviewers`, { reviewers: logins });
|
|
1437
|
+
}
|
|
1438
|
+
/** Takes these people off the requested reviewers. */
|
|
1439
|
+
async removeRequestedReviewers(n2, logins) {
|
|
1440
|
+
this.assertToken("Changing the requested reviewers");
|
|
1441
|
+
await this.request("DELETE", `${this.repoPath}/pulls/${n2}/requested_reviewers`, { reviewers: logins });
|
|
1442
|
+
}
|
|
1443
|
+
/** Replies in the thread of a line comment. */
|
|
1444
|
+
async replyToReviewComment(n2, commentId, body) {
|
|
1445
|
+
this.assertToken("Replying to a comment");
|
|
1446
|
+
const r2 = await this.request("POST", `${this.repoPath}/pulls/${n2}/comments/${commentId}/replies`, { body });
|
|
1447
|
+
return { id: r2.id, url: r2.html_url };
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* What `head` adds on top of `base`, with what library sync needs: the shared start (merge base), the base
|
|
1451
|
+
* commit and its tree, how far behind, and the files with their previous names.
|
|
1452
|
+
*/
|
|
1453
|
+
async compareDetailed(base, head) {
|
|
1454
|
+
const r2 = await this.request("GET", `${this.repoPath}/compare/${encodeRef(base)}...${encodeRef(head)}`);
|
|
1455
|
+
return {
|
|
1456
|
+
behindBy: r2.behind_by,
|
|
1457
|
+
aheadBy: r2.ahead_by,
|
|
1458
|
+
mergeBase: r2.merge_base_commit?.sha ?? null,
|
|
1459
|
+
baseSha: r2.base_commit?.sha ?? null,
|
|
1460
|
+
baseTree: r2.base_commit?.commit?.tree?.sha ?? null,
|
|
1461
|
+
files: (r2.files ?? []).map((f) => ({ filename: f.filename, status: f.status, ...f.previous_filename ? { previous_filename: f.previous_filename } : {} }))
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
/** A tree on top of `baseTree` from entries with their own file mode (blob sha, inline content, or sha null to delete). */
|
|
1465
|
+
async createTreeEntries(baseTree, entries) {
|
|
1466
|
+
const r2 = await this.request("POST", `${this.repoPath}/git/trees`, { base_tree: baseTree, tree: entries.map((e2) => ({ ...e2, type: "blob" })) }, true);
|
|
1467
|
+
return r2.sha;
|
|
1468
|
+
}
|
|
1375
1469
|
}
|
|
1376
1470
|
function toCommitSummary(c10) {
|
|
1377
1471
|
return {
|
|
@@ -1515,7 +1609,7 @@ function importsIn(text) {
|
|
|
1515
1609
|
for (const m2 of text.matchAll(IMPORT_RE)) out.add(m2[1] ?? m2[2]);
|
|
1516
1610
|
return [...out];
|
|
1517
1611
|
}
|
|
1518
|
-
const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1612
|
+
const norm$1 = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1519
1613
|
const isStory = (p) => /\.stories\.[cm]?[jt]sx?$/.test(p);
|
|
1520
1614
|
const isTest = (p) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(p);
|
|
1521
1615
|
const isCss = (p) => /\.(css|scss|less)$/.test(p);
|
|
@@ -1533,14 +1627,14 @@ function componentOf(from, spec, componentsDir) {
|
|
|
1533
1627
|
return abs.slice(componentsDir.length + 1).split("/")[0] || null;
|
|
1534
1628
|
}
|
|
1535
1629
|
function componentForPath(path, componentsDir) {
|
|
1536
|
-
const dir = norm(componentsDir);
|
|
1537
|
-
const p = norm(path);
|
|
1630
|
+
const dir = norm$1(componentsDir);
|
|
1631
|
+
const p = norm$1(path);
|
|
1538
1632
|
if (!p.startsWith(`${dir}/`)) return null;
|
|
1539
1633
|
const rest = p.slice(dir.length + 1).split("/");
|
|
1540
1634
|
return rest.length >= 2 ? rest[0] : null;
|
|
1541
1635
|
}
|
|
1542
1636
|
function buildUsageIndex(files, componentsDir) {
|
|
1543
|
-
const dir = norm(componentsDir);
|
|
1637
|
+
const dir = norm$1(componentsDir);
|
|
1544
1638
|
const modules = {};
|
|
1545
1639
|
const vars = /* @__PURE__ */ new Map();
|
|
1546
1640
|
const imports = /* @__PURE__ */ new Map();
|
|
@@ -1548,7 +1642,7 @@ function buildUsageIndex(files, componentsDir) {
|
|
|
1548
1642
|
const globalVars = {};
|
|
1549
1643
|
const get = (m2, k3) => m2.get(k3) ?? m2.set(k3, /* @__PURE__ */ new Set()).get(k3);
|
|
1550
1644
|
for (const f of files) {
|
|
1551
|
-
const path = norm(f.path);
|
|
1645
|
+
const path = norm$1(f.path);
|
|
1552
1646
|
const id = componentForPath(path, dir);
|
|
1553
1647
|
if (!id) {
|
|
1554
1648
|
if (isCss(path)) {
|
|
@@ -1592,7 +1686,7 @@ function impactedModules(index, input) {
|
|
|
1592
1686
|
for (const v2 of changedVars) for (const id of index.byVar[v2] ?? []) add(id, `uses ${v2}`, false);
|
|
1593
1687
|
for (const f of input.files ?? []) {
|
|
1594
1688
|
const id = componentForPath(f, index.componentsDir);
|
|
1595
|
-
if (id && index.modules[id]) add(id, `file changed: ${norm(f)}`, false);
|
|
1689
|
+
if (id && index.modules[id]) add(id, `file changed: ${norm$1(f)}`, false);
|
|
1596
1690
|
}
|
|
1597
1691
|
const renderedBy = /* @__PURE__ */ new Map();
|
|
1598
1692
|
for (const m2 of Object.values(index.modules)) for (const d2 of m2.imports) (renderedBy.get(d2) ?? renderedBy.set(d2, []).get(d2)).push(m2.id);
|
|
@@ -1605,7 +1699,7 @@ function impactedModules(index, input) {
|
|
|
1605
1699
|
if (!known) queue.push(parent);
|
|
1606
1700
|
}
|
|
1607
1701
|
}
|
|
1608
|
-
const globalHits = Object.entries(index.globalVars).map(([file, vs]) => ({ file, vars: vs.filter((v2) => changedVars.has(v2)) })).filter((
|
|
1702
|
+
const globalHits = Object.entries(index.globalVars).map(([file, vs]) => ({ file, vars: vs.filter((v2) => changedVars.has(v2)) })).filter((g2) => g2.vars.length > 0);
|
|
1609
1703
|
const modules = [...reasons.entries()].map(([id, r2]) => ({ id, reasons: r2.reasons, indirect: r2.indirect })).sort((a, b) => Number(a.indirect) - Number(b.indirect) || a.id.localeCompare(b.id));
|
|
1610
1704
|
return { modules, globalHits };
|
|
1611
1705
|
}
|
|
@@ -1708,8 +1802,8 @@ async function loadGitHubHistory(client, config, opts = {}) {
|
|
|
1708
1802
|
const limit = opts.limit ?? 30;
|
|
1709
1803
|
const backend = gitHubHistoryBackend(client, config);
|
|
1710
1804
|
let listed = 0;
|
|
1711
|
-
const counted = { ...backend, listCommits: async (
|
|
1712
|
-
const r2 = await backend.listCommits(
|
|
1805
|
+
const counted = { ...backend, listCommits: async (o3) => {
|
|
1806
|
+
const r2 = await backend.listCommits(o3);
|
|
1713
1807
|
listed = r2.length;
|
|
1714
1808
|
return r2;
|
|
1715
1809
|
} };
|
|
@@ -1721,10 +1815,10 @@ async function loadGitHubHistory(client, config, opts = {}) {
|
|
|
1721
1815
|
const files = await backend.tree(head).catch(() => []);
|
|
1722
1816
|
for (const dir of packageDirCandidates(config)) {
|
|
1723
1817
|
const p = (f) => dir ? `${dir}/${f}` : f;
|
|
1724
|
-
const
|
|
1818
|
+
const cl2 = files.find((e2) => e2.path === p("CHANGELOG.md"));
|
|
1725
1819
|
const pkg = files.find((e2) => e2.path === p("package.json"));
|
|
1726
|
-
if (!
|
|
1727
|
-
if (
|
|
1820
|
+
if (!cl2 && !pkg) continue;
|
|
1821
|
+
if (cl2) changelog = parseChangelog(await client.getBlobText(cl2.sha));
|
|
1728
1822
|
if (pkg) {
|
|
1729
1823
|
try {
|
|
1730
1824
|
const json = JSON.parse(await client.getBlobText(pkg.sha));
|
|
@@ -1846,10 +1940,10 @@ function oklch(L, C, h3) {
|
|
|
1846
1940
|
return lin.map(encode);
|
|
1847
1941
|
}
|
|
1848
1942
|
function toOklch(rgb) {
|
|
1849
|
-
const [r2,
|
|
1850
|
-
const l2 = Math.cbrt(0.4122214708 * r2 + 0.5363325363 *
|
|
1851
|
-
const m2 = Math.cbrt(0.2119034982 * r2 + 0.6806995451 *
|
|
1852
|
-
const s2 = Math.cbrt(0.0883024619 * r2 + 0.2817188376 *
|
|
1943
|
+
const [r2, g2, b] = rgb.map(decode);
|
|
1944
|
+
const l2 = Math.cbrt(0.4122214708 * r2 + 0.5363325363 * g2 + 0.0514459929 * b);
|
|
1945
|
+
const m2 = Math.cbrt(0.2119034982 * r2 + 0.6806995451 * g2 + 0.1073969566 * b);
|
|
1946
|
+
const s2 = Math.cbrt(0.0883024619 * r2 + 0.2817188376 * g2 + 0.6299787005 * b);
|
|
1853
1947
|
const L = 0.2104542553 * l2 + 0.793617785 * m2 - 0.0040720468 * s2;
|
|
1854
1948
|
const A = 1.9779984951 * l2 - 2.428592205 * m2 + 0.4505937099 * s2;
|
|
1855
1949
|
const B = 0.0259040371 * l2 + 0.7827717662 * m2 - 0.808675766 * s2;
|
|
@@ -1968,8 +2062,8 @@ function rampFor(input, pinStep, kind) {
|
|
|
1968
2062
|
const hex = String(input).trim();
|
|
1969
2063
|
if (!isHex(hex)) throw new Error(`"${hex}" is not a color. Use #rrggbb or a hue in degrees (0 to 360).`);
|
|
1970
2064
|
if (kind === "neutral") {
|
|
1971
|
-
const
|
|
1972
|
-
return { ramp:
|
|
2065
|
+
const n2 = tintedNeutral(hex);
|
|
2066
|
+
return { ramp: n2.ramp, anchor: `${hex.toUpperCase()} (hue ${Math.round(n2.hue)})` };
|
|
1973
2067
|
}
|
|
1974
2068
|
const a = anchoredRamp(hex, pinStep);
|
|
1975
2069
|
const issue = a.distance > PIN_TOLERANCE ? {
|
|
@@ -2183,8 +2277,8 @@ function planNewBrand(source, input) {
|
|
|
2183
2277
|
const src = fill(layers.override, { brand: from, [layers.overrideAxis]: v2 });
|
|
2184
2278
|
const text2 = files.get(src);
|
|
2185
2279
|
if (!text2) continue;
|
|
2186
|
-
const
|
|
2187
|
-
const out = { ...
|
|
2280
|
+
const o3 = JSON.parse(text2);
|
|
2281
|
+
const out = { ...o3, $description: `${name} overrides on top of semantic/${v2}, copied from ${brandDisplayName(from)}.` };
|
|
2188
2282
|
const dst = fill(layers.override, { brand: slug, [layers.overrideAxis]: v2 });
|
|
2189
2283
|
nextFiles.set(dst, `${JSON.stringify(out, null, 2)}
|
|
2190
2284
|
`);
|
|
@@ -2233,10 +2327,10 @@ function planNewBrand(source, input) {
|
|
|
2233
2327
|
...created.map((f) => ({ path: tokensPath(config, f), action: "create", content: nextFiles.get(f) })),
|
|
2234
2328
|
{ path: CONFIG_FILE, action: "modify", content: configText }
|
|
2235
2329
|
];
|
|
2236
|
-
const
|
|
2237
|
-
const changeset =
|
|
2238
|
-
path: `${
|
|
2239
|
-
content: formatChangeset(
|
|
2330
|
+
const cs2 = config.changesets ?? (config.changeset ? { dir: ".changeset", package: config.changeset.package } : void 0);
|
|
2331
|
+
const changeset = cs2 ? {
|
|
2332
|
+
path: `${cs2.dir}/bridge-brand-${slug}.md`,
|
|
2333
|
+
content: formatChangeset(cs2.package, "minor", `Add the ${name} brand (\`${slug}\`), started from ${blank ? "a blank brand" : brandDisplayName(from)}.`, `- ${created.map((f) => `\`${tokensPath(config, f)}\``).join("\n- ")}
|
|
2240
2334
|
- \`${CONFIG_FILE}\`: modes.brand gains "${slug}", so the token build and the contrast gate cover it.`)
|
|
2241
2335
|
} : void 0;
|
|
2242
2336
|
if (changeset) planned.push({ path: changeset.path, action: "create", content: changeset.content });
|
|
@@ -2311,7 +2405,7 @@ function newBrandSection(plan) {
|
|
|
2311
2405
|
}
|
|
2312
2406
|
function composeNewBrandBody(plan, opts) {
|
|
2313
2407
|
const t3 = plan.risk.tier;
|
|
2314
|
-
const box = (
|
|
2408
|
+
const box = (n2) => `- ${n2 === t3 ? "[x]" : "[ ]"} ${RISK_LABELS[n2]}`;
|
|
2315
2409
|
const errors = plan.issues.filter((i2) => i2.level === "error");
|
|
2316
2410
|
const warnings = plan.issues.filter((i2) => i2.level === "warning");
|
|
2317
2411
|
return [
|
|
@@ -2519,9 +2613,9 @@ function formatDocsJson(d2) {
|
|
|
2519
2613
|
function patchNode(old, next) {
|
|
2520
2614
|
if (plainEqual(toPlain(old), next)) return old;
|
|
2521
2615
|
if (old.kind === "object" && next && typeof next === "object" && !Array.isArray(next)) {
|
|
2522
|
-
const
|
|
2523
|
-
const kept = old.entries.filter((e2) =>
|
|
2524
|
-
const added = Object.keys(
|
|
2616
|
+
const n2 = next;
|
|
2617
|
+
const kept = old.entries.filter((e2) => n2[e2.key] !== void 0).map((e2) => ({ key: e2.key, value: patchNode(e2.value, n2[e2.key]) }));
|
|
2618
|
+
const added = Object.keys(n2).filter((k3) => n2[k3] !== void 0 && !getEntry(old, k3)).map((k3) => ({ key: k3, value: fromPlain(n2[k3], void 0, old.inline) }));
|
|
2525
2619
|
return { kind: "object", inline: old.inline, entries: [...kept, ...added] };
|
|
2526
2620
|
}
|
|
2527
2621
|
if (old.kind === "array" && Array.isArray(next)) {
|
|
@@ -2550,7 +2644,7 @@ function cleanDraftText(text) {
|
|
|
2550
2644
|
const values = (s2) => (s2 ?? "").split(", ").map((x) => x.trim()).filter(Boolean);
|
|
2551
2645
|
const article = (word) => /^[aeiou]/i.test(word) ? "an" : "a";
|
|
2552
2646
|
const andList = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
2553
|
-
const plural = (
|
|
2647
|
+
const plural$1 = (n2, word, many = `${word}s`) => `${n2} ${n2 === 1 ? word : many}`;
|
|
2554
2648
|
const capital = (s2) => s2.charAt(0).toUpperCase() + s2.slice(1);
|
|
2555
2649
|
const lower = (s2) => s2.charAt(0).toLowerCase() + s2.slice(1);
|
|
2556
2650
|
function propShape(s2) {
|
|
@@ -2597,7 +2691,7 @@ function firstChange(component, kind, c10, all) {
|
|
|
2597
2691
|
case "new-component": {
|
|
2598
2692
|
const axes = all.filter((x) => x.field.startsWith("Variant "));
|
|
2599
2693
|
const props = all.filter((x) => x.field.startsWith("Prop "));
|
|
2600
|
-
const parts = [axes.length ? plural(axes.length, "variant axis", "variant axes") : "", props.length ? plural(props.length, "prop") : ""].filter(Boolean);
|
|
2694
|
+
const parts = [axes.length ? plural$1(axes.length, "variant axis", "variant axes") : "", props.length ? plural$1(props.length, "prop") : ""].filter(Boolean);
|
|
2601
2695
|
return {
|
|
2602
2696
|
title: `Add the ${component} component`,
|
|
2603
2697
|
summary: `${component} is not in code yet.${parts.length ? ` The Figma component has ${andList(parts)}.` : ""}`
|
|
@@ -2614,7 +2708,7 @@ function firstChange(component, kind, c10, all) {
|
|
|
2614
2708
|
if (!before.length) return { title: `Add a ${axis} variant axis`, summary: `${component} gets a new variant axis, ${axis}, with ${after.length === 1 ? "the value" : "the values"} ${andList(after)}.` };
|
|
2615
2709
|
if (!after.length) return { title: `Remove the ${axis} variant axis`, summary: `${component} no longer has the ${axis} variant axis (${andList(before)}).` };
|
|
2616
2710
|
if (added.length === 1 && !removed.length) return { title: `Add ${article(added[0])} ${added[0]} ${word}`, summary: `${component} gets a new ${axis} value, ${added[0]}.` };
|
|
2617
|
-
if (added.length && !removed.length) return { title: `Add ${andList(added)} to ${axis}`, summary: `${component} gets ${plural(added.length, `new ${axis} value`)}: ${andList(added)}.` };
|
|
2711
|
+
if (added.length && !removed.length) return { title: `Add ${andList(added)} to ${axis}`, summary: `${component} gets ${plural$1(added.length, `new ${axis} value`)}: ${andList(added)}.` };
|
|
2618
2712
|
if (removed.length === 1 && !added.length) return { title: `Remove the ${removed[0]} ${word}`, summary: `${component} no longer has the ${axis} value ${removed[0]}.` };
|
|
2619
2713
|
if (removed.length && !added.length) return { title: `Remove ${andList(removed)} from ${axis}`, summary: `${component} no longer has the ${axis} values ${andList(removed)}.` };
|
|
2620
2714
|
return { title: `Change the ${axis} values`, summary: `${axis} on ${component} goes from ${andList(before)} to ${andList(after)}.` };
|
|
@@ -2666,7 +2760,7 @@ function draftRequestText(component, kind, changes) {
|
|
|
2666
2760
|
if (!changes.length && kind !== "new-component") return { title: "", summary: "" };
|
|
2667
2761
|
const d2 = firstChange(name, kind, changes[0], changes);
|
|
2668
2762
|
const rest = kind === "new-component" ? 0 : changes.length - 1;
|
|
2669
|
-
const summary = rest > 0 ? `${d2.summary} ${capital(plural(rest, "other change"))} ${rest === 1 ? "is" : "are"} listed below.` : d2.summary;
|
|
2763
|
+
const summary = rest > 0 ? `${d2.summary} ${capital(plural$1(rest, "other change"))} ${rest === 1 ? "is" : "are"} listed below.` : d2.summary;
|
|
2670
2764
|
return { title: cleanDraftText(d2.title), summary: cleanDraftText(summary) };
|
|
2671
2765
|
}
|
|
2672
2766
|
const DRAFT_LINE_MAX = 120;
|
|
@@ -2719,7 +2813,7 @@ function draftChangeLine(kind, changes) {
|
|
|
2719
2813
|
if (kind === "new-component") {
|
|
2720
2814
|
const axes = changes.filter((x) => x.field.startsWith("Variant ")).length;
|
|
2721
2815
|
const props = changes.filter((x) => x.field.startsWith("Prop ")).length;
|
|
2722
|
-
const parts = [axes ? plural(axes, "variant axis", "variant axes") : "", props ? plural(props, "prop") : ""].filter(Boolean);
|
|
2816
|
+
const parts = [axes ? plural$1(axes, "variant axis", "variant axes") : "", props ? plural$1(props, "prop") : ""].filter(Boolean);
|
|
2723
2817
|
return `New component${parts.length ? ` with ${andList(parts)}` : ""}, not in code yet`;
|
|
2724
2818
|
}
|
|
2725
2819
|
if (!changes.length) return "";
|
|
@@ -2744,7 +2838,7 @@ function pushChangeRows(entries) {
|
|
|
2744
2838
|
return { field: renamed ? `${before.path} -> ${after.path}` : pathOf(e2), before: show(before), after: show(after) };
|
|
2745
2839
|
});
|
|
2746
2840
|
}
|
|
2747
|
-
function names(paths) {
|
|
2841
|
+
function names$1(paths) {
|
|
2748
2842
|
if (paths.length <= 3) return andList(paths);
|
|
2749
2843
|
return `${paths.length} tokens, including ${andList(paths.slice(0, 2))}`;
|
|
2750
2844
|
}
|
|
@@ -2761,15 +2855,15 @@ function draftPushDescription(entries, components = []) {
|
|
|
2761
2855
|
const modes = only ? valueModes(only) : [];
|
|
2762
2856
|
if (only && fields.size === 1 && fields.has("description")) clauses.push(`updates the description of ${pathOf(only)}`);
|
|
2763
2857
|
else if (only && modes.length) clauses.push(`changes ${pathOf(only)} in ${andList(modes)}`);
|
|
2764
|
-
else clauses.push(`changes ${names(by2.changed.map(pathOf))}`);
|
|
2858
|
+
else clauses.push(`changes ${names$1(by2.changed.map(pathOf))}`);
|
|
2765
2859
|
}
|
|
2766
|
-
if (by2.added.length) clauses.push(`adds ${names(by2.added.map(pathOf))}`);
|
|
2860
|
+
if (by2.added.length) clauses.push(`adds ${names$1(by2.added.map(pathOf))}`);
|
|
2767
2861
|
if (by2.renamed.length) {
|
|
2768
2862
|
const r2 = by2.renamed[0];
|
|
2769
2863
|
const from = (r2.head ?? r2.base)?.path;
|
|
2770
|
-
clauses.push(by2.renamed.length === 1 && from && from !== pathOf(r2) ? `renames ${from} to ${pathOf(r2)}` : `renames ${names(by2.renamed.map(pathOf))}`);
|
|
2864
|
+
clauses.push(by2.renamed.length === 1 && from && from !== pathOf(r2) ? `renames ${from} to ${pathOf(r2)}` : `renames ${names$1(by2.renamed.map(pathOf))}`);
|
|
2771
2865
|
}
|
|
2772
|
-
if (by2.removed.length) clauses.push(`removes ${names(by2.removed.map(pathOf))}`);
|
|
2866
|
+
if (by2.removed.length) clauses.push(`removes ${names$1(by2.removed.map(pathOf))}`);
|
|
2773
2867
|
const sentences2 = [];
|
|
2774
2868
|
if (clauses.length) sentences2.push(`${capital(andList(clauses))}.`);
|
|
2775
2869
|
const fresh = components.filter((c10) => c10.kind === "new-component").map((c10) => c10.component);
|
|
@@ -2846,10 +2940,10 @@ const cleanDocsText = (s2) => cleanDraftText(s2.replace(/[\r\n]+/g, " "));
|
|
|
2846
2940
|
const isExample = (x) => {
|
|
2847
2941
|
if (typeof x === "string") return true;
|
|
2848
2942
|
if (!x || typeof x !== "object" || Array.isArray(x)) return false;
|
|
2849
|
-
const
|
|
2850
|
-
if (typeof
|
|
2851
|
-
if (
|
|
2852
|
-
return
|
|
2943
|
+
const n2 = x;
|
|
2944
|
+
if (typeof n2.c !== "string" || !n2.c) return false;
|
|
2945
|
+
if (n2.p !== void 0 && (!n2.p || typeof n2.p !== "object" || Array.isArray(n2.p))) return false;
|
|
2946
|
+
return n2.children === void 0 || Array.isArray(n2.children) && n2.children.every(isExample);
|
|
2853
2947
|
};
|
|
2854
2948
|
const textList = (x) => {
|
|
2855
2949
|
if (typeof x === "string") x = [x];
|
|
@@ -2860,9 +2954,9 @@ const textList = (x) => {
|
|
|
2860
2954
|
const guidanceList = (x, examples) => {
|
|
2861
2955
|
if (!Array.isArray(x)) return void 0;
|
|
2862
2956
|
const out = [];
|
|
2863
|
-
for (const
|
|
2864
|
-
if (!
|
|
2865
|
-
const r2 =
|
|
2957
|
+
for (const g2 of x) {
|
|
2958
|
+
if (!g2 || typeof g2 !== "object") continue;
|
|
2959
|
+
const r2 = g2;
|
|
2866
2960
|
const title = typeof r2.title === "string" ? cleanDocsText(r2.title) : "";
|
|
2867
2961
|
const text = typeof r2.text === "string" ? cleanDocsText(r2.text) : "";
|
|
2868
2962
|
if (!title || !text) continue;
|
|
@@ -2880,8 +2974,8 @@ function sanitizeDocsSections(raw, keys = PROSE_KEYS, opts = {}) {
|
|
|
2880
2974
|
const s2 = typeof v2 === "string" ? cleanDocsText(v2) : "";
|
|
2881
2975
|
if (s2) out.summary = s2;
|
|
2882
2976
|
} else if (k3 === "dos" || k3 === "donts") {
|
|
2883
|
-
const
|
|
2884
|
-
if (
|
|
2977
|
+
const g2 = guidanceList(v2, opts.examples ?? false);
|
|
2978
|
+
if (g2) out[k3] = g2;
|
|
2885
2979
|
} else {
|
|
2886
2980
|
const l2 = textList(v2);
|
|
2887
2981
|
if (l2) out[k3] = l2;
|
|
@@ -3013,8 +3107,8 @@ function explorationNote(e2) {
|
|
|
3013
3107
|
].join("\n");
|
|
3014
3108
|
}
|
|
3015
3109
|
function explorationPrTitle(e2) {
|
|
3016
|
-
const
|
|
3017
|
-
return `Exploration: ${
|
|
3110
|
+
const n2 = e2.name.trim().replace(/\.$/, "");
|
|
3111
|
+
return `Exploration: ${n2.charAt(0).toLowerCase()}${n2.slice(1)}`;
|
|
3018
3112
|
}
|
|
3019
3113
|
function explorationPrBody(e2, ctx) {
|
|
3020
3114
|
const slug = e2.slug || explorationSlug(e2.name);
|
|
@@ -3088,7 +3182,7 @@ function promotionPlan(input) {
|
|
|
3088
3182
|
const addLabels = [`tier: ${risk.tier}`, ...risk.tier === 3 && tokens ? ["tokens"] : []];
|
|
3089
3183
|
const date = (input.date ?? /* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3090
3184
|
const existing = new Set(input.body ? [...input.body.matchAll(/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi)].map((m2) => Number(m2[1])) : []);
|
|
3091
|
-
const closes = [...new Set(input.requests ?? [])].filter((
|
|
3185
|
+
const closes = [...new Set(input.requests ?? [])].filter((n2) => !existing.has(n2));
|
|
3092
3186
|
const kept = (input.body ?? "").replace(/^> Not mergeable as is\..*$\n?\n?/m, "").replace(/### When it is done[\s\S]*?(?=<!-- |$)/, "").replace(/^## Exploration: /m, "## ").trim();
|
|
3093
3187
|
const body = [
|
|
3094
3188
|
`**Promoted from an exploration on ${date}.** Review it like any change: the tier below comes from the files it touches.`,
|
|
@@ -3097,7 +3191,7 @@ function promotionPlan(input) {
|
|
|
3097
3191
|
"",
|
|
3098
3192
|
`**Risk tier:** ${risk.tier}.`,
|
|
3099
3193
|
...risk.reasons.slice(0, 8).map((r2) => `- ${r2}`),
|
|
3100
|
-
...closes.length ? ["", ...closes.map((
|
|
3194
|
+
...closes.length ? ["", ...closes.map((n2) => `Closes #${n2}`)] : []
|
|
3101
3195
|
].join("\n");
|
|
3102
3196
|
const t3 = title.replace(/^Exploration:\s*/i, "").trim() || exploration?.name || title;
|
|
3103
3197
|
return {
|
|
@@ -3208,8 +3302,8 @@ function declineReasonProblems(reason) {
|
|
|
3208
3302
|
return [];
|
|
3209
3303
|
}
|
|
3210
3304
|
function declineComment(reason, by2) {
|
|
3211
|
-
const
|
|
3212
|
-
return [`Declined${by2 ? ` by @${by2.replace(/^@/, "")}` : ""} in Bridge Studio.`, "",
|
|
3305
|
+
const quote2 = reason.trim().split("\n").map((l2) => `> ${l2}`.trimEnd()).join("\n");
|
|
3306
|
+
return [`Declined${by2 ? ` by @${by2.replace(/^@/, "")}` : ""} in Bridge Studio.`, "", quote2, "", "<sub>Closed as not planned. Reopen the issue if things change.</sub>"].join("\n");
|
|
3213
3307
|
}
|
|
3214
3308
|
function requestStatus(issue) {
|
|
3215
3309
|
const prs = issue.linkedPrs ?? [];
|
|
@@ -3347,8 +3441,8 @@ function validatePendingDrafts(x) {
|
|
|
3347
3441
|
if (p.source !== "figma" && p.source !== "studio") out.push('source must be "figma" or "studio".');
|
|
3348
3442
|
if (!Array.isArray(p.items) || !p.items.length) out.push("A draft session needs at least one item.");
|
|
3349
3443
|
const ids = /* @__PURE__ */ new Set();
|
|
3350
|
-
for (const [
|
|
3351
|
-
const at2 = `Item ${
|
|
3444
|
+
for (const [n2, i2] of (Array.isArray(p.items) ? p.items : []).entries()) {
|
|
3445
|
+
const at2 = `Item ${n2 + 1}`;
|
|
3352
3446
|
if (!i2 || typeof i2 !== "object") {
|
|
3353
3447
|
out.push(`${at2} is not an object.`);
|
|
3354
3448
|
continue;
|
|
@@ -3580,8 +3674,8 @@ async function createDocsDraftSession(client, input) {
|
|
|
3580
3674
|
const DRAFT_POLL_INTERVAL_MS = 5e3;
|
|
3581
3675
|
const DRAFT_POLL_TIMEOUT_MS = 10 * 6e4;
|
|
3582
3676
|
class DraftPoller {
|
|
3583
|
-
constructor(
|
|
3584
|
-
this.o =
|
|
3677
|
+
constructor(o3) {
|
|
3678
|
+
this.o = o3;
|
|
3585
3679
|
}
|
|
3586
3680
|
o;
|
|
3587
3681
|
handle;
|
|
@@ -3694,7 +3788,7 @@ Exploration: never merged as is. Promote it to review or close it.`,
|
|
|
3694
3788
|
const ctx = { owner: gh.owner, repo: gh.name, packages: s2.packages, apps: s2.apps, ...s2.requests?.length ? { requests: s2.requests } : {} };
|
|
3695
3789
|
const title = full.jira ? `${full.jira.trim().toUpperCase()} ${explorationPrTitle(full)}` : explorationPrTitle(full);
|
|
3696
3790
|
const pr = await x.createPullRequest({ title, head: branch, base: s2.base, body: explorationPrBody(full, ctx), draft: true });
|
|
3697
|
-
await x.addLabels(pr.number, [EXPLORATION_LABEL]);
|
|
3791
|
+
await x.addLabels(pr.number, [EXPLORATION_LABEL, ...s2.labels ?? []]);
|
|
3698
3792
|
await x.updatePullRequest(pr.number, { body: explorationPrBody(full, { ...ctx, pr: pr.number }) }).catch(() => void 0);
|
|
3699
3793
|
return {
|
|
3700
3794
|
branch,
|
|
@@ -3759,36 +3853,36 @@ async function closeExplorationOnGitHub(gh, ref) {
|
|
|
3759
3853
|
await gh.updatePullRequest(p.number, { state: "closed" });
|
|
3760
3854
|
return { number: p.number, url: p.url, branch: p.headRef };
|
|
3761
3855
|
}
|
|
3762
|
-
async function linkRequestToExplorationOnGitHub(gh,
|
|
3856
|
+
async function linkRequestToExplorationOnGitHub(gh, n2, branch, opts = {}) {
|
|
3763
3857
|
if (!isExplorationBranch(branch)) throw new GitHubError(422, `"${branch}" is not an exploration branch. Exploration branches are named explore/<name>.`);
|
|
3764
|
-
const issue = await gh.getIssue(
|
|
3858
|
+
const issue = await gh.getIssue(n2);
|
|
3765
3859
|
const body = withRequestExploration(issue.body, branch);
|
|
3766
|
-
if (!body) throw new GitHubError(422, `#${
|
|
3767
|
-
await gh.updateIssue(
|
|
3860
|
+
if (!body) throw new GitHubError(422, `#${n2} has no Bridge request block, so it cannot be linked. Add "exploration: ${branch}" to it on GitHub.`);
|
|
3861
|
+
await gh.updateIssue(n2, { body });
|
|
3768
3862
|
const warnings = [];
|
|
3769
|
-
if (!await gh.addLabels(
|
|
3863
|
+
if (!await gh.addLabels(n2, [EXPLORATION_LABEL])) warnings.push(`The ${EXPLORATION_LABEL} label could not be added to #${n2}.`);
|
|
3770
3864
|
let assignees = [];
|
|
3771
3865
|
if (opts.assignee) {
|
|
3772
3866
|
try {
|
|
3773
|
-
assignees = await gh.addAssignees(
|
|
3867
|
+
assignees = await gh.addAssignees(n2, [opts.assignee.replace(/^@/, "")]);
|
|
3774
3868
|
} catch {
|
|
3775
|
-
warnings.push(`@${opts.assignee.replace(/^@/, "")} could not be assigned to #${
|
|
3869
|
+
warnings.push(`@${opts.assignee.replace(/^@/, "")} could not be assigned to #${n2}.`);
|
|
3776
3870
|
}
|
|
3777
3871
|
}
|
|
3778
|
-
return { number:
|
|
3872
|
+
return { number: n2, branch, assignees, warnings };
|
|
3779
3873
|
}
|
|
3780
|
-
async function assignRequestOnGitHub(gh,
|
|
3874
|
+
async function assignRequestOnGitHub(gh, n2, login) {
|
|
3781
3875
|
const who = login.trim().replace(/^@/, "");
|
|
3782
3876
|
if (!who) throw new GitHubError(422, "Pick who should work on it.");
|
|
3783
|
-
const assignees = await gh.addAssignees(
|
|
3877
|
+
const assignees = await gh.addAssignees(n2, [who]);
|
|
3784
3878
|
if (!assignees.some((a) => a.toLowerCase() === who.toLowerCase())) throw new GitHubError(422, `GitHub did not assign @${who}: they need access to this repository.`);
|
|
3785
3879
|
return assignees;
|
|
3786
3880
|
}
|
|
3787
|
-
async function declineRequestOnGitHub(gh,
|
|
3881
|
+
async function declineRequestOnGitHub(gh, n2, reason, by2) {
|
|
3788
3882
|
const problems = declineReasonProblems(reason);
|
|
3789
3883
|
if (problems.length) throw new GitHubError(422, problems.join(" "));
|
|
3790
|
-
await gh.addComment(
|
|
3791
|
-
await gh.updateIssue(
|
|
3884
|
+
await gh.addComment(n2, declineComment(reason, by2));
|
|
3885
|
+
await gh.updateIssue(n2, { state: "closed", state_reason: "not_planned" });
|
|
3792
3886
|
}
|
|
3793
3887
|
const FIDELITY_DEFAULTS = { scale: 2, threshold: 0.1, maxPercent: 3, sizeTolerance: 1 };
|
|
3794
3888
|
const MAX_DELTA = 35215;
|
|
@@ -3805,8 +3899,8 @@ function colorDelta(a, b) {
|
|
|
3805
3899
|
const db = a[2] - b[2];
|
|
3806
3900
|
const y = dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223;
|
|
3807
3901
|
const i2 = dr * 0.59597799 - dg * 0.2741761 - db * 0.32180189;
|
|
3808
|
-
const
|
|
3809
|
-
return 0.5053 * y * y + 0.299 * i2 * i2 + 0.1957 *
|
|
3902
|
+
const q3 = dr * 0.21147017 - dg * 0.52261711 + db * 0.31114694;
|
|
3903
|
+
return 0.5053 * y * y + 0.299 * i2 * i2 + 0.1957 * q3 * q3;
|
|
3810
3904
|
}
|
|
3811
3905
|
function foundNear(img, x, y, c10, radius, max) {
|
|
3812
3906
|
for (let dy = -radius; dy <= radius; dy++) for (let dx = -radius; dx <= radius; dx++) if (colorDelta(c10, rgbAt(img, x + dx, y + dy)) <= max) return true;
|
|
@@ -3831,16 +3925,16 @@ function compareImages(figma, code2, options = {}) {
|
|
|
3831
3925
|
const same = colorDelta(a, b) <= m2 || foundNear(code2, x, y, a, radius, m2) && foundNear(figma, x, y, b, radius, m2);
|
|
3832
3926
|
if (!same) different++;
|
|
3833
3927
|
if (out) {
|
|
3834
|
-
const
|
|
3928
|
+
const o3 = (y * width + x) * 4;
|
|
3835
3929
|
if (same) {
|
|
3836
3930
|
const gray = 255 + (b[0] * 0.299 + b[1] * 0.587 + b[2] * 0.114 - 255) * 0.1;
|
|
3837
|
-
out[
|
|
3931
|
+
out[o3] = out[o3 + 1] = out[o3 + 2] = gray;
|
|
3838
3932
|
} else {
|
|
3839
|
-
out[
|
|
3840
|
-
out[
|
|
3841
|
-
out[
|
|
3933
|
+
out[o3] = 220;
|
|
3934
|
+
out[o3 + 1] = 38;
|
|
3935
|
+
out[o3 + 2] = 38;
|
|
3842
3936
|
}
|
|
3843
|
-
out[
|
|
3937
|
+
out[o3 + 3] = 255;
|
|
3844
3938
|
}
|
|
3845
3939
|
}
|
|
3846
3940
|
}
|
|
@@ -3877,8 +3971,8 @@ function summarizeComponent(component, variants, reason) {
|
|
|
3877
3971
|
}
|
|
3878
3972
|
const sameSize = (s2) => s2.figma.width === s2.code.width && s2.figma.height === s2.code.height;
|
|
3879
3973
|
function buildFidelityReport(input) {
|
|
3880
|
-
const
|
|
3881
|
-
const scale =
|
|
3974
|
+
const o3 = input.options ?? {};
|
|
3975
|
+
const scale = o3.scale ?? FIDELITY_DEFAULTS.scale;
|
|
3882
3976
|
const c10 = input.components;
|
|
3883
3977
|
return {
|
|
3884
3978
|
version: 1,
|
|
@@ -3887,10 +3981,10 @@ function buildFidelityReport(input) {
|
|
|
3887
3981
|
reference: input.reference,
|
|
3888
3982
|
options: {
|
|
3889
3983
|
scale,
|
|
3890
|
-
threshold:
|
|
3891
|
-
radius: Math.round(
|
|
3892
|
-
maxPercent:
|
|
3893
|
-
sizeTolerance:
|
|
3984
|
+
threshold: o3.threshold ?? FIDELITY_DEFAULTS.threshold,
|
|
3985
|
+
radius: Math.round(o3.radius ?? scale),
|
|
3986
|
+
maxPercent: o3.maxPercent ?? FIDELITY_DEFAULTS.maxPercent,
|
|
3987
|
+
sizeTolerance: o3.sizeTolerance ?? FIDELITY_DEFAULTS.sizeTolerance
|
|
3894
3988
|
},
|
|
3895
3989
|
components: c10,
|
|
3896
3990
|
summary: {
|
|
@@ -3898,8 +3992,8 @@ function buildFidelityReport(input) {
|
|
|
3898
3992
|
matches: c10.filter((x) => x.status === "matches").length,
|
|
3899
3993
|
differs: c10.filter((x) => x.status === "differs").length,
|
|
3900
3994
|
notChecked: c10.filter((x) => x.status === "not-checked").length,
|
|
3901
|
-
variantsChecked: c10.reduce((
|
|
3902
|
-
variantsSkipped: c10.reduce((
|
|
3995
|
+
variantsChecked: c10.reduce((n2, x) => n2 + x.checked, 0),
|
|
3996
|
+
variantsSkipped: c10.reduce((n2, x) => n2 + x.skipped, 0)
|
|
3903
3997
|
}
|
|
3904
3998
|
};
|
|
3905
3999
|
}
|
|
@@ -4044,32 +4138,819 @@ function planReferences(file) {
|
|
|
4044
4138
|
(spec) => spec.variants.map((v2) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v2.props), plan: planReference(spec, v2, file) }))
|
|
4045
4139
|
);
|
|
4046
4140
|
}
|
|
4141
|
+
function parseTier(labels, body) {
|
|
4142
|
+
const found = [];
|
|
4143
|
+
for (const l2 of labels) {
|
|
4144
|
+
const m2 = l2.name.match(/^(?:risk[\s:-]*)?tier[\s:_-]*([123])\b/i) ?? l2.name.match(/^risk[\s:_-]*([123])$/i);
|
|
4145
|
+
if (m2) found.push(Number(m2[1]));
|
|
4146
|
+
}
|
|
4147
|
+
for (const m2 of (body ?? "").matchAll(/-\s*\[[xX]\]\s*\**\s*Tier\s*([123])\b/g)) found.push(Number(m2[1]));
|
|
4148
|
+
for (const m2 of (body ?? "").matchAll(/risk tier\W{0,6}(?:tier\s*)?([123])\b/gi)) found.push(Number(m2[1]));
|
|
4149
|
+
return found.length ? Math.max(...found) : null;
|
|
4150
|
+
}
|
|
4151
|
+
function pullState(p) {
|
|
4152
|
+
if (p.merged_at || p.merged) return "merged";
|
|
4153
|
+
if (p.state && p.state !== "open") return "closed";
|
|
4154
|
+
if (p.draft) return "draft";
|
|
4155
|
+
return "open";
|
|
4156
|
+
}
|
|
4157
|
+
const PULL_STATE_PILL = {
|
|
4158
|
+
merged: { label: "Merged", cls: "" },
|
|
4159
|
+
closed: { label: "Closed", cls: "" },
|
|
4160
|
+
draft: { label: "Draft", cls: "" },
|
|
4161
|
+
open: { label: "Open", cls: "" }
|
|
4162
|
+
};
|
|
4163
|
+
function openPulls(pulls) {
|
|
4164
|
+
return pulls.filter((p) => {
|
|
4165
|
+
const s2 = pullState(p);
|
|
4166
|
+
return s2 === "open" || s2 === "draft";
|
|
4167
|
+
});
|
|
4168
|
+
}
|
|
4169
|
+
const OK = /* @__PURE__ */ new Set(["success", "neutral", "skipped"]);
|
|
4170
|
+
function summarizeCi(runs, statuses = []) {
|
|
4171
|
+
const failed = [];
|
|
4172
|
+
const pending = [];
|
|
4173
|
+
let passed = 0;
|
|
4174
|
+
for (const r2 of runs) {
|
|
4175
|
+
if (r2.status !== "completed") pending.push({ name: r2.name, url: r2.html_url });
|
|
4176
|
+
else if (r2.conclusion && OK.has(r2.conclusion)) passed++;
|
|
4177
|
+
else failed.push({ name: r2.name, url: r2.html_url });
|
|
4178
|
+
}
|
|
4179
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4180
|
+
for (const s2 of statuses) {
|
|
4181
|
+
if (seen.has(s2.context)) continue;
|
|
4182
|
+
seen.add(s2.context);
|
|
4183
|
+
if (s2.state === "success") passed++;
|
|
4184
|
+
else if (s2.state === "pending") pending.push({ name: s2.context, url: s2.target_url ?? void 0 });
|
|
4185
|
+
else failed.push({ name: s2.context, url: s2.target_url ?? void 0 });
|
|
4186
|
+
}
|
|
4187
|
+
const total = passed + failed.length + pending.length;
|
|
4188
|
+
const state = total === 0 ? "none" : failed.length ? "fail" : pending.length ? "pending" : "pass";
|
|
4189
|
+
return { state, total, passed, failed, pending };
|
|
4190
|
+
}
|
|
4191
|
+
function summarizeApprovals(reviews, owners, author) {
|
|
4192
|
+
const latest = /* @__PURE__ */ new Map();
|
|
4193
|
+
const sorted = [...reviews].sort((a2, b) => String(a2.submitted_at ?? "").localeCompare(String(b.submitted_at ?? "")));
|
|
4194
|
+
for (const r2 of sorted) {
|
|
4195
|
+
const login = r2.user?.login;
|
|
4196
|
+
if (!login || login === author) continue;
|
|
4197
|
+
if (r2.state === "APPROVED" || r2.state === "CHANGES_REQUESTED" || r2.state === "DISMISSED") latest.set(login, r2.state);
|
|
4198
|
+
}
|
|
4199
|
+
const approvers = [...latest].filter(([, s2]) => s2 === "APPROVED").map(([l2]) => l2);
|
|
4200
|
+
const changesRequested = [...latest].filter(([, s2]) => s2 === "CHANGES_REQUESTED").map(([l2]) => l2);
|
|
4201
|
+
const lower2 = (xs) => xs.map((x) => x.toLowerCase().replace(/^@/, ""));
|
|
4202
|
+
const d2 = lower2(owners.design);
|
|
4203
|
+
const t3 = lower2(owners.tech);
|
|
4204
|
+
const a = lower2(approvers);
|
|
4205
|
+
let design;
|
|
4206
|
+
let tech;
|
|
4207
|
+
if (d2.length || t3.length) {
|
|
4208
|
+
design = d2.length ? a.some((x) => d2.includes(x)) : a.length > 0;
|
|
4209
|
+
const soloBoth = (x) => a.length === 1 && d2.includes(x);
|
|
4210
|
+
tech = t3.length ? a.some((x) => t3.includes(x) && !soloBoth(x)) : a.length > 1;
|
|
4211
|
+
} else {
|
|
4212
|
+
design = a.length >= 1;
|
|
4213
|
+
tech = a.length >= 2;
|
|
4214
|
+
}
|
|
4215
|
+
return { approvers, changesRequested, design, tech };
|
|
4216
|
+
}
|
|
4217
|
+
const normLogin = (x) => x.trim().toLowerCase().replace(/^@/, "");
|
|
4218
|
+
function canOverride(login, policy) {
|
|
4219
|
+
if (!login || !policy?.allowed.length) return false;
|
|
4220
|
+
return policy.allowed.map(normLogin).includes(normLogin(login));
|
|
4221
|
+
}
|
|
4222
|
+
function mergeGate(input) {
|
|
4223
|
+
const blocking = [];
|
|
4224
|
+
const missing = [];
|
|
4225
|
+
const approvalReasons = [];
|
|
4226
|
+
const lifecycle = pullState(input);
|
|
4227
|
+
if (lifecycle === "merged") blocking.push("This branch is already merged.");
|
|
4228
|
+
else if (lifecycle === "closed") blocking.push("This branch is closed.");
|
|
4229
|
+
if (input.exploration && lifecycle !== "merged" && lifecycle !== "closed") blocking.push("It is an exploration: promote it to review first.");
|
|
4230
|
+
if (input.draft) blocking.push("It is a draft. Mark it ready for review on GitHub first.");
|
|
4231
|
+
if (input.ci.state === "fail") blocking.push(`Tests fail: ${input.ci.failed.map((f) => f.name).join(", ")}.`);
|
|
4232
|
+
if (input.ci.state === "pending") blocking.push("Tests are still running.");
|
|
4233
|
+
if (input.approvals.changesRequested.length) blocking.push(`Changes requested by @${input.approvals.changesRequested.join(", @")}.`);
|
|
4234
|
+
const tier = input.tier ?? 3;
|
|
4235
|
+
if (tier >= 2) {
|
|
4236
|
+
if (!input.approvals.design) {
|
|
4237
|
+
approvalReasons.push("It needs a design owner approval.");
|
|
4238
|
+
missing.push("design approval");
|
|
4239
|
+
}
|
|
4240
|
+
if (!input.approvals.tech) {
|
|
4241
|
+
approvalReasons.push("It needs a tech owner approval.");
|
|
4242
|
+
missing.push("tech approval");
|
|
4243
|
+
}
|
|
4244
|
+
} else if (!input.approvals.approvers.length) {
|
|
4245
|
+
approvalReasons.push("It needs one approval.");
|
|
4246
|
+
missing.push("approval");
|
|
4247
|
+
}
|
|
4248
|
+
const conflict = input.mergeable === false ? [CONFLICT_REASON] : [];
|
|
4249
|
+
const shipper = !input.viewer || canShip(input.viewer, input.ship);
|
|
4250
|
+
const ship = !shipper && lifecycle !== "merged" && lifecycle !== "closed" ? [whoCanShipSentence(input.ship), ...unreadableShipTeams(input.ship).map((t3) => t3.status === "unreadable" ? t3.message : "")] : [];
|
|
4251
|
+
const reasons = [...blocking, ...approvalReasons, ...ship, ...conflict];
|
|
4252
|
+
const allowed = reasons.length === 0;
|
|
4253
|
+
const overridable = !allowed && blocking.length + conflict.length + ship.length === 0 && missing.length > 0 && canOverride(input.viewer, input.override);
|
|
4254
|
+
return { allowed, reasons, overridable, missing, canShip: shipper };
|
|
4255
|
+
}
|
|
4256
|
+
const CONFLICT_REASON = "It conflicts with main: resolve it with Update from main on the branch.";
|
|
4257
|
+
const OVERRIDE_HEADING = "**Bridge owner override**";
|
|
4258
|
+
function overrideReasonProblem(reason, policy) {
|
|
4259
|
+
if (!policy.requireReason) return null;
|
|
4260
|
+
const n2 = reason.trim().length;
|
|
4261
|
+
if (n2 === 0) return `Write why this merges without the missing approvals, in at least ${policy.minReasonLength} characters.`;
|
|
4262
|
+
if (n2 < policy.minReasonLength) return `Write at least ${policy.minReasonLength} characters (${n2} so far), so the history says why.`;
|
|
4263
|
+
return null;
|
|
4264
|
+
}
|
|
4265
|
+
function overrideComment(login, reason, missing) {
|
|
4266
|
+
const why = reason.trim().replace(/\s+/g, " ").replace(/[.\s]+$/, "");
|
|
4267
|
+
return `${OVERRIDE_HEADING}
|
|
4268
|
+
|
|
4269
|
+
Owner override by @${normLogin(login)}: ${why || "no reason given"}. Missing: ${missing.join(", ")}. Merged from Bridge.`;
|
|
4270
|
+
}
|
|
4271
|
+
function overrideRefusedComment(body, message) {
|
|
4272
|
+
return body.replace(/ Merged from Bridge\.$/, ` Merge refused by GitHub: ${message.trim().replace(/[.\s]+$/, "")}. Not merged.`);
|
|
4273
|
+
}
|
|
4274
|
+
function parseOverrideComment(body) {
|
|
4275
|
+
const text = (body ?? "").trim();
|
|
4276
|
+
if (!text.startsWith(OVERRIDE_HEADING)) return null;
|
|
4277
|
+
const m2 = text.match(/Owner override by @([\w-]+): ([\s\S]*?)\. Missing: ([^.]*)\. (Merged from Bridge\.|Merge refused by GitHub)/);
|
|
4278
|
+
if (!m2) return null;
|
|
4279
|
+
return { login: m2[1], reason: m2[2], missing: m2[3].split(/,\s*/).filter(Boolean), merged: m2[4].startsWith("Merged") };
|
|
4280
|
+
}
|
|
4281
|
+
function allowedMergeMethods(repo) {
|
|
4282
|
+
const out = [];
|
|
4283
|
+
if (repo?.allow_squash_merge !== false) out.push("squash");
|
|
4284
|
+
if (repo?.allow_merge_commit !== false) out.push("merge");
|
|
4285
|
+
if (repo?.allow_rebase_merge !== false) out.push("rebase");
|
|
4286
|
+
return out;
|
|
4287
|
+
}
|
|
4288
|
+
function hasWriteAccess(perms) {
|
|
4289
|
+
return Boolean(perms && (perms.admin || perms.maintain || perms.push));
|
|
4290
|
+
}
|
|
4291
|
+
const REVIEW_STATUS_ORDER = {
|
|
4292
|
+
"ci-failing": 0,
|
|
4293
|
+
conflict: 1,
|
|
4294
|
+
"changes-requested": 2,
|
|
4295
|
+
"needs-design": 3,
|
|
4296
|
+
"needs-tech": 4,
|
|
4297
|
+
"needs-approval": 5,
|
|
4298
|
+
ready: 6,
|
|
4299
|
+
"ci-running": 7,
|
|
4300
|
+
checking: 8,
|
|
4301
|
+
unknown: 9,
|
|
4302
|
+
draft: 10,
|
|
4303
|
+
merged: 11,
|
|
4304
|
+
closed: 12
|
|
4305
|
+
};
|
|
4306
|
+
function reviewStatusKind(input) {
|
|
4307
|
+
const out = (kind) => kind;
|
|
4308
|
+
const lifecycle = pullState(input);
|
|
4309
|
+
if (lifecycle === "merged") return out("merged");
|
|
4310
|
+
if (lifecycle === "closed") return out("closed");
|
|
4311
|
+
if (lifecycle === "draft") return out("draft");
|
|
4312
|
+
if (input.ci?.state === "fail") return out("ci-failing");
|
|
4313
|
+
if (input.approvals?.changesRequested.length) return out("changes-requested");
|
|
4314
|
+
if (input.mergeable === false) return out("conflict");
|
|
4315
|
+
if (input.ci?.state === "pending") return out("ci-running");
|
|
4316
|
+
if (!input.ci || !input.approvals) return out("unknown");
|
|
4317
|
+
if (input.mergeable === null) return out("checking");
|
|
4318
|
+
const tier = input.tier ?? 3;
|
|
4319
|
+
if (tier >= 2) {
|
|
4320
|
+
if (!input.approvals.design) return out("needs-design");
|
|
4321
|
+
if (!input.approvals.tech) return out("needs-tech");
|
|
4322
|
+
} else if (!input.approvals.approvers.length) return out("needs-approval");
|
|
4323
|
+
const gate = mergeGate({ tier: input.tier, ci: input.ci, approvals: input.approvals, draft: Boolean(input.draft), mergeable: input.mergeable, state: input.state ?? "open", merged_at: input.merged_at });
|
|
4324
|
+
return gate.allowed ? out("ready") : out("unknown");
|
|
4325
|
+
}
|
|
4326
|
+
const BOTS = /* @__PURE__ */ new Set(["github-actions[bot]", "app/github-actions", "changeset-bot[bot]"]);
|
|
4327
|
+
const isReleasePull = (p) => p.head.ref.startsWith("changeset-release/");
|
|
4328
|
+
function isAutomationPull(p) {
|
|
4329
|
+
return isReleasePull(p) || BOTS.has((p.user?.login ?? "").toLowerCase());
|
|
4330
|
+
}
|
|
4331
|
+
const runningExploration = (p) => p.state === "open" && isActiveExploration(p.labels ?? []);
|
|
4332
|
+
function reviewQueue(pulls) {
|
|
4333
|
+
return openPulls(pulls).filter((p) => !isAutomationPull(p) && !runningExploration(p));
|
|
4334
|
+
}
|
|
4335
|
+
function releasePull(pulls) {
|
|
4336
|
+
return openPulls(pulls).find((p) => isReleasePull(p)) ?? null;
|
|
4337
|
+
}
|
|
4338
|
+
function effectiveTier(labels, body, files) {
|
|
4339
|
+
const labelled = parseTier(labels, body);
|
|
4340
|
+
if (!files) return labelled;
|
|
4341
|
+
const computed = classifyRisk({ files: files.map((f) => ({ path: f.filename, status: f.status })) }).tier;
|
|
4342
|
+
return Math.max(labelled ?? 0, computed);
|
|
4343
|
+
}
|
|
4344
|
+
const norm = (x) => x.trim().toLowerCase().replace(/^@/, "");
|
|
4345
|
+
function reopenEligibility(x) {
|
|
4346
|
+
if (x.state !== "closed" || !x.viewer) return { kind: "none" };
|
|
4347
|
+
const isAuthor = norm(x.viewer) === norm(x.author);
|
|
4348
|
+
const isOwner = x.owners.some((o3) => norm(o3) === norm(x.viewer));
|
|
4349
|
+
if (!isAuthor && !isOwner) return { kind: "none" };
|
|
4350
|
+
if (!x.canWrite && !isAuthor) return { kind: "none" };
|
|
4351
|
+
if (x.branch === "exists") return { kind: "reopen" };
|
|
4352
|
+
if (x.branch === "gone") return { kind: "gone" };
|
|
4353
|
+
return { kind: "none" };
|
|
4354
|
+
}
|
|
4355
|
+
const NO_CI = { state: "pass", total: 0, passed: 0, failed: [], pending: [] };
|
|
4356
|
+
const ROLE_OF_MISSING = { "design approval": "design", "tech approval": "tech", approval: "approval" };
|
|
4357
|
+
function requiredApprovals(tier) {
|
|
4358
|
+
return mergeGate({ tier, ci: NO_CI, approvals: { approvers: [], changesRequested: [], design: false, tech: false }, draft: false, state: "open" }).missing.map((m2) => ROLE_OF_MISSING[m2]);
|
|
4359
|
+
}
|
|
4360
|
+
function pendingApprovals(tier, approvals) {
|
|
4361
|
+
if (tier == null || !approvals) return [];
|
|
4362
|
+
return mergeGate({ tier, ci: NO_CI, approvals, draft: false, state: "open" }).missing.map((m2) => ROLE_OF_MISSING[m2]);
|
|
4363
|
+
}
|
|
4364
|
+
const uniqLogins = (xs) => xs.filter((x, i2) => xs.findIndex((y) => normLogin(y) === normLogin(x)) === i2);
|
|
4365
|
+
function eligibleReviewers(role, p) {
|
|
4366
|
+
const raw = role === "design" ? p.owners.design : role === "tech" ? p.owners.tech : uniqLogins([...p.owners.design, ...p.owners.tech]);
|
|
4367
|
+
const listed = raw.map((x) => x.replace(/^@/, ""));
|
|
4368
|
+
const approvers = (p.approvers ?? []).map(normLogin);
|
|
4369
|
+
const logins = listed.filter((l2) => normLogin(l2) !== normLogin(p.author) && !approvers.includes(normLogin(l2)));
|
|
4370
|
+
return { open: listed.length === 0, logins, listed };
|
|
4371
|
+
}
|
|
4372
|
+
function reviewersToAsk(x) {
|
|
4373
|
+
const roles = pendingApprovals(x.tier, x.approvals);
|
|
4374
|
+
const all = roles.flatMap((r2) => eligibleReviewers(r2, { owners: x.owners, author: x.author, approvers: x.approvals.approvers }).logins);
|
|
4375
|
+
return uniqLogins(all).filter((l2) => normLogin(l2) !== normLogin(x.viewer ?? "") && normLogin(l2) !== normLogin(x.author));
|
|
4376
|
+
}
|
|
4377
|
+
const plural = (n2, one, many = `${one}s`) => `${n2} ${n2 === 1 ? one : many}`;
|
|
4378
|
+
const names = (files) => files.flatMap((f) => f.previous_filename ? [f.filename, f.previous_filename] : [f.filename]);
|
|
4379
|
+
function overlap(changeFiles, libraryFiles, isTokenFile) {
|
|
4380
|
+
const lib = new Set(names(libraryFiles));
|
|
4381
|
+
const both = [...new Set(names(changeFiles).filter((f) => lib.has(f)))].sort();
|
|
4382
|
+
return { tokenFiles: both.filter(isTokenFile), codeFiles: both.filter((f) => !isTokenFile(f)) };
|
|
4383
|
+
}
|
|
4384
|
+
const COMPARE_FILE_LIMIT = 300;
|
|
4385
|
+
function conflictRoute(o3, opts = {}) {
|
|
4386
|
+
if (opts.truncated || !o3.tokenFiles.length && !o3.codeFiles.length) return { kind: "code", alsoTokens: false, files: [] };
|
|
4387
|
+
if (o3.codeFiles.length) return { kind: "code", alsoTokens: o3.tokenFiles.length > 0, files: o3.codeFiles };
|
|
4388
|
+
return { kind: "tokens", files: o3.tokenFiles };
|
|
4389
|
+
}
|
|
4390
|
+
function dismissStaleFrom(rules) {
|
|
4391
|
+
const pr = (rules ?? []).filter((r2) => r2.type === "pull_request");
|
|
4392
|
+
if (!pr.length) return null;
|
|
4393
|
+
return pr.some((r2) => r2.parameters?.dismiss_stale_reviews_on_push === true);
|
|
4394
|
+
}
|
|
4395
|
+
function resolutionMessage(input) {
|
|
4396
|
+
const lines = input.conflicts.map((c10) => `- ${c10.path} (${c10.file}): ${choiceFor(c10, input.choices) === "theirs" ? `used the ${input.library} value` : "kept this change"}`);
|
|
4397
|
+
const title = input.conflicts.length ? `Bring in the latest ${input.library} and resolve ${plural(input.conflicts.length, "token conflict")}` : `Bring in the latest ${input.library}`;
|
|
4398
|
+
const by2 = input.who ? `
|
|
4399
|
+
|
|
4400
|
+
Resolved in Bridge by @${input.who.replace(/^@/, "")}.` : "\n\nResolved in Bridge.";
|
|
4401
|
+
return `${title}${lines.length ? `
|
|
4402
|
+
|
|
4403
|
+
${lines.join("\n")}` : ""}${by2}`;
|
|
4404
|
+
}
|
|
4405
|
+
function freshness(input) {
|
|
4406
|
+
const behindBy = typeof input.compare?.behind_by === "number" ? input.compare.behind_by : null;
|
|
4407
|
+
const mergeBase = input.compare?.merge_base_commit?.sha ?? null;
|
|
4408
|
+
if (!input.open) return { state: "current", behindBy, mergeBase };
|
|
4409
|
+
if (input.mergeable === false) return { state: "conflict", behindBy, mergeBase };
|
|
4410
|
+
if (behindBy === null) return { state: "unknown", behindBy, mergeBase };
|
|
4411
|
+
return { state: behindBy > 0 ? "behind" : "current", behindBy, mergeBase };
|
|
4412
|
+
}
|
|
4413
|
+
const withoutRoot = (root, p) => root && p.startsWith(`${root}/`) ? p.slice(root.length + 1) : p;
|
|
4414
|
+
async function treeAt(gh, sha) {
|
|
4415
|
+
const t3 = await gh.getTree(await gh.getCommitTree(sha));
|
|
4416
|
+
if (t3.truncated) throw new Error("This repository is too big for Bridge to read in one go. A developer resolves the conflict on GitHub.");
|
|
4417
|
+
return new Map(t3.entries.filter((e2) => e2.type === "blob").map((e2) => [e2.path, e2]));
|
|
4418
|
+
}
|
|
4419
|
+
async function textAt(gh, sha, path) {
|
|
4420
|
+
return await gh.readFile(path, sha) ?? "";
|
|
4421
|
+
}
|
|
4422
|
+
async function tokenFilesAt(gh, sha, root, tree) {
|
|
4423
|
+
const prefix = root ? `${root}/` : "";
|
|
4424
|
+
const paths = [...tree.keys()].filter((p) => p.startsWith(prefix) && p.endsWith(".tokens.json"));
|
|
4425
|
+
return Object.fromEntries(await Promise.all(paths.map(async (p) => [withoutRoot(root, p), await textAt(gh, sha, p)])));
|
|
4426
|
+
}
|
|
4427
|
+
async function configAt(gh, sha, tree) {
|
|
4428
|
+
if (!tree.has("bridge.config.json")) return null;
|
|
4429
|
+
try {
|
|
4430
|
+
return loadConfig(await textAt(gh, sha, "bridge.config.json"));
|
|
4431
|
+
} catch {
|
|
4432
|
+
return null;
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
async function readLibraryConflicts(gh, compare, headSha, opts = {}) {
|
|
4436
|
+
const { mergeBase, baseSha: mainSha, baseTree: mainTree } = compare;
|
|
4437
|
+
const changeFiles = compare.files;
|
|
4438
|
+
if (!mergeBase || !mainSha || !mainTree) return { route: conflictRoute({ tokenFiles: [], codeFiles: [] }) };
|
|
4439
|
+
const library = (await gh.compareDetailed(mergeBase, mainSha)).files;
|
|
4440
|
+
const truncated = changeFiles.length >= COMPARE_FILE_LIMIT || library.length >= COMPARE_FILE_LIMIT;
|
|
4441
|
+
const guess = opts.tokensRoot ?? DEFAULT_TOKEN_PATHS.root;
|
|
4442
|
+
const route = conflictRoute(
|
|
4443
|
+
overlap(changeFiles, library, (p) => p.endsWith(".tokens.json") && p.startsWith(`${guess}/`)),
|
|
4444
|
+
{ truncated }
|
|
4445
|
+
);
|
|
4446
|
+
if (route.kind !== "tokens") return { route };
|
|
4447
|
+
const [baseTree, headTree, theirsTree] = await Promise.all([treeAt(gh, mergeBase), treeAt(gh, headSha), treeAt(gh, mainSha)]);
|
|
4448
|
+
const config = await configAt(gh, mainSha, theirsTree) ?? await configAt(gh, mergeBase, baseTree);
|
|
4449
|
+
const root = config?.tokens.root ?? guess;
|
|
4450
|
+
const options = config ? layoutOptions(config) : {};
|
|
4451
|
+
const checksPath = `${root}/${config?.tokens.contrastChecks ?? DEFAULT_TOKEN_PATHS.contrastChecks}`;
|
|
4452
|
+
const [baseFiles, mineFiles, theirsFiles, checks] = await Promise.all([
|
|
4453
|
+
tokenFilesAt(gh, mergeBase, root, baseTree),
|
|
4454
|
+
tokenFilesAt(gh, headSha, root, headTree),
|
|
4455
|
+
tokenFilesAt(gh, mainSha, root, theirsTree),
|
|
4456
|
+
theirsTree.has(checksPath) ? textAt(gh, mainSha, checksPath) : Promise.resolve("")
|
|
4457
|
+
]);
|
|
4458
|
+
const input = { base: baseFiles, mine: mineFiles, theirs: theirsFiles, files: route.files.map((f) => withoutRoot(root, f)) };
|
|
4459
|
+
return {
|
|
4460
|
+
route,
|
|
4461
|
+
tokens: {
|
|
4462
|
+
root,
|
|
4463
|
+
plan: planTokenMerge(input),
|
|
4464
|
+
input,
|
|
4465
|
+
sets: { base: buildTokenSet(baseFiles, options), mine: buildTokenSet(mineFiles, options), theirs: buildTokenSet(theirsFiles, options) },
|
|
4466
|
+
mineFiles,
|
|
4467
|
+
theirsFiles,
|
|
4468
|
+
pairs: parseContrastChecks(checks || opts.contrastFallback),
|
|
4469
|
+
options
|
|
4470
|
+
},
|
|
4471
|
+
write: { mainSha, mainTree, headSha, headTree, changeFiles }
|
|
4472
|
+
};
|
|
4473
|
+
}
|
|
4474
|
+
function resolvedTokenFiles(data, choices) {
|
|
4475
|
+
const t3 = data.tokens;
|
|
4476
|
+
const merged = applyTokenMerge(t3.input, choices).files;
|
|
4477
|
+
const all = { ...t3.theirsFiles };
|
|
4478
|
+
for (const f of data.write.changeFiles) {
|
|
4479
|
+
for (const p of [f.previous_filename, f.filename]) {
|
|
4480
|
+
if (!p || !p.endsWith(".tokens.json") || !p.startsWith(`${t3.root}/`)) continue;
|
|
4481
|
+
const rel = withoutRoot(t3.root, p);
|
|
4482
|
+
if (rel in merged) continue;
|
|
4483
|
+
if (t3.mineFiles[rel] === void 0) delete all[rel];
|
|
4484
|
+
else all[rel] = t3.mineFiles[rel];
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
for (const [rel, text] of Object.entries(merged)) {
|
|
4488
|
+
if (text === null) delete all[rel];
|
|
4489
|
+
else all[rel] = text;
|
|
4490
|
+
}
|
|
4491
|
+
return { all, merged };
|
|
4492
|
+
}
|
|
4493
|
+
async function commitLibraryResolution(gh, pull, data, choices, who) {
|
|
4494
|
+
const t3 = data.tokens;
|
|
4495
|
+
const w = data.write;
|
|
4496
|
+
if (!t3 || !w) throw new Error("Only a token conflict can be resolved in Bridge. A developer resolves code conflicts on GitHub.");
|
|
4497
|
+
if (w.headSha !== pull.head.sha) throw new Error("The branch moved while you were choosing. Read it again and resolve again.");
|
|
4498
|
+
const { merged } = resolvedTokenFiles(data, choices);
|
|
4499
|
+
const shared = new Set(data.route.files);
|
|
4500
|
+
const tree = [];
|
|
4501
|
+
for (const f of w.changeFiles) {
|
|
4502
|
+
if (shared.has(f.filename)) continue;
|
|
4503
|
+
if (f.previous_filename && !shared.has(f.previous_filename)) tree.push({ path: f.previous_filename, mode: "100644", sha: null });
|
|
4504
|
+
if (f.status === "removed") {
|
|
4505
|
+
tree.push({ path: f.filename, mode: "100644", sha: null });
|
|
4506
|
+
continue;
|
|
4507
|
+
}
|
|
4508
|
+
const e2 = w.headTree.get(f.filename);
|
|
4509
|
+
if (!e2) throw new Error(`Bridge could not read ${f.filename} in the branch. Read it again and try again.`);
|
|
4510
|
+
tree.push({ path: e2.path, mode: e2.mode, sha: e2.sha });
|
|
4511
|
+
}
|
|
4512
|
+
for (const [rel, text] of Object.entries(merged)) {
|
|
4513
|
+
const path = t3.root ? `${t3.root}/${rel}` : rel;
|
|
4514
|
+
const mode = w.headTree.get(path)?.mode ?? "100644";
|
|
4515
|
+
tree.push(text === null ? { path, mode, sha: null } : { path, mode, content: text });
|
|
4516
|
+
}
|
|
4517
|
+
const newTree = await gh.createTreeEntries(w.mainTree, tree);
|
|
4518
|
+
const message = resolutionMessage({ library: pull.base.ref, conflicts: t3.plan.conflicts, choices, who });
|
|
4519
|
+
const commit = await gh.createCommit(message, newTree, [w.headSha, w.mainSha]);
|
|
4520
|
+
await gh.updateBranch(pull.head.ref, commit);
|
|
4521
|
+
return commit;
|
|
4522
|
+
}
|
|
4523
|
+
async function mergeOnGitHub(gh, input) {
|
|
4524
|
+
const r2 = await gh.mergePullRequest(input.number, { method: input.method ?? "squash", sha: input.headSha });
|
|
4525
|
+
return { sha: r2.sha };
|
|
4526
|
+
}
|
|
4527
|
+
class OverrideMergeError extends Error {
|
|
4528
|
+
constructor(stage, message, cause, commentUrl) {
|
|
4529
|
+
super(message);
|
|
4530
|
+
this.stage = stage;
|
|
4531
|
+
this.cause = cause;
|
|
4532
|
+
this.commentUrl = commentUrl;
|
|
4533
|
+
this.name = "OverrideMergeError";
|
|
4534
|
+
}
|
|
4535
|
+
stage;
|
|
4536
|
+
cause;
|
|
4537
|
+
commentUrl;
|
|
4538
|
+
}
|
|
4539
|
+
async function overrideMergeOnGitHub(gh, input) {
|
|
4540
|
+
const body = overrideComment(input.login, input.reason, input.missing);
|
|
4541
|
+
let comment;
|
|
4542
|
+
try {
|
|
4543
|
+
comment = await gh.createIssueComment(input.number, body);
|
|
4544
|
+
} catch (err) {
|
|
4545
|
+
throw new OverrideMergeError("comment", `GitHub did not take the comment, so nothing merged: ${err.message}`, err);
|
|
4546
|
+
}
|
|
4547
|
+
try {
|
|
4548
|
+
const r2 = await gh.mergePullRequest(input.number, { method: input.method, sha: input.headSha, commitMessage: body.replace(OVERRIDE_HEADING, "").trim() });
|
|
4549
|
+
return { sha: r2.sha, commentUrl: comment.url, comment: body };
|
|
4550
|
+
} catch (err) {
|
|
4551
|
+
const msg = err.message.trim().replace(/[.\s]+$/, "");
|
|
4552
|
+
await gh.updateIssueComment(comment.id, overrideRefusedComment(body, msg)).catch(() => void 0);
|
|
4553
|
+
throw new OverrideMergeError(
|
|
4554
|
+
"merge",
|
|
4555
|
+
`GitHub refused to merge it: ${msg}. Your reason stays on the branch, marked not merged. The protection of main may need an approving review or an admin bypass.`,
|
|
4556
|
+
err,
|
|
4557
|
+
comment.url
|
|
4558
|
+
);
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
async function reviewOnGitHub(gh, input) {
|
|
4562
|
+
const text = input.body?.trim() ?? "";
|
|
4563
|
+
if (input.event === "REQUEST_CHANGES" && !text) throw new Error("Add a comment that says what to change.");
|
|
4564
|
+
if (input.event === "COMMENT" && !text) throw new Error("Write a comment first.");
|
|
4565
|
+
return gh.submitReview(input.number, { event: input.event, body: text || void 0, commitId: input.headSha });
|
|
4566
|
+
}
|
|
4567
|
+
async function commentOnGitHub(gh, n2, body) {
|
|
4568
|
+
const text = body.trim();
|
|
4569
|
+
if (!text) throw new Error("Write the comment first.");
|
|
4570
|
+
return gh.createIssueComment(n2, text);
|
|
4571
|
+
}
|
|
4572
|
+
async function askOthersToReviewOnGitHub(gh, input) {
|
|
4573
|
+
if (!input.others.length) throw new Error("Nobody else can give the missing approval. Review it, or ask an owner in Settings to add a reviewer.");
|
|
4574
|
+
await gh.requestReviewers(input.number, input.others);
|
|
4575
|
+
if (input.viewer && input.viewerRequested) await gh.removeRequestedReviewers(input.number, [input.viewer]).catch(() => void 0);
|
|
4576
|
+
return input.others;
|
|
4577
|
+
}
|
|
4578
|
+
async function closeOnGitHub(gh, n2) {
|
|
4579
|
+
await gh.updatePullRequest(n2, { state: "closed" });
|
|
4580
|
+
}
|
|
4581
|
+
async function reopenOnGitHub(gh, n2) {
|
|
4582
|
+
await gh.updatePullRequest(n2, { state: "open" });
|
|
4583
|
+
}
|
|
4584
|
+
const sameLogin = (a, b) => Boolean(a && b && a.toLowerCase().replace(/^@/, "") === b.toLowerCase().replace(/^@/, ""));
|
|
4585
|
+
function canAddTo(p, viewer, write) {
|
|
4586
|
+
if (!viewer) return false;
|
|
4587
|
+
return write || sameLogin(p.user?.login, viewer);
|
|
4588
|
+
}
|
|
4589
|
+
function addedComment(paths, viewer, from = "the Bridge token editor") {
|
|
4590
|
+
const lines = paths.slice(0, 20).map((p) => `- \`${p}\``);
|
|
4591
|
+
if (paths.length > 20) lines.push(`- and ${paths.length - 20} more`);
|
|
4592
|
+
return `${viewer ? `@${viewer} added` : "Added"} token edits from ${from}:
|
|
4593
|
+
|
|
4594
|
+
${lines.join("\n")}`;
|
|
4595
|
+
}
|
|
4596
|
+
const JIRA_KEY = /^[A-Z][A-Z0-9_]{0,9}-[1-9]\d{0,6}$/;
|
|
4597
|
+
function parseJiraKey(input) {
|
|
4598
|
+
const t3 = (input ?? "").trim().toUpperCase();
|
|
4599
|
+
if (!t3) return null;
|
|
4600
|
+
if (!JIRA_KEY.test(t3)) throw new Error("Write the Jira ticket as its key, like DS-42, or leave it empty.");
|
|
4601
|
+
return t3;
|
|
4602
|
+
}
|
|
4603
|
+
const isJiraKey = (input) => JIRA_KEY.test(input.trim().toUpperCase());
|
|
4604
|
+
const LEADING_KEY = /^\[?([A-Z][A-Z0-9_]{0,9}-[1-9]\d{0,6})\]?[:\s]\s*/;
|
|
4605
|
+
function jiraKeyOf(title) {
|
|
4606
|
+
return LEADING_KEY.exec(title.trim())?.[1] ?? null;
|
|
4607
|
+
}
|
|
4608
|
+
const withoutJiraKey = (title) => title.trim().replace(LEADING_KEY, "");
|
|
4609
|
+
function titleWithJiraKey(title, key) {
|
|
4610
|
+
if (!key) return title;
|
|
4611
|
+
return `${key} ${withoutJiraKey(title)}`;
|
|
4612
|
+
}
|
|
4613
|
+
function branchWithJiraKey(branch, key) {
|
|
4614
|
+
if (!key || branch.includes(key)) return branch;
|
|
4615
|
+
const i2 = branch.lastIndexOf("/");
|
|
4616
|
+
return i2 < 0 ? `${key}-${branch}` : `${branch.slice(0, i2 + 1)}${key}-${branch.slice(i2 + 1)}`;
|
|
4617
|
+
}
|
|
4618
|
+
const DOCS_EDITOR_ORIGIN = {
|
|
4619
|
+
where: "in the Bridge docs editor",
|
|
4620
|
+
footer: "Written by a person in the Bridge docs editor. No AI involved in this text.",
|
|
4621
|
+
label: "source: bridge-web"
|
|
4622
|
+
};
|
|
4623
|
+
const DOCS_MCP_ORIGIN = {
|
|
4624
|
+
where: "with Claude through the Bridge MCP server",
|
|
4625
|
+
footer: 'Proposed with the Bridge MCP server on behalf of a person. Docs Claude drafted say "draftedBy": "claude" until a reviewer accepts them.',
|
|
4626
|
+
label: "source: mcp"
|
|
4627
|
+
};
|
|
4628
|
+
const EM_DASH = /\u2014/;
|
|
4629
|
+
function validateDocs(d2) {
|
|
4630
|
+
const out = [];
|
|
4631
|
+
if (!d2.summary.trim()) out.push({ field: "summary", message: "Add a summary: one or two sentences on what it is for." });
|
|
4632
|
+
const lists = ["whenToUse", "whenNotToUse", "accessibility", "contentGuidelines"];
|
|
4633
|
+
for (const k3 of lists) (d2[k3] ?? []).forEach((x, i2) => !x.trim() && out.push({ field: `${k3}.${i2}`, message: "Fill or remove the empty item." }));
|
|
4634
|
+
for (const k3 of ["dos", "donts"]) d2[k3].forEach((g2, i2) => (!g2.title.trim() || !g2.text.trim()) && out.push({ field: `${k3}.${i2}`, message: "Give every item a title and a sentence." }));
|
|
4635
|
+
const prose = JSON.stringify([d2.summary, d2.whenToUse, d2.whenNotToUse, d2.dos, d2.donts, d2.accessibility, d2.contentGuidelines ?? [], d2.anatomy ?? {}]);
|
|
4636
|
+
if (EM_DASH.test(prose)) out.push({ field: "voice", message: "Replace the em dashes with a colon, a comma or two sentences." });
|
|
4637
|
+
return out;
|
|
4638
|
+
}
|
|
4639
|
+
function cleanDocs(d2) {
|
|
4640
|
+
const list = (xs) => (xs ?? []).map((x) => x.trim()).filter(Boolean);
|
|
4641
|
+
const guide = (xs) => xs.map((g2) => ({ ...g2, title: g2.title.trim(), text: g2.text.trim() })).filter((g2) => g2.title || g2.text);
|
|
4642
|
+
return {
|
|
4643
|
+
...d2,
|
|
4644
|
+
summary: d2.summary.trim(),
|
|
4645
|
+
whenToUse: list(d2.whenToUse),
|
|
4646
|
+
whenNotToUse: list(d2.whenNotToUse),
|
|
4647
|
+
dos: guide(d2.dos),
|
|
4648
|
+
donts: guide(d2.donts),
|
|
4649
|
+
accessibility: list(d2.accessibility),
|
|
4650
|
+
contentGuidelines: list(d2.contentGuidelines),
|
|
4651
|
+
anatomy: Object.fromEntries(Object.entries(d2.anatomy ?? {}).filter(([k3, v2]) => k3.trim() && v2.trim()))
|
|
4652
|
+
};
|
|
4653
|
+
}
|
|
4654
|
+
const docsText = (d2) => formatDocsJson(cleanDocs(d2));
|
|
4655
|
+
const docsFileText = (d2, original) => patchDocsJson(original, cleanDocs(d2));
|
|
4656
|
+
const slugDate = (d2) => d2.toISOString().slice(0, 10).replace(/-/g, "");
|
|
4657
|
+
const docsSlug = (nameOrSlug) => nameOrSlug.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9-]+/g, "-").slice(0, 40);
|
|
4658
|
+
const listOf = (xs) => xs.length < 2 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
4659
|
+
function docsAuthorLine(a, who, where = "the Bridge docs editor") {
|
|
4660
|
+
const w = who?.trim() ? `@${who.trim().replace(/^@/, "")}` : "a person";
|
|
4661
|
+
if (!a.claude.length && !a.edited.length && !a.pending.length) return `Written by ${w} in ${where}. No AI involved in this text.`;
|
|
4662
|
+
const head = a.edited.length ? `Drafted by Claude AI, edited by ${w} in ${where}.` : a.claude.length ? `Drafted by Claude AI, reviewed and sent by ${w} in ${where}.` : `Drafted by Claude AI, sent by ${w} in ${where} before review.`;
|
|
4663
|
+
const mixed = [a.edited, a.written, a.pending].some((x) => x.length);
|
|
4664
|
+
return [
|
|
4665
|
+
head,
|
|
4666
|
+
a.edited.length ? `Edited by ${w}: ${listOf(a.edited)}.` : "",
|
|
4667
|
+
a.claude.length && mixed ? `As Claude drafted them: ${listOf(a.claude)}.` : "",
|
|
4668
|
+
a.written.length ? `Written by ${w}: ${listOf(a.written)}.` : "",
|
|
4669
|
+
a.pending.length && (a.claude.length || a.edited.length || a.written.length) ? `Not reviewed yet: ${listOf(a.pending)}.` : ""
|
|
4670
|
+
].filter(Boolean).join(" ");
|
|
4671
|
+
}
|
|
4672
|
+
function defaultDocsFooter(edits, origin) {
|
|
4673
|
+
if (origin.label !== DOCS_EDITOR_ORIGIN.label) return origin.footer;
|
|
4674
|
+
const claude = edits.some((e2) => {
|
|
4675
|
+
try {
|
|
4676
|
+
return JSON.parse(e2.after).draftedBy === "claude";
|
|
4677
|
+
} catch {
|
|
4678
|
+
return false;
|
|
4679
|
+
}
|
|
4680
|
+
});
|
|
4681
|
+
return claude ? `Drafted by Claude AI (the docs say "draftedBy": "claude"), sent by a person ${origin.where}.` : origin.footer;
|
|
4682
|
+
}
|
|
4683
|
+
function withAuthorLine(body, line) {
|
|
4684
|
+
const i2 = body.lastIndexOf("\n---\n");
|
|
4685
|
+
return i2 < 0 ? `${body.trimEnd()}
|
|
4686
|
+
|
|
4687
|
+
---
|
|
4688
|
+
${line}` : `${body.slice(0, i2 + 5)}${line}`;
|
|
4689
|
+
}
|
|
4690
|
+
const escapeRe = (s2) => s2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4691
|
+
function isDocsProposalBranch(branch, component) {
|
|
4692
|
+
const slugs = /* @__PURE__ */ new Set([docsSlug(component.slug ?? component.name), docsSlug(component.name), docsSlug(component.name.toLowerCase())]);
|
|
4693
|
+
return [...slugs].some((s2) => new RegExp(`^docs/(?:[A-Z][A-Z0-9]*-\\d+-)?${escapeRe(s2)}-\\d{8}-[a-z0-9]+$`).test(branch));
|
|
4694
|
+
}
|
|
4695
|
+
function findOpenDocsProposal(pulls, component, base) {
|
|
4696
|
+
const hit = pulls.filter((p) => p.state === "open" && isDocsProposalBranch(p.headRef, component) && (!base || !p.baseRef || p.baseRef === base)).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0];
|
|
4697
|
+
return hit ? { number: hit.number, url: hit.url, branch: hit.headRef, author: hit.author, title: hit.title, updatedAt: hit.updatedAt } : null;
|
|
4698
|
+
}
|
|
4699
|
+
async function findOpenDocsProposalOnGitHub(gh, component, base) {
|
|
4700
|
+
return findOpenDocsProposal(await gh.listPullRequests("open"), component, base);
|
|
4701
|
+
}
|
|
4702
|
+
async function continueDocsProposalOnGitHub(gh, open, edit, opts = {}) {
|
|
4703
|
+
const message = opts.message ?? `docs(${docsSlug(edit.component)}): edit usage guidance in the Bridge docs editor`;
|
|
4704
|
+
const cur = await gh.getFile(edit.path, open.branch);
|
|
4705
|
+
if (cur?.text === edit.after) return "";
|
|
4706
|
+
const sha = await gh.putFile({ path: edit.path, content: edit.after, branch: open.branch, message, sha: cur?.sha });
|
|
4707
|
+
if (opts.authorLine) {
|
|
4708
|
+
const line = opts.authorLine;
|
|
4709
|
+
await gh.getPullRequest(open.number).then((pr) => gh.updatePullRequest(open.number, { body: withAuthorLine(pr.body, line) })).catch(() => void 0);
|
|
4710
|
+
}
|
|
4711
|
+
return sha;
|
|
4712
|
+
}
|
|
4713
|
+
function docsProposal(edits, opts) {
|
|
4714
|
+
const origin = opts.origin ?? DOCS_EDITOR_ORIGIN;
|
|
4715
|
+
if (!edits.length) throw new Error("No docs edits in the draft.");
|
|
4716
|
+
const names2 = edits.map((e2) => e2.component);
|
|
4717
|
+
const list = names2.length === 1 ? names2[0] : `${names2.slice(0, -1).join(", ")} and ${names2[names2.length - 1]}`;
|
|
4718
|
+
const title = opts.title?.trim() || `docs(${names2.length === 1 ? edits[0].slug : "components"}): usage guidance for ${list}`;
|
|
4719
|
+
const slug = docsSlug(names2.length === 1 ? edits[0].slug : `${edits[0].slug}-and-${names2.length - 1}-more`);
|
|
4720
|
+
const branch = `docs/${slug}-${slugDate(opts.date)}-${opts.date.getTime().toString(36).slice(-4)}`;
|
|
4721
|
+
const csPath = `${opts.changesetDir ?? ".changeset"}/docs-${slug}-${opts.date.getTime().toString(36)}.md`;
|
|
4722
|
+
const pkg = opts.packageName ?? "@bridge-ds/react";
|
|
4723
|
+
const changeset = `---
|
|
4724
|
+
"${pkg}": patch
|
|
4725
|
+
---
|
|
4726
|
+
|
|
4727
|
+
Usage guidance for ${list}.
|
|
4728
|
+
`;
|
|
4729
|
+
const files = [...edits.map((e2) => ({ path: e2.path, content: e2.after })), { path: csPath, content: changeset }];
|
|
4730
|
+
const risk = classifyRisk({ files: files.map((f) => ({ path: f.path, status: "modified" })) });
|
|
4731
|
+
const rows = edits.map((e2) => {
|
|
4732
|
+
let state = "edited";
|
|
4733
|
+
try {
|
|
4734
|
+
const d2 = JSON.parse(e2.after);
|
|
4735
|
+
const gaps = missingProse(d2);
|
|
4736
|
+
state = gaps.length ? `still missing: ${gaps.join(", ")}` : "every section filled";
|
|
4737
|
+
} catch {
|
|
4738
|
+
state = "not valid JSON";
|
|
4739
|
+
}
|
|
4740
|
+
return `- \`${e2.path}\` (${e2.before === null ? "new" : "changed"}, ${state})`;
|
|
4741
|
+
});
|
|
4742
|
+
const body = [
|
|
4743
|
+
`## Docs change`,
|
|
4744
|
+
"",
|
|
4745
|
+
`Usage guidance for ${list}, written ${origin.where}.`,
|
|
4746
|
+
"",
|
|
4747
|
+
...rows,
|
|
4748
|
+
"",
|
|
4749
|
+
`**Risk:** ${risk.label}. Docs only: the published components do not change. Changeset: patch.`,
|
|
4750
|
+
"",
|
|
4751
|
+
opts.notes?.trim() ? `## Notes
|
|
4752
|
+
|
|
4753
|
+
${opts.notes.trim()}
|
|
4754
|
+
` : "",
|
|
4755
|
+
"---",
|
|
4756
|
+
opts.authorLine?.trim() || defaultDocsFooter(edits, origin)
|
|
4757
|
+
].filter((x, i2, a) => !(x === "" && a[i2 - 1] === "")).join("\n");
|
|
4758
|
+
return { branch, title, body, files, risk, bump: "patch", labels: ["docs", origin.label, `tier-${risk.tier}`] };
|
|
4759
|
+
}
|
|
4760
|
+
async function proposeDocsOnGitHub(gh, edits, opts) {
|
|
4761
|
+
if (opts.continueOpen !== false && edits.length === 1) {
|
|
4762
|
+
const e2 = edits[0];
|
|
4763
|
+
const open = await findOpenDocsProposalOnGitHub(gh, { name: e2.component, slug: e2.slug }, opts.base).catch(() => null);
|
|
4764
|
+
if (open) {
|
|
4765
|
+
const sha = await continueDocsProposalOnGitHub(gh, open, e2, { authorLine: opts.authorLine?.trim() || defaultDocsFooter(edits, opts.origin ?? DOCS_EDITOR_ORIGIN) });
|
|
4766
|
+
return { number: open.number, url: open.url, branch: open.branch, title: open.title, continued: true, unchanged: !sha };
|
|
4767
|
+
}
|
|
4768
|
+
}
|
|
4769
|
+
const p = docsProposal(edits, { title: opts.title, notes: opts.notes, date: opts.date ?? /* @__PURE__ */ new Date(), origin: opts.origin, authorLine: opts.authorLine });
|
|
4770
|
+
const prTitle = titleWithJiraKey(p.title, opts.jira ?? null);
|
|
4771
|
+
const branch = branchWithJiraKey(p.branch, opts.jira ?? null);
|
|
4772
|
+
const mainSha = await gh.getBranchSha(opts.base);
|
|
4773
|
+
if (!mainSha) throw new Error(`Branch ${opts.base} not found.`);
|
|
4774
|
+
const tree = await gh.createTree(await gh.getCommitTree(mainSha), p.files.map((f) => ({ path: f.path, content: f.content })));
|
|
4775
|
+
const commit = await gh.createCommit(`${prTitle}
|
|
4776
|
+
|
|
4777
|
+
${opts.commitNote ?? "Proposed with the Bridge docs editor."}`, tree, [mainSha]);
|
|
4778
|
+
await gh.createBranch(branch, commit);
|
|
4779
|
+
const pr = await gh.createPullRequest({ title: prTitle, head: branch, base: opts.base, body: p.body, draft: false });
|
|
4780
|
+
await gh.addLabels(pr.number, [...p.labels, ...opts.extraLabels ?? []]).catch(() => false);
|
|
4781
|
+
return { number: pr.number, url: pr.url, branch, title: prTitle, continued: false, unchanged: false };
|
|
4782
|
+
}
|
|
4783
|
+
async function commitFileOnGitHub(gh, input) {
|
|
4784
|
+
const cur = await gh.getFile(input.path, input.branch);
|
|
4785
|
+
if (!cur) throw new Error(`${input.path} is not on ${input.branch}. Check the component name and the branch.`);
|
|
4786
|
+
const content = input.next(cur.text);
|
|
4787
|
+
if (content === cur.text) return "";
|
|
4788
|
+
return gh.putFile({ path: input.path, content, branch: input.branch, message: input.message, sha: cur.sha });
|
|
4789
|
+
}
|
|
4790
|
+
const SHARE_EXPLAIN = "Product teams can install this branch in their app before it is merged, and remove it any time.";
|
|
4791
|
+
const packageDir = (name) => `packages/${name.replace(/^@[^/]+\//, "")}/`;
|
|
4792
|
+
function touchesPackages(files, packages) {
|
|
4793
|
+
const dirs = packages.map(packageDir);
|
|
4794
|
+
return files.some((f) => dirs.some((d2) => f.startsWith(d2)));
|
|
4795
|
+
}
|
|
4796
|
+
const canShare = (x) => !x.fromFork && x.packages.length > 0;
|
|
4797
|
+
function shareState(x) {
|
|
4798
|
+
if (!touchesPackages(x.files, x.packages)) return { kind: "no-packages" };
|
|
4799
|
+
const c10 = readPreviewComment(x.comment);
|
|
4800
|
+
if (x.lifecycle === "merged" || x.lifecycle === "closed") {
|
|
4801
|
+
const kind = x.lifecycle === "merged" ? "shipped" : "closed";
|
|
4802
|
+
if (!c10 || !c10.command || c10.source === "none") return { kind, stillWorks: null, command: null };
|
|
4803
|
+
return { kind, stillWorks: c10.source === "pkg.pr.new" ? true : c10.source === "release" ? false : null, command: c10.command };
|
|
4804
|
+
}
|
|
4805
|
+
if (!c10) return { kind: "building" };
|
|
4806
|
+
if (c10.sha7 && !x.headSha.startsWith(c10.sha7)) return { kind: "building" };
|
|
4807
|
+
if (!c10.command || c10.source === "none") return { kind: "failed" };
|
|
4808
|
+
return { kind: "ready", command: c10.command, source: c10.source };
|
|
4809
|
+
}
|
|
4810
|
+
const showShare = (lifecycle, s2) => lifecycle === "open" || lifecycle === "draft" || (s2.kind === "shipped" || s2.kind === "closed") && !!s2.command;
|
|
4811
|
+
const shareDescription = (s2) => s2.kind === "shipped" || s2.kind === "closed" ? stateSentence(s2) ?? SHARE_EXPLAIN : SHARE_EXPLAIN;
|
|
4812
|
+
const stableCommand = (packages) => `npm i ${packages.map((p) => `${p}@latest`).join(" ")}`;
|
|
4813
|
+
function stateSentence(s2) {
|
|
4814
|
+
switch (s2.kind) {
|
|
4815
|
+
case "ready":
|
|
4816
|
+
return null;
|
|
4817
|
+
case "building":
|
|
4818
|
+
return "The preview version is being prepared. It is usually ready a couple of minutes after each update to the branch.";
|
|
4819
|
+
case "failed":
|
|
4820
|
+
return "The preview version could not be prepared for the latest update. Update the branch to try again, or ask a developer to run it again on GitHub.";
|
|
4821
|
+
case "no-packages":
|
|
4822
|
+
return "This branch does not touch the library tokens or components, so there is no preview version to install.";
|
|
4823
|
+
case "shipped":
|
|
4824
|
+
return s2.stillWorks === true ? "This branch was merged. Its preview version still installs, and products get it with the next library release." : s2.stillWorks === false ? "This branch was merged, so its preview version was removed. Products get it with the next library release." : "This branch was merged. Products get it with the next library release.";
|
|
4825
|
+
case "closed":
|
|
4826
|
+
return s2.stillWorks === true ? "This branch was closed without merging. Its preview version still installs, but it will not reach the library." : s2.stillWorks === false ? "This branch was closed without merging, so its preview version was removed." : "This branch was closed without merging.";
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
const quote = (t3) => `"${t3.trim().replace(/"/g, "'")}"`;
|
|
4830
|
+
function shareMessage(x) {
|
|
4831
|
+
const back = stableCommand(x.packages);
|
|
4832
|
+
const s2 = x.state;
|
|
4833
|
+
if (s2.kind === "ready") {
|
|
4834
|
+
const lasts = s2.source === "pkg.pr.new" ? "This preview stays available. Each update to the change makes a new one: ask me for the latest command." : "This preview is replaced on each update to the change and removed when it ships or closes: ask me for the latest command if the install fails.";
|
|
4835
|
+
return {
|
|
4836
|
+
subject: `Try the design system change ${quote(x.title)} before it ships`,
|
|
4837
|
+
body: [
|
|
4838
|
+
"Hi,",
|
|
4839
|
+
"",
|
|
4840
|
+
`Could you try the design system change ${quote(x.title)} in our product before it ships?`,
|
|
4841
|
+
...x.tryUrl ? ["", `See it in the demo product first: ${x.tryUrl}`] : [],
|
|
4842
|
+
"",
|
|
4843
|
+
"Install the preview version in a staging branch (not production):",
|
|
4844
|
+
s2.command,
|
|
4845
|
+
"",
|
|
4846
|
+
lasts,
|
|
4847
|
+
"",
|
|
4848
|
+
"To go back to the stable version:",
|
|
4849
|
+
back,
|
|
4850
|
+
"",
|
|
4851
|
+
`Details for developers: ${x.url}`
|
|
4852
|
+
].join("\n")
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4855
|
+
if ((s2.kind === "shipped" || s2.kind === "closed") && s2.command) {
|
|
4856
|
+
const what = s2.kind === "shipped" ? `The design system change ${quote(x.title)} has shipped. You get it with the next library release.` : `The design system change ${quote(x.title)} was closed without shipping.`;
|
|
4857
|
+
const preview = s2.stillWorks === false ? "Its preview version was removed, so reinstalling it fails." : s2.stillWorks === true ? "Its preview version still installs, but it is not the released version." : "";
|
|
4858
|
+
return {
|
|
4859
|
+
subject: `Design system change ${quote(x.title)} ${s2.kind === "shipped" ? "has shipped" : "was closed"}`,
|
|
4860
|
+
body: ["Hi,", "", [what, preview].filter(Boolean).join(" "), "", "If you installed its preview, go back to the stable version:", back, "", `Details for developers: ${x.url}`].join("\n")
|
|
4861
|
+
};
|
|
4862
|
+
}
|
|
4863
|
+
return null;
|
|
4864
|
+
}
|
|
4865
|
+
const mailtoHref = (m2) => `mailto:?subject=${encodeURIComponent(m2.subject)}&body=${encodeURIComponent(m2.body.replace(/\r?\n/g, "\r\n"))}`;
|
|
4866
|
+
function productPreview(apps, ctx, sources) {
|
|
4867
|
+
if (!apps.admin || ctx.fromFork) return null;
|
|
4868
|
+
const admin = resolveBranchPreviews(apps, { branch: ctx.branch, pr: ctx.pr, sha: ctx.sha }, sources).find((p) => p.app === "admin");
|
|
4869
|
+
if (!admin?.url || admin.source === "template" || admin.state === "failed") return null;
|
|
4870
|
+
return admin.url;
|
|
4871
|
+
}
|
|
4872
|
+
function tryInProductUrl(preview, pr, screen) {
|
|
4873
|
+
const url = new URL(preview);
|
|
4874
|
+
url.pathname = "/";
|
|
4875
|
+
url.search = "";
|
|
4876
|
+
url.searchParams.set("bridge", String(pr));
|
|
4877
|
+
if (screen) {
|
|
4878
|
+
url.searchParams.set("tenant", screen.tenant);
|
|
4879
|
+
url.searchParams.set("theme", screen.theme);
|
|
4880
|
+
}
|
|
4881
|
+
url.hash = screen ? screen.route : "";
|
|
4882
|
+
return url.href;
|
|
4883
|
+
}
|
|
4884
|
+
const MIN_TOGETHER = 2;
|
|
4885
|
+
const MAX_TOGETHER = 4;
|
|
4886
|
+
function onlyTokens(files, tokensRoot) {
|
|
4887
|
+
const root = `${tokensRoot.replace(/^\/+|\/+$/g, "")}/`;
|
|
4888
|
+
return files.length > 0 && files.every((f) => f.startsWith(root) && f.endsWith(".tokens.json") || /contrast\.checks\.json$/.test(f) || f.startsWith(".changeset/"));
|
|
4889
|
+
}
|
|
4890
|
+
function tokenFilesOf(files, tokensRoot) {
|
|
4891
|
+
const root = `${tokensRoot.replace(/^\/+|\/+$/g, "")}/`;
|
|
4892
|
+
return files.filter((f) => f.startsWith(root) && f.endsWith(".tokens.json")).map((f) => f.slice(root.length));
|
|
4893
|
+
}
|
|
4894
|
+
const changeList = (numbers) => numbers.join(",");
|
|
4895
|
+
function tryTogetherUrl(admin, numbers, picks) {
|
|
4896
|
+
const url = new URL(admin);
|
|
4897
|
+
url.pathname = "/";
|
|
4898
|
+
url.hash = "";
|
|
4899
|
+
const use = encodePicks(picks);
|
|
4900
|
+
url.search = `?bridge=${changeList(numbers)}${use ? `&bridge-use=${encodeURIComponent(use)}` : ""}`;
|
|
4901
|
+
return url.href;
|
|
4902
|
+
}
|
|
4903
|
+
const COMBINE_COMPARE_LIMIT = 300;
|
|
4904
|
+
async function readCombineBranch(gh, pull, opts) {
|
|
4905
|
+
const compare = await gh.compareDetailed(opts.base, pull.headSha);
|
|
4906
|
+
const files = compare.files;
|
|
4907
|
+
const names2 = files.flatMap((f) => f.previous_filename ? [f.filename, f.previous_filename] : [f.filename]);
|
|
4908
|
+
const start = compare.mergeBase;
|
|
4909
|
+
if (!start || files.length >= COMBINE_COMPARE_LIMIT || !onlyTokens(names2, opts.tokensRoot)) return { number: pull.number, title: pull.title, tokens: false, edits: null };
|
|
4910
|
+
const root = opts.tokensRoot.replace(/^\/+|\/+$/g, "");
|
|
4911
|
+
const tokenFiles = files.filter((f) => tokenFilesOf([f.filename], root).length);
|
|
4912
|
+
const source = { number: pull.number, base: {}, head: {}, files: tokenFiles.map((f) => tokenFilesOf([f.filename], root)[0]) };
|
|
4913
|
+
await Promise.all(
|
|
4914
|
+
tokenFiles.map(async (f) => {
|
|
4915
|
+
const rel = tokenFilesOf([f.filename], root)[0];
|
|
4916
|
+
source.base[rel] = f.status === "added" ? void 0 : await gh.readFile(f.previous_filename ?? f.filename, start);
|
|
4917
|
+
source.head[rel] = f.status === "removed" ? void 0 : await gh.readFile(f.filename, pull.headSha);
|
|
4918
|
+
})
|
|
4919
|
+
);
|
|
4920
|
+
return { number: pull.number, title: pull.title, tokens: true, edits: changeEdits(source) };
|
|
4921
|
+
}
|
|
4047
4922
|
export {
|
|
4048
|
-
|
|
4049
|
-
|
|
4923
|
+
ao as AAA_MIN,
|
|
4924
|
+
ap as AA_MIN,
|
|
4050
4925
|
ADMIN_ROLE_ID,
|
|
4051
4926
|
ANTHROPIC_MESSAGES_URL,
|
|
4052
4927
|
BELL,
|
|
4053
4928
|
BRAND_FONTS,
|
|
4054
4929
|
BRAND_SLUG_RE,
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4930
|
+
aq as BULK_LIMIT,
|
|
4931
|
+
q as COLOR_FIELDS,
|
|
4932
|
+
COMBINE_COMPARE_LIMIT,
|
|
4933
|
+
COMPARE_FILE_LIMIT,
|
|
4934
|
+
o as COMPONENT_SPECS_VERSION,
|
|
4058
4935
|
CONFIG_FILE,
|
|
4059
|
-
|
|
4060
|
-
|
|
4936
|
+
CONFLICT_REASON,
|
|
4937
|
+
ar as CONTRAST_CELL_MAX,
|
|
4938
|
+
as as CONTRAST_CHECKS_FILE,
|
|
4061
4939
|
ConfigError,
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4940
|
+
at as DEFAULT_COMPONENT_SPECS_FILE,
|
|
4941
|
+
au as DEFAULT_GOVERNANCE,
|
|
4942
|
+
av as DEFAULT_OVERRIDE,
|
|
4943
|
+
aw as DEFAULT_PRODUCTS,
|
|
4065
4944
|
DEFAULT_PROTECTED_BRANCHES,
|
|
4066
|
-
|
|
4945
|
+
ax as DEFAULT_RIGHTS,
|
|
4067
4946
|
DEFAULT_TOKEN_PATHS,
|
|
4068
|
-
|
|
4947
|
+
ay as DEFAULT_VISUAL_IMPACT,
|
|
4069
4948
|
DEMO_RELEASES,
|
|
4070
4949
|
DOCS_CHANGE_BUMP,
|
|
4071
4950
|
DOCS_DRAFT_MODEL,
|
|
4951
|
+
DOCS_EDITOR_ORIGIN,
|
|
4072
4952
|
DOCS_KEYS,
|
|
4953
|
+
DOCS_MCP_ORIGIN,
|
|
4073
4954
|
DOCS_SELECTION_MODES,
|
|
4074
4955
|
DRAFTS_DIR,
|
|
4075
4956
|
DRAFT_EXCHANGE_VERSION,
|
|
@@ -4100,91 +4981,111 @@ export {
|
|
|
4100
4981
|
FIGMA_REFERENCE_STORY,
|
|
4101
4982
|
GitHubClient,
|
|
4102
4983
|
GitHubError,
|
|
4103
|
-
|
|
4984
|
+
az as JsonParseError,
|
|
4104
4985
|
LIGHTNESS,
|
|
4105
4986
|
LayeredTokenSet,
|
|
4987
|
+
MAX_TOGETHER,
|
|
4988
|
+
MIN_TOGETHER,
|
|
4106
4989
|
NEUTRAL_ANCHORS,
|
|
4107
|
-
|
|
4990
|
+
OVERRIDE_HEADING,
|
|
4991
|
+
aA as OWNER_GROUPS,
|
|
4992
|
+
OverrideMergeError,
|
|
4108
4993
|
PIN_TOLERANCE,
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4994
|
+
aB as PREVIEW_APP_LABELS,
|
|
4995
|
+
aC as PREVIEW_COMMENT_MARKER,
|
|
4996
|
+
aD as PREVIEW_PLACEHOLDERS,
|
|
4997
|
+
aE as PRIMITIVE_MODE,
|
|
4998
|
+
aF as PRODUCT_LABEL_PREFIX,
|
|
4113
4999
|
PROSE_KEYS,
|
|
4114
5000
|
PROTECTION_PARTS,
|
|
4115
5001
|
PROTECTION_PART_PHRASE,
|
|
5002
|
+
PULL_STATE_PILL,
|
|
4116
5003
|
ProtectedBranchError,
|
|
4117
5004
|
RADIUS_PRESETS,
|
|
5005
|
+
aG as REDUCED_MOTION_NOTE,
|
|
4118
5006
|
REQUESTS_BRANCH,
|
|
4119
5007
|
REQUEST_KINDS,
|
|
4120
5008
|
REQUEST_LABEL,
|
|
4121
5009
|
REQUEST_STATUS_LABEL,
|
|
4122
|
-
|
|
4123
|
-
|
|
5010
|
+
REVIEW_STATUS_ORDER,
|
|
5011
|
+
aH as RIGHTS,
|
|
5012
|
+
aI as RIGHT_LABEL,
|
|
4124
5013
|
RISK_LABELS,
|
|
4125
|
-
|
|
5014
|
+
aJ as RISK_TIER,
|
|
5015
|
+
SHARE_EXPLAIN,
|
|
4126
5016
|
SIMILARITY_THRESHOLD,
|
|
4127
5017
|
SINGLE_MODE,
|
|
4128
|
-
|
|
5018
|
+
aK as SPEC_FIELDS,
|
|
4129
5019
|
STEPS,
|
|
4130
5020
|
TIER_AXIS,
|
|
4131
5021
|
TIER_NAMES,
|
|
4132
|
-
|
|
5022
|
+
aL as TIER_ORDER,
|
|
4133
5023
|
TOKEN_REJECTED_MESSAGE,
|
|
4134
5024
|
TOKEN_SETTINGS_URL,
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
5025
|
+
aM as TRIGGER_LABELS,
|
|
5026
|
+
aN as TeamDirectory,
|
|
5027
|
+
aO as UNRELEASED,
|
|
5028
|
+
aP as VISUAL_CHECK_LABEL,
|
|
4138
5029
|
acceptDocsDraft,
|
|
4139
5030
|
acceptDocsText,
|
|
4140
5031
|
addBrandToConfigText,
|
|
4141
|
-
|
|
5032
|
+
addedComment,
|
|
5033
|
+
aQ as affectedContrast,
|
|
4142
5034
|
d as aliasOf,
|
|
4143
5035
|
aliasTarget,
|
|
5036
|
+
allowedMergeMethods,
|
|
4144
5037
|
anatomyLabel,
|
|
4145
5038
|
anchoredRamp,
|
|
4146
5039
|
anthropicReplyText,
|
|
4147
5040
|
anthropicRequest,
|
|
4148
|
-
|
|
4149
|
-
|
|
5041
|
+
aR as appForProject,
|
|
5042
|
+
aS as appUrl,
|
|
4150
5043
|
applyConfirmedRenames,
|
|
4151
5044
|
applyDocsSections,
|
|
4152
5045
|
applyTokenEdits,
|
|
4153
5046
|
applyTokenMerge,
|
|
4154
|
-
|
|
4155
|
-
|
|
5047
|
+
aT as approverEntries,
|
|
5048
|
+
aU as approverLists,
|
|
5049
|
+
askOthersToReviewOnGitHub,
|
|
4156
5050
|
assertSafeBranch,
|
|
4157
5051
|
assignRequestOnGitHub,
|
|
4158
|
-
|
|
5052
|
+
aV as assignVersions,
|
|
4159
5053
|
base64ToUtf8,
|
|
4160
|
-
|
|
5054
|
+
aW as baseBranchOf,
|
|
4161
5055
|
bellChroma,
|
|
4162
|
-
|
|
5056
|
+
aX as branchName,
|
|
4163
5057
|
branchPreviewUrls,
|
|
4164
|
-
|
|
5058
|
+
aY as branchSlug,
|
|
5059
|
+
branchWithJiraKey,
|
|
4165
5060
|
brandDisplayName,
|
|
4166
5061
|
brandPreviewVariables,
|
|
4167
5062
|
buildFidelityReport,
|
|
4168
5063
|
buildHistoryEntry,
|
|
4169
5064
|
buildReport,
|
|
4170
|
-
|
|
5065
|
+
buildTokenSet,
|
|
4171
5066
|
buildUsageIndex,
|
|
4172
|
-
|
|
5067
|
+
canAddTo,
|
|
5068
|
+
canOverride,
|
|
5069
|
+
canShare,
|
|
5070
|
+
canShip,
|
|
4173
5071
|
changeEdits,
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
5072
|
+
aZ as changeKind,
|
|
5073
|
+
changeList,
|
|
5074
|
+
a_ as changeTable,
|
|
5075
|
+
a$ as changedModes,
|
|
5076
|
+
b0 as changesetBump,
|
|
4178
5077
|
checkBrandSlug,
|
|
4179
5078
|
checkLayeredPair,
|
|
4180
5079
|
choiceFor,
|
|
4181
5080
|
classifyAuth,
|
|
4182
|
-
|
|
5081
|
+
b1 as classifyLayered,
|
|
4183
5082
|
classifyRisk,
|
|
5083
|
+
cleanDocs,
|
|
4184
5084
|
cleanDocsText,
|
|
4185
5085
|
cleanDraftText,
|
|
4186
|
-
|
|
5086
|
+
b2 as clearRawDeep,
|
|
4187
5087
|
closeExplorationOnGitHub,
|
|
5088
|
+
closeOnGitHub,
|
|
4188
5089
|
closingIssueNumbers,
|
|
4189
5090
|
coerceValue,
|
|
4190
5091
|
collectHistory,
|
|
@@ -4195,28 +5096,35 @@ export {
|
|
|
4195
5096
|
combineChanges,
|
|
4196
5097
|
combineConflicts,
|
|
4197
5098
|
c as comboKey,
|
|
4198
|
-
|
|
5099
|
+
commentOnGitHub,
|
|
5100
|
+
commitFileOnGitHub,
|
|
5101
|
+
commitLibraryResolution,
|
|
5102
|
+
b3 as commitMessage,
|
|
4199
5103
|
m as compactJson,
|
|
4200
5104
|
compareImages,
|
|
4201
5105
|
componentForPath,
|
|
4202
|
-
|
|
5106
|
+
b4 as componentMotion,
|
|
5107
|
+
b5 as componentsUsingMotionToken,
|
|
5108
|
+
b6 as composeChangeset,
|
|
4203
5109
|
composeFiles,
|
|
4204
5110
|
composeNewBrandBody,
|
|
4205
5111
|
composePendingDrafts,
|
|
4206
|
-
|
|
4207
|
-
|
|
5112
|
+
b7 as composePrBody,
|
|
5113
|
+
b8 as composeProposalBody,
|
|
4208
5114
|
composeRequestIssue,
|
|
4209
|
-
|
|
4210
|
-
|
|
5115
|
+
b9 as composite,
|
|
5116
|
+
conflictRoute,
|
|
5117
|
+
ba as contextFor,
|
|
4211
5118
|
contextLabel,
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
5119
|
+
continueDocsProposalOnGitHub,
|
|
5120
|
+
bb as contrastDelta,
|
|
5121
|
+
bc as contrastDeltaTable,
|
|
5122
|
+
bd as contrastLevel,
|
|
5123
|
+
be as contrastPair,
|
|
5124
|
+
bf as contrastRatio,
|
|
4217
5125
|
contrastScope,
|
|
4218
|
-
|
|
4219
|
-
|
|
5126
|
+
bg as countChanges,
|
|
5127
|
+
bh as coverageSummary,
|
|
4220
5128
|
createDocsDraftSession,
|
|
4221
5129
|
createPendingDrafts,
|
|
4222
5130
|
cssModuleClasses,
|
|
@@ -4228,22 +5136,30 @@ export {
|
|
|
4228
5136
|
declineRequestOnGitHub,
|
|
4229
5137
|
decodePicks,
|
|
4230
5138
|
defaultChoiceFor,
|
|
4231
|
-
|
|
5139
|
+
k as defaultContext,
|
|
5140
|
+
defaultDocsFooter,
|
|
4232
5141
|
defaultFile,
|
|
5142
|
+
bi as defaultProduct,
|
|
4233
5143
|
defaultScopes,
|
|
4234
|
-
|
|
4235
|
-
|
|
5144
|
+
bj as defaultTitle,
|
|
5145
|
+
bk as defineConfig,
|
|
4236
5146
|
deletePendingDrafts,
|
|
4237
5147
|
demoHistory,
|
|
4238
|
-
|
|
4239
|
-
|
|
5148
|
+
bl as detectLayout,
|
|
5149
|
+
bm as diffLayered,
|
|
5150
|
+
dismissStaleFrom,
|
|
5151
|
+
docsAuthorLine,
|
|
5152
|
+
docsFileText,
|
|
4240
5153
|
docsPrompt,
|
|
5154
|
+
docsProposal,
|
|
4241
5155
|
docsSectionShapes,
|
|
4242
5156
|
docsSectionsAnswer,
|
|
4243
5157
|
docsSectionsResult,
|
|
4244
5158
|
docsSelectionPrompt,
|
|
5159
|
+
docsSlug,
|
|
4245
5160
|
docsTaskKeys,
|
|
4246
5161
|
docsTaskPrompt,
|
|
5162
|
+
docsText,
|
|
4247
5163
|
draftChangeLine,
|
|
4248
5164
|
draftImagePath,
|
|
4249
5165
|
draftItemBrief,
|
|
@@ -4252,14 +5168,16 @@ export {
|
|
|
4252
5168
|
draftSessionPath,
|
|
4253
5169
|
dtcgColor,
|
|
4254
5170
|
effectiveScopes,
|
|
5171
|
+
effectiveTier,
|
|
5172
|
+
eligibleReviewers,
|
|
4255
5173
|
emptyTokenFile,
|
|
4256
5174
|
encodePicks,
|
|
4257
5175
|
ensureRequestsBranch,
|
|
4258
|
-
|
|
4259
|
-
|
|
5176
|
+
bn as entriesFor,
|
|
5177
|
+
bo as entryLabel,
|
|
4260
5178
|
evaluatePairs,
|
|
4261
|
-
|
|
4262
|
-
|
|
5179
|
+
bp as expandEntries,
|
|
5180
|
+
bq as expandOwners,
|
|
4263
5181
|
explorationBranch,
|
|
4264
5182
|
explorationNote,
|
|
4265
5183
|
explorationNotePath,
|
|
@@ -4277,63 +5195,74 @@ export {
|
|
|
4277
5195
|
explorationTryItOnGitHub,
|
|
4278
5196
|
fidelityComponentLine,
|
|
4279
5197
|
fidelityVerdict,
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
5198
|
+
br as fieldChanges,
|
|
5199
|
+
bs as figmaCell,
|
|
5200
|
+
bt as fileModes,
|
|
5201
|
+
bu as filterHistory,
|
|
4283
5202
|
findExplorationOnGitHub,
|
|
5203
|
+
findOpenDocsProposal,
|
|
5204
|
+
findOpenDocsProposalOnGitHub,
|
|
4284
5205
|
findPreviewComment,
|
|
5206
|
+
bv as findProduct,
|
|
4285
5207
|
findReferenceProblems,
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
5208
|
+
bw as findToken,
|
|
5209
|
+
bx as findVercelComment,
|
|
5210
|
+
by as firstParagraph,
|
|
5211
|
+
n as flattenLayerFile,
|
|
4290
5212
|
flattenTokens,
|
|
4291
5213
|
formatChangeset,
|
|
4292
5214
|
formatDocsJson,
|
|
4293
|
-
|
|
4294
|
-
|
|
5215
|
+
bz as formatNumber,
|
|
5216
|
+
bA as formatOwnerEntry,
|
|
4295
5217
|
formatValue,
|
|
5218
|
+
freshness,
|
|
4296
5219
|
friendlyMessage,
|
|
4297
5220
|
fromHex,
|
|
4298
5221
|
fromPlain,
|
|
4299
5222
|
generateRamp,
|
|
4300
|
-
|
|
5223
|
+
bB as getChild,
|
|
4301
5224
|
getEntry,
|
|
4302
5225
|
gitHubHistoryBackend,
|
|
4303
|
-
|
|
4304
|
-
|
|
5226
|
+
g as globToRegExp,
|
|
5227
|
+
bC as groupsPhrase,
|
|
5228
|
+
hasWriteAccess,
|
|
4305
5229
|
h as hashSpec,
|
|
4306
5230
|
h2 as hexToRgba,
|
|
4307
|
-
|
|
5231
|
+
bD as holdersOf,
|
|
4308
5232
|
i as iconComponentName,
|
|
4309
5233
|
idExtensionFor,
|
|
4310
5234
|
impactedModules,
|
|
4311
5235
|
importsIn,
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
5236
|
+
bE as indexByPath,
|
|
5237
|
+
bF as inheritedTypeAt,
|
|
5238
|
+
bG as installCommand,
|
|
5239
|
+
bH as installCommandFromPreviewComment,
|
|
4316
5240
|
isActiveExploration,
|
|
4317
5241
|
isAlias,
|
|
5242
|
+
isAutomationPull,
|
|
5243
|
+
isDocsProposalBranch,
|
|
4318
5244
|
isDocsSelectionMode,
|
|
4319
5245
|
isDraftSessionId,
|
|
4320
5246
|
isExplorationBranch,
|
|
4321
5247
|
isExplorationPull,
|
|
4322
|
-
|
|
5248
|
+
bI as isGuardrail,
|
|
4323
5249
|
isHex,
|
|
5250
|
+
isJiraKey,
|
|
4324
5251
|
isMissingPermission,
|
|
4325
5252
|
isMultiMode,
|
|
4326
5253
|
isPlanRefusal,
|
|
4327
5254
|
isProseKey,
|
|
4328
|
-
|
|
4329
|
-
|
|
5255
|
+
isReleasePull,
|
|
5256
|
+
bJ as isTeamEntry,
|
|
5257
|
+
bK as isTokenFile,
|
|
4330
5258
|
isTokenRejected,
|
|
4331
|
-
|
|
5259
|
+
bL as isVercelComment,
|
|
4332
5260
|
jiraIssueFields,
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
5261
|
+
jiraKeyOf,
|
|
5262
|
+
bM as kindForMin,
|
|
5263
|
+
bN as layeredImpact,
|
|
5264
|
+
o2 as layeredParityIssues,
|
|
5265
|
+
layoutOptions,
|
|
4337
5266
|
levenshtein,
|
|
4338
5267
|
linkRequestToExplorationOnGitHub,
|
|
4339
5268
|
listBrands,
|
|
@@ -4344,17 +5273,20 @@ export {
|
|
|
4344
5273
|
loadProtection,
|
|
4345
5274
|
loadRepoFiles,
|
|
4346
5275
|
loadRequiredChecks,
|
|
4347
|
-
|
|
5276
|
+
bO as luminance,
|
|
5277
|
+
mailtoHref,
|
|
4348
5278
|
markClaudeDraft,
|
|
4349
5279
|
mergeDocsDraft,
|
|
4350
5280
|
mergeDraftResults,
|
|
5281
|
+
mergeGate,
|
|
5282
|
+
mergeOnGitHub,
|
|
4351
5283
|
mergeReleases,
|
|
4352
5284
|
missingChoices,
|
|
4353
5285
|
missingPermissionMessage,
|
|
4354
5286
|
missingProse,
|
|
4355
5287
|
modeDisplayName,
|
|
4356
5288
|
modeFile,
|
|
4357
|
-
|
|
5289
|
+
bP as modeLabel,
|
|
4358
5290
|
modesOf$1 as modesOf,
|
|
4359
5291
|
moveToken,
|
|
4360
5292
|
nearestStep,
|
|
@@ -4363,91 +5295,125 @@ export {
|
|
|
4363
5295
|
neutralOffset,
|
|
4364
5296
|
newBrandSection,
|
|
4365
5297
|
newDraftSessionId,
|
|
4366
|
-
|
|
4367
|
-
|
|
5298
|
+
bQ as nodesEqual,
|
|
5299
|
+
bR as normalizeRights,
|
|
4368
5300
|
oklch,
|
|
4369
5301
|
oklchToLinear,
|
|
4370
|
-
|
|
4371
|
-
|
|
5302
|
+
onlyTokens,
|
|
5303
|
+
openPulls,
|
|
5304
|
+
overlap,
|
|
5305
|
+
overrideComment,
|
|
5306
|
+
bS as overrideKey,
|
|
5307
|
+
overrideMergeOnGitHub,
|
|
5308
|
+
overrideReasonProblem,
|
|
5309
|
+
overrideRefusedComment,
|
|
5310
|
+
bT as ownerEntryProblems,
|
|
5311
|
+
packageDir,
|
|
4372
5312
|
packageDirCandidates,
|
|
4373
|
-
|
|
5313
|
+
bU as pairKey,
|
|
4374
5314
|
parseChangelog,
|
|
4375
|
-
|
|
5315
|
+
bV as parseChangesetText,
|
|
4376
5316
|
parseColor,
|
|
4377
|
-
|
|
5317
|
+
bW as parseComponentSpecs,
|
|
4378
5318
|
parseContrastChecks,
|
|
4379
|
-
|
|
5319
|
+
q2 as parseDimension,
|
|
4380
5320
|
parseDocsReply,
|
|
4381
5321
|
parseDocsTaskAnswer,
|
|
4382
5322
|
parseExplorationBody,
|
|
5323
|
+
parseJiraKey,
|
|
4383
5324
|
parseJson,
|
|
4384
|
-
|
|
5325
|
+
parseOverrideComment,
|
|
5326
|
+
bX as parseOwnerEntry,
|
|
4385
5327
|
parsePendingDrafts,
|
|
4386
5328
|
parseRepo,
|
|
4387
5329
|
parseRequestIssue,
|
|
4388
5330
|
parseStructureChange,
|
|
5331
|
+
parseTier,
|
|
4389
5332
|
parseTokenFile,
|
|
4390
|
-
|
|
5333
|
+
bY as parseVercelComment,
|
|
4391
5334
|
patchDocsJson,
|
|
4392
5335
|
pathId,
|
|
4393
5336
|
pathSimilarity,
|
|
5337
|
+
pendingApprovals,
|
|
4394
5338
|
permissionLabel,
|
|
4395
5339
|
pickedOption,
|
|
4396
5340
|
plainEqual,
|
|
4397
5341
|
planNewBrand,
|
|
4398
5342
|
planReference,
|
|
4399
5343
|
planReferences,
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
5344
|
+
bZ as planRestore,
|
|
5345
|
+
b_ as planRevert,
|
|
5346
|
+
b$ as planToJson,
|
|
4403
5347
|
planTokenMerge,
|
|
4404
5348
|
e as prNumberFromSubject,
|
|
4405
5349
|
prefillDocs,
|
|
4406
|
-
|
|
5350
|
+
c0 as previewComment,
|
|
4407
5351
|
previewLinks,
|
|
4408
|
-
|
|
5352
|
+
c1 as previewLinksOf,
|
|
4409
5353
|
previewOverrides,
|
|
4410
|
-
|
|
5354
|
+
c2 as previewUrl,
|
|
4411
5355
|
previewsSettled,
|
|
4412
|
-
|
|
5356
|
+
c3 as productForPlace,
|
|
5357
|
+
c4 as productLabel,
|
|
5358
|
+
c5 as productMarks,
|
|
5359
|
+
c6 as productOfBranch,
|
|
5360
|
+
productPreview,
|
|
5361
|
+
c7 as productProblems,
|
|
5362
|
+
c8 as productsOf,
|
|
5363
|
+
c9 as projectOfLabel,
|
|
4413
5364
|
promoteExplorationOnGitHub,
|
|
4414
5365
|
promotionPlan,
|
|
5366
|
+
proposeDocsOnGitHub,
|
|
4415
5367
|
propsTable,
|
|
4416
5368
|
protectLibrary,
|
|
4417
5369
|
protectRefusal,
|
|
4418
|
-
|
|
5370
|
+
ca as protectedBranchesOf,
|
|
4419
5371
|
protectionRuleset,
|
|
4420
5372
|
protectionRulesetName,
|
|
4421
5373
|
protectionSentence,
|
|
4422
5374
|
protectionState,
|
|
5375
|
+
pullState,
|
|
4423
5376
|
pushChangeRows,
|
|
4424
5377
|
rateLimitMessage,
|
|
5378
|
+
readCombineBranch,
|
|
5379
|
+
readLibraryConflicts,
|
|
4425
5380
|
readPendingDrafts,
|
|
4426
5381
|
readPreviewComment,
|
|
4427
5382
|
readTokenAt,
|
|
4428
5383
|
reconcileIds,
|
|
4429
5384
|
referenceImagePath,
|
|
5385
|
+
releasePull,
|
|
4430
5386
|
releasesFromTags,
|
|
4431
5387
|
removeToken,
|
|
4432
5388
|
removedSide,
|
|
4433
|
-
|
|
5389
|
+
cb as renderedColor,
|
|
5390
|
+
reopenEligibility,
|
|
5391
|
+
reopenOnGitHub,
|
|
4434
5392
|
requestBrief,
|
|
4435
5393
|
requestImagePath,
|
|
4436
5394
|
requestIssueTitle,
|
|
4437
5395
|
requestStatus,
|
|
5396
|
+
requiredApprovals,
|
|
4438
5397
|
requiredCheckNames,
|
|
4439
5398
|
requiredPermission,
|
|
5399
|
+
resolutionMessage,
|
|
4440
5400
|
resolve,
|
|
4441
5401
|
resolveBranchPreviews,
|
|
5402
|
+
resolvedTokenFiles,
|
|
5403
|
+
reviewOnGitHub,
|
|
5404
|
+
reviewQueue,
|
|
5405
|
+
reviewStatusKind,
|
|
5406
|
+
reviewersToAsk,
|
|
4442
5407
|
r as rgbaToDtcg,
|
|
4443
5408
|
rgbaToHex,
|
|
4444
|
-
|
|
5409
|
+
cc as rightsProblems,
|
|
4445
5410
|
round3,
|
|
4446
5411
|
rulesSettingsUrl,
|
|
4447
5412
|
rulesetTargetsDefault,
|
|
4448
|
-
|
|
5413
|
+
cd as runLayeredContrast,
|
|
5414
|
+
ce as sameProducts,
|
|
4449
5415
|
sameScopes,
|
|
4450
|
-
|
|
5416
|
+
cf as sameToken,
|
|
4451
5417
|
sanitizeDocsReplacement,
|
|
4452
5418
|
sanitizeDocsResult,
|
|
4453
5419
|
sanitizeDocsSections,
|
|
@@ -4461,60 +5427,76 @@ export {
|
|
|
4461
5427
|
serializePendingDrafts,
|
|
4462
5428
|
serializeTokenFile,
|
|
4463
5429
|
shadowedEdits,
|
|
4464
|
-
|
|
5430
|
+
shareDescription,
|
|
5431
|
+
shareMessage,
|
|
5432
|
+
shareState,
|
|
5433
|
+
cg as shipPolicy,
|
|
4465
5434
|
shortVariant,
|
|
4466
5435
|
shortVariants,
|
|
5436
|
+
showShare,
|
|
4467
5437
|
slugify,
|
|
4468
|
-
|
|
5438
|
+
ch as snapshotUrl,
|
|
4469
5439
|
s as specHash,
|
|
5440
|
+
stableCommand,
|
|
4470
5441
|
startExplorationOnGitHub,
|
|
5442
|
+
stateSentence,
|
|
5443
|
+
summarizeApprovals,
|
|
5444
|
+
summarizeCi,
|
|
4471
5445
|
summarizeComponent,
|
|
4472
|
-
|
|
5446
|
+
ci as summarizeTokenChange,
|
|
4473
5447
|
takeMine,
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
5448
|
+
cj as tarballAssetName,
|
|
5449
|
+
ck as tarballUrl,
|
|
5450
|
+
cl as teamKey,
|
|
5451
|
+
cm as teamKeyOf,
|
|
5452
|
+
cn as teamUnreadableMessage,
|
|
5453
|
+
co as teamsIn,
|
|
4480
5454
|
threeWayDiff,
|
|
4481
5455
|
tintedNeutral,
|
|
4482
|
-
|
|
5456
|
+
cp as titleOf,
|
|
5457
|
+
titleWithJiraKey,
|
|
4483
5458
|
t2 as toAlias,
|
|
4484
|
-
|
|
5459
|
+
cq as toChangeSummary,
|
|
4485
5460
|
toConfigJson,
|
|
4486
5461
|
toHex,
|
|
4487
|
-
|
|
5462
|
+
cr as toMap,
|
|
4488
5463
|
toOklch,
|
|
4489
5464
|
toPlain,
|
|
4490
|
-
|
|
5465
|
+
cs as tokenBump,
|
|
4491
5466
|
t as tokenCssVar,
|
|
4492
|
-
|
|
4493
|
-
|
|
5467
|
+
tokenFilesOf,
|
|
5468
|
+
ct as tokenHistory,
|
|
5469
|
+
cu as tokenNode,
|
|
4494
5470
|
tokenUsage,
|
|
4495
5471
|
tokensPath,
|
|
5472
|
+
touchesPackages,
|
|
5473
|
+
tryInProductUrl,
|
|
5474
|
+
tryTogetherUrl,
|
|
4496
5475
|
unpicked,
|
|
4497
|
-
|
|
4498
|
-
|
|
5476
|
+
unreadableShipTeams,
|
|
5477
|
+
cv as unreadableTeams,
|
|
4499
5478
|
updatePendingDrafts,
|
|
4500
5479
|
upsertToken,
|
|
4501
5480
|
utf8ToBase64,
|
|
4502
5481
|
v as validateComponentSpec,
|
|
5482
|
+
validateDocs,
|
|
4503
5483
|
validateDocsTask,
|
|
4504
5484
|
validateIntegrations,
|
|
4505
5485
|
validateLayered,
|
|
4506
5486
|
validatePendingDrafts,
|
|
4507
5487
|
validateProposal,
|
|
4508
5488
|
validateSet,
|
|
4509
|
-
|
|
5489
|
+
cw as valueIn,
|
|
4510
5490
|
u as valueToCss,
|
|
4511
5491
|
valuesEqual,
|
|
4512
5492
|
variantKeyOf,
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
5493
|
+
cx as variantName,
|
|
5494
|
+
cy as versionTimeline,
|
|
5495
|
+
cz as visualImpactProblems,
|
|
5496
|
+
whoCanShipSentence,
|
|
5497
|
+
withAuthorLine,
|
|
4517
5498
|
withRequestExploration,
|
|
5499
|
+
withoutJiraKey,
|
|
4518
5500
|
writeDraftResults
|
|
4519
5501
|
};
|
|
4520
5502
|
//# sourceMappingURL=index.js.map
|