@bridge-ds/cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +315 -208
- 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 = {}) {
|
|
@@ -3423,10 +3347,16 @@ var GitHubClient = class _GitHubClient {
|
|
|
3423
3347
|
async addComment(issueNumber2, body) {
|
|
3424
3348
|
await this.request("POST", `${this.repoPath}/issues/${issueNumber2}/comments`, { body });
|
|
3425
3349
|
}
|
|
3426
|
-
/**
|
|
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
|
+
*/
|
|
3427
3354
|
async listIssueComments(issueNumber2) {
|
|
3428
|
-
const r = await this.request(
|
|
3429
|
-
|
|
3355
|
+
const r = await this.request(
|
|
3356
|
+
"GET",
|
|
3357
|
+
`${this.repoPath}/issues/${issueNumber2}/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) {
|
|
@@ -3916,6 +3846,148 @@ 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
|
+
// src/index.ts
|
|
3905
|
+
import { fileURLToPath } from "node:url";
|
|
3906
|
+
|
|
3907
|
+
// ../visual/src/run.ts
|
|
3908
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3909
|
+
import { mkdir as mkdir3, readFile as readFile3, rm as rm2, unlink, writeFile as writeFile3 } from "node:fs/promises";
|
|
3910
|
+
import { tmpdir } from "node:os";
|
|
3911
|
+
import { join as join4, relative as relative3, resolve as resolve2 } from "node:path";
|
|
3912
|
+
|
|
3913
|
+
// ../core/src/protection.ts
|
|
3914
|
+
var PROTECTION_PARTS = ["review", "checks", "history", "deletion"];
|
|
3915
|
+
var protectionRulesetName = (defaultBranch) => `Protect the library (${defaultBranch})`;
|
|
3916
|
+
function refPattern(pattern) {
|
|
3917
|
+
const src = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replace(/\u0000/g, ".*");
|
|
3918
|
+
return new RegExp(`^${src}$`);
|
|
3919
|
+
}
|
|
3920
|
+
function rulesetTargetsDefault(ruleset, defaultBranch) {
|
|
3921
|
+
if (ruleset.target && ruleset.target !== "branch") return false;
|
|
3922
|
+
const ref = `refs/heads/${defaultBranch}`;
|
|
3923
|
+
const hit = (p) => p === "~ALL" || p === "~DEFAULT_BRANCH" || p === ref || p === defaultBranch || refPattern(p.startsWith("refs/") ? p : `refs/heads/${p}`).test(ref);
|
|
3924
|
+
const cond = ruleset.conditions?.ref_name;
|
|
3925
|
+
if (!cond) return false;
|
|
3926
|
+
if ((cond.exclude ?? []).some(hit)) return false;
|
|
3927
|
+
return (cond.include ?? []).some(hit);
|
|
3928
|
+
}
|
|
3929
|
+
var count = (v) => typeof v === "number" ? v : 0;
|
|
3930
|
+
function rulesetParts(ruleset) {
|
|
3931
|
+
const parts = /* @__PURE__ */ new Set();
|
|
3932
|
+
if (ruleset.enforcement !== "active") return parts;
|
|
3933
|
+
for (const rule of ruleset.rules ?? []) {
|
|
3934
|
+
if (rule.type === "deletion") parts.add("deletion");
|
|
3935
|
+
if (rule.type === "non_fast_forward") parts.add("history");
|
|
3936
|
+
if (rule.type === "pull_request" && count(rule.parameters?.required_approving_review_count) >= 1) parts.add("review");
|
|
3937
|
+
if (rule.type === "required_status_checks" && Array.isArray(rule.parameters?.required_status_checks) && rule.parameters.required_status_checks.length > 0) parts.add("checks");
|
|
3938
|
+
}
|
|
3939
|
+
return parts;
|
|
3940
|
+
}
|
|
3941
|
+
function classicParts(p) {
|
|
3942
|
+
const parts = /* @__PURE__ */ new Set();
|
|
3943
|
+
if (count(p.required_pull_request_reviews?.required_approving_review_count) >= 1) parts.add("review");
|
|
3944
|
+
if (((p.required_status_checks?.contexts?.length ?? 0) || (p.required_status_checks?.checks?.length ?? 0)) > 0) parts.add("checks");
|
|
3945
|
+
if (!p.allow_force_pushes?.enabled) parts.add("history");
|
|
3946
|
+
if (!p.allow_deletions?.enabled) parts.add("deletion");
|
|
3947
|
+
return parts;
|
|
3948
|
+
}
|
|
3949
|
+
var rulesetUrl = (r) => r.html_url ?? r._links?.html?.href;
|
|
3950
|
+
function protectionState(rulesets, branchProtection, defaultBranch) {
|
|
3951
|
+
const bridge = rulesets?.find((r) => r.name === protectionRulesetName(defaultBranch));
|
|
3952
|
+
const extra = bridge?.id !== void 0 ? { bridgeRulesetId: bridge.id } : {};
|
|
3953
|
+
const covered = /* @__PURE__ */ new Set();
|
|
3954
|
+
let url;
|
|
3955
|
+
for (const r of rulesets ?? []) {
|
|
3956
|
+
if (!rulesetTargetsDefault(r, defaultBranch)) continue;
|
|
3957
|
+
const parts = rulesetParts(r);
|
|
3958
|
+
if (parts.size && !url) url = rulesetUrl(r);
|
|
3959
|
+
parts.forEach((p) => covered.add(p));
|
|
3960
|
+
}
|
|
3961
|
+
if (branchProtection) classicParts(branchProtection).forEach((p) => covered.add(p));
|
|
3962
|
+
const missing = PROTECTION_PARTS.filter((p) => !covered.has(p));
|
|
3963
|
+
const withUrl = url ? { url } : {};
|
|
3964
|
+
if (!missing.length) return { kind: "protected", missing, ...extra, ...withUrl };
|
|
3965
|
+
const unreadable = rulesets == null || branchProtection === void 0;
|
|
3966
|
+
if (covered.size === 0) return { kind: unreadable ? "unknown" : "unprotected", missing, ...extra };
|
|
3967
|
+
return { kind: "partial", missing, ...extra, ...withUrl };
|
|
3968
|
+
}
|
|
3969
|
+
var PROTECTION_PART_PHRASE = {
|
|
3970
|
+
review: "changes can skip review",
|
|
3971
|
+
checks: "checks do not have to pass",
|
|
3972
|
+
history: "past changes can be rewritten",
|
|
3973
|
+
deletion: "the library can be deleted"
|
|
3974
|
+
};
|
|
3975
|
+
var joinAnd = (xs) => xs.length <= 1 ? xs[0] ?? "" : `${xs.slice(0, -1).join(", ")} and ${xs[xs.length - 1]}`;
|
|
3976
|
+
function protectionSentence(state) {
|
|
3977
|
+
switch (state.kind) {
|
|
3978
|
+
case "protected":
|
|
3979
|
+
return "Protected: every change goes through review.";
|
|
3980
|
+
case "unprotected":
|
|
3981
|
+
return "Your library can be changed without review.";
|
|
3982
|
+
case "partial": {
|
|
3983
|
+
const s = joinAnd(state.missing.map((p) => PROTECTION_PART_PHRASE[p]));
|
|
3984
|
+
return `Partly protected: ${s}.`;
|
|
3985
|
+
}
|
|
3986
|
+
default:
|
|
3987
|
+
return "Protection status unknown: GitHub did not let this token read it.";
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3919
3991
|
// ../core/src/protection-github.ts
|
|
3920
3992
|
async function loadProtection(client, defaultBranch) {
|
|
3921
3993
|
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 +4104,6 @@ function impactedModules(index, input) {
|
|
|
4032
4104
|
return { modules, globalHits };
|
|
4033
4105
|
}
|
|
4034
4106
|
|
|
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
4107
|
// ../core/src/fidelity-references.ts
|
|
4091
4108
|
var FIGMA_REFERENCES_DIR = "figma-references";
|
|
4092
4109
|
var FIGMA_REFERENCES_MANIFEST = `${FIGMA_REFERENCES_DIR}/manifest.json`;
|
|
@@ -4310,15 +4327,15 @@ async function shoot(page, r) {
|
|
|
4310
4327
|
}
|
|
4311
4328
|
async function switchStory(page, storyId2) {
|
|
4312
4329
|
const ok = await page.evaluate(
|
|
4313
|
-
(id) => new Promise((
|
|
4330
|
+
(id) => new Promise((resolve6) => {
|
|
4314
4331
|
const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
|
|
4315
|
-
if (!ch) return
|
|
4332
|
+
if (!ch) return resolve6(false);
|
|
4316
4333
|
const events = ["storyRendered", "storyErrored", "storyThrewException", "storyMissing", "playFunctionThrewException"];
|
|
4317
4334
|
const timer = setTimeout(() => done(), 15e3);
|
|
4318
4335
|
function done() {
|
|
4319
4336
|
clearTimeout(timer);
|
|
4320
4337
|
for (const e of events) ch.off(e, done);
|
|
4321
|
-
|
|
4338
|
+
resolve6(true);
|
|
4322
4339
|
}
|
|
4323
4340
|
for (const e of events) ch.on(e, done);
|
|
4324
4341
|
window.scrollTo(0, 0);
|
|
@@ -4731,12 +4748,12 @@ function serveDir(dir) {
|
|
|
4731
4748
|
res.writeHead(200, { "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": "no-store" });
|
|
4732
4749
|
createReadStream(file).pipe(res);
|
|
4733
4750
|
});
|
|
4734
|
-
return new Promise((
|
|
4751
|
+
return new Promise((resolve6, reject) => {
|
|
4735
4752
|
server.once("error", reject);
|
|
4736
4753
|
server.listen(0, "127.0.0.1", () => {
|
|
4737
4754
|
const addr = server.address();
|
|
4738
4755
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
4739
|
-
|
|
4756
|
+
resolve6({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
|
|
4740
4757
|
});
|
|
4741
4758
|
});
|
|
4742
4759
|
}
|
|
@@ -4808,7 +4825,7 @@ async function removeWorktree(repo, dir) {
|
|
|
4808
4825
|
await git(repo, ["worktree", "prune"]).catch(() => void 0);
|
|
4809
4826
|
}
|
|
4810
4827
|
function run(cmd, args, cwd, log, root = cwd, env = {}) {
|
|
4811
|
-
return new Promise((
|
|
4828
|
+
return new Promise((resolve6, reject) => {
|
|
4812
4829
|
const bin = [.../* @__PURE__ */ new Set([join3(cwd, "node_modules", ".bin"), join3(root, "node_modules", ".bin")])].join(":");
|
|
4813
4830
|
const child = spawn(cmd, args, {
|
|
4814
4831
|
cwd,
|
|
@@ -4821,7 +4838,7 @@ function run(cmd, args, cwd, log, root = cwd, env = {}) {
|
|
|
4821
4838
|
child.stderr.on("data", keep);
|
|
4822
4839
|
child.on("error", reject);
|
|
4823
4840
|
child.on("close", (code) => {
|
|
4824
|
-
if (code === 0)
|
|
4841
|
+
if (code === 0) resolve6();
|
|
4825
4842
|
else {
|
|
4826
4843
|
log(tail);
|
|
4827
4844
|
reject(new Error(`${cmd} ${args.join(" ")} failed in ${cwd} (exit ${code})`));
|
|
@@ -5232,12 +5249,12 @@ async function captureFigmaReferences(o) {
|
|
|
5232
5249
|
}
|
|
5233
5250
|
const plan = JSON.stringify(j.plan.render);
|
|
5234
5251
|
await page.evaluate(
|
|
5235
|
-
({ id, plan: plan2 }) => new Promise((
|
|
5252
|
+
({ id, plan: plan2 }) => new Promise((resolve6) => {
|
|
5236
5253
|
const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
|
|
5237
5254
|
const done = () => {
|
|
5238
5255
|
clearTimeout(timer);
|
|
5239
5256
|
ch.off("storyRendered", done);
|
|
5240
|
-
|
|
5257
|
+
resolve6();
|
|
5241
5258
|
};
|
|
5242
5259
|
const timer = setTimeout(done, 1e4);
|
|
5243
5260
|
ch.on("storyRendered", done);
|
|
@@ -5526,7 +5543,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
|
|
|
5526
5543
|
if (!REQUEST_KINDS.some((k) => k.value === kind)) throw new RequestUsageError(`--kind takes ${REQUEST_KINDS.map((k) => k.value).join(", ")}, got "${kind}"`);
|
|
5527
5544
|
const dryRun = has(a, "dry-run");
|
|
5528
5545
|
const ctx = await context(root, io, !dryRun);
|
|
5529
|
-
const
|
|
5546
|
+
const figma2 = flag(a, "figma");
|
|
5530
5547
|
const exploration = flag(a, "exploration");
|
|
5531
5548
|
const r = await requests2.createRequest(ctx, {
|
|
5532
5549
|
component,
|
|
@@ -5534,7 +5551,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
|
|
|
5534
5551
|
title,
|
|
5535
5552
|
summary,
|
|
5536
5553
|
changes: (a.flags.change ?? []).map(parseChange),
|
|
5537
|
-
...
|
|
5554
|
+
...figma2 ? { figmaUrl: figma2 } : {},
|
|
5538
5555
|
...exploration ? { exploration } : {},
|
|
5539
5556
|
...flag(a, "product") ? { product: flag(a, "product") } : {},
|
|
5540
5557
|
source: "cli",
|
|
@@ -6000,6 +6017,92 @@ ${r.fixes.sample.map((s) => ` ${s}`).join("\n")}`);
|
|
|
6000
6017
|
}
|
|
6001
6018
|
}
|
|
6002
6019
|
|
|
6020
|
+
// src/figma.ts
|
|
6021
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
6022
|
+
import { resolve as resolve4 } from "node:path";
|
|
6023
|
+
import { BridgeContext as BridgeContext6, branches as branches3, figma } from "@bridge-ds/mcp";
|
|
6024
|
+
var FIGMA_HELP = `bridge figma: component changes sent from Figma
|
|
6025
|
+
|
|
6026
|
+
Usage:
|
|
6027
|
+
bridge figma [--all] [--json] the library branches the DS Bridge plugin opened for components
|
|
6028
|
+
changed in Figma, and where each stands
|
|
6029
|
+
bridge figma show <n> [--draft] [--json] one of them: its spec and the brief to draft the code; --draft
|
|
6030
|
+
runs Claude Code on the brief (\`claude\` on PATH)
|
|
6031
|
+
bridge figma drafted <n> [--json] the code is pushed: label it drafted by Claude AI for review
|
|
6032
|
+
bridge figma push <diff.json> [--dry-run] [--json]
|
|
6033
|
+
send a structured component diff like the plugin does: an exact
|
|
6034
|
+
token change, or a branch with the spec for Claude AI
|
|
6035
|
+
|
|
6036
|
+
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
6037
|
+
var has5 = (a, k) => Boolean(a.flags[k]);
|
|
6038
|
+
async function context5(root, io) {
|
|
6039
|
+
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
6040
|
+
return BridgeContext6.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
6041
|
+
}
|
|
6042
|
+
function number3(a, usage) {
|
|
6043
|
+
const raw = a._[2];
|
|
6044
|
+
if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
|
|
6045
|
+
return branches3.branchNumber(raw);
|
|
6046
|
+
}
|
|
6047
|
+
async function runFigma(a, root, io = defaultIO()) {
|
|
6048
|
+
const sub = a._[1];
|
|
6049
|
+
if (sub === "help" || has5(a, "help")) {
|
|
6050
|
+
io.print(FIGMA_HELP);
|
|
6051
|
+
return 0;
|
|
6052
|
+
}
|
|
6053
|
+
const json = has5(a, "json");
|
|
6054
|
+
const ctx = await context5(root, io);
|
|
6055
|
+
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
6056
|
+
switch (sub) {
|
|
6057
|
+
case void 0:
|
|
6058
|
+
case "list": {
|
|
6059
|
+
const r = await figma.figmaChangesOp(ctx, { state: has5(a, "all") ? "all" : "open" });
|
|
6060
|
+
return out(r, () => {
|
|
6061
|
+
for (const c of r.changes) io.print(`#${c.pr} ${c.title} (${c.route}): ${c.status}. ${c.said}`);
|
|
6062
|
+
io.print(`Next: ${r.next}`);
|
|
6063
|
+
});
|
|
6064
|
+
}
|
|
6065
|
+
case "show": {
|
|
6066
|
+
const r = await figma.getFigmaChangeOp(ctx, { pr: number3(a, "bridge figma show <n>") });
|
|
6067
|
+
if (has5(a, "draft")) {
|
|
6068
|
+
if (!r.brief) throw new RequestUsageError(`#${r.pr} is an exact token change: there is no code to draft.`);
|
|
6069
|
+
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.");
|
|
6070
|
+
return io.runClaude(r.brief, root);
|
|
6071
|
+
}
|
|
6072
|
+
return out(r, () => {
|
|
6073
|
+
io.print(`#${r.pr} ${r.title} (${r.route}): ${r.status}. ${r.said}`);
|
|
6074
|
+
io.print(` ${r.url}`);
|
|
6075
|
+
if (r.brief) io.print(`
|
|
6076
|
+
${r.brief}`);
|
|
6077
|
+
});
|
|
6078
|
+
}
|
|
6079
|
+
case "drafted": {
|
|
6080
|
+
const r = await figma.markFigmaDraftedOp(ctx, { pr: number3(a, "bridge figma drafted <n>") });
|
|
6081
|
+
return out(r, () => io.print(`#${r.pr}: ${r.status}. ${r.said}`));
|
|
6082
|
+
}
|
|
6083
|
+
case "push": {
|
|
6084
|
+
const file = a._[2];
|
|
6085
|
+
if (!file) throw new RequestUsageError("Usage: bridge figma push <diff.json> [--dry-run]");
|
|
6086
|
+
let diff2;
|
|
6087
|
+
try {
|
|
6088
|
+
diff2 = JSON.parse(readFileSync2(resolve4(root, file), "utf8"));
|
|
6089
|
+
} catch (err) {
|
|
6090
|
+
throw new RequestUsageError(`${file} could not be read as JSON: ${err.message}`);
|
|
6091
|
+
}
|
|
6092
|
+
const r = await figma.pushFigmaComponentOp(ctx, { diff: diff2, dryRun: has5(a, "dry-run"), source: "cli" });
|
|
6093
|
+
return out(r, () => {
|
|
6094
|
+
io.print(`${r.component}: ${r.route}. ${r.said}`);
|
|
6095
|
+
for (const x of r.reasons ?? []) io.print(` ${x}`);
|
|
6096
|
+
for (const x of r.fixInFigma ?? []) io.print(` Fix in Figma: ${x.what} ${x.todo}`);
|
|
6097
|
+
if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
|
|
6098
|
+
io.print(`Next: ${r.next}`);
|
|
6099
|
+
});
|
|
6100
|
+
}
|
|
6101
|
+
default:
|
|
6102
|
+
throw new RequestUsageError(`Unknown subcommand "${sub}". ${FIGMA_HELP}`);
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
|
|
6003
6106
|
// src/protection.ts
|
|
6004
6107
|
var UNKNOWN = { kind: "unknown", missing: ["review", "checks", "history", "deletion"] };
|
|
6005
6108
|
async function protectionReport(opts) {
|
|
@@ -6032,7 +6135,7 @@ async function rolesReport(opts) {
|
|
|
6032
6135
|
}
|
|
6033
6136
|
|
|
6034
6137
|
// src/index.ts
|
|
6035
|
-
import { brand, BridgeContext as
|
|
6138
|
+
import { brand, BridgeContext as BridgeContext7, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
|
|
6036
6139
|
var MCP_PACKAGE = "@bridge-ds/mcp";
|
|
6037
6140
|
var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
|
|
6038
6141
|
|
|
@@ -6118,6 +6221,9 @@ Usage:
|
|
|
6118
6221
|
a product branch and the library (\`bridge pieces help\`): what it adds
|
|
6119
6222
|
that the library lacks, the library branch each piece opens (linked
|
|
6120
6223
|
both ways), its preview in the product branch, the published version
|
|
6224
|
+
bridge figma [show <n> | drafted <n> | push <diff.json>]
|
|
6225
|
+
component changes sent from Figma (\`bridge figma help\`): exact token
|
|
6226
|
+
changes and branches Claude AI drafts from the spec
|
|
6121
6227
|
bridge products | bridge settings | bridge coverage [Component]
|
|
6122
6228
|
products (and this folder's), Studio settings safe to read, which
|
|
6123
6229
|
components are in Figma
|
|
@@ -6146,16 +6252,16 @@ function parse(argv) {
|
|
|
6146
6252
|
return out;
|
|
6147
6253
|
}
|
|
6148
6254
|
var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
6149
|
-
var
|
|
6255
|
+
var has6 = (a, k) => Boolean(a.flags[k]);
|
|
6150
6256
|
var print = (s) => process.stdout.write(`${s}
|
|
6151
6257
|
`);
|
|
6152
6258
|
var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
|
|
6153
6259
|
async function init(a, root) {
|
|
6154
6260
|
const cfgPath = join6(root, CONFIG_FILE2);
|
|
6155
6261
|
let config;
|
|
6156
|
-
if (existsSync6(cfgPath) && !
|
|
6262
|
+
if (existsSync6(cfgPath) && !has6(a, "force")) {
|
|
6157
6263
|
print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
|
|
6158
|
-
config = (await (await
|
|
6264
|
+
config = (await (await BridgeContext7.create({ root })).config()).config;
|
|
6159
6265
|
} else {
|
|
6160
6266
|
const src = new LocalSource(root);
|
|
6161
6267
|
const files = await src.list("");
|
|
@@ -6183,14 +6289,14 @@ async function init(a, root) {
|
|
|
6183
6289
|
print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
|
|
6184
6290
|
}
|
|
6185
6291
|
async function check(a, root) {
|
|
6186
|
-
const ctx = await
|
|
6292
|
+
const ctx = await BridgeContext7.create({ root });
|
|
6187
6293
|
const v = await ops.validateTokens(ctx, { includeWarnings: true });
|
|
6188
6294
|
const c = await ops.checkContrast(ctx, {});
|
|
6189
6295
|
const warnings = "warnings" in v ? v.warnings ?? [] : [];
|
|
6190
|
-
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 ||
|
|
6296
|
+
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has6(a, "strict") && warnings.length > 0;
|
|
6191
6297
|
const protection = await protectionOf(a, root, ctx);
|
|
6192
6298
|
const roles = await rolesOf(a, root, ctx);
|
|
6193
|
-
if (
|
|
6299
|
+
if (has6(a, "json")) {
|
|
6194
6300
|
print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
|
|
6195
6301
|
return failed ? 1 : 0;
|
|
6196
6302
|
}
|
|
@@ -6209,7 +6315,7 @@ async function check(a, root) {
|
|
|
6209
6315
|
return failed ? 1 : 0;
|
|
6210
6316
|
}
|
|
6211
6317
|
async function protectionOf(a, root, ctx) {
|
|
6212
|
-
if (
|
|
6318
|
+
if (has6(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6213
6319
|
const { config } = await ctx.config();
|
|
6214
6320
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6215
6321
|
if (!repo) return void 0;
|
|
@@ -6217,7 +6323,7 @@ async function protectionOf(a, root, ctx) {
|
|
|
6217
6323
|
return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
|
|
6218
6324
|
}
|
|
6219
6325
|
async function rolesOf(a, root, ctx) {
|
|
6220
|
-
if (
|
|
6326
|
+
if (has6(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6221
6327
|
const { config } = await ctx.config();
|
|
6222
6328
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6223
6329
|
if (!repo) return void 0;
|
|
@@ -6227,9 +6333,9 @@ async function rolesOf(a, root, ctx) {
|
|
|
6227
6333
|
async function tokens(a, root) {
|
|
6228
6334
|
const q = a._.slice(1).join(" ");
|
|
6229
6335
|
if (!q) throw new UsageError("bridge tokens <query>");
|
|
6230
|
-
const ctx = await
|
|
6336
|
+
const ctx = await BridgeContext7.create({ root });
|
|
6231
6337
|
const r = await ops.searchTokens(ctx, { query: q, limit: Number(flag4(a, "limit") ?? 20) });
|
|
6232
|
-
if (
|
|
6338
|
+
if (has6(a, "json")) return print(JSON.stringify(r, null, 2));
|
|
6233
6339
|
if (!r.results.length) return print(`No token matches "${q}".`);
|
|
6234
6340
|
const w = Math.max(...r.results.map((x) => x.path.length));
|
|
6235
6341
|
for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
|
|
@@ -6237,7 +6343,7 @@ async function tokens(a, root) {
|
|
|
6237
6343
|
async function diff(a, root) {
|
|
6238
6344
|
const base = a._[1];
|
|
6239
6345
|
if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
|
|
6240
|
-
const ctx = await
|
|
6346
|
+
const ctx = await BridgeContext7.create({ root });
|
|
6241
6347
|
const { config } = await ctx.config();
|
|
6242
6348
|
const before = await core.loadTokenSet(new GitRefSource(root, base), config);
|
|
6243
6349
|
const after = await ctx.tokens();
|
|
@@ -6246,11 +6352,11 @@ async function diff(a, root) {
|
|
|
6246
6352
|
const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
|
|
6247
6353
|
const tier = core.classify(changes, [], config);
|
|
6248
6354
|
const imp = core.impact(before, after, changes, 30);
|
|
6249
|
-
if (
|
|
6355
|
+
if (has6(a, "json")) {
|
|
6250
6356
|
print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
|
|
6251
6357
|
return 0;
|
|
6252
6358
|
}
|
|
6253
|
-
if (
|
|
6359
|
+
if (has6(a, "md")) {
|
|
6254
6360
|
print(`## Token changes since \`${base}\`
|
|
6255
6361
|
|
|
6256
6362
|
${core.tokenTable(changes)}
|
|
@@ -6296,11 +6402,11 @@ async function propose(a, root) {
|
|
|
6296
6402
|
edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
|
|
6297
6403
|
}
|
|
6298
6404
|
const editsFile = flag4(a, "edits");
|
|
6299
|
-
if (editsFile) edits.push(...JSON.parse(await readFile7(
|
|
6405
|
+
if (editsFile) edits.push(...JSON.parse(await readFile7(resolve5(editsFile), "utf8")));
|
|
6300
6406
|
if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
|
|
6301
6407
|
const pr = flag4(a, "pr");
|
|
6302
6408
|
const token = pr ? process.env.BRIDGE_GITHUB_TOKEN || process.env.GITHUB_TOKEN || defaultIO().ghToken() : void 0;
|
|
6303
|
-
const ctx = await
|
|
6409
|
+
const ctx = await BridgeContext7.create({ root, ...token ? { token } : {} });
|
|
6304
6410
|
const summary = flag4(a, "summary");
|
|
6305
6411
|
const branch = flag4(a, "branch");
|
|
6306
6412
|
const jira = flag4(a, "jira");
|
|
@@ -6313,9 +6419,9 @@ async function propose(a, root) {
|
|
|
6313
6419
|
...pr ? { pr } : {},
|
|
6314
6420
|
...jira ? { jira } : {},
|
|
6315
6421
|
...product ? { product } : {},
|
|
6316
|
-
dryRun:
|
|
6422
|
+
dryRun: has6(a, "dry-run")
|
|
6317
6423
|
});
|
|
6318
|
-
if (
|
|
6424
|
+
if (has6(a, "json")) print(JSON.stringify(r, null, 2));
|
|
6319
6425
|
else {
|
|
6320
6426
|
print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
|
|
6321
6427
|
if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
|
|
@@ -6330,10 +6436,10 @@ ${r.prBody}`);
|
|
|
6330
6436
|
}
|
|
6331
6437
|
async function brandCmd(a, root) {
|
|
6332
6438
|
const sub = a._[1];
|
|
6333
|
-
const ctx = await
|
|
6439
|
+
const ctx = await BridgeContext7.create({ root });
|
|
6334
6440
|
if (sub === "list") {
|
|
6335
6441
|
const r2 = await brand.listBrands(ctx);
|
|
6336
|
-
if (
|
|
6442
|
+
if (has6(a, "json")) print(JSON.stringify(r2.brands, null, 2));
|
|
6337
6443
|
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
6444
|
return 0;
|
|
6339
6445
|
}
|
|
@@ -6353,9 +6459,9 @@ async function brandCmd(a, root) {
|
|
|
6353
6459
|
...opt("radius") ? { radius: opt("radius") } : {},
|
|
6354
6460
|
...Object.keys(fonts).length ? { fonts } : {},
|
|
6355
6461
|
...opt("notes") ? { notes: opt("notes") } : {},
|
|
6356
|
-
dryRun:
|
|
6462
|
+
dryRun: has6(a, "dry-run")
|
|
6357
6463
|
});
|
|
6358
|
-
if (
|
|
6464
|
+
if (has6(a, "json")) {
|
|
6359
6465
|
const { fileContents: _f, ...rest } = r;
|
|
6360
6466
|
print(JSON.stringify(rest, null, 2));
|
|
6361
6467
|
return r.status === "blocked" ? 1 : 0;
|
|
@@ -6386,14 +6492,14 @@ async function figmaReferences(a, root) {
|
|
|
6386
6492
|
}
|
|
6387
6493
|
const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
|
|
6388
6494
|
const r = await captureFigmaReferences({
|
|
6389
|
-
storybook:
|
|
6390
|
-
specs:
|
|
6391
|
-
out:
|
|
6495
|
+
storybook: resolve5(root, flag4(a, "storybook") ?? "storybook-static"),
|
|
6496
|
+
specs: resolve5(root, flag4(a, "specs") ?? "packages/react/component-specs.json"),
|
|
6497
|
+
out: resolve5(root, flag4(a, "out") ?? "figma-references"),
|
|
6392
6498
|
sha,
|
|
6393
6499
|
...only.length ? { only } : {},
|
|
6394
|
-
log:
|
|
6500
|
+
log: has6(a, "json") ? void 0 : (line) => print(line)
|
|
6395
6501
|
});
|
|
6396
|
-
if (
|
|
6502
|
+
if (has6(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
|
|
6397
6503
|
return 0;
|
|
6398
6504
|
}
|
|
6399
6505
|
async function impact(a, root) {
|
|
@@ -6411,22 +6517,22 @@ async function impact(a, root) {
|
|
|
6411
6517
|
root,
|
|
6412
6518
|
base: flag4(a, "base") ?? "main",
|
|
6413
6519
|
head: flag4(a, "head") ?? "HEAD",
|
|
6414
|
-
...out ? { out:
|
|
6520
|
+
...out ? { out: resolve5(out) } : {},
|
|
6415
6521
|
...only.length ? { only } : {},
|
|
6416
6522
|
...threshold !== void 0 ? { threshold } : {},
|
|
6417
|
-
...cacheDir ? { cacheDir:
|
|
6523
|
+
...cacheDir ? { cacheDir: resolve5(cacheDir) } : {},
|
|
6418
6524
|
...maxStories ? { maxStoriesPerComponent: maxStories } : {},
|
|
6419
6525
|
...imageBase !== void 0 ? { imageBase } : {},
|
|
6420
6526
|
...artifactUrl ? { artifactUrl } : {},
|
|
6421
|
-
plan:
|
|
6422
|
-
keepUnchanged:
|
|
6527
|
+
plan: has6(a, "plan"),
|
|
6528
|
+
keepUnchanged: has6(a, "keep-unchanged")
|
|
6423
6529
|
});
|
|
6424
|
-
if (
|
|
6530
|
+
if (has6(a, "json")) {
|
|
6425
6531
|
print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
|
|
6426
6532
|
return 0;
|
|
6427
6533
|
}
|
|
6428
6534
|
const sel = r.manifest.selection;
|
|
6429
|
-
if (
|
|
6535
|
+
if (has6(a, "plan")) {
|
|
6430
6536
|
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
6537
|
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
6538
|
print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
|
|
@@ -6442,12 +6548,12 @@ async function impact(a, root) {
|
|
|
6442
6548
|
}
|
|
6443
6549
|
var day = (iso) => iso.slice(0, 10);
|
|
6444
6550
|
async function historyCmd(a, root) {
|
|
6445
|
-
const ctx = await
|
|
6551
|
+
const ctx = await BridgeContext7.create({ root });
|
|
6446
6552
|
const token = a._[1];
|
|
6447
6553
|
const limit = Number(flag4(a, "limit") ?? 20);
|
|
6448
6554
|
if (token) {
|
|
6449
6555
|
const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
|
|
6450
|
-
if (
|
|
6556
|
+
if (has6(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
|
|
6451
6557
|
if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
|
|
6452
6558
|
print(`${r2.path}: ${r2.total} change(s) (${r2.source})
|
|
6453
6559
|
`);
|
|
@@ -6459,8 +6565,8 @@ async function historyCmd(a, root) {
|
|
|
6459
6565
|
return 0;
|
|
6460
6566
|
}
|
|
6461
6567
|
const tier = flag4(a, "tier");
|
|
6462
|
-
const r = await hist.getHistory(ctx, { limit, ...flag4(a, "author") ? { author: flag4(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag4(a, "version") ? { version: flag4(a, "version") } : {}, ...
|
|
6463
|
-
if (
|
|
6568
|
+
const r = await hist.getHistory(ctx, { limit, ...flag4(a, "author") ? { author: flag4(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag4(a, "version") ? { version: flag4(a, "version") } : {}, ...has6(a, "breaking") ? { breaking: true } : {} });
|
|
6569
|
+
if (has6(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
|
|
6464
6570
|
print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
|
|
6465
6571
|
if (!r.total) return print(r.note ?? "No token change matches."), 0;
|
|
6466
6572
|
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 +6584,10 @@ ${r.truncated}`);
|
|
|
6478
6584
|
async function revertCmd(a, root) {
|
|
6479
6585
|
const target = a._[1];
|
|
6480
6586
|
if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
|
|
6481
|
-
const ctx = await
|
|
6587
|
+
const ctx = await BridgeContext7.create({ root });
|
|
6482
6588
|
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 (
|
|
6589
|
+
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has6(a, "restore-conflicts"), dryRun: has6(a, "dry-run") });
|
|
6590
|
+
if (has6(a, "json")) {
|
|
6485
6591
|
print(JSON.stringify(r, null, 2));
|
|
6486
6592
|
return r.status === "blocked" ? 1 : 0;
|
|
6487
6593
|
}
|
|
@@ -6506,11 +6612,11 @@ async function audit(a, root) {
|
|
|
6506
6612
|
const target = a._[1];
|
|
6507
6613
|
if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
|
|
6508
6614
|
if (!existsSync6(target)) throw new UsageError(`No such file or folder: ${target}`);
|
|
6509
|
-
const dsRoot = flag4(a, "ds") ?
|
|
6615
|
+
const dsRoot = flag4(a, "ds") ? resolve5(flag4(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
|
|
6510
6616
|
if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
|
|
6511
6617
|
const ds = loadDesignSystem(dsRoot);
|
|
6512
|
-
if (
|
|
6513
|
-
const write =
|
|
6618
|
+
if (has6(a, "fix")) {
|
|
6619
|
+
const write = has6(a, "write");
|
|
6514
6620
|
const changes = fixCss(target, ds.index, write);
|
|
6515
6621
|
const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
|
|
6516
6622
|
for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
|
|
@@ -6586,7 +6692,7 @@ async function audit(a, root) {
|
|
|
6586
6692
|
code = 1;
|
|
6587
6693
|
}
|
|
6588
6694
|
}
|
|
6589
|
-
if (
|
|
6695
|
+
if (has6(a, "no-regress") && report.baseline) {
|
|
6590
6696
|
const b = report.baseline;
|
|
6591
6697
|
if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
|
|
6592
6698
|
process.stderr.write("bridge audit: adoption regressed since the baseline\n");
|
|
@@ -6598,9 +6704,9 @@ async function audit(a, root) {
|
|
|
6598
6704
|
async function docsCmd(a, root) {
|
|
6599
6705
|
const [, sub, name] = a._;
|
|
6600
6706
|
if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
|
|
6601
|
-
const ctx = await
|
|
6707
|
+
const ctx = await BridgeContext7.create({ root });
|
|
6602
6708
|
const d = await ops.draftDocsOp(ctx, { name });
|
|
6603
|
-
if (
|
|
6709
|
+
if (has6(a, "json")) {
|
|
6604
6710
|
print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
|
|
6605
6711
|
return 0;
|
|
6606
6712
|
}
|
|
@@ -6631,16 +6737,17 @@ async function main() {
|
|
|
6631
6737
|
const argv = process.argv.slice(2);
|
|
6632
6738
|
const a = parse(argv);
|
|
6633
6739
|
const cmd = a._[0];
|
|
6634
|
-
const root =
|
|
6740
|
+
const root = resolve5(flag4(a, "root") ?? process.cwd());
|
|
6635
6741
|
if (cmd === "request") return runRequest(a, root);
|
|
6636
6742
|
if (cmd === "explore") return runExplore(a, root);
|
|
6637
6743
|
if (cmd === "branch") return runBranch(a, root);
|
|
6638
6744
|
if (cmd === "pieces") return runPieces(a, root);
|
|
6745
|
+
if (cmd === "figma") return runFigma(a, root);
|
|
6639
6746
|
if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
|
|
6640
6747
|
if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
|
|
6641
|
-
if (!cmd ||
|
|
6748
|
+
if (!cmd || has6(a, "help") || cmd === "help") {
|
|
6642
6749
|
print(HELP);
|
|
6643
|
-
return cmd ||
|
|
6750
|
+
return cmd || has6(a, "help") ? 0 : 2;
|
|
6644
6751
|
}
|
|
6645
6752
|
if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
|
|
6646
6753
|
switch (cmd) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "bridge CLI: set up Bridge in a design system repo, check tokens in CI, search and diff tokens, propose changes, start the MCP server.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"build:package": "node ../../scripts/build-packages.mjs cli"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@bridge-ds/mcp": "^0.
|
|
47
|
-
"@bridge-ds/visual": "^0.3.
|
|
46
|
+
"@bridge-ds/mcp": "^0.6.0",
|
|
47
|
+
"@bridge-ds/visual": "^0.3.4",
|
|
48
48
|
"typescript": "5.9.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|