@bridge-ds/core 0.5.1 → 0.7.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-cvrSvIZj.js → component-spec-1z2ih4E3.js} +466 -191
- package/dist/component-spec-1z2ih4E3.js.map +1 -0
- package/dist/component-spec.d.ts +238 -7
- package/dist/config.d.ts +4 -0
- package/dist/docs-proposal.d.ts +82 -0
- package/dist/explorations.d.ts +2 -0
- package/dist/fidelity-references.d.ts +78 -0
- package/dist/fidelity.d.ts +138 -0
- package/dist/github-client.d.ts +89 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1490 -307
- 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 +59 -4
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +2020 -119
- package/dist/node/index.js.map +1 -1
- package/dist/node/spec-css.d.ts +11 -0
- package/dist/node/spec-motion.d.ts +66 -0
- package/dist/products.d.ts +56 -0
- package/dist/review-gate.d.ts +251 -0
- package/dist/rights.d.ts +2 -2
- 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-cvrSvIZj.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-1z2ih4E3.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, s, ch, ci, cj, ck, cl, cm, cn, co, cp, cq, cr, t, cs, ct, cu, v, cv, cw, cx, cy } from "./component-spec-1z2ih4E3.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, g
|
|
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,29 +221,29 @@ 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);
|
|
237
237
|
}
|
|
238
238
|
const byGit = /* @__PURE__ */ new Map();
|
|
239
|
-
for (const
|
|
240
|
-
const prev = byGit.get(
|
|
241
|
-
if (!prev ||
|
|
239
|
+
for (const s2 of suggestions) {
|
|
240
|
+
const prev = byGit.get(s2.gitId);
|
|
241
|
+
if (!prev || s2.similarity > prev.similarity) byGit.set(s2.gitId, s2);
|
|
242
242
|
}
|
|
243
243
|
return { tokens, adoptedByName, suggestions: [...byGit.values()] };
|
|
244
244
|
}
|
|
245
245
|
function applyConfirmedRenames(tokens, confirmed) {
|
|
246
|
-
const map = new Map(confirmed.map((
|
|
246
|
+
const map = new Map(confirmed.map((s2) => [s2.figmaId, s2.gitId]));
|
|
247
247
|
return tokens.map((t3) => map.has(t3.id) ? { ...t3, id: map.get(t3.id) } : t3);
|
|
248
248
|
}
|
|
249
249
|
function idExtensionFor(t3) {
|
|
@@ -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 = [];
|
|
@@ -405,7 +405,7 @@ function combineConflicts(changes) {
|
|
|
405
405
|
}
|
|
406
406
|
function pickedOption(c10, picks) {
|
|
407
407
|
const n2 = picks[c10.key];
|
|
408
|
-
return n2 === void 0 ? null : c10.options.find((
|
|
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 = {}) {
|
|
@@ -664,8 +664,8 @@ function protectionSentence(state) {
|
|
|
664
664
|
case "unprotected":
|
|
665
665
|
return "Your library can be changed without review.";
|
|
666
666
|
case "partial": {
|
|
667
|
-
const
|
|
668
|
-
return `Partly protected: ${
|
|
667
|
+
const s2 = joinAnd(state.missing.map((p) => PROTECTION_PART_PHRASE[p]));
|
|
668
|
+
return `Partly protected: ${s2}.`;
|
|
669
669
|
}
|
|
670
670
|
default:
|
|
671
671
|
return "Protection status unknown: GitHub did not let this token read it.";
|
|
@@ -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. */
|
|
@@ -1068,9 +1068,9 @@ 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
1076
|
async getIssue(n2) {
|
|
@@ -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) {
|
|
@@ -1266,7 +1266,7 @@ class GitHubClient {
|
|
|
1266
1266
|
]);
|
|
1267
1267
|
const checks = [
|
|
1268
1268
|
...runs.map((c10) => ({ name: c10.name, status: c10.status, conclusion: c10.conclusion })),
|
|
1269
|
-
...combined.statuses.map((
|
|
1269
|
+
...combined.statuses.map((s2) => ({ name: s2.context, status: s2.state === "pending" ? "in_progress" : "completed", conclusion: s2.state === "pending" ? null : s2.state }))
|
|
1270
1270
|
];
|
|
1271
1271
|
const failing = checks.some((c10) => c10.conclusion && !["success", "neutral", "skipped"].includes(c10.conclusion));
|
|
1272
1272
|
const pending = checks.some((c10) => c10.status !== "completed");
|
|
@@ -1275,7 +1275,7 @@ class GitHubClient {
|
|
|
1275
1275
|
/** Commit statuses of a commit, newest first (Vercel reports each project as "Vercel – <project>"). */
|
|
1276
1276
|
async listCommitStatuses(sha) {
|
|
1277
1277
|
const r2 = await this.request("GET", `${this.repoPath}/commits/${encodeRef(sha)}/statuses?per_page=100`);
|
|
1278
|
-
return r2.map((
|
|
1278
|
+
return r2.map((s2) => ({ context: s2.context, state: s2.state, target_url: s2.target_url ?? null, description: s2.description ?? null }));
|
|
1279
1279
|
}
|
|
1280
1280
|
/**
|
|
1281
1281
|
* GitHub deployments of a commit with their latest status (environment "Preview – <project>", environment_url the
|
|
@@ -1288,14 +1288,14 @@ class GitHubClient {
|
|
|
1288
1288
|
return Promise.all(
|
|
1289
1289
|
[...latest.values()].map(async (d2) => {
|
|
1290
1290
|
const st = await this.request("GET", `${this.repoPath}/deployments/${d2.id}/statuses?per_page=1`).catch(() => []);
|
|
1291
|
-
const
|
|
1292
|
-
return { id: d2.id, environment: d2.environment, state:
|
|
1291
|
+
const s2 = st[0];
|
|
1292
|
+
return { id: d2.id, environment: d2.environment, state: s2?.state ?? null, environmentUrl: s2?.environment_url || null };
|
|
1293
1293
|
})
|
|
1294
1294
|
);
|
|
1295
1295
|
}
|
|
1296
1296
|
async getPullRequest(n2) {
|
|
1297
1297
|
const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}`);
|
|
1298
|
-
return { ...toSummary(r2), body: r2.body ?? "", mergeable: r2.mergeable ?? null };
|
|
1298
|
+
return { ...toSummary(r2), body: r2.body ?? "", mergeable: r2.mergeable ?? null, requestedReviewers: (r2.requested_reviewers ?? []).map((x) => x.login) };
|
|
1299
1299
|
}
|
|
1300
1300
|
async listPullRequestFiles(n2) {
|
|
1301
1301
|
const r2 = await this.request("GET", `${this.repoPath}/pulls/${n2}/files?per_page=100`);
|
|
@@ -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));
|
|
@@ -1816,19 +1910,19 @@ const LIGHTNESS = { 50: 0.975, 100: 0.952, 200: 0.912, 300: 0.85, 400: 0.735, 50
|
|
|
1816
1910
|
const BELL = { 50: 0.12, 100: 0.22, 200: 0.4, 300: 0.62, 400: 0.85, 500: 1, 600: 1, 700: 0.92, 800: 0.8, 900: 0.62, 950: 0.5 };
|
|
1817
1911
|
const NEUTRAL_ANCHORS = { 200: "#E3E8EE", 600: "#5B6672", 900: "#15202B" };
|
|
1818
1912
|
const PIN_TOLERANCE = 0.04;
|
|
1819
|
-
const bellChroma = (peak) => (
|
|
1820
|
-
const neutralChroma = (scale = 1, low = 4e-3, base = 0.01, slope = 0.022) => (
|
|
1821
|
-
function oklchToLinear(L,
|
|
1913
|
+
const bellChroma = (peak) => (s2) => peak * BELL[s2];
|
|
1914
|
+
const neutralChroma = (scale = 1, low = 4e-3, base = 0.01, slope = 0.022) => (s2) => (s2 <= 100 ? low : base + slope * (s2 / 950)) * scale;
|
|
1915
|
+
function oklchToLinear(L, C, h3) {
|
|
1822
1916
|
const hr = h3 * Math.PI / 180;
|
|
1823
|
-
const a =
|
|
1824
|
-
const b =
|
|
1917
|
+
const a = C * Math.cos(hr);
|
|
1918
|
+
const b = C * Math.sin(hr);
|
|
1825
1919
|
const l2 = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
1826
1920
|
const m2 = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
1827
|
-
const
|
|
1921
|
+
const s2 = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
1828
1922
|
return [
|
|
1829
|
-
4.0767416621 * l2 - 3.3077115913 * m2 + 0.2309699292 *
|
|
1830
|
-
-1.2684380046 * l2 + 2.6097574011 * m2 - 0.3413193965 *
|
|
1831
|
-
-0.0041960863 * l2 - 0.7034186147 * m2 + 1.707614701 *
|
|
1923
|
+
4.0767416621 * l2 - 3.3077115913 * m2 + 0.2309699292 * s2,
|
|
1924
|
+
-1.2684380046 * l2 + 2.6097574011 * m2 - 0.3413193965 * s2,
|
|
1925
|
+
-0.0041960863 * l2 - 0.7034186147 * m2 + 1.707614701 * s2
|
|
1832
1926
|
];
|
|
1833
1927
|
}
|
|
1834
1928
|
const encode = (x) => {
|
|
@@ -1836,8 +1930,8 @@ const encode = (x) => {
|
|
|
1836
1930
|
return c10 <= 31308e-7 ? 12.92 * c10 : 1.055 * c10 ** (1 / 2.4) - 0.055;
|
|
1837
1931
|
};
|
|
1838
1932
|
const decode = (x) => x <= 0.04045 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
|
|
1839
|
-
function oklch(L,
|
|
1840
|
-
let c10 =
|
|
1933
|
+
function oklch(L, C, h3) {
|
|
1934
|
+
let c10 = C;
|
|
1841
1935
|
let lin = oklchToLinear(L, c10, h3);
|
|
1842
1936
|
for (let i2 = 0; i2 < 60 && !lin.every((x) => x >= -5e-4 && x <= 1.0005); i2++) {
|
|
1843
1937
|
c10 *= 0.95;
|
|
@@ -1846,18 +1940,18 @@ function oklch(L, C2, 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
|
|
1853
|
-
const L = 0.2104542553 * l2 + 0.793617785 * m2 - 0.0040720468 *
|
|
1854
|
-
const A = 1.9779984951 * l2 - 2.428592205 * m2 + 0.4505937099 *
|
|
1855
|
-
const B = 0.0259040371 * l2 + 0.7827717662 * m2 - 0.808675766 *
|
|
1856
|
-
const
|
|
1857
|
-
const h3 =
|
|
1858
|
-
return { L, C
|
|
1859
|
-
}
|
|
1860
|
-
const isHex = (
|
|
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);
|
|
1947
|
+
const L = 0.2104542553 * l2 + 0.793617785 * m2 - 0.0040720468 * s2;
|
|
1948
|
+
const A = 1.9779984951 * l2 - 2.428592205 * m2 + 0.4505937099 * s2;
|
|
1949
|
+
const B = 0.0259040371 * l2 + 0.7827717662 * m2 - 0.808675766 * s2;
|
|
1950
|
+
const C = Math.sqrt(A * A + B * B);
|
|
1951
|
+
const h3 = C < 1e-6 ? 0 : (Math.atan2(B, A) * 180 / Math.PI + 360) % 360;
|
|
1952
|
+
return { L, C, h: h3 };
|
|
1953
|
+
}
|
|
1954
|
+
const isHex = (s2) => typeof s2 === "string" && /^#[0-9a-f]{6}$/i.test(s2.trim());
|
|
1861
1955
|
const fromHex = (hex) => [1, 3, 5].map((i2) => parseInt(hex.slice(i2, i2 + 2), 16) / 255);
|
|
1862
1956
|
const round3 = (x) => Math.round(x * 1e3) / 1e3;
|
|
1863
1957
|
const toHex = (rgb) => "#" + rgb.map((x) => Math.round(x * 255).toString(16).padStart(2, "0")).join("");
|
|
@@ -1868,35 +1962,35 @@ function dtcgColor(rgb, alpha) {
|
|
|
1868
1962
|
}
|
|
1869
1963
|
function generateRamp({ hue, chroma, offset = () => 0, pins = {} }) {
|
|
1870
1964
|
const out = {};
|
|
1871
|
-
for (const
|
|
1872
|
-
const pin = pins[
|
|
1873
|
-
out[
|
|
1965
|
+
for (const s2 of STEPS) {
|
|
1966
|
+
const pin = pins[s2];
|
|
1967
|
+
out[s2] = pin ? fromHex(pin) : oklch(LIGHTNESS[s2] + offset(s2), chroma(s2), hue);
|
|
1874
1968
|
}
|
|
1875
1969
|
return out;
|
|
1876
1970
|
}
|
|
1877
1971
|
function nearestStep(rgb) {
|
|
1878
1972
|
const { L } = toOklch(rgb);
|
|
1879
1973
|
let best = STEPS[0];
|
|
1880
|
-
for (const
|
|
1974
|
+
for (const s2 of STEPS) if (Math.abs(LIGHTNESS[s2] - L) < Math.abs(LIGHTNESS[best] - L)) best = s2;
|
|
1881
1975
|
return best;
|
|
1882
1976
|
}
|
|
1883
1977
|
function anchoredRamp(hex, pinStep) {
|
|
1884
1978
|
const rgb = fromHex(hex);
|
|
1885
|
-
const { L, C
|
|
1979
|
+
const { L, C, h: h3 } = toOklch(rgb);
|
|
1886
1980
|
const step = pinStep ?? nearestStep(rgb);
|
|
1887
|
-
const peak = Math.min(0.24, Math.max(0.02,
|
|
1981
|
+
const peak = Math.min(0.24, Math.max(0.02, C / BELL[step]));
|
|
1888
1982
|
return { hue: h3, peak, step, distance: Math.abs(L - LIGHTNESS[step]), ramp: generateRamp({ hue: h3, chroma: bellChroma(peak), pins: { [step]: hex.toUpperCase() } }) };
|
|
1889
1983
|
}
|
|
1890
1984
|
function neutralOffset() {
|
|
1891
1985
|
const off = {};
|
|
1892
|
-
for (const [
|
|
1893
|
-
return (
|
|
1986
|
+
for (const [s2, hex] of Object.entries(NEUTRAL_ANCHORS)) off[s2] = toOklch(fromHex(hex)).L - LIGHTNESS[s2];
|
|
1987
|
+
return (s2) => off[s2] ?? 0;
|
|
1894
1988
|
}
|
|
1895
1989
|
function tintedNeutral(hex) {
|
|
1896
1990
|
const rgb = fromHex(hex);
|
|
1897
|
-
const { C
|
|
1991
|
+
const { C, h: h3 } = toOklch(rgb);
|
|
1898
1992
|
const ref = neutralChroma()(nearestStep(rgb));
|
|
1899
|
-
const scale =
|
|
1993
|
+
const scale = C < 4e-3 ? 0 : Math.min(2.5, C / ref);
|
|
1900
1994
|
return { hue: h3, scale, ramp: generateRamp({ hue: h3, chroma: neutralChroma(scale), offset: neutralOffset() }) };
|
|
1901
1995
|
}
|
|
1902
1996
|
const BRAND_FONTS = [
|
|
@@ -1982,9 +2076,9 @@ function rampFor(input, pinStep, kind) {
|
|
|
1982
2076
|
function aliasRamp(doc, role, ramp, description) {
|
|
1983
2077
|
const group = getAt(doc, `palette.${role}`);
|
|
1984
2078
|
if (!group) return;
|
|
1985
|
-
for (const
|
|
1986
|
-
if (!(String(
|
|
1987
|
-
setAt(doc, `palette.${role}.${
|
|
2079
|
+
for (const s2 of STEPS) {
|
|
2080
|
+
if (!(String(s2) in group)) continue;
|
|
2081
|
+
setAt(doc, `palette.${role}.${s2}`, { $value: `{color.${ramp}.${s2}}` });
|
|
1988
2082
|
}
|
|
1989
2083
|
group.$description = description;
|
|
1990
2084
|
}
|
|
@@ -2031,15 +2125,15 @@ function listBrands(source) {
|
|
|
2031
2125
|
}
|
|
2032
2126
|
function checkBrandSlug(config, files, slug) {
|
|
2033
2127
|
const out = [];
|
|
2034
|
-
const
|
|
2035
|
-
if (!
|
|
2036
|
-
if (
|
|
2128
|
+
const s2 = slug.trim();
|
|
2129
|
+
if (!s2) return [{ level: "error", code: "slug-empty", message: "Give the brand a name." }];
|
|
2130
|
+
if (s2.length < 2 || s2.length > 24 || !BRAND_SLUG_RE.test(s2)) out.push({ level: "error", code: "slug-format", message: 'Use 2 to 24 lowercase letters, digits and single dashes, starting with a letter: "acme" or "north-star".' });
|
|
2037
2131
|
const taken = Object.values(config.axes).flatMap((a) => a.values);
|
|
2038
|
-
if (config.axes.brand?.values.includes(
|
|
2039
|
-
else if (taken.includes(
|
|
2132
|
+
if (config.axes.brand?.values.includes(s2)) out.push({ level: "error", code: "slug-taken", message: `A brand called "${s2}" already exists. Pick another name or edit that brand.` });
|
|
2133
|
+
else if (taken.includes(s2) || RESERVED.has(s2)) out.push({ level: "error", code: "slug-reserved", message: `"${s2}" is a reserved mode name. Pick another name.` });
|
|
2040
2134
|
const layer = brandLayers(config).brand;
|
|
2041
2135
|
const map = fileMap(files);
|
|
2042
|
-
if (layer && !config.axes.brand?.values.includes(
|
|
2136
|
+
if (layer && !config.axes.brand?.values.includes(s2) && map.has(fill(layer, { brand: s2 }))) out.push({ level: "error", code: "slug-file-exists", message: `${tokensPath(config, fill(layer, { brand: s2 }))} already exists. Pick another name or add it to bridge.config.json.` });
|
|
2043
2137
|
return out;
|
|
2044
2138
|
}
|
|
2045
2139
|
function planNewBrand(source, input) {
|
|
@@ -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
|
`);
|
|
@@ -2219,7 +2313,7 @@ function planNewBrand(source, input) {
|
|
|
2219
2313
|
const ramps = {};
|
|
2220
2314
|
if (inGate) {
|
|
2221
2315
|
const combo = after.normalizeCombo({ brand: slug });
|
|
2222
|
-
const read = (role) => STEPS.map((
|
|
2316
|
+
const read = (role) => STEPS.map((s2) => ({ step: s2, hex: after.resolve(`palette.${role}.${s2}`, combo).css })).filter((x) => /^#[0-9a-f]{6}$/i.test(x.hex));
|
|
2223
2317
|
ramps.primary = read("primary");
|
|
2224
2318
|
ramps.neutral = read("neutral");
|
|
2225
2319
|
for (const role of ["accent", "link"]) {
|
|
@@ -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 });
|
|
@@ -2281,13 +2375,13 @@ function brandPreviewVariables(source, plan, theme) {
|
|
|
2281
2375
|
}
|
|
2282
2376
|
return out;
|
|
2283
2377
|
}
|
|
2284
|
-
const code = (
|
|
2378
|
+
const code = (s2) => `\`${s2}\``;
|
|
2285
2379
|
function newBrandSection(plan) {
|
|
2286
2380
|
const roles = Object.keys(plan.ramps);
|
|
2287
2381
|
const rampTable = roles.length ? [
|
|
2288
2382
|
`| Step | ${roles.map((r2) => r2[0].toUpperCase() + r2.slice(1)).join(" | ")} |`,
|
|
2289
2383
|
`| --- | ${roles.map(() => "---").join(" | ")} |`,
|
|
2290
|
-
...STEPS.map((
|
|
2384
|
+
...STEPS.map((s2) => `| ${s2}${s2 === plan.actionStep ? " (actions)" : ""} | ${roles.map((r2) => code(plan.ramps[r2].find((x) => x.step === s2)?.hex ?? "")).join(" | ")} |`)
|
|
2291
2385
|
].join("\n") : "_No ramps resolved._";
|
|
2292
2386
|
const c10 = plan.contrast;
|
|
2293
2387
|
const contrastLine = c10.failing.length ? `**${c10.failing.length} of ${c10.checks} pairs fail** for ${plan.name} (${c10.themes.join(" and ")}).` : `All ${c10.checks} contrast checks pass for ${plan.name} in ${c10.themes.join(" and ")}.`;
|
|
@@ -2376,7 +2470,7 @@ function cssModuleClasses(css) {
|
|
|
2376
2470
|
}
|
|
2377
2471
|
return out;
|
|
2378
2472
|
}
|
|
2379
|
-
const cell$1 = (
|
|
2473
|
+
const cell$1 = (s2) => s2.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
2380
2474
|
function propsTable(name, props, inherits) {
|
|
2381
2475
|
if (!props.length) return inherits ? `${name} has no props of its own. It accepts every native <${inherits}> attribute.` : "";
|
|
2382
2476
|
const rows = props.map((p) => `| \`${p.name}\`${p.required ? " (required)" : ""} | \`${cell$1(p.type)}\` | ${p.defaultValue ? `\`${cell$1(p.defaultValue)}\`` : ""} | ${cell$1(p.description ?? "")} |`);
|
|
@@ -2547,16 +2641,16 @@ const DOCS_CHANGE_BUMP = "patch";
|
|
|
2547
2641
|
function cleanDraftText(text) {
|
|
2548
2642
|
return text.replace(/\s*\u2014\s*/g, ", ").replace(/,\s*,/g, ",").replace(/^,\s*/, "").replace(/[ \t]+/g, " ").trim();
|
|
2549
2643
|
}
|
|
2550
|
-
const values = (
|
|
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 = (n2, word, many = `${word}s`) => `${n2} ${n2 === 1 ? word : many}`;
|
|
2554
|
-
const capital = (
|
|
2555
|
-
const lower = (
|
|
2556
|
-
function propShape(
|
|
2557
|
-
if (!
|
|
2558
|
-
const m2 = /^(.*?), default (.*)$/.exec(
|
|
2559
|
-
return m2 ? { type: m2[1], def: m2[2] } : { type:
|
|
2647
|
+
const plural$1 = (n2, word, many = `${word}s`) => `${n2} ${n2 === 1 ? word : many}`;
|
|
2648
|
+
const capital = (s2) => s2.charAt(0).toUpperCase() + s2.slice(1);
|
|
2649
|
+
const lower = (s2) => s2.charAt(0).toLowerCase() + s2.slice(1);
|
|
2650
|
+
function propShape(s2) {
|
|
2651
|
+
if (!s2) return void 0;
|
|
2652
|
+
const m2 = /^(.*?), default (.*)$/.exec(s2);
|
|
2653
|
+
return m2 ? { type: m2[1], def: m2[2] } : { type: s2, def: "" };
|
|
2560
2654
|
}
|
|
2561
2655
|
const VARIANT_FIRST = ["variant", "tone", "intent", "appearance", "kind", "type", "style"];
|
|
2562
2656
|
function shortVariant(key) {
|
|
@@ -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);
|
|
@@ -2842,7 +2936,7 @@ function parseDocsReply(text) {
|
|
|
2842
2936
|
if (!json || typeof json !== "object" || Array.isArray(json)) throw new Error("The reply is not a docs.json object.");
|
|
2843
2937
|
return json;
|
|
2844
2938
|
}
|
|
2845
|
-
const cleanDocsText = (
|
|
2939
|
+
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;
|
|
@@ -2854,15 +2948,15 @@ const isExample = (x) => {
|
|
|
2854
2948
|
const textList = (x) => {
|
|
2855
2949
|
if (typeof x === "string") x = [x];
|
|
2856
2950
|
if (!Array.isArray(x)) return void 0;
|
|
2857
|
-
const out = x.filter((
|
|
2951
|
+
const out = x.filter((s2) => typeof s2 === "string").map(cleanDocsText).filter(Boolean);
|
|
2858
2952
|
return out.length ? out : void 0;
|
|
2859
2953
|
};
|
|
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;
|
|
@@ -2877,11 +2971,11 @@ function sanitizeDocsSections(raw, keys = PROSE_KEYS, opts = {}) {
|
|
|
2877
2971
|
for (const k3 of keys) {
|
|
2878
2972
|
const v2 = r2[k3];
|
|
2879
2973
|
if (k3 === "summary") {
|
|
2880
|
-
const
|
|
2881
|
-
if (
|
|
2974
|
+
const s2 = typeof v2 === "string" ? cleanDocsText(v2) : "";
|
|
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;
|
|
@@ -2970,9 +3064,9 @@ const EXPLORATION_STATUS_LABEL = { active: "Running", promoted: "Promoted to rev
|
|
|
2970
3064
|
const MARK$1 = "bridge-exploration";
|
|
2971
3065
|
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2972
3066
|
function explorationSlug(name) {
|
|
2973
|
-
const
|
|
2974
|
-
if (!/[a-z0-9]/i.test(
|
|
2975
|
-
return slugify(
|
|
3067
|
+
const s2 = name.trim().replace(/^explore\//i, "");
|
|
3068
|
+
if (!/[a-z0-9]/i.test(s2)) return "";
|
|
3069
|
+
return slugify(s2, 40);
|
|
2976
3070
|
}
|
|
2977
3071
|
function explorationBranch(nameOrBranch) {
|
|
2978
3072
|
return `${EXPLORATION_PREFIX}${explorationSlug(nameOrBranch)}`;
|
|
@@ -3079,6 +3173,8 @@ function explorationStatus(p) {
|
|
|
3079
3173
|
const isActiveExploration = (labels) => labelNames(labels).includes(EXPLORATION_LABEL);
|
|
3080
3174
|
function promotionPlan(input) {
|
|
3081
3175
|
const exploration = parseExplorationBody(input.body);
|
|
3176
|
+
const jira = /^([A-Z][A-Z0-9_]*-\d+)\s+/.exec(input.title)?.[1] ?? null;
|
|
3177
|
+
const title = jira ? input.title.slice(jira.length).trim() : input.title;
|
|
3082
3178
|
const removeFiles = input.files.filter((f) => f.path.startsWith(`${EXPLORATION_NOTES_DIR}/`) && f.status !== "removed").map((f) => f.path);
|
|
3083
3179
|
const files = input.files.filter((f) => !f.path.startsWith(`${EXPLORATION_NOTES_DIR}/`));
|
|
3084
3180
|
const risk = classifyRisk({ files, ...input.config ? { config: input.config } : {} });
|
|
@@ -3091,13 +3187,13 @@ function promotionPlan(input) {
|
|
|
3091
3187
|
const body = [
|
|
3092
3188
|
`**Promoted from an exploration on ${date}.** Review it like any change: the tier below comes from the files it touches.`,
|
|
3093
3189
|
"",
|
|
3094
|
-
kept || `## ${
|
|
3190
|
+
kept || `## ${title.replace(/^Exploration:\s*/i, "")}`,
|
|
3095
3191
|
"",
|
|
3096
3192
|
`**Risk tier:** ${risk.tier}.`,
|
|
3097
3193
|
...risk.reasons.slice(0, 8).map((r2) => `- ${r2}`),
|
|
3098
3194
|
...closes.length ? ["", ...closes.map((n2) => `Closes #${n2}`)] : []
|
|
3099
3195
|
].join("\n");
|
|
3100
|
-
const t3 =
|
|
3196
|
+
const t3 = title.replace(/^Exploration:\s*/i, "").trim() || exploration?.name || title;
|
|
3101
3197
|
return {
|
|
3102
3198
|
tier: risk.tier,
|
|
3103
3199
|
reasons: risk.reasons,
|
|
@@ -3105,7 +3201,7 @@ function promotionPlan(input) {
|
|
|
3105
3201
|
addLabels,
|
|
3106
3202
|
removeFiles,
|
|
3107
3203
|
body,
|
|
3108
|
-
title: t3.charAt(0).toUpperCase()
|
|
3204
|
+
title: `${jira ? `${jira} ` : ""}${t3.charAt(0).toUpperCase()}${t3.slice(1)}`,
|
|
3109
3205
|
markReady: true
|
|
3110
3206
|
};
|
|
3111
3207
|
}
|
|
@@ -3206,8 +3302,8 @@ function declineReasonProblems(reason) {
|
|
|
3206
3302
|
return [];
|
|
3207
3303
|
}
|
|
3208
3304
|
function declineComment(reason, by2) {
|
|
3209
|
-
const
|
|
3210
|
-
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");
|
|
3211
3307
|
}
|
|
3212
3308
|
function requestStatus(issue) {
|
|
3213
3309
|
const prs = issue.linkedPrs ?? [];
|
|
@@ -3249,8 +3345,8 @@ function requestBrief(r2, ctx) {
|
|
|
3249
3345
|
"- If the request is unclear or conflicts with the guidance, comment on the issue instead of guessing."
|
|
3250
3346
|
].join("\n");
|
|
3251
3347
|
}
|
|
3252
|
-
const cell = (
|
|
3253
|
-
const sourceLabel = (
|
|
3348
|
+
const cell = (s2) => s2.replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
3349
|
+
const sourceLabel = (s2) => s2 === "figma" ? "Figma" : s2 === "studio" ? "Bridge Studio" : s2 === "cli" ? "the CLI" : "Claude (MCP)";
|
|
3254
3350
|
function validateIntegrations(i2) {
|
|
3255
3351
|
const out = [];
|
|
3256
3352
|
if (!i2?.jira) return out;
|
|
@@ -3308,7 +3404,7 @@ function newDraftSessionId(random = Math.random) {
|
|
|
3308
3404
|
return out;
|
|
3309
3405
|
}
|
|
3310
3406
|
const SESSION_RE = /^[a-z0-9-]{6,64}$/;
|
|
3311
|
-
const isDraftSessionId = (
|
|
3407
|
+
const isDraftSessionId = (s2) => typeof s2 === "string" && SESSION_RE.test(s2);
|
|
3312
3408
|
function draftSessionPath(sessionId) {
|
|
3313
3409
|
if (!isDraftSessionId(sessionId)) throw new Error(`"${sessionId}" is not a draft session id.`);
|
|
3314
3410
|
return `${DRAFTS_DIR}/${sessionId}.json`;
|
|
@@ -3405,7 +3501,7 @@ function clip(text, max) {
|
|
|
3405
3501
|
function sentences(text) {
|
|
3406
3502
|
const t3 = text.replace(/\s+/g, " ").trim();
|
|
3407
3503
|
const parts = t3.match(/[^.!?]+(?:[.!?]+(?=\s|$)|$)/g) ?? [t3];
|
|
3408
|
-
let out = parts.slice(0, DRAFT_SUMMARY_SENTENCES).map((
|
|
3504
|
+
let out = parts.slice(0, DRAFT_SUMMARY_SENTENCES).map((s2) => s2.trim()).join(" ");
|
|
3409
3505
|
if (out.length > SUMMARY_MAX) out = `${clip(out, SUMMARY_MAX)}.`;
|
|
3410
3506
|
return out;
|
|
3411
3507
|
}
|
|
@@ -3478,7 +3574,7 @@ function draftItemBrief(i2) {
|
|
|
3478
3574
|
return [`Item ${i2.id}: component docs${i2.component ? ` of ${i2.component}` : ""} from Bridge Studio (${i2.docs.path ?? i2.docs.slug}): ${what}.`, "", i2.docs.prompt].join("\n");
|
|
3479
3575
|
}
|
|
3480
3576
|
const kind = i2.kind ? REQUEST_KINDS.find((k3) => k3.value === i2.kind)?.label : void 0;
|
|
3481
|
-
const cell2 = (
|
|
3577
|
+
const cell2 = (s2) => (s2 ?? "").replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
3482
3578
|
const lines = [`Item ${i2.id}: ${i2.scope === "request" ? "a component change request (a GitHub issue developers implement)" : "the description of a pull request with design token (variable) edits from Figma"}.`];
|
|
3483
3579
|
if (i2.component) lines.push(`Component: ${i2.component}`);
|
|
3484
3580
|
if (kind) lines.push(`Kind: ${kind}`);
|
|
@@ -3578,8 +3674,8 @@ async function createDocsDraftSession(client, input) {
|
|
|
3578
3674
|
const DRAFT_POLL_INTERVAL_MS = 5e3;
|
|
3579
3675
|
const DRAFT_POLL_TIMEOUT_MS = 10 * 6e4;
|
|
3580
3676
|
class DraftPoller {
|
|
3581
|
-
constructor(
|
|
3582
|
-
this.o =
|
|
3677
|
+
constructor(o3) {
|
|
3678
|
+
this.o = o3;
|
|
3583
3679
|
}
|
|
3584
3680
|
o;
|
|
3585
3681
|
handle;
|
|
@@ -3645,7 +3741,7 @@ class DraftPoller {
|
|
|
3645
3741
|
}
|
|
3646
3742
|
}
|
|
3647
3743
|
}
|
|
3648
|
-
function explorationSummary(p, _owner, _repo,
|
|
3744
|
+
function explorationSummary(p, _owner, _repo, s2) {
|
|
3649
3745
|
const e2 = parseExplorationBody(p.body);
|
|
3650
3746
|
const slug = p.headRef?.startsWith(EXPLORATION_PREFIX) ? p.headRef.slice(EXPLORATION_PREFIX.length) : e2?.slug ?? explorationSlug(p.title);
|
|
3651
3747
|
return {
|
|
@@ -3653,7 +3749,7 @@ function explorationSummary(p, _owner, _repo, s) {
|
|
|
3653
3749
|
url: p.url,
|
|
3654
3750
|
branch: p.headRef,
|
|
3655
3751
|
slug,
|
|
3656
|
-
name: e2?.name ?? p.title.replace(/^Exploration:\s*/i, ""),
|
|
3752
|
+
name: e2?.name ?? p.title.replace(/^[A-Z][A-Z0-9_]*-\d+\s+/, "").replace(/^Exploration:\s*/i, ""),
|
|
3657
3753
|
purpose: e2?.purpose ?? "",
|
|
3658
3754
|
...e2?.useCase ? { useCase: e2.useCase } : {},
|
|
3659
3755
|
status: explorationStatus({ state: p.state, labels: p.labels, headRef: p.headRef, body: p.body }),
|
|
@@ -3663,14 +3759,14 @@ function explorationSummary(p, _owner, _repo, s) {
|
|
|
3663
3759
|
...e2?.agent ? { agent: e2.agent } : {},
|
|
3664
3760
|
updatedAt: p.updatedAt,
|
|
3665
3761
|
headSha: p.headSha,
|
|
3666
|
-
previews: previewLinks(
|
|
3762
|
+
previews: previewLinks(s2.apps, { branch: p.headRef, pr: p.number, sha: p.headSha })
|
|
3667
3763
|
};
|
|
3668
3764
|
}
|
|
3669
|
-
async function listExplorationsOnGitHub(gh,
|
|
3765
|
+
async function listExplorationsOnGitHub(gh, s2, opts = {}) {
|
|
3670
3766
|
const pulls = await gh.listPullRequests(opts.state ?? "open");
|
|
3671
|
-
return pulls.filter((p) => isExplorationPull({ state: p.state, labels: p.labels, headRef: p.headRef, body: p.body })).map((p) => explorationSummary(p, gh.owner, gh.name,
|
|
3767
|
+
return pulls.filter((p) => isExplorationPull({ state: p.state, labels: p.labels, headRef: p.headRef, body: p.body })).map((p) => explorationSummary(p, gh.owner, gh.name, s2));
|
|
3672
3768
|
}
|
|
3673
|
-
async function startExplorationOnGitHub(gh, e2,
|
|
3769
|
+
async function startExplorationOnGitHub(gh, e2, s2) {
|
|
3674
3770
|
const problems = explorationProblems(e2);
|
|
3675
3771
|
if (problems.length) throw new GitHubError(422, problems.join(" "));
|
|
3676
3772
|
if (!gh.canWrite) throw new GitHubError(401, "Starting an exploration needs a GitHub token (contents and pull requests, read and write, on this repository only).");
|
|
@@ -3680,7 +3776,7 @@ async function startExplorationOnGitHub(gh, e2, s) {
|
|
|
3680
3776
|
if (await x.getBranchSha(branch)) throw new GitHubError(422, `An exploration named "${slug}" already exists (${branch}). Pick another name, or continue that one.`);
|
|
3681
3777
|
const full = { ...e2, slug, startedAt: e2.startedAt ?? (/* @__PURE__ */ new Date()).toISOString() };
|
|
3682
3778
|
const commit = await x.commitToNewBranch({
|
|
3683
|
-
base:
|
|
3779
|
+
base: s2.base,
|
|
3684
3780
|
branch,
|
|
3685
3781
|
message: `chore(explore): start ${full.name.trim()}
|
|
3686
3782
|
|
|
@@ -3689,8 +3785,9 @@ ${full.purpose.trim()}
|
|
|
3689
3785
|
Exploration: never merged as is. Promote it to review or close it.`,
|
|
3690
3786
|
files: [{ path: explorationNotePath(slug), content: explorationNote(full) }]
|
|
3691
3787
|
});
|
|
3692
|
-
const ctx = { owner: gh.owner, repo: gh.name, packages:
|
|
3693
|
-
const
|
|
3788
|
+
const ctx = { owner: gh.owner, repo: gh.name, packages: s2.packages, apps: s2.apps, ...s2.requests?.length ? { requests: s2.requests } : {} };
|
|
3789
|
+
const title = full.jira ? `${full.jira.trim().toUpperCase()} ${explorationPrTitle(full)}` : explorationPrTitle(full);
|
|
3790
|
+
const pr = await x.createPullRequest({ title, head: branch, base: s2.base, body: explorationPrBody(full, ctx), draft: true });
|
|
3694
3791
|
await x.addLabels(pr.number, [EXPLORATION_LABEL]);
|
|
3695
3792
|
await x.updatePullRequest(pr.number, { body: explorationPrBody(full, { ...ctx, pr: pr.number }) }).catch(() => void 0);
|
|
3696
3793
|
return {
|
|
@@ -3700,7 +3797,7 @@ Exploration: never merged as is. Promote it to review or close it.`,
|
|
|
3700
3797
|
url: pr.url,
|
|
3701
3798
|
commit: commit.sha,
|
|
3702
3799
|
tryIt: { state: "building" },
|
|
3703
|
-
previews: previewLinks(
|
|
3800
|
+
previews: previewLinks(s2.apps, { branch, pr: pr.number, sha: commit.sha })
|
|
3704
3801
|
};
|
|
3705
3802
|
}
|
|
3706
3803
|
async function explorationTryItOnGitHub(gh, e2) {
|
|
@@ -3787,32 +3884,1009 @@ async function declineRequestOnGitHub(gh, n2, reason, by2) {
|
|
|
3787
3884
|
await gh.addComment(n2, declineComment(reason, by2));
|
|
3788
3885
|
await gh.updateIssue(n2, { state: "closed", state_reason: "not_planned" });
|
|
3789
3886
|
}
|
|
3887
|
+
const FIDELITY_DEFAULTS = { scale: 2, threshold: 0.1, maxPercent: 3, sizeTolerance: 1 };
|
|
3888
|
+
const MAX_DELTA = 35215;
|
|
3889
|
+
const STRICT_THRESHOLD = 0.02;
|
|
3890
|
+
function rgbAt(img, x, y) {
|
|
3891
|
+
if (x < 0 || y < 0 || x >= img.width || y >= img.height) return [255, 255, 255];
|
|
3892
|
+
const i2 = (y * img.width + x) * 4;
|
|
3893
|
+
const a = img.data[i2 + 3] / 255;
|
|
3894
|
+
return [255 + (img.data[i2] - 255) * a, 255 + (img.data[i2 + 1] - 255) * a, 255 + (img.data[i2 + 2] - 255) * a];
|
|
3895
|
+
}
|
|
3896
|
+
function colorDelta(a, b) {
|
|
3897
|
+
const dr = a[0] - b[0];
|
|
3898
|
+
const dg = a[1] - b[1];
|
|
3899
|
+
const db = a[2] - b[2];
|
|
3900
|
+
const y = dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223;
|
|
3901
|
+
const i2 = dr * 0.59597799 - dg * 0.2741761 - db * 0.32180189;
|
|
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;
|
|
3904
|
+
}
|
|
3905
|
+
function foundNear(img, x, y, c10, radius, max) {
|
|
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;
|
|
3907
|
+
return false;
|
|
3908
|
+
}
|
|
3909
|
+
function compareImages(figma, code2, options = {}) {
|
|
3910
|
+
const scale = options.scale ?? FIDELITY_DEFAULTS.scale;
|
|
3911
|
+
const radius = Math.max(0, Math.round(options.radius ?? scale));
|
|
3912
|
+
const max = MAX_DELTA * (options.threshold ?? FIDELITY_DEFAULTS.threshold) ** 2;
|
|
3913
|
+
const strict = MAX_DELTA * STRICT_THRESHOLD ** 2;
|
|
3914
|
+
const width = Math.max(figma.width, code2.width);
|
|
3915
|
+
const height = Math.max(figma.height, code2.height);
|
|
3916
|
+
const total = width * height;
|
|
3917
|
+
const wantDiff = options.diffImage !== false;
|
|
3918
|
+
const out = wantDiff ? new Uint8ClampedArray(total * 4) : void 0;
|
|
3919
|
+
let different = 0;
|
|
3920
|
+
for (let y = 0; y < height; y++) {
|
|
3921
|
+
for (let x = 0; x < width; x++) {
|
|
3922
|
+
const a = rgbAt(figma, x, y);
|
|
3923
|
+
const b = rgbAt(code2, x, y);
|
|
3924
|
+
const m2 = x < Math.min(figma.width, code2.width) && y < Math.min(figma.height, code2.height) ? max : strict;
|
|
3925
|
+
const same = colorDelta(a, b) <= m2 || foundNear(code2, x, y, a, radius, m2) && foundNear(figma, x, y, b, radius, m2);
|
|
3926
|
+
if (!same) different++;
|
|
3927
|
+
if (out) {
|
|
3928
|
+
const o3 = (y * width + x) * 4;
|
|
3929
|
+
if (same) {
|
|
3930
|
+
const gray = 255 + (b[0] * 0.299 + b[1] * 0.587 + b[2] * 0.114 - 255) * 0.1;
|
|
3931
|
+
out[o3] = out[o3 + 1] = out[o3 + 2] = gray;
|
|
3932
|
+
} else {
|
|
3933
|
+
out[o3] = 220;
|
|
3934
|
+
out[o3 + 1] = 38;
|
|
3935
|
+
out[o3 + 2] = 38;
|
|
3936
|
+
}
|
|
3937
|
+
out[o3 + 3] = 255;
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
const css = (img) => ({ width: Math.round(img.width / scale), height: Math.round(img.height / scale) });
|
|
3942
|
+
const size = { figma: css(figma), code: css(code2) };
|
|
3943
|
+
const tol = options.sizeTolerance ?? FIDELITY_DEFAULTS.sizeTolerance;
|
|
3944
|
+
const sizeMatches = Math.abs(size.figma.width - size.code.width) <= tol && Math.abs(size.figma.height - size.code.height) <= tol;
|
|
3945
|
+
const percent = total ? Math.round(different / total * 1e4) / 100 : 0;
|
|
3946
|
+
return {
|
|
3947
|
+
matches: sizeMatches && percent <= (options.maxPercent ?? FIDELITY_DEFAULTS.maxPercent),
|
|
3948
|
+
percent,
|
|
3949
|
+
differentPixels: different,
|
|
3950
|
+
totalPixels: total,
|
|
3951
|
+
size,
|
|
3952
|
+
sizeMatches,
|
|
3953
|
+
...out ? { diff: { width, height, data: out } } : {}
|
|
3954
|
+
};
|
|
3955
|
+
}
|
|
3956
|
+
function summarizeComponent(component, variants, reason) {
|
|
3957
|
+
const compared = variants.filter((v2) => v2.status !== "skipped");
|
|
3958
|
+
const differing = compared.filter((v2) => v2.status === "differs");
|
|
3959
|
+
const rank = (v2) => (v2.status === "differs" ? 1e3 : 0) + (v2.percent ?? 0) + (v2.size && !sameSize(v2.size) ? 500 : 0);
|
|
3960
|
+
const worst = [...compared].sort((a, b) => rank(b) - rank(a))[0];
|
|
3961
|
+
return {
|
|
3962
|
+
component,
|
|
3963
|
+
status: compared.length === 0 ? "not-checked" : differing.length ? "differs" : "matches",
|
|
3964
|
+
checked: compared.length,
|
|
3965
|
+
differing: differing.length,
|
|
3966
|
+
skipped: variants.length - compared.length,
|
|
3967
|
+
...worst && worst.size ? { worst: { key: worst.key, percent: worst.percent ?? 0, size: worst.size } } : {},
|
|
3968
|
+
...compared.length === 0 ? { reason: reason ?? variants.find((v2) => v2.reason)?.reason } : {},
|
|
3969
|
+
variants
|
|
3970
|
+
};
|
|
3971
|
+
}
|
|
3972
|
+
const sameSize = (s2) => s2.figma.width === s2.code.width && s2.figma.height === s2.code.height;
|
|
3973
|
+
function buildFidelityReport(input) {
|
|
3974
|
+
const o3 = input.options ?? {};
|
|
3975
|
+
const scale = o3.scale ?? FIDELITY_DEFAULTS.scale;
|
|
3976
|
+
const c10 = input.components;
|
|
3977
|
+
return {
|
|
3978
|
+
version: 1,
|
|
3979
|
+
checkedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
3980
|
+
...input.file ? { file: input.file } : {},
|
|
3981
|
+
reference: input.reference,
|
|
3982
|
+
options: {
|
|
3983
|
+
scale,
|
|
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
|
|
3988
|
+
},
|
|
3989
|
+
components: c10,
|
|
3990
|
+
summary: {
|
|
3991
|
+
components: c10.length,
|
|
3992
|
+
matches: c10.filter((x) => x.status === "matches").length,
|
|
3993
|
+
differs: c10.filter((x) => x.status === "differs").length,
|
|
3994
|
+
notChecked: c10.filter((x) => x.status === "not-checked").length,
|
|
3995
|
+
variantsChecked: c10.reduce((n2, x) => n2 + x.checked, 0),
|
|
3996
|
+
variantsSkipped: c10.reduce((n2, x) => n2 + x.skipped, 0)
|
|
3997
|
+
}
|
|
3998
|
+
};
|
|
3999
|
+
}
|
|
4000
|
+
const fmtPercent = (p) => p >= 10 ? `${Math.round(p)}%` : p >= 1 ? `${Math.round(p * 10) / 10}%` : p > 0 ? "under 1%" : "0%";
|
|
4001
|
+
const fmtSize = (s2) => `${s2.width}x${s2.height}`;
|
|
4002
|
+
function fidelityVerdict(v2) {
|
|
4003
|
+
if (v2.status === "skipped") return `Not compared: ${v2.reason ?? "no image of the code"}`;
|
|
4004
|
+
if (v2.status === "matches") return "Matches the code";
|
|
4005
|
+
const parts = [`${fmtPercent(v2.percent ?? 0)} of pixels`];
|
|
4006
|
+
if (v2.size && !sameSize(v2.size)) parts.push(`size ${fmtSize(v2.size.figma)} vs ${fmtSize(v2.size.code)}`);
|
|
4007
|
+
return `Differs from the code: ${parts.join(", ")}`;
|
|
4008
|
+
}
|
|
4009
|
+
function fidelityComponentLine(c10) {
|
|
4010
|
+
if (c10.status === "not-checked") return `Not compared: ${c10.reason ?? "no image of the code"}`;
|
|
4011
|
+
if (c10.status === "matches") return c10.checked === 1 ? "Matches the code" : `Matches the code (${c10.checked} variants)`;
|
|
4012
|
+
const w = c10.worst;
|
|
4013
|
+
const head = fidelityVerdict({ status: "differs", percent: w.percent, size: w.size });
|
|
4014
|
+
return c10.checked > 1 ? `${head} (${c10.differing} of ${c10.checked} variants)` : head;
|
|
4015
|
+
}
|
|
4016
|
+
const FIGMA_REFERENCES_BRANCH = "bridge-visual";
|
|
4017
|
+
const FIGMA_REFERENCES_DIR = "figma-references";
|
|
4018
|
+
const FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
|
|
4019
|
+
const FIGMA_REFERENCE_STORY = "internal-figma-reference--variant";
|
|
4020
|
+
const FIGMA_FILL_WIDTH_TOKEN = "size.80";
|
|
4021
|
+
const STATE_PSEUDO = {
|
|
4022
|
+
default: "rest",
|
|
4023
|
+
rest: "rest",
|
|
4024
|
+
enabled: "rest",
|
|
4025
|
+
hover: "hover",
|
|
4026
|
+
hovered: "hover",
|
|
4027
|
+
pressed: "active",
|
|
4028
|
+
active: "active",
|
|
4029
|
+
focus: "focus-visible",
|
|
4030
|
+
focused: "focus-visible",
|
|
4031
|
+
"focus visible": "focus-visible",
|
|
4032
|
+
disabled: "disabled"
|
|
4033
|
+
};
|
|
4034
|
+
const iconName = (component) => component.replace(/^Icon\//, "");
|
|
4035
|
+
const exportName = (name) => name.replace(/[^A-Za-z0-9]+/g, "");
|
|
4036
|
+
function planReference(spec, variant, file) {
|
|
4037
|
+
if (spec.view === "popup") return { ok: false, reason: "drawn open in Figma; the code image shows it closed" };
|
|
4038
|
+
const props = { ...spec.fixed ?? {} };
|
|
4039
|
+
let pseudo;
|
|
4040
|
+
for (const axis of spec.axes) {
|
|
4041
|
+
const value = variant.props[axis.name];
|
|
4042
|
+
if (value === void 0) return { ok: false, reason: `the variant has no ${axis.name} value` };
|
|
4043
|
+
if (axis.prop) {
|
|
4044
|
+
props[axis.prop] = value === "true" ? true : value === "false" ? false : value;
|
|
4045
|
+
continue;
|
|
4046
|
+
}
|
|
4047
|
+
if (axis.name === "Checked") {
|
|
4048
|
+
props.checked = value === "true";
|
|
4049
|
+
if (value === "mixed") props.indeterminate = true;
|
|
4050
|
+
continue;
|
|
4051
|
+
}
|
|
4052
|
+
if (axis.name.toLowerCase() !== "state") return { ok: false, reason: `${axis.name} has no matching option in code` };
|
|
4053
|
+
const s2 = STATE_PSEUDO[value.toLowerCase()];
|
|
4054
|
+
if (!s2) return { ok: false, reason: `the ${value} state has no code equivalent to render` };
|
|
4055
|
+
if (s2 === "disabled") props.disabled = true;
|
|
4056
|
+
else if (s2 !== "rest") pseudo = s2;
|
|
4057
|
+
}
|
|
4058
|
+
const root = spec.parts.find((p) => !p.parent);
|
|
4059
|
+
if (!root) return { ok: false, reason: "the spec has no root layer" };
|
|
4060
|
+
const children = (id) => spec.parts.filter((p) => p.parent === id);
|
|
4061
|
+
const textOf = (part) => {
|
|
4062
|
+
const swapTo = variant.slots?.[part.id] ?? part.swap?.default;
|
|
4063
|
+
if (part.swap && swapTo) {
|
|
4064
|
+
const slot = file?.slots?.find((s2) => s2.name === swapTo);
|
|
4065
|
+
if (!slot) return { reason: `${swapTo} is not in the specs` };
|
|
4066
|
+
if (slot.kind !== "text" || !slot.text) return { reason: `Figma shows a placeholder (${swapTo}) that code has no example of` };
|
|
4067
|
+
return slot.text.default;
|
|
4068
|
+
}
|
|
4069
|
+
const prop = spec.properties.find((p) => p.name === part.text && p.type === "TEXT");
|
|
4070
|
+
return typeof prop?.default === "string" ? prop.default : part.name;
|
|
4071
|
+
};
|
|
4072
|
+
let text;
|
|
4073
|
+
const icons = {};
|
|
4074
|
+
for (const prop of spec.properties) {
|
|
4075
|
+
if (prop.type === "TEXT") {
|
|
4076
|
+
const hidden = spec.properties.some((b) => b.type === "BOOLEAN" && b.prop === prop.prop && b.default === false);
|
|
4077
|
+
if (hidden && prop.prop) continue;
|
|
4078
|
+
if (prop.prop === "children") text = String(prop.default);
|
|
4079
|
+
else if (prop.prop) props[prop.prop] = String(prop.default);
|
|
4080
|
+
continue;
|
|
4081
|
+
}
|
|
4082
|
+
const iconPart = spec.parts.find((p) => p.kind === "icon" && p.visible === prop.name);
|
|
4083
|
+
if (prop.default === true && !iconPart && prop.prop && props[prop.prop] === void 0 && spec.example?.p?.[prop.prop] === void 0) props[prop.prop] = true;
|
|
4084
|
+
}
|
|
4085
|
+
for (const part of spec.parts) {
|
|
4086
|
+
if (part.kind !== "icon" || !part.swap) continue;
|
|
4087
|
+
const shown = part.visible ? spec.properties.find((p) => p.name === part.visible)?.default === true : true;
|
|
4088
|
+
if (!shown) continue;
|
|
4089
|
+
if (!part.swap.prop) return { ok: false, reason: `the ${part.name} icon has no matching option in code` };
|
|
4090
|
+
icons[part.swap.prop] = iconName(variant.slots?.[part.id] ?? part.swap.default);
|
|
4091
|
+
}
|
|
4092
|
+
let parts;
|
|
4093
|
+
const loose = spec.properties.filter((p) => p.type === "TEXT" && !p.prop);
|
|
4094
|
+
const frames = children(root.id).filter((p) => p.kind === "frame");
|
|
4095
|
+
const compound = text === void 0 && frames.length > 0 && frames.every((f) => spec.parts.some((p) => p.parent === f.id && p.kind === "text" && (p.swap || p.text === f.name)));
|
|
4096
|
+
if (compound) {
|
|
4097
|
+
parts = [];
|
|
4098
|
+
for (const f of frames) {
|
|
4099
|
+
const t3 = spec.parts.find((p) => p.parent === f.id && p.kind === "text");
|
|
4100
|
+
const value = textOf(t3);
|
|
4101
|
+
if (typeof value !== "string") return { ok: false, reason: value.reason };
|
|
4102
|
+
parts.push({ component: `${exportName(spec.name)}${exportName(f.name)}`, text: value });
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
for (const p of loose) {
|
|
4106
|
+
if (p.name === "Value") continue;
|
|
4107
|
+
const part = spec.parts.find((x) => x.text === p.name);
|
|
4108
|
+
if (compound && part && part.parent !== root.id) continue;
|
|
4109
|
+
if (part && /placeholder$/.test(part.id)) {
|
|
4110
|
+
props.placeholder = String(p.default);
|
|
4111
|
+
continue;
|
|
4112
|
+
}
|
|
4113
|
+
return { ok: false, reason: `the ${p.name} text has no matching option in code` };
|
|
4114
|
+
}
|
|
4115
|
+
const example = spec.example;
|
|
4116
|
+
const exampleChildren = example?.children?.length ? example.children : void 0;
|
|
4117
|
+
return {
|
|
4118
|
+
ok: true,
|
|
4119
|
+
render: {
|
|
4120
|
+
component: exportName(spec.view || spec.fixed || example ? example?.c ?? spec.component ?? spec.name : spec.name),
|
|
4121
|
+
props: { ...example?.p ?? {}, ...props },
|
|
4122
|
+
...exampleChildren ? { children: exampleChildren } : {},
|
|
4123
|
+
...text !== void 0 && !exampleChildren ? { text } : {},
|
|
4124
|
+
...parts && !exampleChildren ? { parts } : {},
|
|
4125
|
+
...Object.keys(icons).length ? { icons } : {},
|
|
4126
|
+
...pseudo ? { pseudo } : {},
|
|
4127
|
+
...root.fill ? { width: FIGMA_FILL_WIDTH_TOKEN } : {}
|
|
4128
|
+
}
|
|
4129
|
+
};
|
|
4130
|
+
}
|
|
4131
|
+
function referenceImagePath(component, key) {
|
|
4132
|
+
const slug = (s2) => s2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4133
|
+
const file = key.split(",").map((kv) => slug(kv)).filter(Boolean).join("__");
|
|
4134
|
+
return `${slug(component)}/${file || "default"}.png`;
|
|
4135
|
+
}
|
|
4136
|
+
function planReferences(file) {
|
|
4137
|
+
return file.components.flatMap(
|
|
4138
|
+
(spec) => spec.variants.map((v2) => ({ component: spec.name, specHash: spec.hash, key: variantKeyOf(v2.props), plan: planReference(spec, v2, file) }))
|
|
4139
|
+
);
|
|
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: 'Proposed with the Bridge docs editor by a person (no AI involved in this text unless the docs say "draftedBy").',
|
|
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
|
+
function docsProposal(edits, opts) {
|
|
4658
|
+
const origin = opts.origin ?? DOCS_EDITOR_ORIGIN;
|
|
4659
|
+
if (!edits.length) throw new Error("No docs edits in the draft.");
|
|
4660
|
+
const names2 = edits.map((e2) => e2.component);
|
|
4661
|
+
const list = names2.length === 1 ? names2[0] : `${names2.slice(0, -1).join(", ")} and ${names2[names2.length - 1]}`;
|
|
4662
|
+
const title = opts.title?.trim() || `docs(${names2.length === 1 ? edits[0].slug : "components"}): usage guidance for ${list}`;
|
|
4663
|
+
const slug = `${names2.length === 1 ? edits[0].slug : `${edits[0].slug}-and-${names2.length - 1}-more`}`.toLowerCase().replace(/[^a-z0-9-]+/g, "-").slice(0, 40);
|
|
4664
|
+
const branch = `docs/${slug}-${slugDate(opts.date)}-${opts.date.getTime().toString(36).slice(-4)}`;
|
|
4665
|
+
const csPath = `${opts.changesetDir ?? ".changeset"}/docs-${slug}-${opts.date.getTime().toString(36)}.md`;
|
|
4666
|
+
const pkg = opts.packageName ?? "@bridge-ds/react";
|
|
4667
|
+
const changeset = `---
|
|
4668
|
+
"${pkg}": patch
|
|
4669
|
+
---
|
|
4670
|
+
|
|
4671
|
+
Usage guidance for ${list}.
|
|
4672
|
+
`;
|
|
4673
|
+
const files = [...edits.map((e2) => ({ path: e2.path, content: e2.after })), { path: csPath, content: changeset }];
|
|
4674
|
+
const risk = classifyRisk({ files: files.map((f) => ({ path: f.path, status: "modified" })) });
|
|
4675
|
+
const rows = edits.map((e2) => {
|
|
4676
|
+
let state = "edited";
|
|
4677
|
+
try {
|
|
4678
|
+
const d2 = JSON.parse(e2.after);
|
|
4679
|
+
const gaps = missingProse(d2);
|
|
4680
|
+
state = gaps.length ? `still missing: ${gaps.join(", ")}` : "every section filled";
|
|
4681
|
+
} catch {
|
|
4682
|
+
state = "not valid JSON";
|
|
4683
|
+
}
|
|
4684
|
+
return `- \`${e2.path}\` (${e2.before === null ? "new" : "changed"}, ${state})`;
|
|
4685
|
+
});
|
|
4686
|
+
const body = [
|
|
4687
|
+
`## Docs change`,
|
|
4688
|
+
"",
|
|
4689
|
+
`Usage guidance for ${list}, written ${origin.where}.`,
|
|
4690
|
+
"",
|
|
4691
|
+
...rows,
|
|
4692
|
+
"",
|
|
4693
|
+
`**Risk:** ${risk.label}. Docs only: the published components do not change. Changeset: patch.`,
|
|
4694
|
+
"",
|
|
4695
|
+
opts.notes?.trim() ? `## Notes
|
|
4696
|
+
|
|
4697
|
+
${opts.notes.trim()}
|
|
4698
|
+
` : "",
|
|
4699
|
+
"---",
|
|
4700
|
+
origin.footer
|
|
4701
|
+
].filter((x, i2, a) => !(x === "" && a[i2 - 1] === "")).join("\n");
|
|
4702
|
+
return { branch, title, body, files, risk, bump: "patch", labels: ["docs", origin.label, `tier-${risk.tier}`] };
|
|
4703
|
+
}
|
|
4704
|
+
async function proposeDocsOnGitHub(gh, edits, opts) {
|
|
4705
|
+
const p = docsProposal(edits, { title: opts.title, notes: opts.notes, date: opts.date ?? /* @__PURE__ */ new Date(), origin: opts.origin });
|
|
4706
|
+
const prTitle = titleWithJiraKey(p.title, opts.jira ?? null);
|
|
4707
|
+
const branch = branchWithJiraKey(p.branch, opts.jira ?? null);
|
|
4708
|
+
const mainSha = await gh.getBranchSha(opts.base);
|
|
4709
|
+
if (!mainSha) throw new Error(`Branch ${opts.base} not found.`);
|
|
4710
|
+
const tree = await gh.createTree(await gh.getCommitTree(mainSha), p.files.map((f) => ({ path: f.path, content: f.content })));
|
|
4711
|
+
const commit = await gh.createCommit(`${prTitle}
|
|
4712
|
+
|
|
4713
|
+
${opts.commitNote ?? "Proposed with the Bridge docs editor."}`, tree, [mainSha]);
|
|
4714
|
+
await gh.createBranch(branch, commit);
|
|
4715
|
+
const pr = await gh.createPullRequest({ title: prTitle, head: branch, base: opts.base, body: p.body, draft: false });
|
|
4716
|
+
await gh.addLabels(pr.number, [...p.labels, ...opts.extraLabels ?? []]).catch(() => false);
|
|
4717
|
+
return { number: pr.number, url: pr.url, branch, title: prTitle };
|
|
4718
|
+
}
|
|
4719
|
+
async function commitFileOnGitHub(gh, input) {
|
|
4720
|
+
const cur = await gh.getFile(input.path, input.branch);
|
|
4721
|
+
if (!cur) throw new Error(`${input.path} is not on ${input.branch}. Check the component name and the branch.`);
|
|
4722
|
+
const content = input.next(cur.text);
|
|
4723
|
+
if (content === cur.text) return "";
|
|
4724
|
+
return gh.putFile({ path: input.path, content, branch: input.branch, message: input.message, sha: cur.sha });
|
|
4725
|
+
}
|
|
4726
|
+
const SHARE_EXPLAIN = "Product teams can install this branch in their app before it is merged, and remove it any time.";
|
|
4727
|
+
const packageDir = (name) => `packages/${name.replace(/^@[^/]+\//, "")}/`;
|
|
4728
|
+
function touchesPackages(files, packages) {
|
|
4729
|
+
const dirs = packages.map(packageDir);
|
|
4730
|
+
return files.some((f) => dirs.some((d2) => f.startsWith(d2)));
|
|
4731
|
+
}
|
|
4732
|
+
const canShare = (x) => !x.fromFork && x.packages.length > 0;
|
|
4733
|
+
function shareState(x) {
|
|
4734
|
+
if (!touchesPackages(x.files, x.packages)) return { kind: "no-packages" };
|
|
4735
|
+
const c10 = readPreviewComment(x.comment);
|
|
4736
|
+
if (x.lifecycle === "merged" || x.lifecycle === "closed") {
|
|
4737
|
+
const kind = x.lifecycle === "merged" ? "shipped" : "closed";
|
|
4738
|
+
if (!c10 || !c10.command || c10.source === "none") return { kind, stillWorks: null, command: null };
|
|
4739
|
+
return { kind, stillWorks: c10.source === "pkg.pr.new" ? true : c10.source === "release" ? false : null, command: c10.command };
|
|
4740
|
+
}
|
|
4741
|
+
if (!c10) return { kind: "building" };
|
|
4742
|
+
if (c10.sha7 && !x.headSha.startsWith(c10.sha7)) return { kind: "building" };
|
|
4743
|
+
if (!c10.command || c10.source === "none") return { kind: "failed" };
|
|
4744
|
+
return { kind: "ready", command: c10.command, source: c10.source };
|
|
4745
|
+
}
|
|
4746
|
+
const showShare = (lifecycle, s2) => lifecycle === "open" || lifecycle === "draft" || (s2.kind === "shipped" || s2.kind === "closed") && !!s2.command;
|
|
4747
|
+
const shareDescription = (s2) => s2.kind === "shipped" || s2.kind === "closed" ? stateSentence(s2) ?? SHARE_EXPLAIN : SHARE_EXPLAIN;
|
|
4748
|
+
const stableCommand = (packages) => `npm i ${packages.map((p) => `${p}@latest`).join(" ")}`;
|
|
4749
|
+
function stateSentence(s2) {
|
|
4750
|
+
switch (s2.kind) {
|
|
4751
|
+
case "ready":
|
|
4752
|
+
return null;
|
|
4753
|
+
case "building":
|
|
4754
|
+
return "The preview version is being prepared. It is usually ready a couple of minutes after each update to the branch.";
|
|
4755
|
+
case "failed":
|
|
4756
|
+
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.";
|
|
4757
|
+
case "no-packages":
|
|
4758
|
+
return "This branch does not touch the library tokens or components, so there is no preview version to install.";
|
|
4759
|
+
case "shipped":
|
|
4760
|
+
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.";
|
|
4761
|
+
case "closed":
|
|
4762
|
+
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.";
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
const quote = (t3) => `"${t3.trim().replace(/"/g, "'")}"`;
|
|
4766
|
+
function shareMessage(x) {
|
|
4767
|
+
const back = stableCommand(x.packages);
|
|
4768
|
+
const s2 = x.state;
|
|
4769
|
+
if (s2.kind === "ready") {
|
|
4770
|
+
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.";
|
|
4771
|
+
return {
|
|
4772
|
+
subject: `Try the design system change ${quote(x.title)} before it ships`,
|
|
4773
|
+
body: [
|
|
4774
|
+
"Hi,",
|
|
4775
|
+
"",
|
|
4776
|
+
`Could you try the design system change ${quote(x.title)} in our product before it ships?`,
|
|
4777
|
+
...x.tryUrl ? ["", `See it in the demo product first: ${x.tryUrl}`] : [],
|
|
4778
|
+
"",
|
|
4779
|
+
"Install the preview version in a staging branch (not production):",
|
|
4780
|
+
s2.command,
|
|
4781
|
+
"",
|
|
4782
|
+
lasts,
|
|
4783
|
+
"",
|
|
4784
|
+
"To go back to the stable version:",
|
|
4785
|
+
back,
|
|
4786
|
+
"",
|
|
4787
|
+
`Details for developers: ${x.url}`
|
|
4788
|
+
].join("\n")
|
|
4789
|
+
};
|
|
4790
|
+
}
|
|
4791
|
+
if ((s2.kind === "shipped" || s2.kind === "closed") && s2.command) {
|
|
4792
|
+
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.`;
|
|
4793
|
+
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." : "";
|
|
4794
|
+
return {
|
|
4795
|
+
subject: `Design system change ${quote(x.title)} ${s2.kind === "shipped" ? "has shipped" : "was closed"}`,
|
|
4796
|
+
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")
|
|
4797
|
+
};
|
|
4798
|
+
}
|
|
4799
|
+
return null;
|
|
4800
|
+
}
|
|
4801
|
+
const mailtoHref = (m2) => `mailto:?subject=${encodeURIComponent(m2.subject)}&body=${encodeURIComponent(m2.body.replace(/\r?\n/g, "\r\n"))}`;
|
|
4802
|
+
function productPreview(apps, ctx, sources) {
|
|
4803
|
+
if (!apps.admin || ctx.fromFork) return null;
|
|
4804
|
+
const admin = resolveBranchPreviews(apps, { branch: ctx.branch, pr: ctx.pr, sha: ctx.sha }, sources).find((p) => p.app === "admin");
|
|
4805
|
+
if (!admin?.url || admin.source === "template" || admin.state === "failed") return null;
|
|
4806
|
+
return admin.url;
|
|
4807
|
+
}
|
|
4808
|
+
function tryInProductUrl(preview, pr, screen) {
|
|
4809
|
+
const url = new URL(preview);
|
|
4810
|
+
url.pathname = "/";
|
|
4811
|
+
url.search = "";
|
|
4812
|
+
url.searchParams.set("bridge", String(pr));
|
|
4813
|
+
if (screen) {
|
|
4814
|
+
url.searchParams.set("tenant", screen.tenant);
|
|
4815
|
+
url.searchParams.set("theme", screen.theme);
|
|
4816
|
+
}
|
|
4817
|
+
url.hash = screen ? screen.route : "";
|
|
4818
|
+
return url.href;
|
|
4819
|
+
}
|
|
4820
|
+
const MIN_TOGETHER = 2;
|
|
4821
|
+
const MAX_TOGETHER = 4;
|
|
4822
|
+
function onlyTokens(files, tokensRoot) {
|
|
4823
|
+
const root = `${tokensRoot.replace(/^\/+|\/+$/g, "")}/`;
|
|
4824
|
+
return files.length > 0 && files.every((f) => f.startsWith(root) && f.endsWith(".tokens.json") || /contrast\.checks\.json$/.test(f) || f.startsWith(".changeset/"));
|
|
4825
|
+
}
|
|
4826
|
+
function tokenFilesOf(files, tokensRoot) {
|
|
4827
|
+
const root = `${tokensRoot.replace(/^\/+|\/+$/g, "")}/`;
|
|
4828
|
+
return files.filter((f) => f.startsWith(root) && f.endsWith(".tokens.json")).map((f) => f.slice(root.length));
|
|
4829
|
+
}
|
|
4830
|
+
const changeList = (numbers) => numbers.join(",");
|
|
4831
|
+
function tryTogetherUrl(admin, numbers, picks) {
|
|
4832
|
+
const url = new URL(admin);
|
|
4833
|
+
url.pathname = "/";
|
|
4834
|
+
url.hash = "";
|
|
4835
|
+
const use = encodePicks(picks);
|
|
4836
|
+
url.search = `?bridge=${changeList(numbers)}${use ? `&bridge-use=${encodeURIComponent(use)}` : ""}`;
|
|
4837
|
+
return url.href;
|
|
4838
|
+
}
|
|
4839
|
+
const COMBINE_COMPARE_LIMIT = 300;
|
|
4840
|
+
async function readCombineBranch(gh, pull, opts) {
|
|
4841
|
+
const compare = await gh.compareDetailed(opts.base, pull.headSha);
|
|
4842
|
+
const files = compare.files;
|
|
4843
|
+
const names2 = files.flatMap((f) => f.previous_filename ? [f.filename, f.previous_filename] : [f.filename]);
|
|
4844
|
+
const start = compare.mergeBase;
|
|
4845
|
+
if (!start || files.length >= COMBINE_COMPARE_LIMIT || !onlyTokens(names2, opts.tokensRoot)) return { number: pull.number, title: pull.title, tokens: false, edits: null };
|
|
4846
|
+
const root = opts.tokensRoot.replace(/^\/+|\/+$/g, "");
|
|
4847
|
+
const tokenFiles = files.filter((f) => tokenFilesOf([f.filename], root).length);
|
|
4848
|
+
const source = { number: pull.number, base: {}, head: {}, files: tokenFiles.map((f) => tokenFilesOf([f.filename], root)[0]) };
|
|
4849
|
+
await Promise.all(
|
|
4850
|
+
tokenFiles.map(async (f) => {
|
|
4851
|
+
const rel = tokenFilesOf([f.filename], root)[0];
|
|
4852
|
+
source.base[rel] = f.status === "added" ? void 0 : await gh.readFile(f.previous_filename ?? f.filename, start);
|
|
4853
|
+
source.head[rel] = f.status === "removed" ? void 0 : await gh.readFile(f.filename, pull.headSha);
|
|
4854
|
+
})
|
|
4855
|
+
);
|
|
4856
|
+
return { number: pull.number, title: pull.title, tokens: true, edits: changeEdits(source) };
|
|
4857
|
+
}
|
|
3790
4858
|
export {
|
|
3791
|
-
|
|
3792
|
-
|
|
4859
|
+
ao as AAA_MIN,
|
|
4860
|
+
ap as AA_MIN,
|
|
3793
4861
|
ADMIN_ROLE_ID,
|
|
3794
4862
|
ANTHROPIC_MESSAGES_URL,
|
|
3795
4863
|
BELL,
|
|
3796
4864
|
BRAND_FONTS,
|
|
3797
4865
|
BRAND_SLUG_RE,
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
4866
|
+
aq as BULK_LIMIT,
|
|
4867
|
+
q as COLOR_FIELDS,
|
|
4868
|
+
COMBINE_COMPARE_LIMIT,
|
|
4869
|
+
COMPARE_FILE_LIMIT,
|
|
4870
|
+
o as COMPONENT_SPECS_VERSION,
|
|
3801
4871
|
CONFIG_FILE,
|
|
3802
|
-
|
|
3803
|
-
|
|
4872
|
+
CONFLICT_REASON,
|
|
4873
|
+
ar as CONTRAST_CELL_MAX,
|
|
4874
|
+
as as CONTRAST_CHECKS_FILE,
|
|
3804
4875
|
ConfigError,
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
4876
|
+
at as DEFAULT_COMPONENT_SPECS_FILE,
|
|
4877
|
+
au as DEFAULT_GOVERNANCE,
|
|
4878
|
+
av as DEFAULT_OVERRIDE,
|
|
4879
|
+
aw as DEFAULT_PRODUCTS,
|
|
3808
4880
|
DEFAULT_PROTECTED_BRANCHES,
|
|
3809
|
-
|
|
4881
|
+
ax as DEFAULT_RIGHTS,
|
|
3810
4882
|
DEFAULT_TOKEN_PATHS,
|
|
3811
|
-
|
|
4883
|
+
ay as DEFAULT_VISUAL_IMPACT,
|
|
3812
4884
|
DEMO_RELEASES,
|
|
3813
4885
|
DOCS_CHANGE_BUMP,
|
|
3814
4886
|
DOCS_DRAFT_MODEL,
|
|
4887
|
+
DOCS_EDITOR_ORIGIN,
|
|
3815
4888
|
DOCS_KEYS,
|
|
4889
|
+
DOCS_MCP_ORIGIN,
|
|
3816
4890
|
DOCS_SELECTION_MODES,
|
|
3817
4891
|
DRAFTS_DIR,
|
|
3818
4892
|
DRAFT_EXCHANGE_VERSION,
|
|
@@ -3835,121 +4909,157 @@ export {
|
|
|
3835
4909
|
EXPLORATION_USE_CASES,
|
|
3836
4910
|
EXT_ID,
|
|
3837
4911
|
EXT_SCOPES,
|
|
4912
|
+
FIDELITY_DEFAULTS,
|
|
4913
|
+
FIGMA_FILL_WIDTH_TOKEN,
|
|
4914
|
+
FIGMA_REFERENCES_BRANCH,
|
|
4915
|
+
FIGMA_REFERENCES_DIR,
|
|
4916
|
+
FIGMA_REFERENCES_MANIFEST,
|
|
4917
|
+
FIGMA_REFERENCE_STORY,
|
|
3838
4918
|
GitHubClient,
|
|
3839
4919
|
GitHubError,
|
|
3840
|
-
|
|
4920
|
+
az as JsonParseError,
|
|
3841
4921
|
LIGHTNESS,
|
|
3842
4922
|
LayeredTokenSet,
|
|
4923
|
+
MAX_TOGETHER,
|
|
4924
|
+
MIN_TOGETHER,
|
|
3843
4925
|
NEUTRAL_ANCHORS,
|
|
3844
|
-
|
|
4926
|
+
OVERRIDE_HEADING,
|
|
4927
|
+
aA as OWNER_GROUPS,
|
|
4928
|
+
OverrideMergeError,
|
|
3845
4929
|
PIN_TOLERANCE,
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
4930
|
+
aB as PREVIEW_APP_LABELS,
|
|
4931
|
+
aC as PREVIEW_COMMENT_MARKER,
|
|
4932
|
+
aD as PREVIEW_PLACEHOLDERS,
|
|
4933
|
+
aE as PRIMITIVE_MODE,
|
|
4934
|
+
aF as PRODUCT_LABEL_PREFIX,
|
|
3850
4935
|
PROSE_KEYS,
|
|
3851
4936
|
PROTECTION_PARTS,
|
|
3852
4937
|
PROTECTION_PART_PHRASE,
|
|
4938
|
+
PULL_STATE_PILL,
|
|
3853
4939
|
ProtectedBranchError,
|
|
3854
4940
|
RADIUS_PRESETS,
|
|
4941
|
+
aG as REDUCED_MOTION_NOTE,
|
|
3855
4942
|
REQUESTS_BRANCH,
|
|
3856
4943
|
REQUEST_KINDS,
|
|
3857
4944
|
REQUEST_LABEL,
|
|
3858
4945
|
REQUEST_STATUS_LABEL,
|
|
3859
|
-
|
|
3860
|
-
|
|
4946
|
+
REVIEW_STATUS_ORDER,
|
|
4947
|
+
aH as RIGHTS,
|
|
4948
|
+
aI as RIGHT_LABEL,
|
|
3861
4949
|
RISK_LABELS,
|
|
3862
|
-
|
|
4950
|
+
aJ as RISK_TIER,
|
|
4951
|
+
SHARE_EXPLAIN,
|
|
3863
4952
|
SIMILARITY_THRESHOLD,
|
|
3864
4953
|
SINGLE_MODE,
|
|
3865
|
-
|
|
4954
|
+
aK as SPEC_FIELDS,
|
|
3866
4955
|
STEPS,
|
|
3867
4956
|
TIER_AXIS,
|
|
3868
4957
|
TIER_NAMES,
|
|
3869
|
-
|
|
4958
|
+
aL as TIER_ORDER,
|
|
3870
4959
|
TOKEN_REJECTED_MESSAGE,
|
|
3871
4960
|
TOKEN_SETTINGS_URL,
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
4961
|
+
aM as TRIGGER_LABELS,
|
|
4962
|
+
aN as TeamDirectory,
|
|
4963
|
+
aO as UNRELEASED,
|
|
4964
|
+
aP as VISUAL_CHECK_LABEL,
|
|
3875
4965
|
acceptDocsDraft,
|
|
3876
4966
|
acceptDocsText,
|
|
3877
4967
|
addBrandToConfigText,
|
|
3878
|
-
|
|
4968
|
+
addedComment,
|
|
4969
|
+
aQ as affectedContrast,
|
|
3879
4970
|
d as aliasOf,
|
|
3880
4971
|
aliasTarget,
|
|
4972
|
+
allowedMergeMethods,
|
|
3881
4973
|
anatomyLabel,
|
|
3882
4974
|
anchoredRamp,
|
|
3883
4975
|
anthropicReplyText,
|
|
3884
4976
|
anthropicRequest,
|
|
3885
|
-
|
|
3886
|
-
|
|
4977
|
+
aR as appForProject,
|
|
4978
|
+
aS as appUrl,
|
|
3887
4979
|
applyConfirmedRenames,
|
|
3888
4980
|
applyDocsSections,
|
|
3889
4981
|
applyTokenEdits,
|
|
3890
4982
|
applyTokenMerge,
|
|
3891
|
-
|
|
3892
|
-
|
|
4983
|
+
aT as approverEntries,
|
|
4984
|
+
aU as approverLists,
|
|
4985
|
+
askOthersToReviewOnGitHub,
|
|
3893
4986
|
assertSafeBranch,
|
|
3894
4987
|
assignRequestOnGitHub,
|
|
3895
|
-
|
|
4988
|
+
aV as assignVersions,
|
|
3896
4989
|
base64ToUtf8,
|
|
3897
|
-
|
|
4990
|
+
aW as baseBranchOf,
|
|
3898
4991
|
bellChroma,
|
|
3899
|
-
|
|
4992
|
+
aX as branchName,
|
|
3900
4993
|
branchPreviewUrls,
|
|
3901
|
-
|
|
4994
|
+
aY as branchSlug,
|
|
4995
|
+
branchWithJiraKey,
|
|
3902
4996
|
brandDisplayName,
|
|
3903
4997
|
brandPreviewVariables,
|
|
4998
|
+
buildFidelityReport,
|
|
3904
4999
|
buildHistoryEntry,
|
|
3905
5000
|
buildReport,
|
|
3906
|
-
|
|
5001
|
+
buildTokenSet,
|
|
3907
5002
|
buildUsageIndex,
|
|
3908
|
-
|
|
5003
|
+
canAddTo,
|
|
5004
|
+
canOverride,
|
|
5005
|
+
canShare,
|
|
5006
|
+
canShip,
|
|
3909
5007
|
changeEdits,
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
5008
|
+
aZ as changeKind,
|
|
5009
|
+
changeList,
|
|
5010
|
+
a_ as changeTable,
|
|
5011
|
+
a$ as changedModes,
|
|
5012
|
+
b0 as changesetBump,
|
|
3914
5013
|
checkBrandSlug,
|
|
3915
5014
|
checkLayeredPair,
|
|
3916
5015
|
choiceFor,
|
|
3917
5016
|
classifyAuth,
|
|
3918
|
-
|
|
5017
|
+
b1 as classifyLayered,
|
|
3919
5018
|
classifyRisk,
|
|
5019
|
+
cleanDocs,
|
|
3920
5020
|
cleanDocsText,
|
|
3921
5021
|
cleanDraftText,
|
|
3922
|
-
|
|
5022
|
+
b2 as clearRawDeep,
|
|
3923
5023
|
closeExplorationOnGitHub,
|
|
5024
|
+
closeOnGitHub,
|
|
3924
5025
|
closingIssueNumbers,
|
|
3925
5026
|
coerceValue,
|
|
3926
5027
|
collectHistory,
|
|
3927
5028
|
collectRefs,
|
|
5029
|
+
colorDelta,
|
|
3928
5030
|
k2 as colorToCss,
|
|
3929
5031
|
l as colorsEqual,
|
|
3930
5032
|
combineChanges,
|
|
3931
5033
|
combineConflicts,
|
|
3932
5034
|
c as comboKey,
|
|
3933
|
-
|
|
5035
|
+
commentOnGitHub,
|
|
5036
|
+
commitFileOnGitHub,
|
|
5037
|
+
commitLibraryResolution,
|
|
5038
|
+
b3 as commitMessage,
|
|
3934
5039
|
m as compactJson,
|
|
5040
|
+
compareImages,
|
|
3935
5041
|
componentForPath,
|
|
3936
|
-
|
|
5042
|
+
b4 as componentMotion,
|
|
5043
|
+
b5 as componentsUsingMotionToken,
|
|
5044
|
+
b6 as composeChangeset,
|
|
3937
5045
|
composeFiles,
|
|
3938
5046
|
composeNewBrandBody,
|
|
3939
5047
|
composePendingDrafts,
|
|
3940
|
-
|
|
3941
|
-
|
|
5048
|
+
b7 as composePrBody,
|
|
5049
|
+
b8 as composeProposalBody,
|
|
3942
5050
|
composeRequestIssue,
|
|
3943
|
-
|
|
3944
|
-
|
|
5051
|
+
b9 as composite,
|
|
5052
|
+
conflictRoute,
|
|
5053
|
+
ba as contextFor,
|
|
3945
5054
|
contextLabel,
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
5055
|
+
bb as contrastDelta,
|
|
5056
|
+
bc as contrastDeltaTable,
|
|
5057
|
+
bd as contrastLevel,
|
|
5058
|
+
be as contrastPair,
|
|
5059
|
+
bf as contrastRatio,
|
|
3951
5060
|
contrastScope,
|
|
3952
|
-
|
|
5061
|
+
bg as countChanges,
|
|
5062
|
+
bh as coverageSummary,
|
|
3953
5063
|
createDocsDraftSession,
|
|
3954
5064
|
createPendingDrafts,
|
|
3955
5065
|
cssModuleClasses,
|
|
@@ -3961,22 +5071,27 @@ export {
|
|
|
3961
5071
|
declineRequestOnGitHub,
|
|
3962
5072
|
decodePicks,
|
|
3963
5073
|
defaultChoiceFor,
|
|
3964
|
-
|
|
5074
|
+
k as defaultContext,
|
|
3965
5075
|
defaultFile,
|
|
5076
|
+
bi as defaultProduct,
|
|
3966
5077
|
defaultScopes,
|
|
3967
|
-
|
|
3968
|
-
|
|
5078
|
+
bj as defaultTitle,
|
|
5079
|
+
bk as defineConfig,
|
|
3969
5080
|
deletePendingDrafts,
|
|
3970
5081
|
demoHistory,
|
|
3971
|
-
|
|
3972
|
-
|
|
5082
|
+
bl as detectLayout,
|
|
5083
|
+
bm as diffLayered,
|
|
5084
|
+
dismissStaleFrom,
|
|
5085
|
+
docsFileText,
|
|
3973
5086
|
docsPrompt,
|
|
5087
|
+
docsProposal,
|
|
3974
5088
|
docsSectionShapes,
|
|
3975
5089
|
docsSectionsAnswer,
|
|
3976
5090
|
docsSectionsResult,
|
|
3977
5091
|
docsSelectionPrompt,
|
|
3978
5092
|
docsTaskKeys,
|
|
3979
5093
|
docsTaskPrompt,
|
|
5094
|
+
docsText,
|
|
3980
5095
|
draftChangeLine,
|
|
3981
5096
|
draftImagePath,
|
|
3982
5097
|
draftItemBrief,
|
|
@@ -3985,14 +5100,16 @@ export {
|
|
|
3985
5100
|
draftSessionPath,
|
|
3986
5101
|
dtcgColor,
|
|
3987
5102
|
effectiveScopes,
|
|
5103
|
+
effectiveTier,
|
|
5104
|
+
eligibleReviewers,
|
|
3988
5105
|
emptyTokenFile,
|
|
3989
5106
|
encodePicks,
|
|
3990
5107
|
ensureRequestsBranch,
|
|
3991
|
-
|
|
3992
|
-
|
|
5108
|
+
bn as entriesFor,
|
|
5109
|
+
bo as entryLabel,
|
|
3993
5110
|
evaluatePairs,
|
|
3994
|
-
|
|
3995
|
-
|
|
5111
|
+
bp as expandEntries,
|
|
5112
|
+
bq as expandOwners,
|
|
3996
5113
|
explorationBranch,
|
|
3997
5114
|
explorationNote,
|
|
3998
5115
|
explorationNotePath,
|
|
@@ -4008,63 +5125,73 @@ export {
|
|
|
4008
5125
|
explorationTestBuildOnGitHub,
|
|
4009
5126
|
explorationTryIt,
|
|
4010
5127
|
explorationTryItOnGitHub,
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
5128
|
+
fidelityComponentLine,
|
|
5129
|
+
fidelityVerdict,
|
|
5130
|
+
br as fieldChanges,
|
|
5131
|
+
bs as figmaCell,
|
|
5132
|
+
bt as fileModes,
|
|
5133
|
+
bu as filterHistory,
|
|
4014
5134
|
findExplorationOnGitHub,
|
|
4015
5135
|
findPreviewComment,
|
|
5136
|
+
bv as findProduct,
|
|
4016
5137
|
findReferenceProblems,
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
5138
|
+
bw as findToken,
|
|
5139
|
+
bx as findVercelComment,
|
|
5140
|
+
by as firstParagraph,
|
|
4020
5141
|
n as flattenLayerFile,
|
|
4021
5142
|
flattenTokens,
|
|
4022
5143
|
formatChangeset,
|
|
4023
5144
|
formatDocsJson,
|
|
4024
|
-
|
|
4025
|
-
|
|
5145
|
+
bz as formatNumber,
|
|
5146
|
+
bA as formatOwnerEntry,
|
|
4026
5147
|
formatValue,
|
|
5148
|
+
freshness,
|
|
4027
5149
|
friendlyMessage,
|
|
4028
5150
|
fromHex,
|
|
4029
5151
|
fromPlain,
|
|
4030
5152
|
generateRamp,
|
|
4031
|
-
|
|
5153
|
+
bB as getChild,
|
|
4032
5154
|
getEntry,
|
|
4033
5155
|
gitHubHistoryBackend,
|
|
4034
|
-
|
|
4035
|
-
|
|
5156
|
+
g as globToRegExp,
|
|
5157
|
+
bC as groupsPhrase,
|
|
5158
|
+
hasWriteAccess,
|
|
4036
5159
|
h as hashSpec,
|
|
4037
5160
|
h2 as hexToRgba,
|
|
4038
|
-
|
|
4039
|
-
|
|
5161
|
+
bD as holdersOf,
|
|
5162
|
+
i as iconComponentName,
|
|
4040
5163
|
idExtensionFor,
|
|
4041
5164
|
impactedModules,
|
|
4042
5165
|
importsIn,
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
5166
|
+
bE as indexByPath,
|
|
5167
|
+
bF as inheritedTypeAt,
|
|
5168
|
+
bG as installCommand,
|
|
5169
|
+
bH as installCommandFromPreviewComment,
|
|
4047
5170
|
isActiveExploration,
|
|
4048
5171
|
isAlias,
|
|
5172
|
+
isAutomationPull,
|
|
4049
5173
|
isDocsSelectionMode,
|
|
4050
5174
|
isDraftSessionId,
|
|
4051
5175
|
isExplorationBranch,
|
|
4052
5176
|
isExplorationPull,
|
|
4053
|
-
|
|
5177
|
+
bI as isGuardrail,
|
|
4054
5178
|
isHex,
|
|
5179
|
+
isJiraKey,
|
|
4055
5180
|
isMissingPermission,
|
|
4056
5181
|
isMultiMode,
|
|
4057
5182
|
isPlanRefusal,
|
|
4058
5183
|
isProseKey,
|
|
4059
|
-
|
|
4060
|
-
|
|
5184
|
+
isReleasePull,
|
|
5185
|
+
bJ as isTeamEntry,
|
|
5186
|
+
bK as isTokenFile,
|
|
4061
5187
|
isTokenRejected,
|
|
4062
|
-
|
|
5188
|
+
bL as isVercelComment,
|
|
4063
5189
|
jiraIssueFields,
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
5190
|
+
jiraKeyOf,
|
|
5191
|
+
bM as kindForMin,
|
|
5192
|
+
bN as layeredImpact,
|
|
5193
|
+
o2 as layeredParityIssues,
|
|
5194
|
+
layoutOptions,
|
|
4068
5195
|
levenshtein,
|
|
4069
5196
|
linkRequestToExplorationOnGitHub,
|
|
4070
5197
|
listBrands,
|
|
@@ -4075,17 +5202,20 @@ export {
|
|
|
4075
5202
|
loadProtection,
|
|
4076
5203
|
loadRepoFiles,
|
|
4077
5204
|
loadRequiredChecks,
|
|
4078
|
-
|
|
5205
|
+
bO as luminance,
|
|
5206
|
+
mailtoHref,
|
|
4079
5207
|
markClaudeDraft,
|
|
4080
5208
|
mergeDocsDraft,
|
|
4081
5209
|
mergeDraftResults,
|
|
5210
|
+
mergeGate,
|
|
5211
|
+
mergeOnGitHub,
|
|
4082
5212
|
mergeReleases,
|
|
4083
5213
|
missingChoices,
|
|
4084
5214
|
missingPermissionMessage,
|
|
4085
5215
|
missingProse,
|
|
4086
5216
|
modeDisplayName,
|
|
4087
5217
|
modeFile,
|
|
4088
|
-
|
|
5218
|
+
bP as modeLabel,
|
|
4089
5219
|
modesOf$1 as modesOf,
|
|
4090
5220
|
moveToken,
|
|
4091
5221
|
nearestStep,
|
|
@@ -4094,88 +5224,124 @@ export {
|
|
|
4094
5224
|
neutralOffset,
|
|
4095
5225
|
newBrandSection,
|
|
4096
5226
|
newDraftSessionId,
|
|
4097
|
-
|
|
4098
|
-
|
|
5227
|
+
bQ as nodesEqual,
|
|
5228
|
+
bR as normalizeRights,
|
|
4099
5229
|
oklch,
|
|
4100
5230
|
oklchToLinear,
|
|
4101
|
-
|
|
4102
|
-
|
|
5231
|
+
onlyTokens,
|
|
5232
|
+
openPulls,
|
|
5233
|
+
overlap,
|
|
5234
|
+
overrideComment,
|
|
5235
|
+
bS as overrideKey,
|
|
5236
|
+
overrideMergeOnGitHub,
|
|
5237
|
+
overrideReasonProblem,
|
|
5238
|
+
overrideRefusedComment,
|
|
5239
|
+
bT as ownerEntryProblems,
|
|
5240
|
+
packageDir,
|
|
4103
5241
|
packageDirCandidates,
|
|
4104
|
-
|
|
5242
|
+
bU as pairKey,
|
|
4105
5243
|
parseChangelog,
|
|
4106
|
-
|
|
5244
|
+
bV as parseChangesetText,
|
|
4107
5245
|
parseColor,
|
|
4108
|
-
|
|
5246
|
+
bW as parseComponentSpecs,
|
|
4109
5247
|
parseContrastChecks,
|
|
4110
|
-
|
|
5248
|
+
q2 as parseDimension,
|
|
4111
5249
|
parseDocsReply,
|
|
4112
5250
|
parseDocsTaskAnswer,
|
|
4113
5251
|
parseExplorationBody,
|
|
5252
|
+
parseJiraKey,
|
|
4114
5253
|
parseJson,
|
|
4115
|
-
|
|
5254
|
+
parseOverrideComment,
|
|
5255
|
+
bX as parseOwnerEntry,
|
|
4116
5256
|
parsePendingDrafts,
|
|
4117
5257
|
parseRepo,
|
|
4118
5258
|
parseRequestIssue,
|
|
4119
5259
|
parseStructureChange,
|
|
5260
|
+
parseTier,
|
|
4120
5261
|
parseTokenFile,
|
|
4121
|
-
|
|
5262
|
+
bY as parseVercelComment,
|
|
4122
5263
|
patchDocsJson,
|
|
4123
5264
|
pathId,
|
|
4124
5265
|
pathSimilarity,
|
|
5266
|
+
pendingApprovals,
|
|
4125
5267
|
permissionLabel,
|
|
4126
5268
|
pickedOption,
|
|
4127
5269
|
plainEqual,
|
|
4128
5270
|
planNewBrand,
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
5271
|
+
planReference,
|
|
5272
|
+
planReferences,
|
|
5273
|
+
bZ as planRestore,
|
|
5274
|
+
b_ as planRevert,
|
|
5275
|
+
b$ as planToJson,
|
|
4132
5276
|
planTokenMerge,
|
|
4133
5277
|
e as prNumberFromSubject,
|
|
4134
5278
|
prefillDocs,
|
|
4135
|
-
|
|
5279
|
+
c0 as previewComment,
|
|
4136
5280
|
previewLinks,
|
|
4137
|
-
|
|
5281
|
+
c1 as previewLinksOf,
|
|
4138
5282
|
previewOverrides,
|
|
4139
|
-
|
|
5283
|
+
c2 as previewUrl,
|
|
4140
5284
|
previewsSettled,
|
|
4141
|
-
|
|
5285
|
+
c3 as productForPlace,
|
|
5286
|
+
c4 as productLabel,
|
|
5287
|
+
c5 as productOfBranch,
|
|
5288
|
+
productPreview,
|
|
5289
|
+
c6 as productProblems,
|
|
5290
|
+
c7 as productsOf,
|
|
5291
|
+
c8 as projectOfLabel,
|
|
4142
5292
|
promoteExplorationOnGitHub,
|
|
4143
5293
|
promotionPlan,
|
|
5294
|
+
proposeDocsOnGitHub,
|
|
4144
5295
|
propsTable,
|
|
4145
5296
|
protectLibrary,
|
|
4146
5297
|
protectRefusal,
|
|
4147
|
-
|
|
5298
|
+
c9 as protectedBranchesOf,
|
|
4148
5299
|
protectionRuleset,
|
|
4149
5300
|
protectionRulesetName,
|
|
4150
5301
|
protectionSentence,
|
|
4151
5302
|
protectionState,
|
|
5303
|
+
pullState,
|
|
4152
5304
|
pushChangeRows,
|
|
4153
5305
|
rateLimitMessage,
|
|
5306
|
+
readCombineBranch,
|
|
5307
|
+
readLibraryConflicts,
|
|
4154
5308
|
readPendingDrafts,
|
|
4155
5309
|
readPreviewComment,
|
|
4156
5310
|
readTokenAt,
|
|
4157
5311
|
reconcileIds,
|
|
5312
|
+
referenceImagePath,
|
|
5313
|
+
releasePull,
|
|
4158
5314
|
releasesFromTags,
|
|
4159
5315
|
removeToken,
|
|
4160
5316
|
removedSide,
|
|
4161
|
-
|
|
5317
|
+
ca as renderedColor,
|
|
5318
|
+
reopenEligibility,
|
|
5319
|
+
reopenOnGitHub,
|
|
4162
5320
|
requestBrief,
|
|
4163
5321
|
requestImagePath,
|
|
4164
5322
|
requestIssueTitle,
|
|
4165
5323
|
requestStatus,
|
|
5324
|
+
requiredApprovals,
|
|
4166
5325
|
requiredCheckNames,
|
|
4167
5326
|
requiredPermission,
|
|
5327
|
+
resolutionMessage,
|
|
4168
5328
|
resolve,
|
|
4169
5329
|
resolveBranchPreviews,
|
|
5330
|
+
resolvedTokenFiles,
|
|
5331
|
+
reviewOnGitHub,
|
|
5332
|
+
reviewQueue,
|
|
5333
|
+
reviewStatusKind,
|
|
5334
|
+
reviewersToAsk,
|
|
4170
5335
|
r as rgbaToDtcg,
|
|
4171
5336
|
rgbaToHex,
|
|
4172
|
-
|
|
5337
|
+
cb as rightsProblems,
|
|
4173
5338
|
round3,
|
|
4174
5339
|
rulesSettingsUrl,
|
|
4175
5340
|
rulesetTargetsDefault,
|
|
4176
|
-
|
|
5341
|
+
cc as runLayeredContrast,
|
|
5342
|
+
cd as sameProducts,
|
|
4177
5343
|
sameScopes,
|
|
4178
|
-
|
|
5344
|
+
ce as sameToken,
|
|
4179
5345
|
sanitizeDocsReplacement,
|
|
4180
5346
|
sanitizeDocsResult,
|
|
4181
5347
|
sanitizeDocsSections,
|
|
@@ -4189,58 +5355,75 @@ export {
|
|
|
4189
5355
|
serializePendingDrafts,
|
|
4190
5356
|
serializeTokenFile,
|
|
4191
5357
|
shadowedEdits,
|
|
4192
|
-
|
|
5358
|
+
shareDescription,
|
|
5359
|
+
shareMessage,
|
|
5360
|
+
shareState,
|
|
5361
|
+
cf as shipPolicy,
|
|
4193
5362
|
shortVariant,
|
|
4194
5363
|
shortVariants,
|
|
5364
|
+
showShare,
|
|
4195
5365
|
slugify,
|
|
4196
|
-
|
|
4197
|
-
|
|
5366
|
+
cg as snapshotUrl,
|
|
5367
|
+
s as specHash,
|
|
5368
|
+
stableCommand,
|
|
4198
5369
|
startExplorationOnGitHub,
|
|
4199
|
-
|
|
5370
|
+
stateSentence,
|
|
5371
|
+
summarizeApprovals,
|
|
5372
|
+
summarizeCi,
|
|
5373
|
+
summarizeComponent,
|
|
5374
|
+
ch as summarizeTokenChange,
|
|
4200
5375
|
takeMine,
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
5376
|
+
ci as tarballAssetName,
|
|
5377
|
+
cj as tarballUrl,
|
|
5378
|
+
ck as teamKey,
|
|
5379
|
+
cl as teamKeyOf,
|
|
5380
|
+
cm as teamUnreadableMessage,
|
|
5381
|
+
cn as teamsIn,
|
|
4207
5382
|
threeWayDiff,
|
|
4208
5383
|
tintedNeutral,
|
|
4209
|
-
|
|
5384
|
+
co as titleOf,
|
|
5385
|
+
titleWithJiraKey,
|
|
4210
5386
|
t2 as toAlias,
|
|
4211
|
-
|
|
5387
|
+
cp as toChangeSummary,
|
|
4212
5388
|
toConfigJson,
|
|
4213
5389
|
toHex,
|
|
4214
|
-
|
|
5390
|
+
cq as toMap,
|
|
4215
5391
|
toOklch,
|
|
4216
5392
|
toPlain,
|
|
4217
|
-
|
|
5393
|
+
cr as tokenBump,
|
|
4218
5394
|
t as tokenCssVar,
|
|
4219
|
-
|
|
4220
|
-
|
|
5395
|
+
tokenFilesOf,
|
|
5396
|
+
cs as tokenHistory,
|
|
5397
|
+
ct as tokenNode,
|
|
4221
5398
|
tokenUsage,
|
|
4222
5399
|
tokensPath,
|
|
5400
|
+
touchesPackages,
|
|
5401
|
+
tryInProductUrl,
|
|
5402
|
+
tryTogetherUrl,
|
|
4223
5403
|
unpicked,
|
|
4224
|
-
|
|
4225
|
-
|
|
5404
|
+
unreadableShipTeams,
|
|
5405
|
+
cu as unreadableTeams,
|
|
4226
5406
|
updatePendingDrafts,
|
|
4227
5407
|
upsertToken,
|
|
4228
5408
|
utf8ToBase64,
|
|
5409
|
+
v as validateComponentSpec,
|
|
5410
|
+
validateDocs,
|
|
4229
5411
|
validateDocsTask,
|
|
4230
5412
|
validateIntegrations,
|
|
4231
5413
|
validateLayered,
|
|
4232
5414
|
validatePendingDrafts,
|
|
4233
5415
|
validateProposal,
|
|
4234
5416
|
validateSet,
|
|
4235
|
-
|
|
5417
|
+
cv as valueIn,
|
|
4236
5418
|
u as valueToCss,
|
|
4237
5419
|
valuesEqual,
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
5420
|
+
variantKeyOf,
|
|
5421
|
+
cw as variantName,
|
|
5422
|
+
cx as versionTimeline,
|
|
5423
|
+
cy as visualImpactProblems,
|
|
5424
|
+
whoCanShipSentence,
|
|
4243
5425
|
withRequestExploration,
|
|
5426
|
+
withoutJiraKey,
|
|
4244
5427
|
writeDraftResults
|
|
4245
5428
|
};
|
|
4246
5429
|
//# sourceMappingURL=index.js.map
|