@bridge-ds/cli 0.7.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 +406 -157
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3336,25 +3336,25 @@ var GitHubClient = class _GitHubClient {
|
|
|
3336
3336
|
return pr ? { number: pr.number, url: pr.html_url, title: pr.title, draft: !!pr.draft, headRef: pr.head.ref } : void 0;
|
|
3337
3337
|
}
|
|
3338
3338
|
/** Best effort: labels need the repo to allow it; failure is not fatal. */
|
|
3339
|
-
async addLabels(
|
|
3339
|
+
async addLabels(issueNumber3, labels) {
|
|
3340
3340
|
try {
|
|
3341
|
-
await this.request("POST", `${this.repoPath}/issues/${
|
|
3341
|
+
await this.request("POST", `${this.repoPath}/issues/${issueNumber3}/labels`, { labels });
|
|
3342
3342
|
return true;
|
|
3343
3343
|
} catch {
|
|
3344
3344
|
return false;
|
|
3345
3345
|
}
|
|
3346
3346
|
}
|
|
3347
|
-
async addComment(
|
|
3348
|
-
await this.request("POST", `${this.repoPath}/issues/${
|
|
3347
|
+
async addComment(issueNumber3, body) {
|
|
3348
|
+
await this.request("POST", `${this.repoPath}/issues/${issueNumber3}/comments`, { body });
|
|
3349
3349
|
}
|
|
3350
3350
|
/**
|
|
3351
3351
|
* Comments on an issue or pull request, oldest first (first 100: the sticky preview comment is posted early).
|
|
3352
3352
|
* `authorAssociation` is GitHub's OWNER, MEMBER, COLLABORATOR, CONTRIBUTOR, NONE... (suggestion approvals read it).
|
|
3353
3353
|
*/
|
|
3354
|
-
async listIssueComments(
|
|
3354
|
+
async listIssueComments(issueNumber3) {
|
|
3355
3355
|
const r = await this.request(
|
|
3356
3356
|
"GET",
|
|
3357
|
-
`${this.repoPath}/issues/${
|
|
3357
|
+
`${this.repoPath}/issues/${issueNumber3}/comments?per_page=100`
|
|
3358
3358
|
);
|
|
3359
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" }));
|
|
3360
3360
|
}
|
|
@@ -3671,8 +3671,8 @@ var GitHubClient = class _GitHubClient {
|
|
|
3671
3671
|
});
|
|
3672
3672
|
return { id: r.id, name: r.name, status: r.status, conclusion: r.conclusion, url: r.html_url };
|
|
3673
3673
|
}
|
|
3674
|
-
async removeLabel(
|
|
3675
|
-
await this.request("DELETE", `${this.repoPath}/issues/${
|
|
3674
|
+
async removeLabel(issueNumber3, label) {
|
|
3675
|
+
await this.request("DELETE", `${this.repoPath}/issues/${issueNumber3}/labels/${encodeURIComponent(label)}`);
|
|
3676
3676
|
}
|
|
3677
3677
|
// --- branch actions shared by Studio, the MCP server and the CLI (branch-actions.ts) ---
|
|
3678
3678
|
/** The account the token belongs to (GET /user). Needs a token. */
|
|
@@ -3901,6 +3901,27 @@ function jiraIssueFields(r, jira, issueUrl) {
|
|
|
3901
3901
|
};
|
|
3902
3902
|
}
|
|
3903
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
|
+
|
|
3904
3925
|
// src/index.ts
|
|
3905
3926
|
import { fileURLToPath } from "node:url";
|
|
3906
3927
|
|
|
@@ -5710,7 +5731,195 @@ ${EXPLORE_HELP}`);
|
|
|
5710
5731
|
// src/branch.ts
|
|
5711
5732
|
import { readFile as readFile6 } from "node:fs/promises";
|
|
5712
5733
|
import { resolve as resolve3 } from "node:path";
|
|
5713
|
-
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
|
|
5714
5923
|
var BRANCH_HELP = `bridge branch: branches in review, the same actions as Bridge Studio
|
|
5715
5924
|
|
|
5716
5925
|
Usage:
|
|
@@ -5740,11 +5949,11 @@ Usage:
|
|
|
5740
5949
|
|
|
5741
5950
|
Start work for a product: bridge propose|explore start|request create ... --product <key> [--jira DS-42].
|
|
5742
5951
|
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
5743
|
-
var
|
|
5744
|
-
var
|
|
5745
|
-
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) {
|
|
5746
5955
|
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
5747
|
-
return
|
|
5956
|
+
return BridgeContext5.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
5748
5957
|
}
|
|
5749
5958
|
function number(a, at, usage) {
|
|
5750
5959
|
const raw = a._[at];
|
|
@@ -5763,16 +5972,16 @@ function pairs(list2, what) {
|
|
|
5763
5972
|
var said = (r) => String(r.said ?? r.summary ?? r.status ?? "Done.");
|
|
5764
5973
|
async function runBranch(a, root, io = defaultIO()) {
|
|
5765
5974
|
const sub = a._[1];
|
|
5766
|
-
if (!sub || sub === "help" ||
|
|
5975
|
+
if (!sub || sub === "help" || has4(a, "help")) {
|
|
5767
5976
|
io.print(BRANCH_HELP);
|
|
5768
|
-
return sub ||
|
|
5977
|
+
return sub || has4(a, "help") ? 0 : 2;
|
|
5769
5978
|
}
|
|
5770
|
-
const json =
|
|
5771
|
-
const ctx = await
|
|
5979
|
+
const json = has4(a, "json");
|
|
5980
|
+
const ctx = await context4(root, io);
|
|
5772
5981
|
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
5773
5982
|
switch (sub) {
|
|
5774
5983
|
case "list": {
|
|
5775
|
-
const limit =
|
|
5984
|
+
const limit = flag4(a, "limit");
|
|
5776
5985
|
const r = await branches.whatNeedsMe(ctx, limit ? { limit: Number(limit) } : {});
|
|
5777
5986
|
return out(r, () => {
|
|
5778
5987
|
io.print(r.summary);
|
|
@@ -5806,28 +6015,28 @@ async function runBranch(a, root, io = defaultIO()) {
|
|
|
5806
6015
|
}
|
|
5807
6016
|
case "review": {
|
|
5808
6017
|
const n = number(a, 2, 'bridge branch review <n> --approve | --request-changes "..." | --comment "..."');
|
|
5809
|
-
const decision =
|
|
6018
|
+
const decision = has4(a, "approve") ? "approve" : has4(a, "request-changes") ? "request_changes" : has4(a, "comment") ? "comment" : null;
|
|
5810
6019
|
if (!decision) throw new RequestUsageError('Say what you decide: --approve, --request-changes "what to change" or --comment "text".');
|
|
5811
|
-
const body = decision === "approve" ?
|
|
6020
|
+
const body = decision === "approve" ? flag4(a, "body") : flag4(a, decision === "request_changes" ? "request-changes" : "comment");
|
|
5812
6021
|
const r = await branches.reviewBranch(ctx, { pr: n, decision, ...body && body !== "true" ? { body } : {} });
|
|
5813
6022
|
return out(r, () => io.print(r.said));
|
|
5814
6023
|
}
|
|
5815
6024
|
case "ask": {
|
|
5816
|
-
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") });
|
|
5817
6026
|
return out(r, () => io.print(r.said));
|
|
5818
6027
|
}
|
|
5819
6028
|
case "comment": {
|
|
5820
6029
|
const n = number(a, 2, 'bridge branch comment <n> "text"');
|
|
5821
|
-
const body = a._.slice(3).join(" ") ||
|
|
6030
|
+
const body = a._.slice(3).join(" ") || flag4(a, "body");
|
|
5822
6031
|
if (!body) throw new RequestUsageError('Usage: bridge branch comment <n> "text" [--reply-to <id>]');
|
|
5823
|
-
const reply =
|
|
6032
|
+
const reply = flag4(a, "reply-to");
|
|
5824
6033
|
const r = await branches.commentOnBranch(ctx, { pr: n, body, ...reply ? { replyTo: Number(reply) } : {} });
|
|
5825
6034
|
return out(r, () => io.print(r.said));
|
|
5826
6035
|
}
|
|
5827
6036
|
case "merge": {
|
|
5828
6037
|
const n = number(a, 2, 'bridge branch merge <n> [--override "reason"] [--dry-run]');
|
|
5829
|
-
const reason =
|
|
5830
|
-
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") });
|
|
5831
6040
|
return out(r, () => io.print(r.said));
|
|
5832
6041
|
}
|
|
5833
6042
|
case "close": {
|
|
@@ -5842,7 +6051,7 @@ async function runBranch(a, root, io = defaultIO()) {
|
|
|
5842
6051
|
const n = number(a, 2, "bridge branch update <n> [--choice key=mine|theirs] [--dry-run]");
|
|
5843
6052
|
const choices = pairs(a.flags.choice, "choice");
|
|
5844
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}".`);
|
|
5845
|
-
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") });
|
|
5846
6055
|
return out(r, () => {
|
|
5847
6056
|
io.print(said(r));
|
|
5848
6057
|
for (const c of r.conflicts ?? []) io.print(` ${c.key} (${c.token}, default ${c.default ?? "none: pick one"})`);
|
|
@@ -5874,8 +6083,8 @@ ${BRANCH_HELP}`);
|
|
|
5874
6083
|
}
|
|
5875
6084
|
}
|
|
5876
6085
|
async function runStudioRead(cmd, a, root, io = defaultIO()) {
|
|
5877
|
-
const ctx = await
|
|
5878
|
-
const json =
|
|
6086
|
+
const ctx = await context4(root, io);
|
|
6087
|
+
const json = has4(a, "json");
|
|
5879
6088
|
if (cmd === "products") {
|
|
5880
6089
|
const r2 = await branches.listProducts(ctx);
|
|
5881
6090
|
if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
|
|
@@ -5904,7 +6113,7 @@ This folder: ${r2.current.name}. Pick another with --product <key>.`);
|
|
|
5904
6113
|
}
|
|
5905
6114
|
async function runDocsSave(a, root, io = defaultIO()) {
|
|
5906
6115
|
const component = a._[2];
|
|
5907
|
-
const file =
|
|
6116
|
+
const file = flag4(a, "file");
|
|
5908
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]');
|
|
5909
6118
|
let docs;
|
|
5910
6119
|
try {
|
|
@@ -5912,28 +6121,32 @@ async function runDocsSave(a, root, io = defaultIO()) {
|
|
|
5912
6121
|
} catch (e) {
|
|
5913
6122
|
throw new RequestUsageError(`${file} is not a readable JSON file: ${e.message}`);
|
|
5914
6123
|
}
|
|
5915
|
-
const ctx = await
|
|
5916
|
-
const pr =
|
|
6124
|
+
const ctx = await context4(root, io);
|
|
6125
|
+
const pr = flag4(a, "pr");
|
|
6126
|
+
const changes = changesFlag(a);
|
|
5917
6127
|
const r = await studio.saveDocs(ctx, {
|
|
5918
6128
|
component,
|
|
5919
6129
|
docs,
|
|
5920
6130
|
...pr ? { pr } : {},
|
|
5921
|
-
...
|
|
5922
|
-
...
|
|
5923
|
-
...
|
|
5924
|
-
...
|
|
5925
|
-
|
|
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")
|
|
5926
6138
|
});
|
|
5927
|
-
if (
|
|
6139
|
+
if (has4(a, "json")) io.print(JSON.stringify(r, null, 2));
|
|
5928
6140
|
else io.print(r.status === "dry-run" || r.status === "preview" ? `${r.said ?? "Dry run, nothing written."}
|
|
5929
6141
|
|
|
5930
6142
|
${r.path}:
|
|
5931
|
-
${r.content}` : String(r.said)
|
|
6143
|
+
${r.content}` : `${String(r.said)}${r.next ? `
|
|
6144
|
+
${String(r.next)}` : ""}`);
|
|
5932
6145
|
return 0;
|
|
5933
6146
|
}
|
|
5934
6147
|
|
|
5935
6148
|
// src/pieces.ts
|
|
5936
|
-
import { BridgeContext as
|
|
6149
|
+
import { BridgeContext as BridgeContext6, branches as branches2, pieces } from "@bridge-ds/mcp";
|
|
5937
6150
|
var PIECES_HELP = `bridge pieces: a product branch and the library
|
|
5938
6151
|
|
|
5939
6152
|
Usage:
|
|
@@ -5948,10 +6161,10 @@ Usage:
|
|
|
5948
6161
|
bridge pieces switch <n> once the library shipped: main into the product branch
|
|
5949
6162
|
|
|
5950
6163
|
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
5951
|
-
var
|
|
5952
|
-
async function
|
|
6164
|
+
var has5 = (a, k) => Boolean(a.flags[k]);
|
|
6165
|
+
async function context5(root, io) {
|
|
5953
6166
|
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
5954
|
-
return
|
|
6167
|
+
return BridgeContext6.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
5955
6168
|
}
|
|
5956
6169
|
function number2(a, at, usage) {
|
|
5957
6170
|
const raw = a._[at];
|
|
@@ -5965,12 +6178,12 @@ function piece(a, at, usage) {
|
|
|
5965
6178
|
}
|
|
5966
6179
|
async function runPieces(a, root, io = defaultIO()) {
|
|
5967
6180
|
const sub = a._[1];
|
|
5968
|
-
if (!sub || sub === "help" ||
|
|
6181
|
+
if (!sub || sub === "help" || has5(a, "help")) {
|
|
5969
6182
|
io.print(PIECES_HELP);
|
|
5970
|
-
return sub ||
|
|
6183
|
+
return sub || has5(a, "help") ? 0 : 2;
|
|
5971
6184
|
}
|
|
5972
|
-
const json =
|
|
5973
|
-
const ctx = await
|
|
6185
|
+
const json = has5(a, "json");
|
|
6186
|
+
const ctx = await context5(root, io);
|
|
5974
6187
|
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
5975
6188
|
switch (sub) {
|
|
5976
6189
|
case "status": {
|
|
@@ -5985,9 +6198,10 @@ async function runPieces(a, root, io = defaultIO()) {
|
|
|
5985
6198
|
}
|
|
5986
6199
|
case "open": {
|
|
5987
6200
|
const usage = "bridge pieces open <n> <piece>";
|
|
5988
|
-
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 } : {} });
|
|
5989
6203
|
return out(r, () => {
|
|
5990
|
-
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}.`);
|
|
5991
6205
|
if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
|
|
5992
6206
|
if (r.preview) io.print(` ${r.preview.said}`);
|
|
5993
6207
|
for (const w of r.warnings ?? []) io.print(` ${w}`);
|
|
@@ -6004,7 +6218,7 @@ async function runPieces(a, root, io = defaultIO()) {
|
|
|
6004
6218
|
return out(r, () => io.print(r.said));
|
|
6005
6219
|
}
|
|
6006
6220
|
default: {
|
|
6007
|
-
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") });
|
|
6008
6222
|
return out(r, () => {
|
|
6009
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.`);
|
|
6010
6224
|
for (const p of r.pieces) io.print(` ${p.id} ${p.title}: ${p.summary} Opens ${p.opens}.${"linked" in p ? ` Linked: #${p.linked}.` : ""}`);
|
|
@@ -6020,7 +6234,7 @@ ${r.fixes.sample.map((s) => ` ${s}`).join("\n")}`);
|
|
|
6020
6234
|
// src/figma.ts
|
|
6021
6235
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
6022
6236
|
import { resolve as resolve4 } from "node:path";
|
|
6023
|
-
import { BridgeContext as
|
|
6237
|
+
import { BridgeContext as BridgeContext7, branches as branches3, figma } from "@bridge-ds/mcp";
|
|
6024
6238
|
var FIGMA_HELP = `bridge figma: component changes sent from Figma
|
|
6025
6239
|
|
|
6026
6240
|
Usage:
|
|
@@ -6034,10 +6248,10 @@ Usage:
|
|
|
6034
6248
|
token change, or a branch with the spec for Claude AI
|
|
6035
6249
|
|
|
6036
6250
|
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
6037
|
-
var
|
|
6038
|
-
async function
|
|
6251
|
+
var has6 = (a, k) => Boolean(a.flags[k]);
|
|
6252
|
+
async function context6(root, io) {
|
|
6039
6253
|
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
6040
|
-
return
|
|
6254
|
+
return BridgeContext7.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
6041
6255
|
}
|
|
6042
6256
|
function number3(a, usage) {
|
|
6043
6257
|
const raw = a._[2];
|
|
@@ -6046,17 +6260,17 @@ function number3(a, usage) {
|
|
|
6046
6260
|
}
|
|
6047
6261
|
async function runFigma(a, root, io = defaultIO()) {
|
|
6048
6262
|
const sub = a._[1];
|
|
6049
|
-
if (sub === "help" ||
|
|
6263
|
+
if (sub === "help" || has6(a, "help")) {
|
|
6050
6264
|
io.print(FIGMA_HELP);
|
|
6051
6265
|
return 0;
|
|
6052
6266
|
}
|
|
6053
|
-
const json =
|
|
6054
|
-
const ctx = await
|
|
6267
|
+
const json = has6(a, "json");
|
|
6268
|
+
const ctx = await context6(root, io);
|
|
6055
6269
|
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
6056
6270
|
switch (sub) {
|
|
6057
6271
|
case void 0:
|
|
6058
6272
|
case "list": {
|
|
6059
|
-
const r = await figma.figmaChangesOp(ctx, { state:
|
|
6273
|
+
const r = await figma.figmaChangesOp(ctx, { state: has6(a, "all") ? "all" : "open" });
|
|
6060
6274
|
return out(r, () => {
|
|
6061
6275
|
for (const c of r.changes) io.print(`#${c.pr} ${c.title} (${c.route}): ${c.status}. ${c.said}`);
|
|
6062
6276
|
io.print(`Next: ${r.next}`);
|
|
@@ -6064,7 +6278,7 @@ async function runFigma(a, root, io = defaultIO()) {
|
|
|
6064
6278
|
}
|
|
6065
6279
|
case "show": {
|
|
6066
6280
|
const r = await figma.getFigmaChangeOp(ctx, { pr: number3(a, "bridge figma show <n>") });
|
|
6067
|
-
if (
|
|
6281
|
+
if (has6(a, "draft")) {
|
|
6068
6282
|
if (!r.brief) throw new RequestUsageError(`#${r.pr} is an exact token change: there is no code to draft.`);
|
|
6069
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.");
|
|
6070
6284
|
return io.runClaude(r.brief, root);
|
|
@@ -6089,12 +6303,14 @@ ${r.brief}`);
|
|
|
6089
6303
|
} catch (err) {
|
|
6090
6304
|
throw new RequestUsageError(`${file} could not be read as JSON: ${err.message}`);
|
|
6091
6305
|
}
|
|
6092
|
-
const
|
|
6306
|
+
const changes = changesFlag(a);
|
|
6307
|
+
const r = await figma.pushFigmaComponentOp(ctx, { diff: diff2, dryRun: has6(a, "dry-run"), source: "cli", ...changes ? { changes } : {} });
|
|
6093
6308
|
return out(r, () => {
|
|
6094
6309
|
io.print(`${r.component}: ${r.route}. ${r.said}`);
|
|
6095
6310
|
for (const x of r.reasons ?? []) io.print(` ${x}`);
|
|
6096
6311
|
for (const x of r.fixInFigma ?? []) io.print(` Fix in Figma: ${x.what} ${x.todo}`);
|
|
6097
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}`);
|
|
6098
6314
|
io.print(`Next: ${r.next}`);
|
|
6099
6315
|
});
|
|
6100
6316
|
}
|
|
@@ -6135,7 +6351,7 @@ async function rolesReport(opts) {
|
|
|
6135
6351
|
}
|
|
6136
6352
|
|
|
6137
6353
|
// src/index.ts
|
|
6138
|
-
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";
|
|
6139
6355
|
var MCP_PACKAGE = "@bridge-ds/mcp";
|
|
6140
6356
|
var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
|
|
6141
6357
|
|
|
@@ -6151,8 +6367,10 @@ Usage:
|
|
|
6151
6367
|
bridge diff <base> [--json|--md] designer-readable token diff: working tree vs <base> (git ref)
|
|
6152
6368
|
bridge propose --title "..." --set path=value [--set ...] [--file f | --mode theme=dark]
|
|
6153
6369
|
[--edits edits.json] [--summary "..."] [--branch explore/<name>] [--pr <n>]
|
|
6154
|
-
[--product <key>] [--jira DS-42] [--dry-run]
|
|
6155
|
-
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).
|
|
6156
6374
|
--branch: write the edits to the checked-out exploration instead.
|
|
6157
6375
|
--pr: add them to that branch in review (GitHub token).
|
|
6158
6376
|
--product: the product it is for (bridge products); --jira: the
|
|
@@ -6160,9 +6378,10 @@ Usage:
|
|
|
6160
6378
|
bridge brand list [--json] brands: slug, name, default, primary action color, theme overrides
|
|
6161
6379
|
bridge brand create <slug> --from bridge --primary <hex|hue> --neutral <hex|hue>
|
|
6162
6380
|
[--name "Acme"] [--accent x] [--link x] [--radius soft|balanced|crisp|i,c,co,o]
|
|
6163
|
-
[--heading font] [--text font] [--notes "..."] [--dry-run] [--json]
|
|
6164
|
-
new brand: generated ramps, contrast gate for every theme,
|
|
6165
|
-
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).
|
|
6166
6385
|
Exit 1 when blocked
|
|
6167
6386
|
bridge impact [--base main] [--head HEAD] [--out dir] [--only stories|app] [--threshold 0.03]
|
|
6168
6387
|
[--plan] [--json] [--image-base url] [--artifact-url url] [--keep-unchanged]
|
|
@@ -6176,9 +6395,9 @@ Usage:
|
|
|
6176
6395
|
Needs Chrome installed
|
|
6177
6396
|
bridge history [token] [--json] [--limit n] [--author a] [--tier n] [--version v]
|
|
6178
6397
|
token history: versions, merged changes, who, when, why
|
|
6179
|
-
bridge revert <pr|commit|id> [--dry-run] [--restore-conflicts] [--use-before path@file] [--json]
|
|
6180
|
-
undo a merged token change against HEAD: conflicts shown,
|
|
6181
|
-
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)
|
|
6182
6401
|
bridge audit <path> [--json [out.json]] [--md [out.md]] [--fail-under 80] [--baseline prev.json]
|
|
6183
6402
|
[--label "..."] [--ds dir] [--no-regress] [--allowlist known.json]
|
|
6184
6403
|
design system adoption in a product folder: token and component
|
|
@@ -6221,6 +6440,17 @@ Usage:
|
|
|
6221
6440
|
a product branch and the library (\`bridge pieces help\`): what it adds
|
|
6222
6441
|
that the library lacks, the library branch each piece opens (linked
|
|
6223
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)
|
|
6224
6454
|
bridge figma [show <n> | drafted <n> | push <diff.json>]
|
|
6225
6455
|
component changes sent from Figma (\`bridge figma help\`): exact token
|
|
6226
6456
|
changes and branches Claude AI drafts from the spec
|
|
@@ -6234,8 +6464,11 @@ Usage:
|
|
|
6234
6464
|
Options:
|
|
6235
6465
|
--root <dir> repo root (default: current directory)
|
|
6236
6466
|
--ds <dir> audit: design system repo with bridge.config.json (default: --root, or the nearest one above)
|
|
6237
|
-
GITHUB_TOKEN env, optional: lets
|
|
6238
|
-
|
|
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.
|
|
6239
6472
|
`;
|
|
6240
6473
|
function parse(argv) {
|
|
6241
6474
|
const out = { _: [], flags: {} };
|
|
@@ -6245,23 +6478,32 @@ function parse(argv) {
|
|
|
6245
6478
|
const [k, inline] = a.slice(2).split(/=(.*)/s, 2);
|
|
6246
6479
|
const next = argv[i + 1];
|
|
6247
6480
|
const fileArg = k === "json" && /\.json$/i.test(next ?? "") || k === "md" && /\.(md|markdown)$/i.test(next ?? "");
|
|
6248
|
-
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));
|
|
6249
6482
|
(out.flags[k] ??= []).push(inline ?? (takesValue ? argv[++i] : "true"));
|
|
6250
6483
|
} else out._.push(a);
|
|
6251
6484
|
}
|
|
6252
6485
|
return out;
|
|
6253
6486
|
}
|
|
6254
|
-
var
|
|
6255
|
-
var
|
|
6487
|
+
var flag5 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
6488
|
+
var has7 = (a, k) => Boolean(a.flags[k]);
|
|
6256
6489
|
var print = (s) => process.stdout.write(`${s}
|
|
6257
6490
|
`);
|
|
6258
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
|
+
}
|
|
6259
6501
|
async function init(a, root) {
|
|
6260
6502
|
const cfgPath = join6(root, CONFIG_FILE2);
|
|
6261
6503
|
let config;
|
|
6262
|
-
if (existsSync6(cfgPath) && !
|
|
6504
|
+
if (existsSync6(cfgPath) && !has7(a, "force")) {
|
|
6263
6505
|
print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
|
|
6264
|
-
config = (await (await
|
|
6506
|
+
config = (await (await BridgeContext8.create({ root })).config()).config;
|
|
6265
6507
|
} else {
|
|
6266
6508
|
const src = new LocalSource(root);
|
|
6267
6509
|
const files = await src.list("");
|
|
@@ -6289,14 +6531,14 @@ async function init(a, root) {
|
|
|
6289
6531
|
print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
|
|
6290
6532
|
}
|
|
6291
6533
|
async function check(a, root) {
|
|
6292
|
-
const ctx = await
|
|
6534
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6293
6535
|
const v = await ops.validateTokens(ctx, { includeWarnings: true });
|
|
6294
6536
|
const c = await ops.checkContrast(ctx, {});
|
|
6295
6537
|
const warnings = "warnings" in v ? v.warnings ?? [] : [];
|
|
6296
|
-
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;
|
|
6297
6539
|
const protection = await protectionOf(a, root, ctx);
|
|
6298
6540
|
const roles = await rolesOf(a, root, ctx);
|
|
6299
|
-
if (
|
|
6541
|
+
if (has7(a, "json")) {
|
|
6300
6542
|
print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
|
|
6301
6543
|
return failed ? 1 : 0;
|
|
6302
6544
|
}
|
|
@@ -6315,7 +6557,7 @@ async function check(a, root) {
|
|
|
6315
6557
|
return failed ? 1 : 0;
|
|
6316
6558
|
}
|
|
6317
6559
|
async function protectionOf(a, root, ctx) {
|
|
6318
|
-
if (
|
|
6560
|
+
if (has7(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6319
6561
|
const { config } = await ctx.config();
|
|
6320
6562
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6321
6563
|
if (!repo) return void 0;
|
|
@@ -6323,7 +6565,7 @@ async function protectionOf(a, root, ctx) {
|
|
|
6323
6565
|
return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
|
|
6324
6566
|
}
|
|
6325
6567
|
async function rolesOf(a, root, ctx) {
|
|
6326
|
-
if (
|
|
6568
|
+
if (has7(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6327
6569
|
const { config } = await ctx.config();
|
|
6328
6570
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6329
6571
|
if (!repo) return void 0;
|
|
@@ -6333,9 +6575,9 @@ async function rolesOf(a, root, ctx) {
|
|
|
6333
6575
|
async function tokens(a, root) {
|
|
6334
6576
|
const q = a._.slice(1).join(" ");
|
|
6335
6577
|
if (!q) throw new UsageError("bridge tokens <query>");
|
|
6336
|
-
const ctx = await
|
|
6337
|
-
const r = await ops.searchTokens(ctx, { query: q, limit: Number(
|
|
6338
|
-
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));
|
|
6339
6581
|
if (!r.results.length) return print(`No token matches "${q}".`);
|
|
6340
6582
|
const w = Math.max(...r.results.map((x) => x.path.length));
|
|
6341
6583
|
for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
|
|
@@ -6343,7 +6585,7 @@ async function tokens(a, root) {
|
|
|
6343
6585
|
async function diff(a, root) {
|
|
6344
6586
|
const base = a._[1];
|
|
6345
6587
|
if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
|
|
6346
|
-
const ctx = await
|
|
6588
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6347
6589
|
const { config } = await ctx.config();
|
|
6348
6590
|
const before = await core.loadTokenSet(new GitRefSource(root, base), config);
|
|
6349
6591
|
const after = await ctx.tokens();
|
|
@@ -6352,11 +6594,11 @@ async function diff(a, root) {
|
|
|
6352
6594
|
const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
|
|
6353
6595
|
const tier = core.classify(changes, [], config);
|
|
6354
6596
|
const imp = core.impact(before, after, changes, 30);
|
|
6355
|
-
if (
|
|
6597
|
+
if (has7(a, "json")) {
|
|
6356
6598
|
print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
|
|
6357
6599
|
return 0;
|
|
6358
6600
|
}
|
|
6359
|
-
if (
|
|
6601
|
+
if (has7(a, "md")) {
|
|
6360
6602
|
print(`## Token changes since \`${base}\`
|
|
6361
6603
|
|
|
6362
6604
|
${core.tokenTable(changes)}
|
|
@@ -6391,26 +6633,27 @@ ${imp.total} other token(s) change value through aliases:`);
|
|
|
6391
6633
|
return delta2.some((d) => d.passBefore && !d.passAfter) ? 1 : 0;
|
|
6392
6634
|
}
|
|
6393
6635
|
async function propose(a, root) {
|
|
6394
|
-
const title =
|
|
6636
|
+
const title = flag5(a, "title");
|
|
6395
6637
|
if (!title) throw new UsageError('bridge propose --title "Darken muted text" --set color.text.muted={palette.neutral.700} --mode theme=light');
|
|
6396
6638
|
const edits = [];
|
|
6397
|
-
const file =
|
|
6398
|
-
const mode2 = modeArg(
|
|
6639
|
+
const file = flag5(a, "file");
|
|
6640
|
+
const mode2 = modeArg(flag5(a, "mode"));
|
|
6399
6641
|
for (const s of a.flags.set ?? []) {
|
|
6400
6642
|
const i = s.indexOf("=");
|
|
6401
6643
|
if (i < 0) throw new UsageError(`--set needs path=value, got "${s}"`);
|
|
6402
6644
|
edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
|
|
6403
6645
|
}
|
|
6404
|
-
const editsFile =
|
|
6646
|
+
const editsFile = flag5(a, "edits");
|
|
6405
6647
|
if (editsFile) edits.push(...JSON.parse(await readFile7(resolve5(editsFile), "utf8")));
|
|
6406
6648
|
if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
|
|
6407
|
-
const pr =
|
|
6408
|
-
const
|
|
6409
|
-
const
|
|
6410
|
-
const
|
|
6411
|
-
const
|
|
6412
|
-
const
|
|
6413
|
-
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");
|
|
6414
6657
|
const r = await ops.proposeTokenChange(ctx, {
|
|
6415
6658
|
title,
|
|
6416
6659
|
edits,
|
|
@@ -6419,34 +6662,36 @@ async function propose(a, root) {
|
|
|
6419
6662
|
...pr ? { pr } : {},
|
|
6420
6663
|
...jira ? { jira } : {},
|
|
6421
6664
|
...product ? { product } : {},
|
|
6422
|
-
|
|
6665
|
+
...changes ? { changes } : {},
|
|
6666
|
+
source: "cli",
|
|
6667
|
+
dryRun: has7(a, "dry-run")
|
|
6423
6668
|
});
|
|
6424
|
-
if (
|
|
6669
|
+
if (has7(a, "json")) print(JSON.stringify(r, null, 2));
|
|
6425
6670
|
else {
|
|
6426
6671
|
print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
|
|
6427
6672
|
if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
|
|
6428
6673
|
for (const c of r.changes ?? []) print(` ${c.kind} ${c.path}: ${c.before ?? ""} -> ${c.after ?? ""}`);
|
|
6429
|
-
|
|
6430
|
-
if (r.next) print(String(r.next));
|
|
6431
|
-
if (r.hint) print(String(r.hint));
|
|
6674
|
+
printSent(r);
|
|
6432
6675
|
if (r.status === "dry-run") print(`
|
|
6433
|
-
${r.prBody}`);
|
|
6676
|
+
${r.suggestion?.body ?? r.prBody}`);
|
|
6434
6677
|
}
|
|
6435
6678
|
return r.status === "blocked" ? 1 : 0;
|
|
6436
6679
|
}
|
|
6437
6680
|
async function brandCmd(a, root) {
|
|
6438
6681
|
const sub = a._[1];
|
|
6439
|
-
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 } : {} });
|
|
6440
6685
|
if (sub === "list") {
|
|
6441
6686
|
const r2 = await brand.listBrands(ctx);
|
|
6442
|
-
if (
|
|
6687
|
+
if (has7(a, "json")) print(JSON.stringify(r2.brands, null, 2));
|
|
6443
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(", ")}` : ""}`);
|
|
6444
6689
|
return 0;
|
|
6445
6690
|
}
|
|
6446
6691
|
if (sub !== "create") throw new UsageError('bridge brand list | bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
|
|
6447
6692
|
const slug = a._[2];
|
|
6448
6693
|
if (!slug) throw new UsageError('bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
|
|
6449
|
-
const opt = (k) =>
|
|
6694
|
+
const opt = (k) => flag5(a, k);
|
|
6450
6695
|
const fonts = { ...opt("heading") ? { heading: opt("heading") } : {}, ...opt("text") ? { text: opt("text") } : {} };
|
|
6451
6696
|
const r = await brand.proposeBrand(ctx, {
|
|
6452
6697
|
slug,
|
|
@@ -6459,9 +6704,11 @@ async function brandCmd(a, root) {
|
|
|
6459
6704
|
...opt("radius") ? { radius: opt("radius") } : {},
|
|
6460
6705
|
...Object.keys(fonts).length ? { fonts } : {},
|
|
6461
6706
|
...opt("notes") ? { notes: opt("notes") } : {},
|
|
6462
|
-
|
|
6707
|
+
...changes ? { changes } : {},
|
|
6708
|
+
source: "cli",
|
|
6709
|
+
dryRun: has7(a, "dry-run")
|
|
6463
6710
|
});
|
|
6464
|
-
if (
|
|
6711
|
+
if (has7(a, "json")) {
|
|
6465
6712
|
const { fileContents: _f, ...rest } = r;
|
|
6466
6713
|
print(JSON.stringify(rest, null, 2));
|
|
6467
6714
|
return r.status === "blocked" ? 1 : 0;
|
|
@@ -6476,13 +6723,11 @@ async function brandCmd(a, root) {
|
|
|
6476
6723
|
for (const i of r.issues) print(` ${i.level}: ${i.message}`);
|
|
6477
6724
|
print("files:");
|
|
6478
6725
|
for (const f of r.files) print(` ${f.action} ${f.path}`);
|
|
6479
|
-
|
|
6480
|
-
if (r.next) print(String(r.next));
|
|
6481
|
-
if (r.hint) print(String(r.hint));
|
|
6726
|
+
printSent(r);
|
|
6482
6727
|
return r.status === "blocked" ? 1 : 0;
|
|
6483
6728
|
}
|
|
6484
6729
|
async function figmaReferences(a, root) {
|
|
6485
|
-
let sha =
|
|
6730
|
+
let sha = flag5(a, "sha");
|
|
6486
6731
|
if (!sha) {
|
|
6487
6732
|
try {
|
|
6488
6733
|
sha = execFileSync2("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
@@ -6490,33 +6735,33 @@ async function figmaReferences(a, root) {
|
|
|
6490
6735
|
sha = "unknown";
|
|
6491
6736
|
}
|
|
6492
6737
|
}
|
|
6493
|
-
const only = (
|
|
6738
|
+
const only = (flag5(a, "only") ?? "").split(",").filter(Boolean);
|
|
6494
6739
|
const r = await captureFigmaReferences({
|
|
6495
|
-
storybook: resolve5(root,
|
|
6496
|
-
specs: resolve5(root,
|
|
6497
|
-
out: resolve5(root,
|
|
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"),
|
|
6498
6743
|
sha,
|
|
6499
6744
|
...only.length ? { only } : {},
|
|
6500
|
-
log:
|
|
6745
|
+
log: has7(a, "json") ? void 0 : (line) => print(line)
|
|
6501
6746
|
});
|
|
6502
|
-
if (
|
|
6747
|
+
if (has7(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
|
|
6503
6748
|
return 0;
|
|
6504
6749
|
}
|
|
6505
6750
|
async function impact(a, root) {
|
|
6506
|
-
const only = (
|
|
6751
|
+
const only = (flag5(a, "only") ?? "").split(",").filter(Boolean);
|
|
6507
6752
|
for (const x of only) if (x !== "stories" && x !== "app") throw new UsageError(`--only takes stories or app, got "${x}"`);
|
|
6508
|
-
const num2 = (k) =>
|
|
6753
|
+
const num2 = (k) => flag5(a, k) !== void 0 ? Number(flag5(a, k)) : void 0;
|
|
6509
6754
|
const threshold = num2("threshold");
|
|
6510
6755
|
if (threshold !== void 0 && !(threshold >= 0 && threshold <= 1)) throw new UsageError("--threshold is between 0 and 1 (pixelmatch color distance)");
|
|
6511
|
-
const imageBase =
|
|
6512
|
-
const artifactUrl =
|
|
6513
|
-
const out =
|
|
6514
|
-
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");
|
|
6515
6760
|
const maxStories = num2("max-stories");
|
|
6516
6761
|
const r = await runImpact({
|
|
6517
6762
|
root,
|
|
6518
|
-
base:
|
|
6519
|
-
head:
|
|
6763
|
+
base: flag5(a, "base") ?? "main",
|
|
6764
|
+
head: flag5(a, "head") ?? "HEAD",
|
|
6520
6765
|
...out ? { out: resolve5(out) } : {},
|
|
6521
6766
|
...only.length ? { only } : {},
|
|
6522
6767
|
...threshold !== void 0 ? { threshold } : {},
|
|
@@ -6524,15 +6769,15 @@ async function impact(a, root) {
|
|
|
6524
6769
|
...maxStories ? { maxStoriesPerComponent: maxStories } : {},
|
|
6525
6770
|
...imageBase !== void 0 ? { imageBase } : {},
|
|
6526
6771
|
...artifactUrl ? { artifactUrl } : {},
|
|
6527
|
-
plan:
|
|
6528
|
-
keepUnchanged:
|
|
6772
|
+
plan: has7(a, "plan"),
|
|
6773
|
+
keepUnchanged: has7(a, "keep-unchanged")
|
|
6529
6774
|
});
|
|
6530
|
-
if (
|
|
6775
|
+
if (has7(a, "json")) {
|
|
6531
6776
|
print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
|
|
6532
6777
|
return 0;
|
|
6533
6778
|
}
|
|
6534
6779
|
const sel = r.manifest.selection;
|
|
6535
|
-
if (
|
|
6780
|
+
if (has7(a, "plan")) {
|
|
6536
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)})`);
|
|
6537
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(", ")})` : ""}`);
|
|
6538
6783
|
print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
|
|
@@ -6548,12 +6793,12 @@ async function impact(a, root) {
|
|
|
6548
6793
|
}
|
|
6549
6794
|
var day = (iso) => iso.slice(0, 10);
|
|
6550
6795
|
async function historyCmd(a, root) {
|
|
6551
|
-
const ctx = await
|
|
6796
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6552
6797
|
const token = a._[1];
|
|
6553
|
-
const limit = Number(
|
|
6798
|
+
const limit = Number(flag5(a, "limit") ?? 20);
|
|
6554
6799
|
if (token) {
|
|
6555
6800
|
const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
|
|
6556
|
-
if (
|
|
6801
|
+
if (has7(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
|
|
6557
6802
|
if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
|
|
6558
6803
|
print(`${r2.path}: ${r2.total} change(s) (${r2.source})
|
|
6559
6804
|
`);
|
|
@@ -6564,9 +6809,9 @@ async function historyCmd(a, root) {
|
|
|
6564
6809
|
}
|
|
6565
6810
|
return 0;
|
|
6566
6811
|
}
|
|
6567
|
-
const tier =
|
|
6568
|
-
const r = await hist.getHistory(ctx, { limit, ...
|
|
6569
|
-
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;
|
|
6570
6815
|
print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
|
|
6571
6816
|
if (!r.total) return print(r.note ?? "No token change matches."), 0;
|
|
6572
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" : ""}`);
|
|
@@ -6584,10 +6829,12 @@ ${r.truncated}`);
|
|
|
6584
6829
|
async function revertCmd(a, root) {
|
|
6585
6830
|
const target = a._[1];
|
|
6586
6831
|
if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
|
|
6587
|
-
const
|
|
6832
|
+
const changes = changesFlag(a);
|
|
6833
|
+
const token = changes !== "direct" ? githubToken() : void 0;
|
|
6834
|
+
const ctx = await BridgeContext8.create({ root, ...token ? { token } : {} });
|
|
6588
6835
|
const resolutions = Object.fromEntries((a.flags["use-before"] ?? []).map((k) => [k, "use-before"]));
|
|
6589
|
-
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts:
|
|
6590
|
-
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")) {
|
|
6591
6838
|
print(JSON.stringify(r, null, 2));
|
|
6592
6839
|
return r.status === "blocked" ? 1 : 0;
|
|
6593
6840
|
}
|
|
@@ -6602,35 +6849,34 @@ async function revertCmd(a, root) {
|
|
|
6602
6849
|
}
|
|
6603
6850
|
for (const s of r.skipped ?? []) print(` skipped ${s}`);
|
|
6604
6851
|
if (r.contrast?.moved) print(`contrast: ${r.contrast.moved} pair(s) move, ${r.contrast.newFailures.length} new failure(s)`);
|
|
6605
|
-
|
|
6606
|
-
if (r.next) print(String(r.next));
|
|
6852
|
+
printSent(r);
|
|
6607
6853
|
if (r.status === "dry-run") print(`
|
|
6608
|
-
${r.prBody}`);
|
|
6854
|
+
${r.suggestion?.body ?? r.prBody}`);
|
|
6609
6855
|
return r.status === "blocked" ? 1 : 0;
|
|
6610
6856
|
}
|
|
6611
6857
|
async function audit(a, root) {
|
|
6612
6858
|
const target = a._[1];
|
|
6613
6859
|
if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
|
|
6614
6860
|
if (!existsSync6(target)) throw new UsageError(`No such file or folder: ${target}`);
|
|
6615
|
-
const dsRoot =
|
|
6861
|
+
const dsRoot = flag5(a, "ds") ? resolve5(flag5(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
|
|
6616
6862
|
if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
|
|
6617
6863
|
const ds = loadDesignSystem(dsRoot);
|
|
6618
|
-
if (
|
|
6619
|
-
const write =
|
|
6864
|
+
if (has7(a, "fix")) {
|
|
6865
|
+
const write = has7(a, "write");
|
|
6620
6866
|
const changes = fixCss(target, ds.index, write);
|
|
6621
6867
|
const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
|
|
6622
6868
|
for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
|
|
6623
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.");
|
|
6624
6870
|
return 0;
|
|
6625
6871
|
}
|
|
6626
|
-
const baselinePath =
|
|
6872
|
+
const baselinePath = flag5(a, "baseline");
|
|
6627
6873
|
let baseline;
|
|
6628
6874
|
if (baselinePath) {
|
|
6629
6875
|
if (!existsSync6(baselinePath)) throw new UsageError(`Baseline not found: ${baselinePath}`);
|
|
6630
6876
|
baseline = JSON.parse(await readFile7(baselinePath, "utf8"));
|
|
6631
6877
|
}
|
|
6632
|
-
const label =
|
|
6633
|
-
const allowlistPath =
|
|
6878
|
+
const label = flag5(a, "label");
|
|
6879
|
+
const allowlistPath = flag5(a, "allowlist");
|
|
6634
6880
|
if (allowlistPath && !existsSync6(allowlistPath)) throw new UsageError(`Allowlist not found: ${allowlistPath}`);
|
|
6635
6881
|
let allowlist;
|
|
6636
6882
|
try {
|
|
@@ -6640,8 +6886,8 @@ async function audit(a, root) {
|
|
|
6640
6886
|
}
|
|
6641
6887
|
const report = runAudit(target, { ds: dsRoot, version: VERSION, ...label ? { label } : {}, ...baseline ? { baseline } : {}, ...allowlist ? { allowlist } : {} });
|
|
6642
6888
|
const md = auditMarkdown(report);
|
|
6643
|
-
const jsonOut =
|
|
6644
|
-
const mdOut =
|
|
6889
|
+
const jsonOut = flag5(a, "json");
|
|
6890
|
+
const mdOut = flag5(a, "md");
|
|
6645
6891
|
if (jsonOut && jsonOut !== "true") await writeFile5(jsonOut, `${JSON.stringify(report, null, 2)}
|
|
6646
6892
|
`);
|
|
6647
6893
|
if (mdOut && mdOut !== "true") await writeFile5(mdOut, `${md}
|
|
@@ -6681,7 +6927,7 @@ async function audit(a, root) {
|
|
|
6681
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}
|
|
6682
6928
|
`);
|
|
6683
6929
|
let code = 0;
|
|
6684
|
-
const failUnder =
|
|
6930
|
+
const failUnder = flag5(a, "fail-under");
|
|
6685
6931
|
if (failUnder !== void 0) {
|
|
6686
6932
|
const min = Number(failUnder);
|
|
6687
6933
|
if (!Number.isFinite(min)) throw new UsageError("--fail-under needs a number, e.g. --fail-under 80");
|
|
@@ -6692,7 +6938,7 @@ async function audit(a, root) {
|
|
|
6692
6938
|
code = 1;
|
|
6693
6939
|
}
|
|
6694
6940
|
}
|
|
6695
|
-
if (
|
|
6941
|
+
if (has7(a, "no-regress") && report.baseline) {
|
|
6696
6942
|
const b = report.baseline;
|
|
6697
6943
|
if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
|
|
6698
6944
|
process.stderr.write("bridge audit: adoption regressed since the baseline\n");
|
|
@@ -6704,9 +6950,9 @@ async function audit(a, root) {
|
|
|
6704
6950
|
async function docsCmd(a, root) {
|
|
6705
6951
|
const [, sub, name] = a._;
|
|
6706
6952
|
if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
|
|
6707
|
-
const ctx = await
|
|
6953
|
+
const ctx = await BridgeContext8.create({ root });
|
|
6708
6954
|
const d = await ops.draftDocsOp(ctx, { name });
|
|
6709
|
-
if (
|
|
6955
|
+
if (has7(a, "json")) {
|
|
6710
6956
|
print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
|
|
6711
6957
|
return 0;
|
|
6712
6958
|
}
|
|
@@ -6737,17 +6983,20 @@ async function main() {
|
|
|
6737
6983
|
const argv = process.argv.slice(2);
|
|
6738
6984
|
const a = parse(argv);
|
|
6739
6985
|
const cmd = a._[0];
|
|
6740
|
-
const root = resolve5(
|
|
6986
|
+
const root = resolve5(flag5(a, "root") ?? process.cwd());
|
|
6741
6987
|
if (cmd === "request") return runRequest(a, root);
|
|
6742
6988
|
if (cmd === "explore") return runExplore(a, root);
|
|
6743
6989
|
if (cmd === "branch") return runBranch(a, root);
|
|
6744
6990
|
if (cmd === "pieces") return runPieces(a, root);
|
|
6745
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);
|
|
6746
6995
|
if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
|
|
6747
6996
|
if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
|
|
6748
|
-
if (!cmd ||
|
|
6997
|
+
if (!cmd || has7(a, "help") || cmd === "help") {
|
|
6749
6998
|
print(HELP);
|
|
6750
|
-
return cmd ||
|
|
6999
|
+
return cmd || has7(a, "help") ? 0 : 2;
|
|
6751
7000
|
}
|
|
6752
7001
|
if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
|
|
6753
7002
|
switch (cmd) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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,7 +43,7 @@
|
|
|
43
43
|
"build:package": "node ../../scripts/build-packages.mjs cli"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@bridge-ds/mcp": "^0.
|
|
46
|
+
"@bridge-ds/mcp": "^0.7.0",
|
|
47
47
|
"@bridge-ds/visual": "^0.3.4",
|
|
48
48
|
"typescript": "5.9.3"
|
|
49
49
|
},
|