@bridge-ds/cli 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +664 -308
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { existsSync as existsSync6 } from "node:fs";
|
|
5
5
|
import { readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
|
|
6
|
-
import { isAbsolute, join as join6, relative as relative4, resolve as
|
|
6
|
+
import { isAbsolute, join as join6, relative as relative4, resolve as resolve5 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// ../core/src/audit/types.ts
|
|
9
9
|
var AUDIT_SCHEMA_VERSION = 1;
|
|
@@ -2424,6 +2424,8 @@ function productProblems(raw) {
|
|
|
2424
2424
|
|
|
2425
2425
|
// ../core/src/config.ts
|
|
2426
2426
|
var CONFIG_FILE = "bridge.config.json";
|
|
2427
|
+
var CHANGES_MODES = ["suggest", "direct"];
|
|
2428
|
+
var DEFAULT_CHANGES_MODE = "suggest";
|
|
2427
2429
|
var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
|
|
2428
2430
|
var DEFAULT_TOKEN_PATHS = {
|
|
2429
2431
|
root: "tokens",
|
|
@@ -2563,6 +2565,8 @@ function defineConfig(input) {
|
|
|
2563
2565
|
...g.rights ? { rights: normalizeRights(g.rights) } : {}
|
|
2564
2566
|
},
|
|
2565
2567
|
products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
|
|
2568
|
+
changes: input.changes === "direct" ? "direct" : DEFAULT_CHANGES_MODE,
|
|
2569
|
+
...input.storybook?.url ? { storybook: { url: input.storybook.url.trim().replace(/\/+$/, "") } } : {},
|
|
2566
2570
|
...input.audit ? { audit: { ...input.audit } } : {},
|
|
2567
2571
|
...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
|
|
2568
2572
|
...input.integrations ? { integrations: { ...input.integrations } } : {},
|
|
@@ -2685,6 +2689,13 @@ function loadConfig(source) {
|
|
|
2685
2689
|
}
|
|
2686
2690
|
}
|
|
2687
2691
|
problems.push(...visualImpactProblems(d.visualImpact));
|
|
2692
|
+
if (d.changes !== void 0 && !CHANGES_MODES.includes(d.changes)) problems.push(`"changes" must be "suggest" or "direct" (got ${JSON.stringify(d.changes)})`);
|
|
2693
|
+
const sb = d.storybook;
|
|
2694
|
+
if (sb !== void 0) {
|
|
2695
|
+
if (!isObj(sb) || !isStr(sb.url)) problems.push('"storybook" must be { "url" } with the deployed Storybook address');
|
|
2696
|
+
else if (!/^https:\/\/[^\s/]+/.test(sb.url.trim())) problems.push('"storybook.url" must start with https://, for example https://storybook.example.com');
|
|
2697
|
+
else for (const k of Object.keys(sb)) if (k !== "url") problems.push(`"storybook.${k}" is not a known key`);
|
|
2698
|
+
}
|
|
2688
2699
|
problems.push(...productProblems(d.products));
|
|
2689
2700
|
const audit2 = d.audit;
|
|
2690
2701
|
if (audit2 !== void 0) {
|
|
@@ -2739,10 +2750,10 @@ function assertSafeBranch(branch, rules = {}) {
|
|
|
2739
2750
|
|
|
2740
2751
|
// ../core/src/node/git-local.ts
|
|
2741
2752
|
function git(root, args, opts = {}) {
|
|
2742
|
-
return new Promise((
|
|
2753
|
+
return new Promise((resolve6, reject) => {
|
|
2743
2754
|
const child = execFile("git", args, { cwd: root, env: { ...process.env, ...opts.env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
2744
2755
|
if (err) reject(new Error(`git ${args.join(" ")}: ${stderr || err.message}`.trim()));
|
|
2745
|
-
else
|
|
2756
|
+
else resolve6(stdout);
|
|
2746
2757
|
});
|
|
2747
2758
|
if (opts.input !== void 0) child.stdin.end(opts.input);
|
|
2748
2759
|
});
|
|
@@ -2958,93 +2969,6 @@ function parseComponentSpecs(text) {
|
|
|
2958
2969
|
return f;
|
|
2959
2970
|
}
|
|
2960
2971
|
|
|
2961
|
-
// src/index.ts
|
|
2962
|
-
import { fileURLToPath } from "node:url";
|
|
2963
|
-
|
|
2964
|
-
// ../visual/src/run.ts
|
|
2965
|
-
import { existsSync as existsSync4 } from "node:fs";
|
|
2966
|
-
import { mkdir as mkdir3, readFile as readFile3, rm as rm2, unlink, writeFile as writeFile3 } from "node:fs/promises";
|
|
2967
|
-
import { tmpdir } from "node:os";
|
|
2968
|
-
import { join as join4, relative as relative3, resolve as resolve2 } from "node:path";
|
|
2969
|
-
|
|
2970
|
-
// ../core/src/protection.ts
|
|
2971
|
-
var PROTECTION_PARTS = ["review", "checks", "history", "deletion"];
|
|
2972
|
-
var protectionRulesetName = (defaultBranch) => `Protect the library (${defaultBranch})`;
|
|
2973
|
-
function refPattern(pattern) {
|
|
2974
|
-
const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
|
|
2975
|
-
return new RegExp(`^${src}$`);
|
|
2976
|
-
}
|
|
2977
|
-
function rulesetTargetsDefault(ruleset, defaultBranch) {
|
|
2978
|
-
if (ruleset.target && ruleset.target !== "branch") return false;
|
|
2979
|
-
const ref = `refs/heads/${defaultBranch}`;
|
|
2980
|
-
const hit = (p) => p === "~ALL" || p === "~DEFAULT_BRANCH" || p === ref || p === defaultBranch || refPattern(p.startsWith("refs/") ? p : `refs/heads/${p}`).test(ref);
|
|
2981
|
-
const cond = ruleset.conditions?.ref_name;
|
|
2982
|
-
if (!cond) return false;
|
|
2983
|
-
if ((cond.exclude ?? []).some(hit)) return false;
|
|
2984
|
-
return (cond.include ?? []).some(hit);
|
|
2985
|
-
}
|
|
2986
|
-
var count = (v) => typeof v === "number" ? v : 0;
|
|
2987
|
-
function rulesetParts(ruleset) {
|
|
2988
|
-
const parts = /* @__PURE__ */ new Set();
|
|
2989
|
-
if (ruleset.enforcement !== "active") return parts;
|
|
2990
|
-
for (const rule of ruleset.rules ?? []) {
|
|
2991
|
-
if (rule.type === "deletion") parts.add("deletion");
|
|
2992
|
-
if (rule.type === "non_fast_forward") parts.add("history");
|
|
2993
|
-
if (rule.type === "pull_request" && count(rule.parameters?.required_approving_review_count) >= 1) parts.add("review");
|
|
2994
|
-
if (rule.type === "required_status_checks" && Array.isArray(rule.parameters?.required_status_checks) && rule.parameters.required_status_checks.length > 0) parts.add("checks");
|
|
2995
|
-
}
|
|
2996
|
-
return parts;
|
|
2997
|
-
}
|
|
2998
|
-
function classicParts(p) {
|
|
2999
|
-
const parts = /* @__PURE__ */ new Set();
|
|
3000
|
-
if (count(p.required_pull_request_reviews?.required_approving_review_count) >= 1) parts.add("review");
|
|
3001
|
-
if (((p.required_status_checks?.contexts?.length ?? 0) || (p.required_status_checks?.checks?.length ?? 0)) > 0) parts.add("checks");
|
|
3002
|
-
if (!p.allow_force_pushes?.enabled) parts.add("history");
|
|
3003
|
-
if (!p.allow_deletions?.enabled) parts.add("deletion");
|
|
3004
|
-
return parts;
|
|
3005
|
-
}
|
|
3006
|
-
var rulesetUrl = (r) => r.html_url ?? r._links?.html?.href;
|
|
3007
|
-
function protectionState(rulesets, branchProtection, defaultBranch) {
|
|
3008
|
-
const bridge = rulesets?.find((r) => r.name === protectionRulesetName(defaultBranch));
|
|
3009
|
-
const extra = bridge?.id !== void 0 ? { bridgeRulesetId: bridge.id } : {};
|
|
3010
|
-
const covered = /* @__PURE__ */ new Set();
|
|
3011
|
-
let url;
|
|
3012
|
-
for (const r of rulesets ?? []) {
|
|
3013
|
-
if (!rulesetTargetsDefault(r, defaultBranch)) continue;
|
|
3014
|
-
const parts = rulesetParts(r);
|
|
3015
|
-
if (parts.size && !url) url = rulesetUrl(r);
|
|
3016
|
-
parts.forEach((p) => covered.add(p));
|
|
3017
|
-
}
|
|
3018
|
-
if (branchProtection) classicParts(branchProtection).forEach((p) => covered.add(p));
|
|
3019
|
-
const missing = PROTECTION_PARTS.filter((p) => !covered.has(p));
|
|
3020
|
-
const withUrl = url ? { url } : {};
|
|
3021
|
-
if (!missing.length) return { kind: "protected", missing, ...extra, ...withUrl };
|
|
3022
|
-
const unreadable = rulesets == null || branchProtection === void 0;
|
|
3023
|
-
if (covered.size === 0) return { kind: unreadable ? "unknown" : "unprotected", missing, ...extra };
|
|
3024
|
-
return { kind: "partial", missing, ...extra, ...withUrl };
|
|
3025
|
-
}
|
|
3026
|
-
var PROTECTION_PART_PHRASE = {
|
|
3027
|
-
review: "changes can skip review",
|
|
3028
|
-
checks: "checks do not have to pass",
|
|
3029
|
-
history: "past changes can be rewritten",
|
|
3030
|
-
deletion: "the library can be deleted"
|
|
3031
|
-
};
|
|
3032
|
-
var joinAnd = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
3033
|
-
function protectionSentence(state) {
|
|
3034
|
-
switch (state.kind) {
|
|
3035
|
-
case "protected":
|
|
3036
|
-
return "Protected: every change goes through review.";
|
|
3037
|
-
case "unprotected":
|
|
3038
|
-
return "Your library can be changed without review.";
|
|
3039
|
-
case "partial": {
|
|
3040
|
-
const s = joinAnd(state.missing.map((p) => PROTECTION_PART_PHRASE[p]));
|
|
3041
|
-
return `Partly protected: ${s}.`;
|
|
3042
|
-
}
|
|
3043
|
-
default:
|
|
3044
|
-
return "Protection status unknown: GitHub did not let this token read it.";
|
|
3045
|
-
}
|
|
3046
|
-
}
|
|
3047
|
-
|
|
3048
2972
|
// ../core/src/github-client.ts
|
|
3049
2973
|
var GitHubError = class extends Error {
|
|
3050
2974
|
constructor(status, message, detail, info = {}) {
|
|
@@ -3412,21 +3336,27 @@ var GitHubClient = class _GitHubClient {
|
|
|
3412
3336
|
return pr ? { number: pr.number, url: pr.html_url, title: pr.title, draft: !!pr.draft, headRef: pr.head.ref } : void 0;
|
|
3413
3337
|
}
|
|
3414
3338
|
/** Best effort: labels need the repo to allow it; failure is not fatal. */
|
|
3415
|
-
async addLabels(
|
|
3339
|
+
async addLabels(issueNumber3, labels) {
|
|
3416
3340
|
try {
|
|
3417
|
-
await this.request("POST", `${this.repoPath}/issues/${
|
|
3341
|
+
await this.request("POST", `${this.repoPath}/issues/${issueNumber3}/labels`, { labels });
|
|
3418
3342
|
return true;
|
|
3419
3343
|
} catch {
|
|
3420
3344
|
return false;
|
|
3421
3345
|
}
|
|
3422
3346
|
}
|
|
3423
|
-
async addComment(
|
|
3424
|
-
await this.request("POST", `${this.repoPath}/issues/${
|
|
3347
|
+
async addComment(issueNumber3, body) {
|
|
3348
|
+
await this.request("POST", `${this.repoPath}/issues/${issueNumber3}/comments`, { body });
|
|
3425
3349
|
}
|
|
3426
|
-
/**
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3350
|
+
/**
|
|
3351
|
+
* Comments on an issue or pull request, oldest first (first 100: the sticky preview comment is posted early).
|
|
3352
|
+
* `authorAssociation` is GitHub's OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, NONE... (suggestion approvals read it).
|
|
3353
|
+
*/
|
|
3354
|
+
async listIssueComments(issueNumber3) {
|
|
3355
|
+
const r = await this.request(
|
|
3356
|
+
"GET",
|
|
3357
|
+
`${this.repoPath}/issues/${issueNumber3}/comments?per_page=100`
|
|
3358
|
+
);
|
|
3359
|
+
return r.map((c) => ({ id: c.id, body: c.body ?? "", author: c.user?.login ?? "unknown", updatedAt: c.updated_at, createdAt: c.created_at ?? c.updated_at, authorAssociation: c.author_association ?? "NONE" }));
|
|
3430
3360
|
}
|
|
3431
3361
|
// --- Issues (component change requests) ---------------------------------------------------
|
|
3432
3362
|
async createIssue(input) {
|
|
@@ -3741,8 +3671,8 @@ var GitHubClient = class _GitHubClient {
|
|
|
3741
3671
|
});
|
|
3742
3672
|
return { id: r.id, name: r.name, status: r.status, conclusion: r.conclusion, url: r.html_url };
|
|
3743
3673
|
}
|
|
3744
|
-
async removeLabel(
|
|
3745
|
-
await this.request("DELETE", `${this.repoPath}/issues/${
|
|
3674
|
+
async removeLabel(issueNumber3, label) {
|
|
3675
|
+
await this.request("DELETE", `${this.repoPath}/issues/${issueNumber3}/labels/${encodeURIComponent(label)}`);
|
|
3746
3676
|
}
|
|
3747
3677
|
// --- branch actions shared by Studio, the MCP server and the CLI (branch-actions.ts) ---
|
|
3748
3678
|
/** The account the token belongs to (GET /user). Needs a token. */
|
|
@@ -3916,6 +3846,169 @@ function toIssue(r) {
|
|
|
3916
3846
|
};
|
|
3917
3847
|
}
|
|
3918
3848
|
|
|
3849
|
+
// ../core/src/explorations.ts
|
|
3850
|
+
var EXPLORATION_USE_CASES = [
|
|
3851
|
+
{ value: "user-test", label: "Prototype for a user test", hint: "A coded prototype on a product staging branch, with the real components and tokens." },
|
|
3852
|
+
{ value: "stakeholder-demo", label: "Stakeholder demo", hint: "Show an idea on real screens before anyone commits to it." },
|
|
3853
|
+
{ value: "ab-idea", label: "A/B idea", hint: "Ship a variant to a staging or experiment branch and compare it with what is live." },
|
|
3854
|
+
{ value: "terminal", label: "Exploring with Claude Code", hint: "A designer works from the terminal with Claude and the Bridge MCP server on this branch, never on main." },
|
|
3855
|
+
{ value: "other", label: "Other", hint: "Anything else worth trying before it becomes a change." }
|
|
3856
|
+
];
|
|
3857
|
+
var EXPLORATION_STATUS_LABEL = { active: "Running", promoted: "Promoted to review", closed: "Closed" };
|
|
3858
|
+
|
|
3859
|
+
// ../core/src/requests.ts
|
|
3860
|
+
var REQUEST_KINDS = [
|
|
3861
|
+
{ value: "new-component", label: "New component", hint: "A component the design system does not have yet." },
|
|
3862
|
+
{ value: "new-variant", label: "New variant", hint: "A size, tone or state the component does not have yet." },
|
|
3863
|
+
{ value: "prop-change", label: "Option change (prop)", hint: "Add, rename or change an option (a prop in code) or its default." },
|
|
3864
|
+
{ value: "structure", label: "Structure change", hint: "A part was added, removed or moved." },
|
|
3865
|
+
{ value: "visual", label: "Visual change", hint: "Spacing, radius, layout or icon placement." },
|
|
3866
|
+
{ value: "token-binding", label: "Token change", hint: "The component should paint with another token." },
|
|
3867
|
+
{ value: "bug", label: "Bug", hint: "It looks or behaves differently from the guidance." },
|
|
3868
|
+
{ value: "other", label: "Other", hint: "Anything else." }
|
|
3869
|
+
];
|
|
3870
|
+
var REQUEST_LABEL = "component-change";
|
|
3871
|
+
function requestIssueTitle(r) {
|
|
3872
|
+
const t = r.title.trim().replace(/\.$/, "");
|
|
3873
|
+
return `${r.component}: ${t.charAt(0).toLowerCase()}${t.slice(1)}`;
|
|
3874
|
+
}
|
|
3875
|
+
var REQUEST_STATUS_LABEL = { requested: "Requested", "in-progress": "In progress", done: "Done", declined: "Declined" };
|
|
3876
|
+
function validateIntegrations(i) {
|
|
3877
|
+
const out = [];
|
|
3878
|
+
if (!i?.jira) return out;
|
|
3879
|
+
if (!/^https:\/\/[^/]+/.test(i.jira.baseUrl ?? "")) out.push("Jira site must start with https://, for example https://acme.atlassian.net.");
|
|
3880
|
+
if (!/^[A-Z][A-Z0-9_]{1,9}$/.test(i.jira.project ?? "")) out.push("Jira project key is 2 to 10 capital letters or digits, like DS.");
|
|
3881
|
+
return out;
|
|
3882
|
+
}
|
|
3883
|
+
function jiraIssueFields(r, jira, issueUrl) {
|
|
3884
|
+
const text = (t) => ({ type: "text", text: t });
|
|
3885
|
+
const para = (...c) => ({ type: "paragraph", content: c });
|
|
3886
|
+
return {
|
|
3887
|
+
project: { key: jira.project },
|
|
3888
|
+
issuetype: { name: jira.issueType ?? "Task" },
|
|
3889
|
+
summary: requestIssueTitle(r),
|
|
3890
|
+
labels: [.../* @__PURE__ */ new Set(["bridge", REQUEST_LABEL, ...jira.labels ?? []])].map((l) => l.replace(/\s+/g, "-")),
|
|
3891
|
+
description: {
|
|
3892
|
+
type: "doc",
|
|
3893
|
+
version: 1,
|
|
3894
|
+
content: [
|
|
3895
|
+
para(text(r.summary.trim() || r.title)),
|
|
3896
|
+
...r.line?.trim() ? [para(text(r.line.trim()))] : [],
|
|
3897
|
+
...r.changes.map((c) => para(text(`${c.field}: ${c.before ?? "(none)"} -> ${c.after ?? "(removed)"}`))),
|
|
3898
|
+
para({ type: "text", text: "GitHub request", marks: [{ type: "link", attrs: { href: issueUrl } }] })
|
|
3899
|
+
]
|
|
3900
|
+
}
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
// ../core/src/suggestions.ts
|
|
3905
|
+
var SUGGESTION_STATUS_LABEL = {
|
|
3906
|
+
"needs-review": "Needs review",
|
|
3907
|
+
"changes-requested": "Changes requested",
|
|
3908
|
+
approved: "Approved, ready to apply",
|
|
3909
|
+
applying: "Being applied",
|
|
3910
|
+
applied: "Applied",
|
|
3911
|
+
declined: "Declined",
|
|
3912
|
+
outdated: "Outdated"
|
|
3913
|
+
};
|
|
3914
|
+
|
|
3915
|
+
// ../core/src/suggestion-apply.ts
|
|
3916
|
+
var SUGGESTION_APPLY_EXIT = {
|
|
3917
|
+
ready: 0,
|
|
3918
|
+
"already-applied": 0,
|
|
3919
|
+
conflict: 3,
|
|
3920
|
+
"not-approved": 1,
|
|
3921
|
+
blocked: 1,
|
|
3922
|
+
refused: 1
|
|
3923
|
+
};
|
|
3924
|
+
|
|
3925
|
+
// src/index.ts
|
|
3926
|
+
import { fileURLToPath } from "node:url";
|
|
3927
|
+
|
|
3928
|
+
// ../visual/src/run.ts
|
|
3929
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3930
|
+
import { mkdir as mkdir3, readFile as readFile3, rm as rm2, unlink, writeFile as writeFile3 } from "node:fs/promises";
|
|
3931
|
+
import { tmpdir } from "node:os";
|
|
3932
|
+
import { join as join4, relative as relative3, resolve as resolve2 } from "node:path";
|
|
3933
|
+
|
|
3934
|
+
// ../core/src/protection.ts
|
|
3935
|
+
var PROTECTION_PARTS = ["review", "checks", "history", "deletion"];
|
|
3936
|
+
var protectionRulesetName = (defaultBranch) => `Protect the library (${defaultBranch})`;
|
|
3937
|
+
function refPattern(pattern) {
|
|
3938
|
+
const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
|
|
3939
|
+
return new RegExp(`^${src}$`);
|
|
3940
|
+
}
|
|
3941
|
+
function rulesetTargetsDefault(ruleset, defaultBranch) {
|
|
3942
|
+
if (ruleset.target && ruleset.target !== "branch") return false;
|
|
3943
|
+
const ref = `refs/heads/${defaultBranch}`;
|
|
3944
|
+
const hit = (p) => p === "~ALL" || p === "~DEFAULT_BRANCH" || p === ref || p === defaultBranch || refPattern(p.startsWith("refs/") ? p : `refs/heads/${p}`).test(ref);
|
|
3945
|
+
const cond = ruleset.conditions?.ref_name;
|
|
3946
|
+
if (!cond) return false;
|
|
3947
|
+
if ((cond.exclude ?? []).some(hit)) return false;
|
|
3948
|
+
return (cond.include ?? []).some(hit);
|
|
3949
|
+
}
|
|
3950
|
+
var count = (v) => typeof v === "number" ? v : 0;
|
|
3951
|
+
function rulesetParts(ruleset) {
|
|
3952
|
+
const parts = /* @__PURE__ */ new Set();
|
|
3953
|
+
if (ruleset.enforcement !== "active") return parts;
|
|
3954
|
+
for (const rule of ruleset.rules ?? []) {
|
|
3955
|
+
if (rule.type === "deletion") parts.add("deletion");
|
|
3956
|
+
if (rule.type === "non_fast_forward") parts.add("history");
|
|
3957
|
+
if (rule.type === "pull_request" && count(rule.parameters?.required_approving_review_count) >= 1) parts.add("review");
|
|
3958
|
+
if (rule.type === "required_status_checks" && Array.isArray(rule.parameters?.required_status_checks) && rule.parameters.required_status_checks.length > 0) parts.add("checks");
|
|
3959
|
+
}
|
|
3960
|
+
return parts;
|
|
3961
|
+
}
|
|
3962
|
+
function classicParts(p) {
|
|
3963
|
+
const parts = /* @__PURE__ */ new Set();
|
|
3964
|
+
if (count(p.required_pull_request_reviews?.required_approving_review_count) >= 1) parts.add("review");
|
|
3965
|
+
if (((p.required_status_checks?.contexts?.length ?? 0) || (p.required_status_checks?.checks?.length ?? 0)) > 0) parts.add("checks");
|
|
3966
|
+
if (!p.allow_force_pushes?.enabled) parts.add("history");
|
|
3967
|
+
if (!p.allow_deletions?.enabled) parts.add("deletion");
|
|
3968
|
+
return parts;
|
|
3969
|
+
}
|
|
3970
|
+
var rulesetUrl = (r) => r.html_url ?? r._links?.html?.href;
|
|
3971
|
+
function protectionState(rulesets, branchProtection, defaultBranch) {
|
|
3972
|
+
const bridge = rulesets?.find((r) => r.name === protectionRulesetName(defaultBranch));
|
|
3973
|
+
const extra = bridge?.id !== void 0 ? { bridgeRulesetId: bridge.id } : {};
|
|
3974
|
+
const covered = /* @__PURE__ */ new Set();
|
|
3975
|
+
let url;
|
|
3976
|
+
for (const r of rulesets ?? []) {
|
|
3977
|
+
if (!rulesetTargetsDefault(r, defaultBranch)) continue;
|
|
3978
|
+
const parts = rulesetParts(r);
|
|
3979
|
+
if (parts.size && !url) url = rulesetUrl(r);
|
|
3980
|
+
parts.forEach((p) => covered.add(p));
|
|
3981
|
+
}
|
|
3982
|
+
if (branchProtection) classicParts(branchProtection).forEach((p) => covered.add(p));
|
|
3983
|
+
const missing = PROTECTION_PARTS.filter((p) => !covered.has(p));
|
|
3984
|
+
const withUrl = url ? { url } : {};
|
|
3985
|
+
if (!missing.length) return { kind: "protected", missing, ...extra, ...withUrl };
|
|
3986
|
+
const unreadable = rulesets == null || branchProtection === void 0;
|
|
3987
|
+
if (covered.size === 0) return { kind: unreadable ? "unknown" : "unprotected", missing, ...extra };
|
|
3988
|
+
return { kind: "partial", missing, ...extra, ...withUrl };
|
|
3989
|
+
}
|
|
3990
|
+
var PROTECTION_PART_PHRASE = {
|
|
3991
|
+
review: "changes can skip review",
|
|
3992
|
+
checks: "checks do not have to pass",
|
|
3993
|
+
history: "past changes can be rewritten",
|
|
3994
|
+
deletion: "the library can be deleted"
|
|
3995
|
+
};
|
|
3996
|
+
var joinAnd = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
3997
|
+
function protectionSentence(state) {
|
|
3998
|
+
switch (state.kind) {
|
|
3999
|
+
case "protected":
|
|
4000
|
+
return "Protected: every change goes through review.";
|
|
4001
|
+
case "unprotected":
|
|
4002
|
+
return "Your library can be changed without review.";
|
|
4003
|
+
case "partial": {
|
|
4004
|
+
const s = joinAnd(state.missing.map((p) => PROTECTION_PART_PHRASE[p]));
|
|
4005
|
+
return `Partly protected: ${s}.`;
|
|
4006
|
+
}
|
|
4007
|
+
default:
|
|
4008
|
+
return "Protection status unknown: GitHub did not let this token read it.";
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
|
|
3919
4012
|
// ../core/src/protection-github.ts
|
|
3920
4013
|
async function loadProtection(client, defaultBranch) {
|
|
3921
4014
|
const rulesets = await client.listRulesets().then((list2) => Promise.all(list2.map((r) => r.enforcement === "disabled" || r.id === void 0 ? r : client.getRuleset(r.id)))).catch(() => null);
|
|
@@ -4032,61 +4125,6 @@ function impactedModules(index, input) {
|
|
|
4032
4125
|
return { modules, globalHits };
|
|
4033
4126
|
}
|
|
4034
4127
|
|
|
4035
|
-
// ../core/src/explorations.ts
|
|
4036
|
-
var EXPLORATION_USE_CASES = [
|
|
4037
|
-
{ value: "user-test", label: "Prototype for a user test", hint: "A coded prototype on a product staging branch, with the real components and tokens." },
|
|
4038
|
-
{ value: "stakeholder-demo", label: "Stakeholder demo", hint: "Show an idea on real screens before anyone commits to it." },
|
|
4039
|
-
{ value: "ab-idea", label: "A/B idea", hint: "Ship a variant to a staging or experiment branch and compare it with what is live." },
|
|
4040
|
-
{ value: "terminal", label: "Exploring with Claude Code", hint: "A designer works from the terminal with Claude and the Bridge MCP server on this branch, never on main." },
|
|
4041
|
-
{ value: "other", label: "Other", hint: "Anything else worth trying before it becomes a change." }
|
|
4042
|
-
];
|
|
4043
|
-
var EXPLORATION_STATUS_LABEL = { active: "Running", promoted: "Promoted to review", closed: "Closed" };
|
|
4044
|
-
|
|
4045
|
-
// ../core/src/requests.ts
|
|
4046
|
-
var REQUEST_KINDS = [
|
|
4047
|
-
{ value: "new-component", label: "New component", hint: "A component the design system does not have yet." },
|
|
4048
|
-
{ value: "new-variant", label: "New variant", hint: "A size, tone or state the component does not have yet." },
|
|
4049
|
-
{ value: "prop-change", label: "Option change (prop)", hint: "Add, rename or change an option (a prop in code) or its default." },
|
|
4050
|
-
{ value: "structure", label: "Structure change", hint: "A part was added, removed or moved." },
|
|
4051
|
-
{ value: "visual", label: "Visual change", hint: "Spacing, radius, layout or icon placement." },
|
|
4052
|
-
{ value: "token-binding", label: "Token change", hint: "The component should paint with another token." },
|
|
4053
|
-
{ value: "bug", label: "Bug", hint: "It looks or behaves differently from the guidance." },
|
|
4054
|
-
{ value: "other", label: "Other", hint: "Anything else." }
|
|
4055
|
-
];
|
|
4056
|
-
var REQUEST_LABEL = "component-change";
|
|
4057
|
-
function requestIssueTitle(r) {
|
|
4058
|
-
const t = r.title.trim().replace(/\.$/, "");
|
|
4059
|
-
return `${r.component}: ${t.charAt(0).toLowerCase()}${t.slice(1)}`;
|
|
4060
|
-
}
|
|
4061
|
-
var REQUEST_STATUS_LABEL = { requested: "Requested", "in-progress": "In progress", done: "Done", declined: "Declined" };
|
|
4062
|
-
function validateIntegrations(i) {
|
|
4063
|
-
const out = [];
|
|
4064
|
-
if (!i?.jira) return out;
|
|
4065
|
-
if (!/^https:\/\/[^/]+/.test(i.jira.baseUrl ?? "")) out.push("Jira site must start with https://, for example https://acme.atlassian.net.");
|
|
4066
|
-
if (!/^[A-Z][A-Z0-9_]{1,9}$/.test(i.jira.project ?? "")) out.push("Jira project key is 2 to 10 capital letters or digits, like DS.");
|
|
4067
|
-
return out;
|
|
4068
|
-
}
|
|
4069
|
-
function jiraIssueFields(r, jira, issueUrl) {
|
|
4070
|
-
const text = (t) => ({ type: "text", text: t });
|
|
4071
|
-
const para = (...c) => ({ type: "paragraph", content: c });
|
|
4072
|
-
return {
|
|
4073
|
-
project: { key: jira.project },
|
|
4074
|
-
issuetype: { name: jira.issueType ?? "Task" },
|
|
4075
|
-
summary: requestIssueTitle(r),
|
|
4076
|
-
labels: [.../* @__PURE__ */ new Set(["bridge", REQUEST_LABEL, ...jira.labels ?? []])].map((l) => l.replace(/\s+/g, "-")),
|
|
4077
|
-
description: {
|
|
4078
|
-
type: "doc",
|
|
4079
|
-
version: 1,
|
|
4080
|
-
content: [
|
|
4081
|
-
para(text(r.summary.trim() || r.title)),
|
|
4082
|
-
...r.line?.trim() ? [para(text(r.line.trim()))] : [],
|
|
4083
|
-
...r.changes.map((c) => para(text(`${c.field}: ${c.before ?? "(none)"} -> ${c.after ?? "(removed)"}`))),
|
|
4084
|
-
para({ type: "text", text: "GitHub request", marks: [{ type: "link", attrs: { href: issueUrl } }] })
|
|
4085
|
-
]
|
|
4086
|
-
}
|
|
4087
|
-
};
|
|
4088
|
-
}
|
|
4089
|
-
|
|
4090
4128
|
// ../core/src/fidelity-references.ts
|
|
4091
4129
|
var FIGMA_REFERENCES_DIR = "figma-references";
|
|
4092
4130
|
var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
|
|
@@ -4310,15 +4348,15 @@ async function shoot(page, r) {
|
|
|
4310
4348
|
}
|
|
4311
4349
|
async function switchStory(page, storyId2) {
|
|
4312
4350
|
const ok = await page.evaluate(
|
|
4313
|
-
(id) => new Promise((
|
|
4351
|
+
(id) => new Promise((resolve6) => {
|
|
4314
4352
|
const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
|
|
4315
|
-
if (!ch) return
|
|
4353
|
+
if (!ch) return resolve6(false);
|
|
4316
4354
|
const events = ["storyRendered", "storyErrored", "storyThrewException", "storyMissing", "playFunctionThrewException"];
|
|
4317
4355
|
const timer = setTimeout(() => done(), 15e3);
|
|
4318
4356
|
function done() {
|
|
4319
4357
|
clearTimeout(timer);
|
|
4320
4358
|
for (const e of events) ch.off(e, done);
|
|
4321
|
-
|
|
4359
|
+
resolve6(true);
|
|
4322
4360
|
}
|
|
4323
4361
|
for (const e of events) ch.on(e, done);
|
|
4324
4362
|
window.scrollTo(0, 0);
|
|
@@ -4731,12 +4769,12 @@ function serveDir(dir) {
|
|
|
4731
4769
|
res.writeHead(200, { "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": "no-store" });
|
|
4732
4770
|
createReadStream(file).pipe(res);
|
|
4733
4771
|
});
|
|
4734
|
-
return new Promise((
|
|
4772
|
+
return new Promise((resolve6, reject) => {
|
|
4735
4773
|
server.once("error", reject);
|
|
4736
4774
|
server.listen(0, "127.0.0.1", () => {
|
|
4737
4775
|
const addr = server.address();
|
|
4738
4776
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
4739
|
-
|
|
4777
|
+
resolve6({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
|
|
4740
4778
|
});
|
|
4741
4779
|
});
|
|
4742
4780
|
}
|
|
@@ -4808,7 +4846,7 @@ async function removeWorktree(repo, dir) {
|
|
|
4808
4846
|
await git(repo, ["worktree", "prune"]).catch(() => void 0);
|
|
4809
4847
|
}
|
|
4810
4848
|
function run(cmd, args, cwd, log, root = cwd, env = {}) {
|
|
4811
|
-
return new Promise((
|
|
4849
|
+
return new Promise((resolve6, reject) => {
|
|
4812
4850
|
const bin = [.../* @__PURE__ */ new Set([join3(cwd, "node_modules", ".bin"), join3(root, "node_modules", ".bin")])].join(":");
|
|
4813
4851
|
const child = spawn(cmd, args, {
|
|
4814
4852
|
cwd,
|
|
@@ -4821,7 +4859,7 @@ function run(cmd, args, cwd, log, root = cwd, env = {}) {
|
|
|
4821
4859
|
child.stderr.on("data", keep);
|
|
4822
4860
|
child.on("error", reject);
|
|
4823
4861
|
child.on("close", (code) => {
|
|
4824
|
-
if (code === 0)
|
|
4862
|
+
if (code === 0) resolve6();
|
|
4825
4863
|
else {
|
|
4826
4864
|
log(tail);
|
|
4827
4865
|
reject(new Error(`${cmd} ${args.join(" ")} failed in ${cwd} (exit ${code})`));
|
|
@@ -5232,12 +5270,12 @@ async function captureFigmaReferences(o) {
|
|
|
5232
5270
|
}
|
|
5233
5271
|
const plan = JSON.stringify(j.plan.render);
|
|
5234
5272
|
await page.evaluate(
|
|
5235
|
-
({ id, plan: plan2 }) => new Promise((
|
|
5273
|
+
({ id, plan: plan2 }) => new Promise((resolve6) => {
|
|
5236
5274
|
const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
|
|
5237
5275
|
const done = () => {
|
|
5238
5276
|
clearTimeout(timer);
|
|
5239
5277
|
ch.off("storyRendered", done);
|
|
5240
|
-
|
|
5278
|
+
resolve6();
|
|
5241
5279
|
};
|
|
5242
5280
|
const timer = setTimeout(done, 1e4);
|
|
5243
5281
|
ch.on("storyRendered", done);
|
|
@@ -5526,7 +5564,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
|
|
|
5526
5564
|
if (!REQUEST_KINDS.some((k) => k.value === kind)) throw new RequestUsageError(`--kind takes ${REQUEST_KINDS.map((k) => k.value).join(", ")}, got "${kind}"`);
|
|
5527
5565
|
const dryRun = has(a, "dry-run");
|
|
5528
5566
|
const ctx = await context(root, io, !dryRun);
|
|
5529
|
-
const
|
|
5567
|
+
const figma2 = flag(a, "figma");
|
|
5530
5568
|
const exploration = flag(a, "exploration");
|
|
5531
5569
|
const r = await requests2.createRequest(ctx, {
|
|
5532
5570
|
component,
|
|
@@ -5534,7 +5572,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
|
|
|
5534
5572
|
title,
|
|
5535
5573
|
summary,
|
|
5536
5574
|
changes: (a.flags.change ?? []).map(parseChange),
|
|
5537
|
-
...
|
|
5575
|
+
...figma2 ? { figmaUrl: figma2 } : {},
|
|
5538
5576
|
...exploration ? { exploration } : {},
|
|
5539
5577
|
...flag(a, "product") ? { product: flag(a, "product") } : {},
|
|
5540
5578
|
source: "cli",
|
|
@@ -5693,7 +5731,195 @@ ${EXPLORE_HELP}`);
|
|
|
5693
5731
|
// src/branch.ts
|
|
5694
5732
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
5695
5733
|
import { resolve as resolve3 } from "node:path";
|
|
5696
|
-
import { BridgeContext as
|
|
5734
|
+
import { BridgeContext as BridgeContext5, branches, studio } from "@bridge-ds/mcp";
|
|
5735
|
+
|
|
5736
|
+
// src/suggest.ts
|
|
5737
|
+
import { BridgeContext as BridgeContext4, suggestions } from "@bridge-ds/mcp";
|
|
5738
|
+
var SuggestUsageError = class extends Error {
|
|
5739
|
+
};
|
|
5740
|
+
var SUGGESTIONS_HELP = `bridge suggestions: changes filed as GitHub issues (label bridge-suggestion), applied by a developer
|
|
5741
|
+
|
|
5742
|
+
Usage:
|
|
5743
|
+
bridge suggestions [list] [--status needs-review|changes-requested|approved|applying|applied|declined|outdated|all]
|
|
5744
|
+
[--kind token-change|docs|component-spec|brand|revert|config] [--json]
|
|
5745
|
+
bridge suggestions show <n> [--json]
|
|
5746
|
+
the change, the checks, its version, who approved it and what is missing
|
|
5747
|
+
bridge suggestions link <n> --pr <m> | --branch <name>
|
|
5748
|
+
record the pull request that applies it (it must say "Closes #<n>")
|
|
5749
|
+
bridge apply <n> [--write | --patch] [--dry-run] [--accept-edited] [--force-unapproved] [--branch name] [--json]
|
|
5750
|
+
apply it on today's code: a new local branch by default (working tree untouched, nothing
|
|
5751
|
+
pushed), --write into the working tree, --patch prints a patch for git am. --dry-run shows
|
|
5752
|
+
the plan and the diff and writes nothing
|
|
5753
|
+
bridge review <n> --approve | --request-changes --message "..." | --decline --message "..." [--version <sha>]
|
|
5754
|
+
as the owner your GitHub token belongs to; counts only for owners in bridge.config.json
|
|
5755
|
+
|
|
5756
|
+
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Reading a public repo works without one.
|
|
5757
|
+
Exit codes (apply): 0 applied, already applied or a clean dry run; 1 not approved, blocked by a check or refused;
|
|
5758
|
+
2 usage or setup error; 3 conflict with today's code.`;
|
|
5759
|
+
var flag3 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
5760
|
+
var has3 = (a, k) => Boolean(a.flags[k]);
|
|
5761
|
+
function changesFlag(a) {
|
|
5762
|
+
if (has3(a, "suggest") && has3(a, "direct")) throw new SuggestUsageError("Pass --suggest or --direct, not both.");
|
|
5763
|
+
return has3(a, "direct") ? "direct" : has3(a, "suggest") ? "suggest" : void 0;
|
|
5764
|
+
}
|
|
5765
|
+
function tokenFrom2(io) {
|
|
5766
|
+
return io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
5767
|
+
}
|
|
5768
|
+
async function context3(root, io) {
|
|
5769
|
+
const token = tokenFrom2(io);
|
|
5770
|
+
return BridgeContext4.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
5771
|
+
}
|
|
5772
|
+
function issueNumber2(raw, usage) {
|
|
5773
|
+
const n = Number((raw ?? "").replace(/^#/, ""));
|
|
5774
|
+
if (!raw || !Number.isInteger(n) || n <= 0) throw new SuggestUsageError(`Usage: ${usage}`);
|
|
5775
|
+
return n;
|
|
5776
|
+
}
|
|
5777
|
+
var STATUSES = ["needs-review", "changes-requested", "approved", "applying", "applied", "declined", "outdated", "all"];
|
|
5778
|
+
var KINDS = ["token-change", "docs", "component-spec", "brand", "revert", "config"];
|
|
5779
|
+
async function runSuggestions(a, root, io = defaultIO()) {
|
|
5780
|
+
const sub = a._[1] ?? "list";
|
|
5781
|
+
if (sub === "help" || has3(a, "help")) return io.print(SUGGESTIONS_HELP), 0;
|
|
5782
|
+
switch (sub) {
|
|
5783
|
+
case "list": {
|
|
5784
|
+
const status = flag3(a, "status") ?? "all";
|
|
5785
|
+
if (!STATUSES.includes(status)) throw new SuggestUsageError(`--status takes ${STATUSES.join(", ")}, got "${status}"`);
|
|
5786
|
+
const kind = flag3(a, "kind");
|
|
5787
|
+
if (kind && !KINDS.includes(kind)) throw new SuggestUsageError(`--kind takes ${KINDS.join(", ")}, got "${kind}"`);
|
|
5788
|
+
const ctx = await context3(root, io);
|
|
5789
|
+
const r = await suggestions.listSuggestionsOp(ctx, { status, ...kind ? { kind } : {} });
|
|
5790
|
+
if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
|
|
5791
|
+
if (!r.suggestions.length) return io.print(`No ${status === "all" ? "open " : `${status} `}suggestion in ${r.repo}.`), 0;
|
|
5792
|
+
for (const x of r.suggestions) {
|
|
5793
|
+
const prs = x.linkedPrs.length ? ` PR ${x.linkedPrs.map((p) => `#${p.number}${p.merged ? " merged" : ` ${p.state}`}`).join(", ")}` : "";
|
|
5794
|
+
io.print(`#${String(x.number).padEnd(5)} ${x.said.padEnd(25)} ${(x.kind ?? "").padEnd(14)} ${x.tier ? `tier ${x.tier}` : " "} ${x.title}${prs}`);
|
|
5795
|
+
}
|
|
5796
|
+
io.print(`
|
|
5797
|
+
${r.next}`);
|
|
5798
|
+
return 0;
|
|
5799
|
+
}
|
|
5800
|
+
case "show": {
|
|
5801
|
+
const n = issueNumber2(a._[2], "bridge suggestions show <number>");
|
|
5802
|
+
const ctx = await context3(root, io);
|
|
5803
|
+
const r = await suggestions.getSuggestionOp(ctx, { number: n });
|
|
5804
|
+
if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
|
|
5805
|
+
io.print(`#${r.number} ${r.title}`);
|
|
5806
|
+
io.print(`${r.said}${r.kind ? `, ${r.kind}` : ""}${r.tier ? `, tier ${r.tier} (${r.bump})` : ""}${r.source ? `, from ${r.source}` : ""}${r.suggestedBy ? ` by @${r.suggestedBy}` : ""}`);
|
|
5807
|
+
io.print(r.url);
|
|
5808
|
+
if (r.problem) {
|
|
5809
|
+
io.print(`
|
|
5810
|
+
${r.problem}`);
|
|
5811
|
+
return 1;
|
|
5812
|
+
}
|
|
5813
|
+
if (r.summary) io.print(`
|
|
5814
|
+
${r.summary}`);
|
|
5815
|
+
const c = r.change ?? {};
|
|
5816
|
+
if (c.tokens?.length) {
|
|
5817
|
+
io.print("\nTokens:");
|
|
5818
|
+
for (const t of c.tokens) io.print(` ${t.kind ?? t.op} ${t.path}${t.before !== void 0 || t.after !== void 0 ? `: ${fmt(t.before)} -> ${fmt(t.after)}` : ""}`);
|
|
5819
|
+
}
|
|
5820
|
+
for (const d of c.docs ?? []) io.print(`Docs: ${d.component} (${d.path})`);
|
|
5821
|
+
if (c.componentSpec) io.print(`Component spec: ${c.componentSpec.component} (${c.componentSpec.specPath})`);
|
|
5822
|
+
for (const f of c.files ?? []) io.print(`File: ${f.change} ${f.path}`);
|
|
5823
|
+
io.print(`
|
|
5824
|
+
Version ${r.version}${r.edited ? ` (${r.edited})` : ""}`);
|
|
5825
|
+
io.print(`Approved by: ${r.approvals.approvedBy.map((l) => `@${l}`).join(", ") || "nobody yet"}`);
|
|
5826
|
+
if (r.approvals.missing.length) io.print(`Missing: ${r.approvals.missing.join(" and ")}`);
|
|
5827
|
+
if (r.approvals.changesRequestedBy.length) io.print(`Changes requested by: ${r.approvals.changesRequestedBy.map((l) => `@${l}`).join(", ")}`);
|
|
5828
|
+
for (const p of r.linkedPrs ?? []) io.print(`Pull request: #${p.number} ${p.merged ? "merged" : p.state} ${p.url}`);
|
|
5829
|
+
io.print(`
|
|
5830
|
+
Next: ${r.next}`);
|
|
5831
|
+
return 0;
|
|
5832
|
+
}
|
|
5833
|
+
case "link": {
|
|
5834
|
+
const n = issueNumber2(a._[2], "bridge suggestions link <number> --pr <m>");
|
|
5835
|
+
const prRaw = flag3(a, "pr");
|
|
5836
|
+
const pr = prRaw ? issueNumber2(prRaw, "bridge suggestions link <number> --pr <m>") : void 0;
|
|
5837
|
+
const branch = flag3(a, "branch");
|
|
5838
|
+
if (!pr && !branch) throw new SuggestUsageError("Usage: bridge suggestions link <number> --pr <m> (or --branch <name>)");
|
|
5839
|
+
const ctx = await context3(root, io);
|
|
5840
|
+
const r = await suggestions.linkSuggestionOp(ctx, { number: n, ...pr ? { pr } : {}, ...branch ? { branch } : {} });
|
|
5841
|
+
if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
|
|
5842
|
+
io.print(`#${n} is being applied${pr ? ` in #${pr}` : branch ? ` on ${branch}` : ""}.`);
|
|
5843
|
+
for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
|
|
5844
|
+
return 0;
|
|
5845
|
+
}
|
|
5846
|
+
default:
|
|
5847
|
+
throw new SuggestUsageError(`Unknown suggestions command "${sub}".
|
|
5848
|
+
|
|
5849
|
+
${SUGGESTIONS_HELP}`);
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
var fmt = (v) => v === void 0 ? "(none)" : typeof v === "string" ? v : JSON.stringify(v);
|
|
5853
|
+
async function runApply(a, root, io = defaultIO()) {
|
|
5854
|
+
if (has3(a, "help") || a._[1] === "help") return io.print(SUGGESTIONS_HELP), 0;
|
|
5855
|
+
const n = issueNumber2(a._[1], "bridge apply <number> [--write | --patch] [--dry-run]");
|
|
5856
|
+
if (has3(a, "write") && has3(a, "patch")) throw new SuggestUsageError("Pass --write or --patch, not both.");
|
|
5857
|
+
const mode2 = has3(a, "write") ? "write" : has3(a, "patch") ? "patch" : "branch";
|
|
5858
|
+
const ctx = await context3(root, io);
|
|
5859
|
+
if (ctx.mode !== "local" || !ctx.isGit) throw new SuggestUsageError("bridge apply runs in a git checkout of the design system: cd into it, or pass --root.");
|
|
5860
|
+
const branch = flag3(a, "branch");
|
|
5861
|
+
const r = await suggestions.applySuggestionOp(ctx, {
|
|
5862
|
+
number: n,
|
|
5863
|
+
mode: has3(a, "dry-run") ? "dry-run" : mode2,
|
|
5864
|
+
acceptEdited: has3(a, "accept-edited"),
|
|
5865
|
+
forceUnapproved: has3(a, "force-unapproved"),
|
|
5866
|
+
...branch ? { branch } : {}
|
|
5867
|
+
});
|
|
5868
|
+
const code = typeof r.exitCode === "number" ? r.exitCode : SUGGESTION_APPLY_EXIT[r.outcome] ?? 1;
|
|
5869
|
+
if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), code;
|
|
5870
|
+
if (mode2 === "patch" && r.patch && !has3(a, "dry-run")) {
|
|
5871
|
+
io.print(r.patch.trimEnd());
|
|
5872
|
+
io.error(r.message);
|
|
5873
|
+
for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
|
|
5874
|
+
return code;
|
|
5875
|
+
}
|
|
5876
|
+
io.print(`#${n}: ${r.message}`);
|
|
5877
|
+
if (r.table) io.print(`
|
|
5878
|
+
${r.table}`);
|
|
5879
|
+
for (const p of r.problems ?? []) io.print(` ${p}`);
|
|
5880
|
+
for (const note of r.notes ?? []) io.print(` note: ${note}`);
|
|
5881
|
+
if (r.diff) io.print(`
|
|
5882
|
+
${r.diff.trimEnd()}`);
|
|
5883
|
+
if (r.brief) io.print(`
|
|
5884
|
+
Brief to implement it:
|
|
5885
|
+
${r.brief}`);
|
|
5886
|
+
if (r.branch) io.print(`
|
|
5887
|
+
branch: ${r.branch}${r.commit ? ` (${String(r.commit).slice(0, 7)})` : ""}`);
|
|
5888
|
+
for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
|
|
5889
|
+
if (r.next && r.next !== r.message) io.print(`
|
|
5890
|
+
Next: ${r.next}`);
|
|
5891
|
+
return code;
|
|
5892
|
+
}
|
|
5893
|
+
async function runReview(a, root, io = defaultIO()) {
|
|
5894
|
+
if (has3(a, "help") || a._[1] === "help") return io.print(SUGGESTIONS_HELP), 0;
|
|
5895
|
+
const usage = 'bridge review <number> --approve | --request-changes --message "..." | --decline --message "..."';
|
|
5896
|
+
const n = issueNumber2(a._[1], usage);
|
|
5897
|
+
const events = ["approve", "request-changes", "decline", "comment"].filter((e) => has3(a, e));
|
|
5898
|
+
if (events.length !== 1) throw new SuggestUsageError(`Usage: ${usage}`);
|
|
5899
|
+
const event = events[0];
|
|
5900
|
+
const inline = flag3(a, event);
|
|
5901
|
+
const message = flag3(a, "message") ?? (inline && inline !== "true" ? inline : void 0);
|
|
5902
|
+
if ((event === "request-changes" || event === "decline" || event === "comment") && !message?.trim()) throw new SuggestUsageError(`--${event} needs --message "..." (the author reads it).`);
|
|
5903
|
+
const role = flag3(a, "role");
|
|
5904
|
+
if (role && !["design", "tech", "approval"].includes(role)) throw new SuggestUsageError(`--role takes design, tech or approval, got "${role}"`);
|
|
5905
|
+
const ctx = await context3(root, io);
|
|
5906
|
+
const version = flag3(a, "version");
|
|
5907
|
+
const r = await suggestions.reviewSuggestionOp(ctx, {
|
|
5908
|
+
number: n,
|
|
5909
|
+
event,
|
|
5910
|
+
...message ? { message } : {},
|
|
5911
|
+
...role ? { role } : {},
|
|
5912
|
+
...version ? { payloadSha: version } : {}
|
|
5913
|
+
});
|
|
5914
|
+
if (has3(a, "json")) return io.print(JSON.stringify(r, null, 2)), 0;
|
|
5915
|
+
const done = event === "approve" ? "Approved" : event === "request-changes" ? "Changes requested on" : event === "decline" ? "Declined" : "Commented on";
|
|
5916
|
+
io.print(`${done} #${n}${r.version ? ` (version ${r.version})` : ""}. Now: ${SUGGESTION_STATUS_LABEL[r.status] ?? r.status}.`);
|
|
5917
|
+
for (const w of r.warnings ?? []) io.error(`warning: ${w}`);
|
|
5918
|
+
if (r.next) io.print(`Next: ${r.next}`);
|
|
5919
|
+
return r.counts === false ? 1 : 0;
|
|
5920
|
+
}
|
|
5921
|
+
|
|
5922
|
+
// src/branch.ts
|
|
5697
5923
|
var BRANCH_HELP = `bridge branch: branches in review, the same actions as Bridge Studio
|
|
5698
5924
|
|
|
5699
5925
|
Usage:
|
|
@@ -5723,11 +5949,11 @@ Usage:
|
|
|
5723
5949
|
|
|
5724
5950
|
Start work for a product: bridge propose|explore start|request create ... --product <key> [--jira DS-42].
|
|
5725
5951
|
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
5726
|
-
var
|
|
5727
|
-
var
|
|
5728
|
-
async function
|
|
5952
|
+
var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
5953
|
+
var has4 = (a, k) => Boolean(a.flags[k]);
|
|
5954
|
+
async function context4(root, io) {
|
|
5729
5955
|
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
5730
|
-
return
|
|
5956
|
+
return BridgeContext5.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
5731
5957
|
}
|
|
5732
5958
|
function number(a, at, usage) {
|
|
5733
5959
|
const raw = a._[at];
|
|
@@ -5746,16 +5972,16 @@ function pairs(list2, what) {
|
|
|
5746
5972
|
var said = (r) => String(r.said ?? r.summary ?? r.status ?? "Done.");
|
|
5747
5973
|
async function runBranch(a, root, io = defaultIO()) {
|
|
5748
5974
|
const sub = a._[1];
|
|
5749
|
-
if (!sub || sub === "help" ||
|
|
5975
|
+
if (!sub || sub === "help" || has4(a, "help")) {
|
|
5750
5976
|
io.print(BRANCH_HELP);
|
|
5751
|
-
return sub ||
|
|
5977
|
+
return sub || has4(a, "help") ? 0 : 2;
|
|
5752
5978
|
}
|
|
5753
|
-
const json =
|
|
5754
|
-
const ctx = await
|
|
5979
|
+
const json = has4(a, "json");
|
|
5980
|
+
const ctx = await context4(root, io);
|
|
5755
5981
|
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
5756
5982
|
switch (sub) {
|
|
5757
5983
|
case "list": {
|
|
5758
|
-
const limit =
|
|
5984
|
+
const limit = flag4(a, "limit");
|
|
5759
5985
|
const r = await branches.whatNeedsMe(ctx, limit ? { limit: Number(limit) } : {});
|
|
5760
5986
|
return out(r, () => {
|
|
5761
5987
|
io.print(r.summary);
|
|
@@ -5789,28 +6015,28 @@ async function runBranch(a, root, io = defaultIO()) {
|
|
|
5789
6015
|
}
|
|
5790
6016
|
case "review": {
|
|
5791
6017
|
const n = number(a, 2, 'bridge branch review <n> --approve | --request-changes "..." | --comment "..."');
|
|
5792
|
-
const decision =
|
|
6018
|
+
const decision = has4(a, "approve") ? "approve" : has4(a, "request-changes") ? "request_changes" : has4(a, "comment") ? "comment" : null;
|
|
5793
6019
|
if (!decision) throw new RequestUsageError('Say what you decide: --approve, --request-changes "what to change" or --comment "text".');
|
|
5794
|
-
const body = decision === "approve" ?
|
|
6020
|
+
const body = decision === "approve" ? flag4(a, "body") : flag4(a, decision === "request_changes" ? "request-changes" : "comment");
|
|
5795
6021
|
const r = await branches.reviewBranch(ctx, { pr: n, decision, ...body && body !== "true" ? { body } : {} });
|
|
5796
6022
|
return out(r, () => io.print(r.said));
|
|
5797
6023
|
}
|
|
5798
6024
|
case "ask": {
|
|
5799
|
-
const r = await branches.askSomeoneElse(ctx, { pr: number(a, 2, "bridge branch ask <n>"), dryRun:
|
|
6025
|
+
const r = await branches.askSomeoneElse(ctx, { pr: number(a, 2, "bridge branch ask <n>"), dryRun: has4(a, "dry-run") });
|
|
5800
6026
|
return out(r, () => io.print(r.said));
|
|
5801
6027
|
}
|
|
5802
6028
|
case "comment": {
|
|
5803
6029
|
const n = number(a, 2, 'bridge branch comment <n> "text"');
|
|
5804
|
-
const body = a._.slice(3).join(" ") ||
|
|
6030
|
+
const body = a._.slice(3).join(" ") || flag4(a, "body");
|
|
5805
6031
|
if (!body) throw new RequestUsageError('Usage: bridge branch comment <n> "text" [--reply-to <id>]');
|
|
5806
|
-
const reply =
|
|
6032
|
+
const reply = flag4(a, "reply-to");
|
|
5807
6033
|
const r = await branches.commentOnBranch(ctx, { pr: n, body, ...reply ? { replyTo: Number(reply) } : {} });
|
|
5808
6034
|
return out(r, () => io.print(r.said));
|
|
5809
6035
|
}
|
|
5810
6036
|
case "merge": {
|
|
5811
6037
|
const n = number(a, 2, 'bridge branch merge <n> [--override "reason"] [--dry-run]');
|
|
5812
|
-
const reason =
|
|
5813
|
-
const r = await branches.mergeBranch(ctx, { pr: n, ...reason ? { overrideReason: reason } : {}, dryRun:
|
|
6038
|
+
const reason = flag4(a, "override");
|
|
6039
|
+
const r = await branches.mergeBranch(ctx, { pr: n, ...reason ? { overrideReason: reason } : {}, dryRun: has4(a, "dry-run") });
|
|
5814
6040
|
return out(r, () => io.print(r.said));
|
|
5815
6041
|
}
|
|
5816
6042
|
case "close": {
|
|
@@ -5825,7 +6051,7 @@ async function runBranch(a, root, io = defaultIO()) {
|
|
|
5825
6051
|
const n = number(a, 2, "bridge branch update <n> [--choice key=mine|theirs] [--dry-run]");
|
|
5826
6052
|
const choices = pairs(a.flags.choice, "choice");
|
|
5827
6053
|
for (const [k, v] of Object.entries(choices)) if (v !== "mine" && v !== "theirs") throw new RequestUsageError(`--choice ${k}= takes mine or theirs, got "${v}".`);
|
|
5828
|
-
const r = await branches.updateFromMain(ctx, { pr: n, ...Object.keys(choices).length ? { choices } : {}, dryRun:
|
|
6054
|
+
const r = await branches.updateFromMain(ctx, { pr: n, ...Object.keys(choices).length ? { choices } : {}, dryRun: has4(a, "dry-run") });
|
|
5829
6055
|
return out(r, () => {
|
|
5830
6056
|
io.print(said(r));
|
|
5831
6057
|
for (const c of r.conflicts ?? []) io.print(` ${c.key} (${c.token}, default ${c.default ?? "none: pick one"})`);
|
|
@@ -5857,8 +6083,8 @@ ${BRANCH_HELP}`);
|
|
|
5857
6083
|
}
|
|
5858
6084
|
}
|
|
5859
6085
|
async function runStudioRead(cmd, a, root, io = defaultIO()) {
|
|
5860
|
-
const ctx = await
|
|
5861
|
-
const json =
|
|
6086
|
+
const ctx = await context4(root, io);
|
|
6087
|
+
const json = has4(a, "json");
|
|
5862
6088
|
if (cmd === "products") {
|
|
5863
6089
|
const r2 = await branches.listProducts(ctx);
|
|
5864
6090
|
if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
|
|
@@ -5887,7 +6113,7 @@ This folder: ${r2.current.name}. Pick another with --product <key>.`);
|
|
|
5887
6113
|
}
|
|
5888
6114
|
async function runDocsSave(a, root, io = defaultIO()) {
|
|
5889
6115
|
const component = a._[2];
|
|
5890
|
-
const file =
|
|
6116
|
+
const file = flag4(a, "file");
|
|
5891
6117
|
if (!component || !file) throw new RequestUsageError('Usage: bridge docs save <Component> --file docs.json [--pr <n>] [--title "..."] [--jira DS-42] [--product admin] [--dry-run]');
|
|
5892
6118
|
let docs;
|
|
5893
6119
|
try {
|
|
@@ -5895,28 +6121,32 @@ async function runDocsSave(a, root, io = defaultIO()) {
|
|
|
5895
6121
|
} catch (e) {
|
|
5896
6122
|
throw new RequestUsageError(`${file} is not a readable JSON file: ${e.message}`);
|
|
5897
6123
|
}
|
|
5898
|
-
const ctx = await
|
|
5899
|
-
const pr =
|
|
6124
|
+
const ctx = await context4(root, io);
|
|
6125
|
+
const pr = flag4(a, "pr");
|
|
6126
|
+
const changes = changesFlag(a);
|
|
5900
6127
|
const r = await studio.saveDocs(ctx, {
|
|
5901
6128
|
component,
|
|
5902
6129
|
docs,
|
|
5903
6130
|
...pr ? { pr } : {},
|
|
5904
|
-
...
|
|
5905
|
-
...
|
|
5906
|
-
...
|
|
5907
|
-
...
|
|
5908
|
-
|
|
6131
|
+
...flag4(a, "title") ? { title: flag4(a, "title") } : {},
|
|
6132
|
+
...flag4(a, "notes") ? { notes: flag4(a, "notes") } : {},
|
|
6133
|
+
...flag4(a, "jira") ? { jira: flag4(a, "jira") } : {},
|
|
6134
|
+
...flag4(a, "product") ? { product: flag4(a, "product") } : {},
|
|
6135
|
+
...changes ? { changes } : {},
|
|
6136
|
+
source: "cli",
|
|
6137
|
+
dryRun: has4(a, "dry-run")
|
|
5909
6138
|
});
|
|
5910
|
-
if (
|
|
6139
|
+
if (has4(a, "json")) io.print(JSON.stringify(r, null, 2));
|
|
5911
6140
|
else io.print(r.status === "dry-run" || r.status === "preview" ? `${r.said ?? "Dry run, nothing written."}
|
|
5912
6141
|
|
|
5913
6142
|
${r.path}:
|
|
5914
|
-
${r.content}` : String(r.said)
|
|
6143
|
+
${r.content}` : `${String(r.said)}${r.next ? `
|
|
6144
|
+
${String(r.next)}` : ""}`);
|
|
5915
6145
|
return 0;
|
|
5916
6146
|
}
|
|
5917
6147
|
|
|
5918
6148
|
// src/pieces.ts
|
|
5919
|
-
import { BridgeContext as
|
|
6149
|
+
import { BridgeContext as BridgeContext6, branches as branches2, pieces } from "@bridge-ds/mcp";
|
|
5920
6150
|
var PIECES_HELP = `bridge pieces: a product branch and the library
|
|
5921
6151
|
|
|
5922
6152
|
Usage:
|
|
@@ -5931,10 +6161,10 @@ Usage:
|
|
|
5931
6161
|
bridge pieces switch <n> once the library shipped: main into the product branch
|
|
5932
6162
|
|
|
5933
6163
|
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
5934
|
-
var
|
|
5935
|
-
async function
|
|
6164
|
+
var has5 = (a, k) => Boolean(a.flags[k]);
|
|
6165
|
+
async function context5(root, io) {
|
|
5936
6166
|
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
5937
|
-
return
|
|
6167
|
+
return BridgeContext6.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
5938
6168
|
}
|
|
5939
6169
|
function number2(a, at, usage) {
|
|
5940
6170
|
const raw = a._[at];
|
|
@@ -5948,12 +6178,12 @@ function piece(a, at, usage) {
|
|
|
5948
6178
|
}
|
|
5949
6179
|
async function runPieces(a, root, io = defaultIO()) {
|
|
5950
6180
|
const sub = a._[1];
|
|
5951
|
-
if (!sub || sub === "help" ||
|
|
6181
|
+
if (!sub || sub === "help" || has5(a, "help")) {
|
|
5952
6182
|
io.print(PIECES_HELP);
|
|
5953
|
-
return sub ||
|
|
6183
|
+
return sub || has5(a, "help") ? 0 : 2;
|
|
5954
6184
|
}
|
|
5955
|
-
const json =
|
|
5956
|
-
const ctx = await
|
|
6185
|
+
const json = has5(a, "json");
|
|
6186
|
+
const ctx = await context5(root, io);
|
|
5957
6187
|
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
5958
6188
|
switch (sub) {
|
|
5959
6189
|
case "status": {
|
|
@@ -5968,9 +6198,10 @@ async function runPieces(a, root, io = defaultIO()) {
|
|
|
5968
6198
|
}
|
|
5969
6199
|
case "open": {
|
|
5970
6200
|
const usage = "bridge pieces open <n> <piece>";
|
|
5971
|
-
const
|
|
6201
|
+
const changes = changesFlag(a);
|
|
6202
|
+
const r = await pieces.openLibraryBranchOp(ctx, { pr: number2(a, 2, usage), piece: piece(a, 3, usage), usePreview: !has5(a, "no-preview"), dryRun: has5(a, "dry-run"), source: "cli", ...changes ? { changes } : {} });
|
|
5972
6203
|
return out(r, () => {
|
|
5973
|
-
io.print(`${r.status === "dry-run" ? "Would open" : r.status === "already-linked" ? "Already linked" : "Opened"} a ${r.opens}: ${r.title}, needed by ${r.neededBy}.`);
|
|
6204
|
+
io.print(`${r.status === "dry-run" ? "Would open" : r.status === "already-linked" ? "Already linked" : r.status === "suggested" ? "Suggested" : "Opened"} a ${r.opens}: ${r.title}, needed by ${r.neededBy}.`);
|
|
5974
6205
|
if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
|
|
5975
6206
|
if (r.preview) io.print(` ${r.preview.said}`);
|
|
5976
6207
|
for (const w of r.warnings ?? []) io.print(` ${w}`);
|
|
@@ -5987,7 +6218,7 @@ async function runPieces(a, root, io = defaultIO()) {
|
|
|
5987
6218
|
return out(r, () => io.print(r.said));
|
|
5988
6219
|
}
|
|
5989
6220
|
default: {
|
|
5990
|
-
const r = await pieces.missingPiecesOp(ctx, { pr: number2(a, 1, "bridge pieces <n> [--record]"), record:
|
|
6221
|
+
const r = await pieces.missingPiecesOp(ctx, { pr: number2(a, 1, "bridge pieces <n> [--record]"), record: has5(a, "record") });
|
|
5991
6222
|
return out(r, () => {
|
|
5992
6223
|
io.print(`#${r.pr} ${r.branch} (${r.product}): ${r.pieces.length ? `${r.pieces.length} ${r.pieces.length === 1 ? "piece is" : "pieces are"} missing from the library` : "nothing is missing from the library"}, ${r.files} files read.`);
|
|
5993
6224
|
for (const p of r.pieces) io.print(` ${p.id} ${p.title}: ${p.summary} Opens ${p.opens}.${"linked" in p ? ` Linked: #${p.linked}.` : ""}`);
|
|
@@ -6000,6 +6231,94 @@ ${r.fixes.sample.map((s) => ` ${s}`).join("\n")}`);
|
|
|
6000
6231
|
}
|
|
6001
6232
|
}
|
|
6002
6233
|
|
|
6234
|
+
// src/figma.ts
|
|
6235
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
6236
|
+
import { resolve as resolve4 } from "node:path";
|
|
6237
|
+
import { BridgeContext as BridgeContext7, branches as branches3, figma } from "@bridge-ds/mcp";
|
|
6238
|
+
var FIGMA_HELP = `bridge figma: component changes sent from Figma
|
|
6239
|
+
|
|
6240
|
+
Usage:
|
|
6241
|
+
bridge figma [--all] [--json] the library branches the DS Bridge plugin opened for components
|
|
6242
|
+
changed in Figma, and where each stands
|
|
6243
|
+
bridge figma show <n> [--draft] [--json] one of them: its spec and the brief to draft the code; --draft
|
|
6244
|
+
runs Claude Code on the brief (\`claude\` on PATH)
|
|
6245
|
+
bridge figma drafted <n> [--json] the code is pushed: label it drafted by Claude AI for review
|
|
6246
|
+
bridge figma push <diff.json> [--dry-run] [--json]
|
|
6247
|
+
send a structured component diff like the plugin does: an exact
|
|
6248
|
+
token change, or a branch with the spec for Claude AI
|
|
6249
|
+
|
|
6250
|
+
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
6251
|
+
var has6 = (a, k) => Boolean(a.flags[k]);
|
|
6252
|
+
async function context6(root, io) {
|
|
6253
|
+
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
6254
|
+
return BridgeContext7.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
6255
|
+
}
|
|
6256
|
+
function number3(a, usage) {
|
|
6257
|
+
const raw = a._[2];
|
|
6258
|
+
if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
|
|
6259
|
+
return branches3.branchNumber(raw);
|
|
6260
|
+
}
|
|
6261
|
+
async function runFigma(a, root, io = defaultIO()) {
|
|
6262
|
+
const sub = a._[1];
|
|
6263
|
+
if (sub === "help" || has6(a, "help")) {
|
|
6264
|
+
io.print(FIGMA_HELP);
|
|
6265
|
+
return 0;
|
|
6266
|
+
}
|
|
6267
|
+
const json = has6(a, "json");
|
|
6268
|
+
const ctx = await context6(root, io);
|
|
6269
|
+
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
6270
|
+
switch (sub) {
|
|
6271
|
+
case void 0:
|
|
6272
|
+
case "list": {
|
|
6273
|
+
const r = await figma.figmaChangesOp(ctx, { state: has6(a, "all") ? "all" : "open" });
|
|
6274
|
+
return out(r, () => {
|
|
6275
|
+
for (const c of r.changes) io.print(`#${c.pr} ${c.title} (${c.route}): ${c.status}. ${c.said}`);
|
|
6276
|
+
io.print(`Next: ${r.next}`);
|
|
6277
|
+
});
|
|
6278
|
+
}
|
|
6279
|
+
case "show": {
|
|
6280
|
+
const r = await figma.getFigmaChangeOp(ctx, { pr: number3(a, "bridge figma show <n>") });
|
|
6281
|
+
if (has6(a, "draft")) {
|
|
6282
|
+
if (!r.brief) throw new RequestUsageError(`#${r.pr} is an exact token change: there is no code to draft.`);
|
|
6283
|
+
if (!io.hasClaude()) throw new RequestUsageError("`claude` is not on PATH: install Claude Code, or paste the brief from `bridge figma show <n>` into Claude.");
|
|
6284
|
+
return io.runClaude(r.brief, root);
|
|
6285
|
+
}
|
|
6286
|
+
return out(r, () => {
|
|
6287
|
+
io.print(`#${r.pr} ${r.title} (${r.route}): ${r.status}. ${r.said}`);
|
|
6288
|
+
io.print(` ${r.url}`);
|
|
6289
|
+
if (r.brief) io.print(`
|
|
6290
|
+
${r.brief}`);
|
|
6291
|
+
});
|
|
6292
|
+
}
|
|
6293
|
+
case "drafted": {
|
|
6294
|
+
const r = await figma.markFigmaDraftedOp(ctx, { pr: number3(a, "bridge figma drafted <n>") });
|
|
6295
|
+
return out(r, () => io.print(`#${r.pr}: ${r.status}. ${r.said}`));
|
|
6296
|
+
}
|
|
6297
|
+
case "push": {
|
|
6298
|
+
const file = a._[2];
|
|
6299
|
+
if (!file) throw new RequestUsageError("Usage: bridge figma push <diff.json> [--dry-run]");
|
|
6300
|
+
let diff2;
|
|
6301
|
+
try {
|
|
6302
|
+
diff2 = JSON.parse(readFileSync2(resolve4(root, file), "utf8"));
|
|
6303
|
+
} catch (err) {
|
|
6304
|
+
throw new RequestUsageError(`${file} could not be read as JSON: ${err.message}`);
|
|
6305
|
+
}
|
|
6306
|
+
const changes = changesFlag(a);
|
|
6307
|
+
const r = await figma.pushFigmaComponentOp(ctx, { diff: diff2, dryRun: has6(a, "dry-run"), source: "cli", ...changes ? { changes } : {} });
|
|
6308
|
+
return out(r, () => {
|
|
6309
|
+
io.print(`${r.component}: ${r.route}. ${r.said}`);
|
|
6310
|
+
for (const x of r.reasons ?? []) io.print(` ${x}`);
|
|
6311
|
+
for (const x of r.fixInFigma ?? []) io.print(` Fix in Figma: ${x.what} ${x.todo}`);
|
|
6312
|
+
if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
|
|
6313
|
+
if (r.suggestion) io.print(` suggestion #${r.suggestion.number} ${r.suggestion.url}`);
|
|
6314
|
+
io.print(`Next: ${r.next}`);
|
|
6315
|
+
});
|
|
6316
|
+
}
|
|
6317
|
+
default:
|
|
6318
|
+
throw new RequestUsageError(`Unknown subcommand "${sub}". ${FIGMA_HELP}`);
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
|
|
6003
6322
|
// src/protection.ts
|
|
6004
6323
|
var UNKNOWN = { kind: "unknown", missing: ["review", "checks", "history", "deletion"] };
|
|
6005
6324
|
async function protectionReport(opts) {
|
|
@@ -6032,7 +6351,7 @@ async function rolesReport(opts) {
|
|
|
6032
6351
|
}
|
|
6033
6352
|
|
|
6034
6353
|
// src/index.ts
|
|
6035
|
-
import { brand, BridgeContext as
|
|
6354
|
+
import { brand, BridgeContext as BridgeContext8, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
|
|
6036
6355
|
var MCP_PACKAGE = "@bridge-ds/mcp";
|
|
6037
6356
|
var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
|
|
6038
6357
|
|
|
@@ -6048,8 +6367,10 @@ Usage:
|
|
|
6048
6367
|
bridge diff <base> [--json|--md] designer-readable token diff: working tree vs <base> (git ref)
|
|
6049
6368
|
bridge propose --title "..." --set path=value [--set ...] [--file f | --mode theme=dark]
|
|
6050
6369
|
[--edits edits.json] [--summary "..."] [--branch explore/<name>] [--pr <n>]
|
|
6051
|
-
[--product <key>] [--jira DS-42] [--dry-run]
|
|
6052
|
-
validate + contrast
|
|
6370
|
+
[--product <key>] [--jira DS-42] [--suggest | --direct] [--dry-run]
|
|
6371
|
+
validate + contrast, then a suggestion (a GitHub issue a developer
|
|
6372
|
+
applies with bridge apply) or, with "changes": "direct" in
|
|
6373
|
+
bridge.config.json or --direct, a new local bridge/* branch (never pushes).
|
|
6053
6374
|
--branch: write the edits to the checked-out exploration instead.
|
|
6054
6375
|
--pr: add them to that branch in review (GitHub token).
|
|
6055
6376
|
--product: the product it is for (bridge products); --jira: the
|
|
@@ -6057,9 +6378,10 @@ Usage:
|
|
|
6057
6378
|
bridge brand list [--json] brands: slug, name, default, primary action color, theme overrides
|
|
6058
6379
|
bridge brand create <slug> --from bridge --primary <hex|hue> --neutral <hex|hue>
|
|
6059
6380
|
[--name "Acme"] [--accent x] [--link x] [--radius soft|balanced|crisp|i,c,co,o]
|
|
6060
|
-
[--heading font] [--text font] [--notes "..."] [--dry-run] [--json]
|
|
6061
|
-
new brand: generated ramps, contrast gate for every theme,
|
|
6062
|
-
new local bridge/brand-<slug> branch
|
|
6381
|
+
[--heading font] [--text font] [--notes "..."] [--suggest | --direct] [--dry-run] [--json]
|
|
6382
|
+
new brand: generated ramps, contrast gate for every theme, then a
|
|
6383
|
+
suggestion, or in direct mode a new local bridge/brand-<slug> branch
|
|
6384
|
+
(never pushes, never main).
|
|
6063
6385
|
Exit 1 when blocked
|
|
6064
6386
|
bridge impact [--base main] [--head HEAD] [--out dir] [--only stories|app] [--threshold 0.03]
|
|
6065
6387
|
[--plan] [--json] [--image-base url] [--artifact-url url] [--keep-unchanged]
|
|
@@ -6073,9 +6395,9 @@ Usage:
|
|
|
6073
6395
|
Needs Chrome installed
|
|
6074
6396
|
bridge history [token] [--json] [--limit n] [--author a] [--tier n] [--version v]
|
|
6075
6397
|
token history: versions, merged changes, who, when, why
|
|
6076
|
-
bridge revert <pr|commit|id> [--dry-run] [--restore-conflicts] [--use-before path@file] [--json]
|
|
6077
|
-
undo a merged token change against HEAD: conflicts shown,
|
|
6078
|
-
new local bridge/* branch (never pushes
|
|
6398
|
+
bridge revert <pr|commit|id> [--dry-run] [--restore-conflicts] [--use-before path@file] [--suggest | --direct] [--json]
|
|
6399
|
+
undo a merged token change against HEAD: conflicts shown, then a
|
|
6400
|
+
suggestion, or in direct mode a new local bridge/* branch (never pushes)
|
|
6079
6401
|
bridge audit <path> [--json [out.json]] [--md [out.md]] [--fail-under 80] [--baseline prev.json]
|
|
6080
6402
|
[--label "..."] [--ds dir] [--no-regress] [--allowlist known.json]
|
|
6081
6403
|
design system adoption in a product folder: token and component
|
|
@@ -6118,6 +6440,20 @@ Usage:
|
|
|
6118
6440
|
a product branch and the library (\`bridge pieces help\`): what it adds
|
|
6119
6441
|
that the library lacks, the library branch each piece opens (linked
|
|
6120
6442
|
both ways), its preview in the product branch, the published version
|
|
6443
|
+
bridge suggestions [list | show <n> | link <n> --pr <m>] [--status approved] [--json]
|
|
6444
|
+
suggestions: changes filed as GitHub issues (label bridge-suggestion)
|
|
6445
|
+
that the owners approve and a developer applies (\`bridge suggestions help\`)
|
|
6446
|
+
bridge apply <n> [--write | --patch] [--dry-run] [--accept-edited] [--force-unapproved] [--json]
|
|
6447
|
+
apply suggestion #n on today's code: a new local branch (working tree
|
|
6448
|
+
untouched, nothing pushed), --write into the working tree, --patch for
|
|
6449
|
+
git am. Exit 0 applied or already applied, 1 not approved or blocked,
|
|
6450
|
+
2 usage, 3 conflict
|
|
6451
|
+
bridge review <n> --approve | --request-changes --message "..." | --decline --message "..."
|
|
6452
|
+
review a suggestion as its owner (counts only for owners in
|
|
6453
|
+
bridge.config.json, on the version you saw)
|
|
6454
|
+
bridge figma [show <n> | drafted <n> | push <diff.json>]
|
|
6455
|
+
component changes sent from Figma (\`bridge figma help\`): exact token
|
|
6456
|
+
changes and branches Claude AI drafts from the spec
|
|
6121
6457
|
bridge products | bridge settings | bridge coverage [Component]
|
|
6122
6458
|
products (and this folder's), Studio settings safe to read, which
|
|
6123
6459
|
components are in Figma
|
|
@@ -6128,8 +6464,11 @@ Usage:
|
|
|
6128
6464
|
Options:
|
|
6129
6465
|
--root <dir> repo root (default: current directory)
|
|
6130
6466
|
--ds <dir> audit: design system repo with bridge.config.json (default: --root, or the nearest one above)
|
|
6131
|
-
GITHUB_TOKEN env, optional: lets
|
|
6132
|
-
|
|
6467
|
+
GITHUB_TOKEN env, optional: lets bridge file suggestions and requests (Issues: read and write) and the
|
|
6468
|
+
MCP server open PRs on a remote repo in direct mode (else \`gh auth token\`). Never needed for
|
|
6469
|
+
local token work in direct mode.
|
|
6470
|
+
--suggest | --direct how a change reaches the code for this command (propose, brand create, revert,
|
|
6471
|
+
docs save, pieces open, figma push). Default: "changes" in bridge.config.json, else suggest.
|
|
6133
6472
|
`;
|
|
6134
6473
|
function parse(argv) {
|
|
6135
6474
|
const out = { _: [], flags: {} };
|
|
@@ -6139,23 +6478,32 @@ function parse(argv) {
|
|
|
6139
6478
|
const [k, inline] = a.slice(2).split(/=(.*)/s, 2);
|
|
6140
6479
|
const next = argv[i + 1];
|
|
6141
6480
|
const fileArg = k === "json" && /\.json$/i.test(next ?? "") || k === "md" && /\.(md|markdown)$/i.test(next ?? "");
|
|
6142
|
-
const takesValue = inline === void 0 && next !== void 0 && !next.startsWith("--") && (fileArg || !["force", "json", "md", "strict", "dry-run", "help", "fix", "write", "no-regress", "plan", "keep-unchanged", "restore-conflicts", "breaking", "claude", "all", "offline", "approve"].includes(k));
|
|
6481
|
+
const takesValue = inline === void 0 && next !== void 0 && !next.startsWith("--") && (fileArg || !["force", "json", "md", "strict", "dry-run", "help", "fix", "write", "no-regress", "plan", "keep-unchanged", "restore-conflicts", "breaking", "claude", "all", "offline", "approve", "patch", "accept-edited", "force-unapproved", "suggest", "direct"].includes(k));
|
|
6143
6482
|
(out.flags[k] ??= []).push(inline ?? (takesValue ? argv[++i] : "true"));
|
|
6144
6483
|
} else out._.push(a);
|
|
6145
6484
|
}
|
|
6146
6485
|
return out;
|
|
6147
6486
|
}
|
|
6148
|
-
var
|
|
6149
|
-
var
|
|
6487
|
+
var flag5 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
6488
|
+
var has7 = (a, k) => Boolean(a.flags[k]);
|
|
6150
6489
|
var print = (s) => process.stdout.write(`${s}
|
|
6151
6490
|
`);
|
|
6152
6491
|
var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
|
|
6492
|
+
var githubToken = () => process.env.BRIDGE_GITHUB_TOKEN || process.env.GITHUB_TOKEN || defaultIO().ghToken();
|
|
6493
|
+
function printSent(r) {
|
|
6494
|
+
if (r.status === "suggested") print(`suggestion: #${r.number} ${r.url}`);
|
|
6495
|
+
if (r.message) print(String(r.message));
|
|
6496
|
+
if (r.branch && r.status !== "suggested") print(`branch: ${r.branch}`);
|
|
6497
|
+
for (const w of r.warnings ?? []) print(`warning: ${w}`);
|
|
6498
|
+
if (r.next) print(String(r.next));
|
|
6499
|
+
if (r.hint) print(String(r.hint));
|
|
6500
|
+
}
|
|
6153
6501
|
async function init(a, root) {
|
|
6154
6502
|
const cfgPath = join6(root, CONFIG_FILE2);
|
|
6155
6503
|
let config;
|
|
6156
|
-
if (existsSync6(cfgPath) && !
|
|
6504
|
+
if (existsSync6(cfgPath) && !has7(a, "force")) {
|
|
6157
6505
|
print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
|
|
6158
|
-
config = (await (await
|
|
6506
|
+
config = (await (await BridgeContext8.create({ root })).config()).config;
|
|
6159
6507
|
} else {
|
|
6160
6508
|
const src = new LocalSource(root);
|
|
6161
6509
|
const files = await src.list("");
|
|
@@ -6183,14 +6531,14 @@ async function init(a, root) {
|
|
|
6183
6531
|
print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
|
|
6184
6532
|
}
|
|
6185
6533
|
async function check(a, root) {
|
|
6186
|
-
const ctx = await
|
|
6534
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6187
6535
|
const v = await ops.validateTokens(ctx, { includeWarnings: true });
|
|
6188
6536
|
const c = await ops.checkContrast(ctx, {});
|
|
6189
6537
|
const warnings = "warnings" in v ? v.warnings ?? [] : [];
|
|
6190
|
-
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 ||
|
|
6538
|
+
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has7(a, "strict") && warnings.length > 0;
|
|
6191
6539
|
const protection = await protectionOf(a, root, ctx);
|
|
6192
6540
|
const roles = await rolesOf(a, root, ctx);
|
|
6193
|
-
if (
|
|
6541
|
+
if (has7(a, "json")) {
|
|
6194
6542
|
print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
|
|
6195
6543
|
return failed ? 1 : 0;
|
|
6196
6544
|
}
|
|
@@ -6209,7 +6557,7 @@ async function check(a, root) {
|
|
|
6209
6557
|
return failed ? 1 : 0;
|
|
6210
6558
|
}
|
|
6211
6559
|
async function protectionOf(a, root, ctx) {
|
|
6212
|
-
if (
|
|
6560
|
+
if (has7(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6213
6561
|
const { config } = await ctx.config();
|
|
6214
6562
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6215
6563
|
if (!repo) return void 0;
|
|
@@ -6217,7 +6565,7 @@ async function protectionOf(a, root, ctx) {
|
|
|
6217
6565
|
return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
|
|
6218
6566
|
}
|
|
6219
6567
|
async function rolesOf(a, root, ctx) {
|
|
6220
|
-
if (
|
|
6568
|
+
if (has7(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6221
6569
|
const { config } = await ctx.config();
|
|
6222
6570
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6223
6571
|
if (!repo) return void 0;
|
|
@@ -6227,9 +6575,9 @@ async function rolesOf(a, root, ctx) {
|
|
|
6227
6575
|
async function tokens(a, root) {
|
|
6228
6576
|
const q = a._.slice(1).join(" ");
|
|
6229
6577
|
if (!q) throw new UsageError("bridge tokens <query>");
|
|
6230
|
-
const ctx = await
|
|
6231
|
-
const r = await ops.searchTokens(ctx, { query: q, limit: Number(
|
|
6232
|
-
if (
|
|
6578
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6579
|
+
const r = await ops.searchTokens(ctx, { query: q, limit: Number(flag5(a, "limit") ?? 20) });
|
|
6580
|
+
if (has7(a, "json")) return print(JSON.stringify(r, null, 2));
|
|
6233
6581
|
if (!r.results.length) return print(`No token matches "${q}".`);
|
|
6234
6582
|
const w = Math.max(...r.results.map((x) => x.path.length));
|
|
6235
6583
|
for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
|
|
@@ -6237,7 +6585,7 @@ async function tokens(a, root) {
|
|
|
6237
6585
|
async function diff(a, root) {
|
|
6238
6586
|
const base = a._[1];
|
|
6239
6587
|
if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
|
|
6240
|
-
const ctx = await
|
|
6588
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6241
6589
|
const { config } = await ctx.config();
|
|
6242
6590
|
const before = await core.loadTokenSet(new GitRefSource(root, base), config);
|
|
6243
6591
|
const after = await ctx.tokens();
|
|
@@ -6246,11 +6594,11 @@ async function diff(a, root) {
|
|
|
6246
6594
|
const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
|
|
6247
6595
|
const tier = core.classify(changes, [], config);
|
|
6248
6596
|
const imp = core.impact(before, after, changes, 30);
|
|
6249
|
-
if (
|
|
6597
|
+
if (has7(a, "json")) {
|
|
6250
6598
|
print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
|
|
6251
6599
|
return 0;
|
|
6252
6600
|
}
|
|
6253
|
-
if (
|
|
6601
|
+
if (has7(a, "md")) {
|
|
6254
6602
|
print(`## Token changes since \`${base}\`
|
|
6255
6603
|
|
|
6256
6604
|
${core.tokenTable(changes)}
|
|
@@ -6285,26 +6633,27 @@ ${imp.total} other token(s) change value through aliases:`);
|
|
|
6285
6633
|
return delta2.some((d) => d.passBefore && !d.passAfter) ? 1 : 0;
|
|
6286
6634
|
}
|
|
6287
6635
|
async function propose(a, root) {
|
|
6288
|
-
const title =
|
|
6636
|
+
const title = flag5(a, "title");
|
|
6289
6637
|
if (!title) throw new UsageError('bridge propose --title "Darken muted text" --set color.text.muted={palette.neutral.700} --mode theme=light');
|
|
6290
6638
|
const edits = [];
|
|
6291
|
-
const file =
|
|
6292
|
-
const mode2 = modeArg(
|
|
6639
|
+
const file = flag5(a, "file");
|
|
6640
|
+
const mode2 = modeArg(flag5(a, "mode"));
|
|
6293
6641
|
for (const s of a.flags.set ?? []) {
|
|
6294
6642
|
const i = s.indexOf("=");
|
|
6295
6643
|
if (i < 0) throw new UsageError(`--set needs path=value, got "${s}"`);
|
|
6296
6644
|
edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
|
|
6297
6645
|
}
|
|
6298
|
-
const editsFile =
|
|
6299
|
-
if (editsFile) edits.push(...JSON.parse(await readFile7(
|
|
6646
|
+
const editsFile = flag5(a, "edits");
|
|
6647
|
+
if (editsFile) edits.push(...JSON.parse(await readFile7(resolve5(editsFile), "utf8")));
|
|
6300
6648
|
if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
|
|
6301
|
-
const pr =
|
|
6302
|
-
const
|
|
6303
|
-
const
|
|
6304
|
-
const
|
|
6305
|
-
const
|
|
6306
|
-
const
|
|
6307
|
-
const
|
|
6649
|
+
const pr = flag5(a, "pr");
|
|
6650
|
+
const changes = changesFlag(a);
|
|
6651
|
+
const token = pr || changes !== "direct" ? githubToken() : void 0;
|
|
6652
|
+
const ctx = await BridgeContext8.create({ root, ...token ? { token } : {} });
|
|
6653
|
+
const summary = flag5(a, "summary");
|
|
6654
|
+
const branch = flag5(a, "branch");
|
|
6655
|
+
const jira = flag5(a, "jira");
|
|
6656
|
+
const product = flag5(a, "product");
|
|
6308
6657
|
const r = await ops.proposeTokenChange(ctx, {
|
|
6309
6658
|
title,
|
|
6310
6659
|
edits,
|
|
@@ -6313,34 +6662,36 @@ async function propose(a, root) {
|
|
|
6313
6662
|
...pr ? { pr } : {},
|
|
6314
6663
|
...jira ? { jira } : {},
|
|
6315
6664
|
...product ? { product } : {},
|
|
6316
|
-
|
|
6665
|
+
...changes ? { changes } : {},
|
|
6666
|
+
source: "cli",
|
|
6667
|
+
dryRun: has7(a, "dry-run")
|
|
6317
6668
|
});
|
|
6318
|
-
if (
|
|
6669
|
+
if (has7(a, "json")) print(JSON.stringify(r, null, 2));
|
|
6319
6670
|
else {
|
|
6320
6671
|
print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
|
|
6321
6672
|
if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
|
|
6322
6673
|
for (const c of r.changes ?? []) print(` ${c.kind} ${c.path}: ${c.before ?? ""} -> ${c.after ?? ""}`);
|
|
6323
|
-
|
|
6324
|
-
if (r.next) print(String(r.next));
|
|
6325
|
-
if (r.hint) print(String(r.hint));
|
|
6674
|
+
printSent(r);
|
|
6326
6675
|
if (r.status === "dry-run") print(`
|
|
6327
|
-
${r.prBody}`);
|
|
6676
|
+
${r.suggestion?.body ?? r.prBody}`);
|
|
6328
6677
|
}
|
|
6329
6678
|
return r.status === "blocked" ? 1 : 0;
|
|
6330
6679
|
}
|
|
6331
6680
|
async function brandCmd(a, root) {
|
|
6332
6681
|
const sub = a._[1];
|
|
6333
|
-
const
|
|
6682
|
+
const changes = changesFlag(a);
|
|
6683
|
+
const token = sub === "create" && changes !== "direct" ? githubToken() : void 0;
|
|
6684
|
+
const ctx = await BridgeContext8.create({ root, ...token ? { token } : {} });
|
|
6334
6685
|
if (sub === "list") {
|
|
6335
6686
|
const r2 = await brand.listBrands(ctx);
|
|
6336
|
-
if (
|
|
6687
|
+
if (has7(a, "json")) print(JSON.stringify(r2.brands, null, 2));
|
|
6337
6688
|
else for (const b of r2.brands) print(`${b.default ? "*" : " "} ${b.slug.padEnd(14)} ${b.name.padEnd(18)} ${b.primary ?? ""}${b.overrides.length ? ` overrides: ${b.overrides.join(", ")}` : ""}`);
|
|
6338
6689
|
return 0;
|
|
6339
6690
|
}
|
|
6340
6691
|
if (sub !== "create") throw new UsageError('bridge brand list | bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
|
|
6341
6692
|
const slug = a._[2];
|
|
6342
6693
|
if (!slug) throw new UsageError('bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
|
|
6343
|
-
const opt = (k) =>
|
|
6694
|
+
const opt = (k) => flag5(a, k);
|
|
6344
6695
|
const fonts = { ...opt("heading") ? { heading: opt("heading") } : {}, ...opt("text") ? { text: opt("text") } : {} };
|
|
6345
6696
|
const r = await brand.proposeBrand(ctx, {
|
|
6346
6697
|
slug,
|
|
@@ -6353,9 +6704,11 @@ async function brandCmd(a, root) {
|
|
|
6353
6704
|
...opt("radius") ? { radius: opt("radius") } : {},
|
|
6354
6705
|
...Object.keys(fonts).length ? { fonts } : {},
|
|
6355
6706
|
...opt("notes") ? { notes: opt("notes") } : {},
|
|
6356
|
-
|
|
6707
|
+
...changes ? { changes } : {},
|
|
6708
|
+
source: "cli",
|
|
6709
|
+
dryRun: has7(a, "dry-run")
|
|
6357
6710
|
});
|
|
6358
|
-
if (
|
|
6711
|
+
if (has7(a, "json")) {
|
|
6359
6712
|
const { fileContents: _f, ...rest } = r;
|
|
6360
6713
|
print(JSON.stringify(rest, null, 2));
|
|
6361
6714
|
return r.status === "blocked" ? 1 : 0;
|
|
@@ -6370,13 +6723,11 @@ async function brandCmd(a, root) {
|
|
|
6370
6723
|
for (const i of r.issues) print(` ${i.level}: ${i.message}`);
|
|
6371
6724
|
print("files:");
|
|
6372
6725
|
for (const f of r.files) print(` ${f.action} ${f.path}`);
|
|
6373
|
-
|
|
6374
|
-
if (r.next) print(String(r.next));
|
|
6375
|
-
if (r.hint) print(String(r.hint));
|
|
6726
|
+
printSent(r);
|
|
6376
6727
|
return r.status === "blocked" ? 1 : 0;
|
|
6377
6728
|
}
|
|
6378
6729
|
async function figmaReferences(a, root) {
|
|
6379
|
-
let sha =
|
|
6730
|
+
let sha = flag5(a, "sha");
|
|
6380
6731
|
if (!sha) {
|
|
6381
6732
|
try {
|
|
6382
6733
|
sha = execFileSync2("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
@@ -6384,49 +6735,49 @@ async function figmaReferences(a, root) {
|
|
|
6384
6735
|
sha = "unknown";
|
|
6385
6736
|
}
|
|
6386
6737
|
}
|
|
6387
|
-
const only = (
|
|
6738
|
+
const only = (flag5(a, "only") ?? "").split(",").filter(Boolean);
|
|
6388
6739
|
const r = await captureFigmaReferences({
|
|
6389
|
-
storybook:
|
|
6390
|
-
specs:
|
|
6391
|
-
out:
|
|
6740
|
+
storybook: resolve5(root, flag5(a, "storybook") ?? "storybook-static"),
|
|
6741
|
+
specs: resolve5(root, flag5(a, "specs") ?? "packages/react/component-specs.json"),
|
|
6742
|
+
out: resolve5(root, flag5(a, "out") ?? "figma-references"),
|
|
6392
6743
|
sha,
|
|
6393
6744
|
...only.length ? { only } : {},
|
|
6394
|
-
log:
|
|
6745
|
+
log: has7(a, "json") ? void 0 : (line) => print(line)
|
|
6395
6746
|
});
|
|
6396
|
-
if (
|
|
6747
|
+
if (has7(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
|
|
6397
6748
|
return 0;
|
|
6398
6749
|
}
|
|
6399
6750
|
async function impact(a, root) {
|
|
6400
|
-
const only = (
|
|
6751
|
+
const only = (flag5(a, "only") ?? "").split(",").filter(Boolean);
|
|
6401
6752
|
for (const x of only) if (x !== "stories" && x !== "app") throw new UsageError(`--only takes stories or app, got "${x}"`);
|
|
6402
|
-
const num2 = (k) =>
|
|
6753
|
+
const num2 = (k) => flag5(a, k) !== void 0 ? Number(flag5(a, k)) : void 0;
|
|
6403
6754
|
const threshold = num2("threshold");
|
|
6404
6755
|
if (threshold !== void 0 && !(threshold >= 0 && threshold <= 1)) throw new UsageError("--threshold is between 0 and 1 (pixelmatch color distance)");
|
|
6405
|
-
const imageBase =
|
|
6406
|
-
const artifactUrl =
|
|
6407
|
-
const out =
|
|
6408
|
-
const cacheDir =
|
|
6756
|
+
const imageBase = flag5(a, "image-base");
|
|
6757
|
+
const artifactUrl = flag5(a, "artifact-url");
|
|
6758
|
+
const out = flag5(a, "out");
|
|
6759
|
+
const cacheDir = flag5(a, "cache");
|
|
6409
6760
|
const maxStories = num2("max-stories");
|
|
6410
6761
|
const r = await runImpact({
|
|
6411
6762
|
root,
|
|
6412
|
-
base:
|
|
6413
|
-
head:
|
|
6414
|
-
...out ? { out:
|
|
6763
|
+
base: flag5(a, "base") ?? "main",
|
|
6764
|
+
head: flag5(a, "head") ?? "HEAD",
|
|
6765
|
+
...out ? { out: resolve5(out) } : {},
|
|
6415
6766
|
...only.length ? { only } : {},
|
|
6416
6767
|
...threshold !== void 0 ? { threshold } : {},
|
|
6417
|
-
...cacheDir ? { cacheDir:
|
|
6768
|
+
...cacheDir ? { cacheDir: resolve5(cacheDir) } : {},
|
|
6418
6769
|
...maxStories ? { maxStoriesPerComponent: maxStories } : {},
|
|
6419
6770
|
...imageBase !== void 0 ? { imageBase } : {},
|
|
6420
6771
|
...artifactUrl ? { artifactUrl } : {},
|
|
6421
|
-
plan:
|
|
6422
|
-
keepUnchanged:
|
|
6772
|
+
plan: has7(a, "plan"),
|
|
6773
|
+
keepUnchanged: has7(a, "keep-unchanged")
|
|
6423
6774
|
});
|
|
6424
|
-
if (
|
|
6775
|
+
if (has7(a, "json")) {
|
|
6425
6776
|
print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
|
|
6426
6777
|
return 0;
|
|
6427
6778
|
}
|
|
6428
6779
|
const sel = r.manifest.selection;
|
|
6429
|
-
if (
|
|
6780
|
+
if (has7(a, "plan")) {
|
|
6430
6781
|
print(`Plan ${r.manifest.base.ref} (${r.manifest.base.sha.slice(0, 7)}) -> ${r.manifest.head.ref} (${r.manifest.head.sha.slice(0, 7)})`);
|
|
6431
6782
|
print(` tokens changing value: ${sel.tokens.length}${sel.tokens.length ? ` (${sel.tokens.filter((t) => t.kind === "direct").map((t) => t.path).slice(0, 6).join(", ")})` : ""}`);
|
|
6432
6783
|
print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
|
|
@@ -6442,12 +6793,12 @@ async function impact(a, root) {
|
|
|
6442
6793
|
}
|
|
6443
6794
|
var day = (iso) => iso.slice(0, 10);
|
|
6444
6795
|
async function historyCmd(a, root) {
|
|
6445
|
-
const ctx = await
|
|
6796
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6446
6797
|
const token = a._[1];
|
|
6447
|
-
const limit = Number(
|
|
6798
|
+
const limit = Number(flag5(a, "limit") ?? 20);
|
|
6448
6799
|
if (token) {
|
|
6449
6800
|
const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
|
|
6450
|
-
if (
|
|
6801
|
+
if (has7(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
|
|
6451
6802
|
if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
|
|
6452
6803
|
print(`${r2.path}: ${r2.total} change(s) (${r2.source})
|
|
6453
6804
|
`);
|
|
@@ -6458,9 +6809,9 @@ async function historyCmd(a, root) {
|
|
|
6458
6809
|
}
|
|
6459
6810
|
return 0;
|
|
6460
6811
|
}
|
|
6461
|
-
const tier =
|
|
6462
|
-
const r = await hist.getHistory(ctx, { limit, ...
|
|
6463
|
-
if (
|
|
6812
|
+
const tier = flag5(a, "tier");
|
|
6813
|
+
const r = await hist.getHistory(ctx, { limit, ...flag5(a, "author") ? { author: flag5(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag5(a, "version") ? { version: flag5(a, "version") } : {}, ...has7(a, "breaking") ? { breaking: true } : {} });
|
|
6814
|
+
if (has7(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
|
|
6464
6815
|
print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
|
|
6465
6816
|
if (!r.total) return print(r.note ?? "No token change matches."), 0;
|
|
6466
6817
|
for (const v of r.versions) print(` ${v.version.padEnd(11)} ${day(v.date)} ${v.entries} change(s), ${v.tokenChanges} token edit(s)${v.breaking ? ", breaking" : ""}`);
|
|
@@ -6478,10 +6829,12 @@ ${r.truncated}`);
|
|
|
6478
6829
|
async function revertCmd(a, root) {
|
|
6479
6830
|
const target = a._[1];
|
|
6480
6831
|
if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
|
|
6481
|
-
const
|
|
6832
|
+
const changes = changesFlag(a);
|
|
6833
|
+
const token = changes !== "direct" ? githubToken() : void 0;
|
|
6834
|
+
const ctx = await BridgeContext8.create({ root, ...token ? { token } : {} });
|
|
6482
6835
|
const resolutions = Object.fromEntries((a.flags["use-before"] ?? []).map((k) => [k, "use-before"]));
|
|
6483
|
-
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts:
|
|
6484
|
-
if (
|
|
6836
|
+
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has7(a, "restore-conflicts"), dryRun: has7(a, "dry-run"), ...changes ? { changes } : {}, source: "cli" });
|
|
6837
|
+
if (has7(a, "json")) {
|
|
6485
6838
|
print(JSON.stringify(r, null, 2));
|
|
6486
6839
|
return r.status === "blocked" ? 1 : 0;
|
|
6487
6840
|
}
|
|
@@ -6496,35 +6849,34 @@ async function revertCmd(a, root) {
|
|
|
6496
6849
|
}
|
|
6497
6850
|
for (const s of r.skipped ?? []) print(` skipped ${s}`);
|
|
6498
6851
|
if (r.contrast?.moved) print(`contrast: ${r.contrast.moved} pair(s) move, ${r.contrast.newFailures.length} new failure(s)`);
|
|
6499
|
-
|
|
6500
|
-
if (r.next) print(String(r.next));
|
|
6852
|
+
printSent(r);
|
|
6501
6853
|
if (r.status === "dry-run") print(`
|
|
6502
|
-
${r.prBody}`);
|
|
6854
|
+
${r.suggestion?.body ?? r.prBody}`);
|
|
6503
6855
|
return r.status === "blocked" ? 1 : 0;
|
|
6504
6856
|
}
|
|
6505
6857
|
async function audit(a, root) {
|
|
6506
6858
|
const target = a._[1];
|
|
6507
6859
|
if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
|
|
6508
6860
|
if (!existsSync6(target)) throw new UsageError(`No such file or folder: ${target}`);
|
|
6509
|
-
const dsRoot =
|
|
6861
|
+
const dsRoot = flag5(a, "ds") ? resolve5(flag5(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
|
|
6510
6862
|
if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
|
|
6511
6863
|
const ds = loadDesignSystem(dsRoot);
|
|
6512
|
-
if (
|
|
6513
|
-
const write =
|
|
6864
|
+
if (has7(a, "fix")) {
|
|
6865
|
+
const write = has7(a, "write");
|
|
6514
6866
|
const changes = fixCss(target, ds.index, write);
|
|
6515
6867
|
const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
|
|
6516
6868
|
for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
|
|
6517
6869
|
print(n ? `${write ? "Replaced" : "Would replace"} ${n} value(s) in ${changes.length} file(s) with exact token matches.${write ? "" : " Run again with --write to apply."}` : "Nothing to fix: no CSS value equals a token exactly.");
|
|
6518
6870
|
return 0;
|
|
6519
6871
|
}
|
|
6520
|
-
const baselinePath =
|
|
6872
|
+
const baselinePath = flag5(a, "baseline");
|
|
6521
6873
|
let baseline;
|
|
6522
6874
|
if (baselinePath) {
|
|
6523
6875
|
if (!existsSync6(baselinePath)) throw new UsageError(`Baseline not found: ${baselinePath}`);
|
|
6524
6876
|
baseline = JSON.parse(await readFile7(baselinePath, "utf8"));
|
|
6525
6877
|
}
|
|
6526
|
-
const label =
|
|
6527
|
-
const allowlistPath =
|
|
6878
|
+
const label = flag5(a, "label");
|
|
6879
|
+
const allowlistPath = flag5(a, "allowlist");
|
|
6528
6880
|
if (allowlistPath && !existsSync6(allowlistPath)) throw new UsageError(`Allowlist not found: ${allowlistPath}`);
|
|
6529
6881
|
let allowlist;
|
|
6530
6882
|
try {
|
|
@@ -6534,8 +6886,8 @@ async function audit(a, root) {
|
|
|
6534
6886
|
}
|
|
6535
6887
|
const report = runAudit(target, { ds: dsRoot, version: VERSION, ...label ? { label } : {}, ...baseline ? { baseline } : {}, ...allowlist ? { allowlist } : {} });
|
|
6536
6888
|
const md = auditMarkdown(report);
|
|
6537
|
-
const jsonOut =
|
|
6538
|
-
const mdOut =
|
|
6889
|
+
const jsonOut = flag5(a, "json");
|
|
6890
|
+
const mdOut = flag5(a, "md");
|
|
6539
6891
|
if (jsonOut && jsonOut !== "true") await writeFile5(jsonOut, `${JSON.stringify(report, null, 2)}
|
|
6540
6892
|
`);
|
|
6541
6893
|
if (mdOut && mdOut !== "true") await writeFile5(mdOut, `${md}
|
|
@@ -6575,7 +6927,7 @@ async function audit(a, root) {
|
|
|
6575
6927
|
for (const f of report.files) for (const e of f.ignoreErrors ?? []) process.stderr.write(`${report.target.path}/${f.path}:${e.line}: ${e.message}
|
|
6576
6928
|
`);
|
|
6577
6929
|
let code = 0;
|
|
6578
|
-
const failUnder =
|
|
6930
|
+
const failUnder = flag5(a, "fail-under");
|
|
6579
6931
|
if (failUnder !== void 0) {
|
|
6580
6932
|
const min = Number(failUnder);
|
|
6581
6933
|
if (!Number.isFinite(min)) throw new UsageError("--fail-under needs a number, e.g. --fail-under 80");
|
|
@@ -6586,7 +6938,7 @@ async function audit(a, root) {
|
|
|
6586
6938
|
code = 1;
|
|
6587
6939
|
}
|
|
6588
6940
|
}
|
|
6589
|
-
if (
|
|
6941
|
+
if (has7(a, "no-regress") && report.baseline) {
|
|
6590
6942
|
const b = report.baseline;
|
|
6591
6943
|
if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
|
|
6592
6944
|
process.stderr.write("bridge audit: adoption regressed since the baseline\n");
|
|
@@ -6598,9 +6950,9 @@ async function audit(a, root) {
|
|
|
6598
6950
|
async function docsCmd(a, root) {
|
|
6599
6951
|
const [, sub, name] = a._;
|
|
6600
6952
|
if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
|
|
6601
|
-
const ctx = await
|
|
6953
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6602
6954
|
const d = await ops.draftDocsOp(ctx, { name });
|
|
6603
|
-
if (
|
|
6955
|
+
if (has7(a, "json")) {
|
|
6604
6956
|
print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
|
|
6605
6957
|
return 0;
|
|
6606
6958
|
}
|
|
@@ -6631,16 +6983,20 @@ async function main() {
|
|
|
6631
6983
|
const argv = process.argv.slice(2);
|
|
6632
6984
|
const a = parse(argv);
|
|
6633
6985
|
const cmd = a._[0];
|
|
6634
|
-
const root =
|
|
6986
|
+
const root = resolve5(flag5(a, "root") ?? process.cwd());
|
|
6635
6987
|
if (cmd === "request") return runRequest(a, root);
|
|
6636
6988
|
if (cmd === "explore") return runExplore(a, root);
|
|
6637
6989
|
if (cmd === "branch") return runBranch(a, root);
|
|
6638
6990
|
if (cmd === "pieces") return runPieces(a, root);
|
|
6991
|
+
if (cmd === "figma") return runFigma(a, root);
|
|
6992
|
+
if (cmd === "suggestions" || cmd === "suggest") return runSuggestions(a, root);
|
|
6993
|
+
if (cmd === "apply") return runApply(a, root);
|
|
6994
|
+
if (cmd === "review") return runReview(a, root);
|
|
6639
6995
|
if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
|
|
6640
6996
|
if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
|
|
6641
|
-
if (!cmd ||
|
|
6997
|
+
if (!cmd || has7(a, "help") || cmd === "help") {
|
|
6642
6998
|
print(HELP);
|
|
6643
|
-
return cmd ||
|
|
6999
|
+
return cmd || has7(a, "help") ? 0 : 2;
|
|
6644
7000
|
}
|
|
6645
7001
|
if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
|
|
6646
7002
|
switch (cmd) {
|