@bridge-ds/cli 0.4.0 → 0.5.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 +493 -100
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { existsSync as existsSync6 } from "node:fs";
|
|
5
|
-
import { readFile as
|
|
6
|
-
import { isAbsolute, join as join6, relative as relative4, resolve as
|
|
5
|
+
import { readFile as readFile7, writeFile as writeFile5 } from "node:fs/promises";
|
|
6
|
+
import { isAbsolute, join as join6, relative as relative4, resolve as resolve4 } from "node:path";
|
|
7
7
|
|
|
8
8
|
// ../core/src/audit/types.ts
|
|
9
9
|
var AUDIT_SCHEMA_VERSION = 1;
|
|
@@ -2182,10 +2182,10 @@ function assertSafeBranch(branch, rules = {}) {
|
|
|
2182
2182
|
|
|
2183
2183
|
// ../core/src/node/git-local.ts
|
|
2184
2184
|
function git(root, args, opts = {}) {
|
|
2185
|
-
return new Promise((
|
|
2185
|
+
return new Promise((resolve5, reject) => {
|
|
2186
2186
|
const child = execFile("git", args, { cwd: root, env: { ...process.env, ...opts.env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
2187
2187
|
if (err) reject(new Error(`git ${args.join(" ")}: ${stderr || err.message}`.trim()));
|
|
2188
|
-
else
|
|
2188
|
+
else resolve5(stdout);
|
|
2189
2189
|
});
|
|
2190
2190
|
if (opts.input !== void 0) child.stdin.end(opts.input);
|
|
2191
2191
|
});
|
|
@@ -2432,6 +2432,47 @@ function visualImpactProblems(raw) {
|
|
|
2432
2432
|
return out;
|
|
2433
2433
|
}
|
|
2434
2434
|
|
|
2435
|
+
// ../core/src/products.ts
|
|
2436
|
+
var DEFAULT_PRODUCTS = [
|
|
2437
|
+
{ key: "admin", name: "Admin demo", path: "apps/admin", framework: "react" },
|
|
2438
|
+
{ key: "library", name: "Library", default: true }
|
|
2439
|
+
];
|
|
2440
|
+
var KEY = /^[a-z0-9][a-z0-9-]*$/;
|
|
2441
|
+
var REPO = /^[\w.-]+\/[\w.-]+$/;
|
|
2442
|
+
var FIELDS = ["key", "name", "repo", "path", "preview", "framework", "default"];
|
|
2443
|
+
function productProblems(raw) {
|
|
2444
|
+
if (raw === void 0) return [];
|
|
2445
|
+
if (!Array.isArray(raw)) return ['"products" must be a list of { "key", "name" } objects'];
|
|
2446
|
+
if (!raw.length) return ['"products" must list at least one product, or be left out to use the defaults'];
|
|
2447
|
+
const problems = [];
|
|
2448
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2449
|
+
let defaults = 0;
|
|
2450
|
+
raw.forEach((p, i) => {
|
|
2451
|
+
const at = `"products[${i}]`;
|
|
2452
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) {
|
|
2453
|
+
problems.push(`${at}" must be an object with a "key" and a "name"`);
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
const o = p;
|
|
2457
|
+
for (const k of Object.keys(o)) if (!FIELDS.includes(k)) problems.push(`${at}.${k}" is not a known field (${FIELDS.join(", ")})`);
|
|
2458
|
+
if (typeof o.key !== "string" || !KEY.test(o.key)) problems.push(`${at}.key" must be lowercase letters, digits and dashes, such as "admin"`);
|
|
2459
|
+
else if (seen.has(o.key)) problems.push(`${at}.key" ("${o.key}") is used twice: give each product its own key`);
|
|
2460
|
+
else seen.add(o.key);
|
|
2461
|
+
if (typeof o.name !== "string" || !o.name.trim()) problems.push(`${at}.name" must be the name people read, such as "Admin demo"`);
|
|
2462
|
+
if (o.repo !== void 0 && (typeof o.repo !== "string" || !REPO.test(o.repo))) problems.push(`${at}.repo" must be "owner/name"`);
|
|
2463
|
+
if (o.path !== void 0) {
|
|
2464
|
+
const bad = typeof o.path !== "string" || !o.path.trim() || o.path.startsWith("/") || o.path.split("/").includes("..");
|
|
2465
|
+
if (bad) problems.push(`${at}.path" must be a folder relative to the repository root, without a leading "/" or "..", such as "apps/admin"`);
|
|
2466
|
+
}
|
|
2467
|
+
if (o.preview !== void 0 && (typeof o.preview !== "string" || !/^https?:\/\//.test(o.preview))) problems.push(`${at}.preview" must be an http(s) URL template, such as "https://app-git-{branch}.vercel.app"`);
|
|
2468
|
+
if (o.framework !== void 0 && (typeof o.framework !== "string" || !o.framework.trim())) problems.push(`${at}.framework" must be a name such as "react", "vue" or "other"`);
|
|
2469
|
+
if (o.default !== void 0 && typeof o.default !== "boolean") problems.push(`${at}.default" must be true or false`);
|
|
2470
|
+
if (o.default === true) defaults++;
|
|
2471
|
+
});
|
|
2472
|
+
if (defaults > 1) problems.push('"products" marks more than one product as default: keep "default": true on the library only');
|
|
2473
|
+
return problems;
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2435
2476
|
// ../core/src/config.ts
|
|
2436
2477
|
var CONFIG_FILE = "bridge.config.json";
|
|
2437
2478
|
var DEFAULT_OVERRIDE = { allowed: [], requireReason: true, minReasonLength: 20 };
|
|
@@ -2572,6 +2613,7 @@ function defineConfig(input) {
|
|
|
2572
2613
|
} : {},
|
|
2573
2614
|
...g.rights ? { rights: normalizeRights(g.rights) } : {}
|
|
2574
2615
|
},
|
|
2616
|
+
products: input.products?.length ? input.products.map((p) => ({ ...p })) : DEFAULT_PRODUCTS.map((p) => ({ ...p })),
|
|
2575
2617
|
...input.audit ? { audit: { ...input.audit } } : {},
|
|
2576
2618
|
...input.visualImpact ? { visualImpact: { mode: input.visualImpact.mode === "on-demand" ? "on-demand" : "auto" } } : {},
|
|
2577
2619
|
...input.integrations ? { integrations: { ...input.integrations } } : {},
|
|
@@ -2694,6 +2736,7 @@ function loadConfig(source) {
|
|
|
2694
2736
|
}
|
|
2695
2737
|
}
|
|
2696
2738
|
problems.push(...visualImpactProblems(d.visualImpact));
|
|
2739
|
+
problems.push(...productProblems(d.products));
|
|
2697
2740
|
const audit2 = d.audit;
|
|
2698
2741
|
if (audit2 !== void 0) {
|
|
2699
2742
|
if (!isObj(audit2)) problems.push('"audit" must be an object');
|
|
@@ -3624,7 +3667,7 @@ var GitHubClient = class _GitHubClient {
|
|
|
3624
3667
|
}
|
|
3625
3668
|
async getPullRequest(n) {
|
|
3626
3669
|
const r = await this.request("GET", `${this.repoPath}/pulls/${n}`);
|
|
3627
|
-
return { ...toSummary(r), body: r.body ?? "", mergeable: r.mergeable ?? null };
|
|
3670
|
+
return { ...toSummary(r), body: r.body ?? "", mergeable: r.mergeable ?? null, requestedReviewers: (r.requested_reviewers ?? []).map((x) => x.login) };
|
|
3628
3671
|
}
|
|
3629
3672
|
async listPullRequestFiles(n) {
|
|
3630
3673
|
const r = await this.request("GET", `${this.repoPath}/pulls/${n}/files?per_page=100`);
|
|
@@ -3701,6 +3744,100 @@ var GitHubClient = class _GitHubClient {
|
|
|
3701
3744
|
async removeLabel(issueNumber2, label) {
|
|
3702
3745
|
await this.request("DELETE", `${this.repoPath}/issues/${issueNumber2}/labels/${encodeURIComponent(label)}`);
|
|
3703
3746
|
}
|
|
3747
|
+
// --- branch actions shared by Studio, the MCP server and the CLI (branch-actions.ts) ---
|
|
3748
|
+
/** The account the token belongs to (GET /user). Needs a token. */
|
|
3749
|
+
async getViewer() {
|
|
3750
|
+
this.assertToken("Reading who you are on GitHub");
|
|
3751
|
+
const r = await this.request("GET", "/user");
|
|
3752
|
+
return { login: r.login };
|
|
3753
|
+
}
|
|
3754
|
+
/** The repository's merge settings and the viewer's permissions (unknown settings count as allowed). */
|
|
3755
|
+
async getMergeSettings() {
|
|
3756
|
+
return this.request("GET", this.repoPath);
|
|
3757
|
+
}
|
|
3758
|
+
/**
|
|
3759
|
+
* Merges a pull request, only at `sha` (GitHub refuses when the branch moved meanwhile). The repository's branch
|
|
3760
|
+
* rules still apply: GitHub refuses a merge they block, whatever Bridge says.
|
|
3761
|
+
*/
|
|
3762
|
+
async mergePullRequest(n, input) {
|
|
3763
|
+
this.assertToken("Merging a branch");
|
|
3764
|
+
const r = await this.request("PUT", `${this.repoPath}/pulls/${n}/merge`, {
|
|
3765
|
+
merge_method: input.method,
|
|
3766
|
+
sha: input.sha,
|
|
3767
|
+
...input.commitMessage !== void 0 && input.method !== "rebase" ? { commit_message: input.commitMessage } : {}
|
|
3768
|
+
});
|
|
3769
|
+
return { sha: r?.sha ?? "", merged: r?.merged ?? true };
|
|
3770
|
+
}
|
|
3771
|
+
/** GitHub's "Update branch": merges the base into the pull request, only when its head is still `expectedHeadSha`. */
|
|
3772
|
+
async updatePullRequestBranch(n, expectedHeadSha) {
|
|
3773
|
+
this.assertToken("Updating a branch from main");
|
|
3774
|
+
await this.request("PUT", `${this.repoPath}/pulls/${n}/update-branch`, { expected_head_sha: expectedHeadSha });
|
|
3775
|
+
}
|
|
3776
|
+
/** Posts a comment on an issue or pull request and returns its id and link. */
|
|
3777
|
+
async createIssueComment(n, body) {
|
|
3778
|
+
this.assertToken("Commenting");
|
|
3779
|
+
const r = await this.request("POST", `${this.repoPath}/issues/${n}/comments`, { body });
|
|
3780
|
+
return { id: r.id, url: r.html_url };
|
|
3781
|
+
}
|
|
3782
|
+
/** Replaces the text of a comment. */
|
|
3783
|
+
async updateIssueComment(id, body) {
|
|
3784
|
+
this.assertToken("Editing a comment");
|
|
3785
|
+
await this.request("PATCH", `${this.repoPath}/issues/comments/${id}`, { body });
|
|
3786
|
+
}
|
|
3787
|
+
/** Submits a review on a given commit (the head the reviewer saw); the body may be empty for an approval. */
|
|
3788
|
+
async submitReview(n, input) {
|
|
3789
|
+
this.assertToken("Reviewing a branch");
|
|
3790
|
+
const r = await this.request("POST", `${this.repoPath}/pulls/${n}/reviews`, {
|
|
3791
|
+
event: input.event,
|
|
3792
|
+
...input.body ? { body: input.body } : {},
|
|
3793
|
+
...input.commitId ? { commit_id: input.commitId } : {}
|
|
3794
|
+
});
|
|
3795
|
+
return { id: r.id, user: r.user?.login, state: r.state, body: r.body ?? "" };
|
|
3796
|
+
}
|
|
3797
|
+
/** Line comments of a pull request (review threads), oldest first. */
|
|
3798
|
+
async listReviewComments(n) {
|
|
3799
|
+
const r = await this.request(
|
|
3800
|
+
"GET",
|
|
3801
|
+
`${this.repoPath}/pulls/${n}/comments?per_page=100`
|
|
3802
|
+
);
|
|
3803
|
+
return r.map((c) => ({ id: c.id, body: c.body ?? "", author: c.user?.login ?? "unknown", path: c.path, line: c.line ?? null, inReplyTo: c.in_reply_to_id ?? null, url: c.html_url, createdAt: c.created_at }));
|
|
3804
|
+
}
|
|
3805
|
+
/** Asks these people to review a pull request. */
|
|
3806
|
+
async requestReviewers(n, logins) {
|
|
3807
|
+
this.assertToken("Asking for a review");
|
|
3808
|
+
await this.request("POST", `${this.repoPath}/pulls/${n}/requested_reviewers`, { reviewers: logins });
|
|
3809
|
+
}
|
|
3810
|
+
/** Takes these people off the requested reviewers. */
|
|
3811
|
+
async removeRequestedReviewers(n, logins) {
|
|
3812
|
+
this.assertToken("Changing the requested reviewers");
|
|
3813
|
+
await this.request("DELETE", `${this.repoPath}/pulls/${n}/requested_reviewers`, { reviewers: logins });
|
|
3814
|
+
}
|
|
3815
|
+
/** Replies in the thread of a line comment. */
|
|
3816
|
+
async replyToReviewComment(n, commentId, body) {
|
|
3817
|
+
this.assertToken("Replying to a comment");
|
|
3818
|
+
const r = await this.request("POST", `${this.repoPath}/pulls/${n}/comments/${commentId}/replies`, { body });
|
|
3819
|
+
return { id: r.id, url: r.html_url };
|
|
3820
|
+
}
|
|
3821
|
+
/**
|
|
3822
|
+
* What `head` adds on top of `base`, with what library sync needs: the shared start (merge base), the base
|
|
3823
|
+
* commit and its tree, how far behind, and the files with their previous names.
|
|
3824
|
+
*/
|
|
3825
|
+
async compareDetailed(base, head) {
|
|
3826
|
+
const r = await this.request("GET", `${this.repoPath}/compare/${encodeRef(base)}...${encodeRef(head)}`);
|
|
3827
|
+
return {
|
|
3828
|
+
behindBy: r.behind_by,
|
|
3829
|
+
aheadBy: r.ahead_by,
|
|
3830
|
+
mergeBase: r.merge_base_commit?.sha ?? null,
|
|
3831
|
+
baseSha: r.base_commit?.sha ?? null,
|
|
3832
|
+
baseTree: r.base_commit?.commit?.tree?.sha ?? null,
|
|
3833
|
+
files: (r.files ?? []).map((f) => ({ filename: f.filename, status: f.status, ...f.previous_filename ? { previous_filename: f.previous_filename } : {} }))
|
|
3834
|
+
};
|
|
3835
|
+
}
|
|
3836
|
+
/** A tree on top of `baseTree` from entries with their own file mode (blob sha, inline content, or sha null to delete). */
|
|
3837
|
+
async createTreeEntries(baseTree, entries) {
|
|
3838
|
+
const r = await this.request("POST", `${this.repoPath}/git/trees`, { base_tree: baseTree, tree: entries.map((e) => ({ ...e, type: "blob" })) }, true);
|
|
3839
|
+
return r.sha;
|
|
3840
|
+
}
|
|
3704
3841
|
};
|
|
3705
3842
|
function toCommitSummary(c) {
|
|
3706
3843
|
return {
|
|
@@ -4162,15 +4299,15 @@ async function shoot(page, r) {
|
|
|
4162
4299
|
}
|
|
4163
4300
|
async function switchStory(page, storyId2) {
|
|
4164
4301
|
const ok = await page.evaluate(
|
|
4165
|
-
(id) => new Promise((
|
|
4302
|
+
(id) => new Promise((resolve5) => {
|
|
4166
4303
|
const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
|
|
4167
|
-
if (!ch) return
|
|
4304
|
+
if (!ch) return resolve5(false);
|
|
4168
4305
|
const events = ["storyRendered", "storyErrored", "storyThrewException", "storyMissing", "playFunctionThrewException"];
|
|
4169
4306
|
const timer = setTimeout(() => done(), 15e3);
|
|
4170
4307
|
function done() {
|
|
4171
4308
|
clearTimeout(timer);
|
|
4172
4309
|
for (const e of events) ch.off(e, done);
|
|
4173
|
-
|
|
4310
|
+
resolve5(true);
|
|
4174
4311
|
}
|
|
4175
4312
|
for (const e of events) ch.on(e, done);
|
|
4176
4313
|
window.scrollTo(0, 0);
|
|
@@ -4583,12 +4720,12 @@ function serveDir(dir) {
|
|
|
4583
4720
|
res.writeHead(200, { "content-type": TYPES[extname(file)] ?? "application/octet-stream", "cache-control": "no-store" });
|
|
4584
4721
|
createReadStream(file).pipe(res);
|
|
4585
4722
|
});
|
|
4586
|
-
return new Promise((
|
|
4723
|
+
return new Promise((resolve5, reject) => {
|
|
4587
4724
|
server.once("error", reject);
|
|
4588
4725
|
server.listen(0, "127.0.0.1", () => {
|
|
4589
4726
|
const addr = server.address();
|
|
4590
4727
|
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
4591
|
-
|
|
4728
|
+
resolve5({ url: `http://127.0.0.1:${port}`, close: () => new Promise((r) => server.close(() => r())) });
|
|
4592
4729
|
});
|
|
4593
4730
|
});
|
|
4594
4731
|
}
|
|
@@ -4660,7 +4797,7 @@ async function removeWorktree(repo, dir) {
|
|
|
4660
4797
|
await git(repo, ["worktree", "prune"]).catch(() => void 0);
|
|
4661
4798
|
}
|
|
4662
4799
|
function run(cmd, args, cwd, log, root = cwd, env = {}) {
|
|
4663
|
-
return new Promise((
|
|
4800
|
+
return new Promise((resolve5, reject) => {
|
|
4664
4801
|
const bin = [.../* @__PURE__ */ new Set([join3(cwd, "node_modules", ".bin"), join3(root, "node_modules", ".bin")])].join(":");
|
|
4665
4802
|
const child = spawn(cmd, args, {
|
|
4666
4803
|
cwd,
|
|
@@ -4673,7 +4810,7 @@ function run(cmd, args, cwd, log, root = cwd, env = {}) {
|
|
|
4673
4810
|
child.stderr.on("data", keep);
|
|
4674
4811
|
child.on("error", reject);
|
|
4675
4812
|
child.on("close", (code) => {
|
|
4676
|
-
if (code === 0)
|
|
4813
|
+
if (code === 0) resolve5();
|
|
4677
4814
|
else {
|
|
4678
4815
|
log(tail);
|
|
4679
4816
|
reject(new Error(`${cmd} ${args.join(" ")} failed in ${cwd} (exit ${code})`));
|
|
@@ -5084,12 +5221,12 @@ async function captureFigmaReferences(o) {
|
|
|
5084
5221
|
}
|
|
5085
5222
|
const plan = JSON.stringify(j.plan.render);
|
|
5086
5223
|
await page.evaluate(
|
|
5087
|
-
({ id, plan: plan2 }) => new Promise((
|
|
5224
|
+
({ id, plan: plan2 }) => new Promise((resolve5) => {
|
|
5088
5225
|
const ch = window.__STORYBOOK_ADDONS_CHANNEL__;
|
|
5089
5226
|
const done = () => {
|
|
5090
5227
|
clearTimeout(timer);
|
|
5091
5228
|
ch.off("storyRendered", done);
|
|
5092
|
-
|
|
5229
|
+
resolve5();
|
|
5093
5230
|
};
|
|
5094
5231
|
const timer = setTimeout(done, 1e4);
|
|
5095
5232
|
ch.on("storyRendered", done);
|
|
@@ -5233,7 +5370,7 @@ Usage:
|
|
|
5233
5370
|
--branch: implement it on that exploration, never in a pull request to main
|
|
5234
5371
|
bridge request create --component Button --kind new-variant --title "Add a danger ghost variant"
|
|
5235
5372
|
--summary "..." [--change field=before:after ...] [--figma url]
|
|
5236
|
-
[--exploration explore/<name>] [--dry-run] [--json]
|
|
5373
|
+
[--exploration explore/<name>] [--product <key>] [--dry-run] [--json]
|
|
5237
5374
|
bridge request jira-sync
|
|
5238
5375
|
GitHub Action step: mirror a request issue to Jira (see docs/REQUESTS.md)
|
|
5239
5376
|
|
|
@@ -5388,6 +5525,7 @@ Claude Code is not installed (no \`claude\` on PATH). Install it (npm install -g
|
|
|
5388
5525
|
changes: (a.flags.change ?? []).map(parseChange),
|
|
5389
5526
|
...figma ? { figmaUrl: figma } : {},
|
|
5390
5527
|
...exploration ? { exploration } : {},
|
|
5528
|
+
...flag(a, "product") ? { product: flag(a, "product") } : {},
|
|
5391
5529
|
source: "cli",
|
|
5392
5530
|
dryRun
|
|
5393
5531
|
});
|
|
@@ -5433,7 +5571,7 @@ staging branch without touching main (an explore/<name> branch with a draft pull
|
|
|
5433
5571
|
|
|
5434
5572
|
Usage:
|
|
5435
5573
|
bridge explore start <name> --purpose "..." [--use-case ${EXPLORATION_USE_CASES.map((u) => u.value).join("|")}]
|
|
5436
|
-
[--experimental "..." ...] [--dry-run] [--json]
|
|
5574
|
+
[--experimental "..." ...] [--product <key>] [--dry-run] [--json]
|
|
5437
5575
|
GitHub token: the branch and the draft pull request on GitHub. Without one, in a git repo:
|
|
5438
5576
|
a local branch only, nothing pushed
|
|
5439
5577
|
bridge explore list [--all] [--json]
|
|
@@ -5471,7 +5609,7 @@ async function runExplore(a, root, io = defaultIO()) {
|
|
|
5471
5609
|
if (!name || !purpose) throw new RequestUsageError('Usage: bridge explore start <name> --purpose "What you want to learn"');
|
|
5472
5610
|
const useCase = flag2(a, "use-case");
|
|
5473
5611
|
const ctx = await context2(root, io);
|
|
5474
|
-
const r = await explorations.startExploration(ctx, { name, purpose, ...exploreSource(io.env), ...useCase ? { useCase } : {}, ...a.flags.experimental ? { experimental: a.flags.experimental } : {}, dryRun: has2(a, "dry-run") });
|
|
5612
|
+
const r = await explorations.startExploration(ctx, { name, purpose, ...exploreSource(io.env), ...useCase ? { useCase } : {}, ...a.flags.experimental ? { experimental: a.flags.experimental } : {}, ...flag2(a, "product") ? { product: flag2(a, "product") } : {}, dryRun: has2(a, "dry-run") });
|
|
5475
5613
|
if (json) return io.print(JSON.stringify(r, null, 2)), 0;
|
|
5476
5614
|
if (r.status === "dry-run") {
|
|
5477
5615
|
io.print(`Dry run, nothing written.
|
|
@@ -5541,6 +5679,231 @@ ${EXPLORE_HELP}`);
|
|
|
5541
5679
|
}
|
|
5542
5680
|
}
|
|
5543
5681
|
|
|
5682
|
+
// src/branch.ts
|
|
5683
|
+
import { readFile as readFile6 } from "node:fs/promises";
|
|
5684
|
+
import { resolve as resolve3 } from "node:path";
|
|
5685
|
+
import { BridgeContext as BridgeContext4, branches, studio } from "@bridge-ds/mcp";
|
|
5686
|
+
var BRANCH_HELP = `bridge branch: branches in review, the same actions as Bridge Studio
|
|
5687
|
+
|
|
5688
|
+
Usage:
|
|
5689
|
+
bridge branch list [--limit 20] [--json] what needs me: the review queue, mine first
|
|
5690
|
+
bridge branch status <n> [--json] status, approvals, tests, main, may I merge, next step
|
|
5691
|
+
bridge branch comments <n> [--json] the conversation and line threads (ids to reply to)
|
|
5692
|
+
bridge branch ready <n> send a draft for review (its author)
|
|
5693
|
+
bridge branch draft <n> back to draft (its author)
|
|
5694
|
+
bridge branch review <n> --approve [--body "..."] | --request-changes "..." | --comment "..."
|
|
5695
|
+
bridge branch ask <n> [--dry-run] ask someone else to review (who can give the missing approvals)
|
|
5696
|
+
bridge branch comment <n> "text" [--reply-to <id>]
|
|
5697
|
+
bridge branch merge <n> [--override "reason"] [--dry-run]
|
|
5698
|
+
same gate as the Merge button; --override: owner override,
|
|
5699
|
+
the reason is posted on the branch first
|
|
5700
|
+
bridge branch close <n> | bridge branch reopen <n>
|
|
5701
|
+
bridge branch update <n> [--choice <key>=mine|theirs ...] [--dry-run]
|
|
5702
|
+
update from main; token conflicts: pick per key
|
|
5703
|
+
bridge branch try <n> [--json] try it in a product, share it with a product team
|
|
5704
|
+
bridge branch combine <n> <n> [<n> <n>] [--pick <key>=<n> ...] [--json]
|
|
5705
|
+
try 2 to 4 token branches together (writes nothing)
|
|
5706
|
+
bridge products [--json] the products and the one this folder belongs to
|
|
5707
|
+
bridge settings [--json] roles, who ships, owner override, protection, products
|
|
5708
|
+
bridge coverage [Component] [--json] which components are in Figma
|
|
5709
|
+
bridge docs save <Component> --file docs.json [--pr <n>] [--title "..."] [--jira DS-42]
|
|
5710
|
+
[--product admin] [--dry-run] [--json]
|
|
5711
|
+
save usage guidance as in the Library edit mode
|
|
5712
|
+
|
|
5713
|
+
Start work for a product: bridge propose|explore start|request create ... --product <key> [--jira DS-42].
|
|
5714
|
+
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
5715
|
+
var flag3 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
5716
|
+
var has3 = (a, k) => Boolean(a.flags[k]);
|
|
5717
|
+
async function context3(root, io) {
|
|
5718
|
+
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
5719
|
+
return BridgeContext4.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
5720
|
+
}
|
|
5721
|
+
function number(a, at, usage) {
|
|
5722
|
+
const raw = a._[at];
|
|
5723
|
+
if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
|
|
5724
|
+
return branches.branchNumber(raw);
|
|
5725
|
+
}
|
|
5726
|
+
function pairs(list2, what) {
|
|
5727
|
+
const out = {};
|
|
5728
|
+
for (const s of list2 ?? []) {
|
|
5729
|
+
const i = s.lastIndexOf("=");
|
|
5730
|
+
if (i <= 0) throw new RequestUsageError(`--${what} needs key=value, got "${s}".`);
|
|
5731
|
+
out[s.slice(0, i)] = s.slice(i + 1);
|
|
5732
|
+
}
|
|
5733
|
+
return out;
|
|
5734
|
+
}
|
|
5735
|
+
var said = (r) => String(r.said ?? r.summary ?? r.status ?? "Done.");
|
|
5736
|
+
async function runBranch(a, root, io = defaultIO()) {
|
|
5737
|
+
const sub = a._[1];
|
|
5738
|
+
if (!sub || sub === "help" || has3(a, "help")) {
|
|
5739
|
+
io.print(BRANCH_HELP);
|
|
5740
|
+
return sub || has3(a, "help") ? 0 : 2;
|
|
5741
|
+
}
|
|
5742
|
+
const json = has3(a, "json");
|
|
5743
|
+
const ctx = await context3(root, io);
|
|
5744
|
+
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
5745
|
+
switch (sub) {
|
|
5746
|
+
case "list": {
|
|
5747
|
+
const limit = flag3(a, "limit");
|
|
5748
|
+
const r = await branches.whatNeedsMe(ctx, limit ? { limit: Number(limit) } : {});
|
|
5749
|
+
return out(r, () => {
|
|
5750
|
+
io.print(r.summary);
|
|
5751
|
+
for (const b of r.branches) io.print(`#${b.pr} ${b.statusLabel.padEnd(22)} ${b.title}${b.needsYou ? ` <- ${b.needsYou}` : ""}`);
|
|
5752
|
+
});
|
|
5753
|
+
}
|
|
5754
|
+
case "status": {
|
|
5755
|
+
const r = await branches.branchStatus(ctx, { pr: number(a, 2, "bridge branch status <n>") });
|
|
5756
|
+
return out(r, () => {
|
|
5757
|
+
io.print(`#${r.pr} ${r.title} (${r.product.name})`);
|
|
5758
|
+
io.print(`${r.status.label}. ${r.approvalNeeded} needed; approved by ${r.approvals.approvedBy.map((x) => `@${x}`).join(", ") || "nobody yet"}.`);
|
|
5759
|
+
if (r.merge.blockedBy?.length) for (const x of r.merge.blockedBy) io.print(` ${x}`);
|
|
5760
|
+
io.print(`Next: ${r.next}`);
|
|
5761
|
+
io.print(r.url);
|
|
5762
|
+
});
|
|
5763
|
+
}
|
|
5764
|
+
case "comments": {
|
|
5765
|
+
const r = await branches.listComments(ctx, { pr: number(a, 2, "bridge branch comments <n>") });
|
|
5766
|
+
return out(r, () => {
|
|
5767
|
+
for (const c of r.conversation) io.print(`@${c.author}: ${c.body}`);
|
|
5768
|
+
for (const t of r.threads) io.print(`[thread ${t.id}] ${t.file}:${t.line ?? ""} @${t.author}: ${t.body}${t.replies.length ? ` (${t.replies.length} replies)` : ""}`);
|
|
5769
|
+
});
|
|
5770
|
+
}
|
|
5771
|
+
case "ready": {
|
|
5772
|
+
const r = await branches.sendForReview(ctx, { pr: number(a, 2, "bridge branch ready <n>") });
|
|
5773
|
+
return out(r, () => io.print(r.said));
|
|
5774
|
+
}
|
|
5775
|
+
case "draft": {
|
|
5776
|
+
const r = await branches.backToDraft(ctx, { pr: number(a, 2, "bridge branch draft <n>") });
|
|
5777
|
+
return out(r, () => io.print(r.said));
|
|
5778
|
+
}
|
|
5779
|
+
case "review": {
|
|
5780
|
+
const n = number(a, 2, 'bridge branch review <n> --approve | --request-changes "..." | --comment "..."');
|
|
5781
|
+
const decision = has3(a, "approve") ? "approve" : has3(a, "request-changes") ? "request_changes" : has3(a, "comment") ? "comment" : null;
|
|
5782
|
+
if (!decision) throw new RequestUsageError('Say what you decide: --approve, --request-changes "what to change" or --comment "text".');
|
|
5783
|
+
const body = decision === "approve" ? flag3(a, "body") : flag3(a, decision === "request_changes" ? "request-changes" : "comment");
|
|
5784
|
+
const r = await branches.reviewBranch(ctx, { pr: n, decision, ...body && body !== "true" ? { body } : {} });
|
|
5785
|
+
return out(r, () => io.print(r.said));
|
|
5786
|
+
}
|
|
5787
|
+
case "ask": {
|
|
5788
|
+
const r = await branches.askSomeoneElse(ctx, { pr: number(a, 2, "bridge branch ask <n>"), dryRun: has3(a, "dry-run") });
|
|
5789
|
+
return out(r, () => io.print(r.said));
|
|
5790
|
+
}
|
|
5791
|
+
case "comment": {
|
|
5792
|
+
const n = number(a, 2, 'bridge branch comment <n> "text"');
|
|
5793
|
+
const body = a._.slice(3).join(" ") || flag3(a, "body");
|
|
5794
|
+
if (!body) throw new RequestUsageError('Usage: bridge branch comment <n> "text" [--reply-to <id>]');
|
|
5795
|
+
const reply = flag3(a, "reply-to");
|
|
5796
|
+
const r = await branches.commentOnBranch(ctx, { pr: n, body, ...reply ? { replyTo: Number(reply) } : {} });
|
|
5797
|
+
return out(r, () => io.print(r.said));
|
|
5798
|
+
}
|
|
5799
|
+
case "merge": {
|
|
5800
|
+
const n = number(a, 2, 'bridge branch merge <n> [--override "reason"] [--dry-run]');
|
|
5801
|
+
const reason = flag3(a, "override");
|
|
5802
|
+
const r = await branches.mergeBranch(ctx, { pr: n, ...reason ? { overrideReason: reason } : {}, dryRun: has3(a, "dry-run") });
|
|
5803
|
+
return out(r, () => io.print(r.said));
|
|
5804
|
+
}
|
|
5805
|
+
case "close": {
|
|
5806
|
+
const r = await branches.closeBranch(ctx, { pr: number(a, 2, "bridge branch close <n>") });
|
|
5807
|
+
return out(r, () => io.print(r.said));
|
|
5808
|
+
}
|
|
5809
|
+
case "reopen": {
|
|
5810
|
+
const r = await branches.reopenBranch(ctx, { pr: number(a, 2, "bridge branch reopen <n>") });
|
|
5811
|
+
return out(r, () => io.print(r.said));
|
|
5812
|
+
}
|
|
5813
|
+
case "update": {
|
|
5814
|
+
const n = number(a, 2, "bridge branch update <n> [--choice key=mine|theirs] [--dry-run]");
|
|
5815
|
+
const choices = pairs(a.flags.choice, "choice");
|
|
5816
|
+
for (const [k, v] of Object.entries(choices)) if (v !== "mine" && v !== "theirs") throw new RequestUsageError(`--choice ${k}= takes mine or theirs, got "${v}".`);
|
|
5817
|
+
const r = await branches.updateFromMain(ctx, { pr: n, ...Object.keys(choices).length ? { choices } : {}, dryRun: has3(a, "dry-run") });
|
|
5818
|
+
return out(r, () => {
|
|
5819
|
+
io.print(said(r));
|
|
5820
|
+
for (const c of r.conflicts ?? []) io.print(` ${c.key} (${c.token}, default ${c.default ?? "none: pick one"})`);
|
|
5821
|
+
});
|
|
5822
|
+
}
|
|
5823
|
+
case "try": {
|
|
5824
|
+
const r = await branches.tryBranch(ctx, { pr: number(a, 2, "bridge branch try <n>") });
|
|
5825
|
+
return out(r, () => {
|
|
5826
|
+
for (const p of r.tryInProduct) io.print(`${p.name}: ${p.url ?? p.how}`);
|
|
5827
|
+
io.print(`
|
|
5828
|
+
Share with a product team: ${r.share.install ? r.share.install : r.share.sentence}`);
|
|
5829
|
+
});
|
|
5830
|
+
}
|
|
5831
|
+
case "combine": {
|
|
5832
|
+
const prs = a._.slice(2);
|
|
5833
|
+
const picks = Object.fromEntries(Object.entries(pairs(a.flags.pick, "pick")).map(([k, v]) => [k, branches.branchNumber(v)]));
|
|
5834
|
+
const r = await branches.combineBranches(ctx, { prs, ...Object.keys(picks).length ? { picks } : {} });
|
|
5835
|
+
return out(r, () => {
|
|
5836
|
+
io.print(`${r.tokens} tokens change together.`);
|
|
5837
|
+
for (const c of r.conflicts) io.print(` ${c.key}: pick one of ${c.options.map((o) => `#${o.use}`).join(", ")}${c.picked ? ` (picked #${c.picked})` : ""}`);
|
|
5838
|
+
for (const [k, v] of Object.entries(r.open)) io.print(`${k}: ${v}`);
|
|
5839
|
+
io.print(r.next);
|
|
5840
|
+
});
|
|
5841
|
+
}
|
|
5842
|
+
default:
|
|
5843
|
+
throw new RequestUsageError(`Unknown branch command "${sub}".
|
|
5844
|
+
|
|
5845
|
+
${BRANCH_HELP}`);
|
|
5846
|
+
}
|
|
5847
|
+
}
|
|
5848
|
+
async function runStudioRead(cmd, a, root, io = defaultIO()) {
|
|
5849
|
+
const ctx = await context3(root, io);
|
|
5850
|
+
const json = has3(a, "json");
|
|
5851
|
+
if (cmd === "products") {
|
|
5852
|
+
const r2 = await branches.listProducts(ctx);
|
|
5853
|
+
if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
|
|
5854
|
+
for (const p of r2.products) io.print(`${p.key === r2.current.key ? "*" : " "} ${p.key.padEnd(12)} ${p.name}${p.default ? " (default)" : ""}${p.path ? ` ${p.path}` : ""}${p.repo ? ` ${p.repo}` : ""}`);
|
|
5855
|
+
io.print(`
|
|
5856
|
+
This folder: ${r2.current.name}. Pick another with --product <key>.`);
|
|
5857
|
+
return 0;
|
|
5858
|
+
}
|
|
5859
|
+
if (cmd === "settings") {
|
|
5860
|
+
const r2 = await studio.getSettings(ctx);
|
|
5861
|
+
if (json) return io.print(JSON.stringify(r2, null, 2)), 0;
|
|
5862
|
+
io.print(`Design approval: ${r2.roles.designApproval.join(", ") || "any reviewer"}`);
|
|
5863
|
+
io.print(`Tech approval: ${r2.roles.techApproval.join(", ") || "any reviewer"}`);
|
|
5864
|
+
io.print(`Merge: ${r2.roles.ship}`);
|
|
5865
|
+
io.print(`Owner override: ${r2.ownerOverride.on ? `${r2.ownerOverride.who.join(", ")}. ${r2.ownerOverride.reason}` : r2.ownerOverride.reason}`);
|
|
5866
|
+
io.print(`Protection: ${r2.protection}`);
|
|
5867
|
+
io.print(`Products: ${r2.products.map((p) => p.name).join(", ")}`);
|
|
5868
|
+
return 0;
|
|
5869
|
+
}
|
|
5870
|
+
const r = await studio.figmaCoverage(ctx, a._[1] ? { component: a._[1] } : {});
|
|
5871
|
+
if (json) return io.print(JSON.stringify(r, null, 2)), 0;
|
|
5872
|
+
io.print(r.summary);
|
|
5873
|
+
for (const c of r.components) io.print(` ${c.name.padEnd(20)} ${c.inFigma}${c.why ? ` (${c.why})` : ""}`);
|
|
5874
|
+
if (r.fidelity) io.print(r.fidelity);
|
|
5875
|
+
return 0;
|
|
5876
|
+
}
|
|
5877
|
+
async function runDocsSave(a, root, io = defaultIO()) {
|
|
5878
|
+
const component = a._[2];
|
|
5879
|
+
const file = flag3(a, "file");
|
|
5880
|
+
if (!component || !file) throw new RequestUsageError('Usage: bridge docs save <Component> --file docs.json [--pr <n>] [--title "..."] [--jira DS-42] [--product admin] [--dry-run]');
|
|
5881
|
+
let docs;
|
|
5882
|
+
try {
|
|
5883
|
+
docs = JSON.parse(await readFile6(resolve3(root, file), "utf8"));
|
|
5884
|
+
} catch (e) {
|
|
5885
|
+
throw new RequestUsageError(`${file} is not a readable JSON file: ${e.message}`);
|
|
5886
|
+
}
|
|
5887
|
+
const ctx = await context3(root, io);
|
|
5888
|
+
const pr = flag3(a, "pr");
|
|
5889
|
+
const r = await studio.saveDocs(ctx, {
|
|
5890
|
+
component,
|
|
5891
|
+
docs,
|
|
5892
|
+
...pr ? { pr } : {},
|
|
5893
|
+
...flag3(a, "title") ? { title: flag3(a, "title") } : {},
|
|
5894
|
+
...flag3(a, "notes") ? { notes: flag3(a, "notes") } : {},
|
|
5895
|
+
...flag3(a, "jira") ? { jira: flag3(a, "jira") } : {},
|
|
5896
|
+
...flag3(a, "product") ? { product: flag3(a, "product") } : {},
|
|
5897
|
+
dryRun: has3(a, "dry-run")
|
|
5898
|
+
});
|
|
5899
|
+
if (has3(a, "json")) io.print(JSON.stringify(r, null, 2));
|
|
5900
|
+
else io.print(r.status === "dry-run" || r.status === "preview" ? `${r.said ?? "Dry run, nothing written."}
|
|
5901
|
+
|
|
5902
|
+
${r.path}:
|
|
5903
|
+
${r.content}` : String(r.said));
|
|
5904
|
+
return 0;
|
|
5905
|
+
}
|
|
5906
|
+
|
|
5544
5907
|
// src/protection.ts
|
|
5545
5908
|
var UNKNOWN = { kind: "unknown", missing: ["review", "checks", "history", "deletion"] };
|
|
5546
5909
|
async function protectionReport(opts) {
|
|
@@ -5573,7 +5936,7 @@ async function rolesReport(opts) {
|
|
|
5573
5936
|
}
|
|
5574
5937
|
|
|
5575
5938
|
// src/index.ts
|
|
5576
|
-
import { brand, BridgeContext as
|
|
5939
|
+
import { brand, BridgeContext as BridgeContext5, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
|
|
5577
5940
|
var MCP_PACKAGE = "@bridge-ds/mcp";
|
|
5578
5941
|
var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
|
|
5579
5942
|
|
|
@@ -5588,9 +5951,13 @@ Usage:
|
|
|
5588
5951
|
bridge tokens <query> [--json] search tokens by name, description or value
|
|
5589
5952
|
bridge diff <base> [--json|--md] designer-readable token diff: working tree vs <base> (git ref)
|
|
5590
5953
|
bridge propose --title "..." --set path=value [--set ...] [--file f | --mode theme=dark]
|
|
5591
|
-
[--edits edits.json] [--summary "..."] [--branch explore/<name>] [--
|
|
5954
|
+
[--edits edits.json] [--summary "..."] [--branch explore/<name>] [--pr <n>]
|
|
5955
|
+
[--product <key>] [--jira DS-42] [--dry-run]
|
|
5592
5956
|
validate + contrast + new local bridge/* branch (never pushes).
|
|
5593
|
-
--branch: write the edits to the checked-out exploration instead
|
|
5957
|
+
--branch: write the edits to the checked-out exploration instead.
|
|
5958
|
+
--pr: add them to that branch in review (GitHub token).
|
|
5959
|
+
--product: the product it is for (bridge products); --jira: the
|
|
5960
|
+
ticket key, first in the branch name and title
|
|
5594
5961
|
bridge brand list [--json] brands: slug, name, default, primary action color, theme overrides
|
|
5595
5962
|
bridge brand create <slug> --from bridge --primary <hex|hue> --neutral <hex|hue>
|
|
5596
5963
|
[--name "Acme"] [--accent x] [--link x] [--radius soft|balanced|crisp|i,c,co,o]
|
|
@@ -5646,6 +6013,16 @@ Usage:
|
|
|
5646
6013
|
explorations: an explore/<name> branch with a draft pull request
|
|
5647
6014
|
labeled exploration, previews and a snapshot package for a product
|
|
5648
6015
|
staging branch. Never merged as is: promote it to review or close it
|
|
6016
|
+
bridge branch list|status|comments|ready|draft|review|ask|comment|merge|close|reopen|update|try|combine
|
|
6017
|
+
branches in review, the same actions as Bridge Studio
|
|
6018
|
+
(\`bridge branch help\`): what needs me, send for review, review,
|
|
6019
|
+
merge through the same gate (owner override with a reason), update
|
|
6020
|
+
from main with token conflicts, try it in a product, try together
|
|
6021
|
+
bridge products | bridge settings | bridge coverage [Component]
|
|
6022
|
+
products (and this folder's), Studio settings safe to read, which
|
|
6023
|
+
components are in Figma
|
|
6024
|
+
bridge docs save <Component> --file docs.json [--pr n] [--jira DS-42] [--product key] [--dry-run]
|
|
6025
|
+
save usage guidance as in the Library edit mode (GitHub token)
|
|
5649
6026
|
bridge mcp [--root dir | --repo owner/name] start the MCP server on stdio
|
|
5650
6027
|
|
|
5651
6028
|
Options:
|
|
@@ -5662,23 +6039,23 @@ function parse(argv) {
|
|
|
5662
6039
|
const [k, inline] = a.slice(2).split(/=(.*)/s, 2);
|
|
5663
6040
|
const next = argv[i + 1];
|
|
5664
6041
|
const fileArg = k === "json" && /\.json$/i.test(next ?? "") || k === "md" && /\.(md|markdown)$/i.test(next ?? "");
|
|
5665
|
-
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"].includes(k));
|
|
6042
|
+
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));
|
|
5666
6043
|
(out.flags[k] ??= []).push(inline ?? (takesValue ? argv[++i] : "true"));
|
|
5667
6044
|
} else out._.push(a);
|
|
5668
6045
|
}
|
|
5669
6046
|
return out;
|
|
5670
6047
|
}
|
|
5671
|
-
var
|
|
5672
|
-
var
|
|
6048
|
+
var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
6049
|
+
var has4 = (a, k) => Boolean(a.flags[k]);
|
|
5673
6050
|
var print = (s) => process.stdout.write(`${s}
|
|
5674
6051
|
`);
|
|
5675
6052
|
var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
|
|
5676
6053
|
async function init(a, root) {
|
|
5677
6054
|
const cfgPath = join6(root, CONFIG_FILE2);
|
|
5678
6055
|
let config;
|
|
5679
|
-
if (existsSync6(cfgPath) && !
|
|
6056
|
+
if (existsSync6(cfgPath) && !has4(a, "force")) {
|
|
5680
6057
|
print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
|
|
5681
|
-
config = (await (await
|
|
6058
|
+
config = (await (await BridgeContext5.create({ root })).config()).config;
|
|
5682
6059
|
} else {
|
|
5683
6060
|
const src = new LocalSource(root);
|
|
5684
6061
|
const files = await src.list("");
|
|
@@ -5694,7 +6071,7 @@ async function init(a, root) {
|
|
|
5694
6071
|
const inRepo = !relServer.startsWith("..") && !isAbsolute(relServer);
|
|
5695
6072
|
const server = (dir) => inRepo ? { command: "node", args: [relServer, "--root", dir] } : { command: "npx", args: ["-y", MCP_PACKAGE, "--root", dir] };
|
|
5696
6073
|
const mcpPath = join6(root, ".mcp.json");
|
|
5697
|
-
const existing = existsSync6(mcpPath) ? JSON.parse(await
|
|
6074
|
+
const existing = existsSync6(mcpPath) ? JSON.parse(await readFile7(mcpPath, "utf8")) : {};
|
|
5698
6075
|
existing.mcpServers = { ...existing.mcpServers ?? {}, bridge: server(".") };
|
|
5699
6076
|
await writeFile5(mcpPath, `${JSON.stringify(existing, null, 2)}
|
|
5700
6077
|
`);
|
|
@@ -5706,14 +6083,14 @@ async function init(a, root) {
|
|
|
5706
6083
|
print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
|
|
5707
6084
|
}
|
|
5708
6085
|
async function check(a, root) {
|
|
5709
|
-
const ctx = await
|
|
6086
|
+
const ctx = await BridgeContext5.create({ root });
|
|
5710
6087
|
const v = await ops.validateTokens(ctx, { includeWarnings: true });
|
|
5711
6088
|
const c = await ops.checkContrast(ctx, {});
|
|
5712
6089
|
const warnings = "warnings" in v ? v.warnings ?? [] : [];
|
|
5713
|
-
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 ||
|
|
6090
|
+
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has4(a, "strict") && warnings.length > 0;
|
|
5714
6091
|
const protection = await protectionOf(a, root, ctx);
|
|
5715
6092
|
const roles = await rolesOf(a, root, ctx);
|
|
5716
|
-
if (
|
|
6093
|
+
if (has4(a, "json")) {
|
|
5717
6094
|
print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
|
|
5718
6095
|
return failed ? 1 : 0;
|
|
5719
6096
|
}
|
|
@@ -5732,7 +6109,7 @@ async function check(a, root) {
|
|
|
5732
6109
|
return failed ? 1 : 0;
|
|
5733
6110
|
}
|
|
5734
6111
|
async function protectionOf(a, root, ctx) {
|
|
5735
|
-
if (
|
|
6112
|
+
if (has4(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
5736
6113
|
const { config } = await ctx.config();
|
|
5737
6114
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
5738
6115
|
if (!repo) return void 0;
|
|
@@ -5740,7 +6117,7 @@ async function protectionOf(a, root, ctx) {
|
|
|
5740
6117
|
return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
|
|
5741
6118
|
}
|
|
5742
6119
|
async function rolesOf(a, root, ctx) {
|
|
5743
|
-
if (
|
|
6120
|
+
if (has4(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
5744
6121
|
const { config } = await ctx.config();
|
|
5745
6122
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
5746
6123
|
if (!repo) return void 0;
|
|
@@ -5750,9 +6127,9 @@ async function rolesOf(a, root, ctx) {
|
|
|
5750
6127
|
async function tokens(a, root) {
|
|
5751
6128
|
const q = a._.slice(1).join(" ");
|
|
5752
6129
|
if (!q) throw new UsageError("bridge tokens <query>");
|
|
5753
|
-
const ctx = await
|
|
5754
|
-
const r = await ops.searchTokens(ctx, { query: q, limit: Number(
|
|
5755
|
-
if (
|
|
6130
|
+
const ctx = await BridgeContext5.create({ root });
|
|
6131
|
+
const r = await ops.searchTokens(ctx, { query: q, limit: Number(flag4(a, "limit") ?? 20) });
|
|
6132
|
+
if (has4(a, "json")) return print(JSON.stringify(r, null, 2));
|
|
5756
6133
|
if (!r.results.length) return print(`No token matches "${q}".`);
|
|
5757
6134
|
const w = Math.max(...r.results.map((x) => x.path.length));
|
|
5758
6135
|
for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
|
|
@@ -5760,20 +6137,20 @@ async function tokens(a, root) {
|
|
|
5760
6137
|
async function diff(a, root) {
|
|
5761
6138
|
const base = a._[1];
|
|
5762
6139
|
if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
|
|
5763
|
-
const ctx = await
|
|
6140
|
+
const ctx = await BridgeContext5.create({ root });
|
|
5764
6141
|
const { config } = await ctx.config();
|
|
5765
6142
|
const before = await core.loadTokenSet(new GitRefSource(root, base), config);
|
|
5766
6143
|
const after = await ctx.tokens();
|
|
5767
6144
|
const changes = core.diff(before, after);
|
|
5768
|
-
const
|
|
5769
|
-
const delta2 =
|
|
6145
|
+
const pairs2 = await core.readContrastPairs(ctx.source, config).catch(() => []);
|
|
6146
|
+
const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
|
|
5770
6147
|
const tier = core.classify(changes, [], config);
|
|
5771
6148
|
const imp = core.impact(before, after, changes, 30);
|
|
5772
|
-
if (
|
|
6149
|
+
if (has4(a, "json")) {
|
|
5773
6150
|
print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
|
|
5774
6151
|
return 0;
|
|
5775
6152
|
}
|
|
5776
|
-
if (
|
|
6153
|
+
if (has4(a, "md")) {
|
|
5777
6154
|
print(`## Token changes since \`${base}\`
|
|
5778
6155
|
|
|
5779
6156
|
${core.tokenTable(changes)}
|
|
@@ -5808,24 +6185,37 @@ ${imp.total} other token(s) change value through aliases:`);
|
|
|
5808
6185
|
return delta2.some((d) => d.passBefore && !d.passAfter) ? 1 : 0;
|
|
5809
6186
|
}
|
|
5810
6187
|
async function propose(a, root) {
|
|
5811
|
-
const title =
|
|
6188
|
+
const title = flag4(a, "title");
|
|
5812
6189
|
if (!title) throw new UsageError('bridge propose --title "Darken muted text" --set color.text.muted={palette.neutral.700} --mode theme=light');
|
|
5813
6190
|
const edits = [];
|
|
5814
|
-
const file =
|
|
5815
|
-
const mode2 = modeArg(
|
|
6191
|
+
const file = flag4(a, "file");
|
|
6192
|
+
const mode2 = modeArg(flag4(a, "mode"));
|
|
5816
6193
|
for (const s of a.flags.set ?? []) {
|
|
5817
6194
|
const i = s.indexOf("=");
|
|
5818
6195
|
if (i < 0) throw new UsageError(`--set needs path=value, got "${s}"`);
|
|
5819
6196
|
edits.push({ op: "set", path: s.slice(0, i), value: s.slice(i + 1), ...file ? { file } : {}, ...mode2 ? { mode: mode2 } : {} });
|
|
5820
6197
|
}
|
|
5821
|
-
const editsFile =
|
|
5822
|
-
if (editsFile) edits.push(...JSON.parse(await
|
|
6198
|
+
const editsFile = flag4(a, "edits");
|
|
6199
|
+
if (editsFile) edits.push(...JSON.parse(await readFile7(resolve4(editsFile), "utf8")));
|
|
5823
6200
|
if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
|
|
5824
|
-
const
|
|
5825
|
-
const
|
|
5826
|
-
const
|
|
5827
|
-
const
|
|
5828
|
-
|
|
6201
|
+
const pr = flag4(a, "pr");
|
|
6202
|
+
const token = pr ? process.env.BRIDGE_GITHUB_TOKEN || process.env.GITHUB_TOKEN || defaultIO().ghToken() : void 0;
|
|
6203
|
+
const ctx = await BridgeContext5.create({ root, ...token ? { token } : {} });
|
|
6204
|
+
const summary = flag4(a, "summary");
|
|
6205
|
+
const branch = flag4(a, "branch");
|
|
6206
|
+
const jira = flag4(a, "jira");
|
|
6207
|
+
const product = flag4(a, "product");
|
|
6208
|
+
const r = await ops.proposeTokenChange(ctx, {
|
|
6209
|
+
title,
|
|
6210
|
+
edits,
|
|
6211
|
+
...summary ? { summary } : {},
|
|
6212
|
+
...branch ? { branch } : {},
|
|
6213
|
+
...pr ? { pr } : {},
|
|
6214
|
+
...jira ? { jira } : {},
|
|
6215
|
+
...product ? { product } : {},
|
|
6216
|
+
dryRun: has4(a, "dry-run")
|
|
6217
|
+
});
|
|
6218
|
+
if (has4(a, "json")) print(JSON.stringify(r, null, 2));
|
|
5829
6219
|
else {
|
|
5830
6220
|
print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
|
|
5831
6221
|
if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
|
|
@@ -5840,17 +6230,17 @@ ${r.prBody}`);
|
|
|
5840
6230
|
}
|
|
5841
6231
|
async function brandCmd(a, root) {
|
|
5842
6232
|
const sub = a._[1];
|
|
5843
|
-
const ctx = await
|
|
6233
|
+
const ctx = await BridgeContext5.create({ root });
|
|
5844
6234
|
if (sub === "list") {
|
|
5845
6235
|
const r2 = await brand.listBrands(ctx);
|
|
5846
|
-
if (
|
|
6236
|
+
if (has4(a, "json")) print(JSON.stringify(r2.brands, null, 2));
|
|
5847
6237
|
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(", ")}` : ""}`);
|
|
5848
6238
|
return 0;
|
|
5849
6239
|
}
|
|
5850
6240
|
if (sub !== "create") throw new UsageError('bridge brand list | bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
|
|
5851
6241
|
const slug = a._[2];
|
|
5852
6242
|
if (!slug) throw new UsageError('bridge brand create <slug> --from bridge --primary "#1F5F3A" --neutral "#2A2F2C"');
|
|
5853
|
-
const opt = (k) =>
|
|
6243
|
+
const opt = (k) => flag4(a, k);
|
|
5854
6244
|
const fonts = { ...opt("heading") ? { heading: opt("heading") } : {}, ...opt("text") ? { text: opt("text") } : {} };
|
|
5855
6245
|
const r = await brand.proposeBrand(ctx, {
|
|
5856
6246
|
slug,
|
|
@@ -5863,9 +6253,9 @@ async function brandCmd(a, root) {
|
|
|
5863
6253
|
...opt("radius") ? { radius: opt("radius") } : {},
|
|
5864
6254
|
...Object.keys(fonts).length ? { fonts } : {},
|
|
5865
6255
|
...opt("notes") ? { notes: opt("notes") } : {},
|
|
5866
|
-
dryRun:
|
|
6256
|
+
dryRun: has4(a, "dry-run")
|
|
5867
6257
|
});
|
|
5868
|
-
if (
|
|
6258
|
+
if (has4(a, "json")) {
|
|
5869
6259
|
const { fileContents: _f, ...rest } = r;
|
|
5870
6260
|
print(JSON.stringify(rest, null, 2));
|
|
5871
6261
|
return r.status === "blocked" ? 1 : 0;
|
|
@@ -5886,7 +6276,7 @@ async function brandCmd(a, root) {
|
|
|
5886
6276
|
return r.status === "blocked" ? 1 : 0;
|
|
5887
6277
|
}
|
|
5888
6278
|
async function figmaReferences(a, root) {
|
|
5889
|
-
let sha =
|
|
6279
|
+
let sha = flag4(a, "sha");
|
|
5890
6280
|
if (!sha) {
|
|
5891
6281
|
try {
|
|
5892
6282
|
sha = execFileSync2("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
@@ -5894,49 +6284,49 @@ async function figmaReferences(a, root) {
|
|
|
5894
6284
|
sha = "unknown";
|
|
5895
6285
|
}
|
|
5896
6286
|
}
|
|
5897
|
-
const only = (
|
|
6287
|
+
const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
|
|
5898
6288
|
const r = await captureFigmaReferences({
|
|
5899
|
-
storybook:
|
|
5900
|
-
specs:
|
|
5901
|
-
out:
|
|
6289
|
+
storybook: resolve4(root, flag4(a, "storybook") ?? "storybook-static"),
|
|
6290
|
+
specs: resolve4(root, flag4(a, "specs") ?? "packages/react/component-specs.json"),
|
|
6291
|
+
out: resolve4(root, flag4(a, "out") ?? "figma-references"),
|
|
5902
6292
|
sha,
|
|
5903
6293
|
...only.length ? { only } : {},
|
|
5904
|
-
log:
|
|
6294
|
+
log: has4(a, "json") ? void 0 : (line) => print(line)
|
|
5905
6295
|
});
|
|
5906
|
-
if (
|
|
6296
|
+
if (has4(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
|
|
5907
6297
|
return 0;
|
|
5908
6298
|
}
|
|
5909
6299
|
async function impact(a, root) {
|
|
5910
|
-
const only = (
|
|
6300
|
+
const only = (flag4(a, "only") ?? "").split(",").filter(Boolean);
|
|
5911
6301
|
for (const x of only) if (x !== "stories" && x !== "app") throw new UsageError(`--only takes stories or app, got "${x}"`);
|
|
5912
|
-
const num2 = (k) =>
|
|
6302
|
+
const num2 = (k) => flag4(a, k) !== void 0 ? Number(flag4(a, k)) : void 0;
|
|
5913
6303
|
const threshold = num2("threshold");
|
|
5914
6304
|
if (threshold !== void 0 && !(threshold >= 0 && threshold <= 1)) throw new UsageError("--threshold is between 0 and 1 (pixelmatch color distance)");
|
|
5915
|
-
const imageBase =
|
|
5916
|
-
const artifactUrl =
|
|
5917
|
-
const out =
|
|
5918
|
-
const cacheDir =
|
|
6305
|
+
const imageBase = flag4(a, "image-base");
|
|
6306
|
+
const artifactUrl = flag4(a, "artifact-url");
|
|
6307
|
+
const out = flag4(a, "out");
|
|
6308
|
+
const cacheDir = flag4(a, "cache");
|
|
5919
6309
|
const maxStories = num2("max-stories");
|
|
5920
6310
|
const r = await runImpact({
|
|
5921
6311
|
root,
|
|
5922
|
-
base:
|
|
5923
|
-
head:
|
|
5924
|
-
...out ? { out:
|
|
6312
|
+
base: flag4(a, "base") ?? "main",
|
|
6313
|
+
head: flag4(a, "head") ?? "HEAD",
|
|
6314
|
+
...out ? { out: resolve4(out) } : {},
|
|
5925
6315
|
...only.length ? { only } : {},
|
|
5926
6316
|
...threshold !== void 0 ? { threshold } : {},
|
|
5927
|
-
...cacheDir ? { cacheDir:
|
|
6317
|
+
...cacheDir ? { cacheDir: resolve4(cacheDir) } : {},
|
|
5928
6318
|
...maxStories ? { maxStoriesPerComponent: maxStories } : {},
|
|
5929
6319
|
...imageBase !== void 0 ? { imageBase } : {},
|
|
5930
6320
|
...artifactUrl ? { artifactUrl } : {},
|
|
5931
|
-
plan:
|
|
5932
|
-
keepUnchanged:
|
|
6321
|
+
plan: has4(a, "plan"),
|
|
6322
|
+
keepUnchanged: has4(a, "keep-unchanged")
|
|
5933
6323
|
});
|
|
5934
|
-
if (
|
|
6324
|
+
if (has4(a, "json")) {
|
|
5935
6325
|
print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
|
|
5936
6326
|
return 0;
|
|
5937
6327
|
}
|
|
5938
6328
|
const sel = r.manifest.selection;
|
|
5939
|
-
if (
|
|
6329
|
+
if (has4(a, "plan")) {
|
|
5940
6330
|
print(`Plan ${r.manifest.base.ref} (${r.manifest.base.sha.slice(0, 7)}) -> ${r.manifest.head.ref} (${r.manifest.head.sha.slice(0, 7)})`);
|
|
5941
6331
|
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(", ")})` : ""}`);
|
|
5942
6332
|
print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
|
|
@@ -5952,12 +6342,12 @@ async function impact(a, root) {
|
|
|
5952
6342
|
}
|
|
5953
6343
|
var day = (iso) => iso.slice(0, 10);
|
|
5954
6344
|
async function historyCmd(a, root) {
|
|
5955
|
-
const ctx = await
|
|
6345
|
+
const ctx = await BridgeContext5.create({ root });
|
|
5956
6346
|
const token = a._[1];
|
|
5957
|
-
const limit = Number(
|
|
6347
|
+
const limit = Number(flag4(a, "limit") ?? 20);
|
|
5958
6348
|
if (token) {
|
|
5959
6349
|
const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
|
|
5960
|
-
if (
|
|
6350
|
+
if (has4(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
|
|
5961
6351
|
if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
|
|
5962
6352
|
print(`${r2.path}: ${r2.total} change(s) (${r2.source})
|
|
5963
6353
|
`);
|
|
@@ -5968,9 +6358,9 @@ async function historyCmd(a, root) {
|
|
|
5968
6358
|
}
|
|
5969
6359
|
return 0;
|
|
5970
6360
|
}
|
|
5971
|
-
const tier =
|
|
5972
|
-
const r = await hist.getHistory(ctx, { limit, ...
|
|
5973
|
-
if (
|
|
6361
|
+
const tier = flag4(a, "tier");
|
|
6362
|
+
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") } : {}, ...has4(a, "breaking") ? { breaking: true } : {} });
|
|
6363
|
+
if (has4(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
|
|
5974
6364
|
print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
|
|
5975
6365
|
if (!r.total) return print(r.note ?? "No token change matches."), 0;
|
|
5976
6366
|
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" : ""}`);
|
|
@@ -5988,10 +6378,10 @@ ${r.truncated}`);
|
|
|
5988
6378
|
async function revertCmd(a, root) {
|
|
5989
6379
|
const target = a._[1];
|
|
5990
6380
|
if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
|
|
5991
|
-
const ctx = await
|
|
6381
|
+
const ctx = await BridgeContext5.create({ root });
|
|
5992
6382
|
const resolutions = Object.fromEntries((a.flags["use-before"] ?? []).map((k) => [k, "use-before"]));
|
|
5993
|
-
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts:
|
|
5994
|
-
if (
|
|
6383
|
+
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has4(a, "restore-conflicts"), dryRun: has4(a, "dry-run") });
|
|
6384
|
+
if (has4(a, "json")) {
|
|
5995
6385
|
print(JSON.stringify(r, null, 2));
|
|
5996
6386
|
return r.status === "blocked" ? 1 : 0;
|
|
5997
6387
|
}
|
|
@@ -6016,25 +6406,25 @@ async function audit(a, root) {
|
|
|
6016
6406
|
const target = a._[1];
|
|
6017
6407
|
if (!target) throw new UsageError("bridge audit <path> (a product folder or file that uses the design system)");
|
|
6018
6408
|
if (!existsSync6(target)) throw new UsageError(`No such file or folder: ${target}`);
|
|
6019
|
-
const dsRoot =
|
|
6409
|
+
const dsRoot = flag4(a, "ds") ? resolve4(flag4(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
|
|
6020
6410
|
if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
|
|
6021
6411
|
const ds = loadDesignSystem(dsRoot);
|
|
6022
|
-
if (
|
|
6023
|
-
const write =
|
|
6412
|
+
if (has4(a, "fix")) {
|
|
6413
|
+
const write = has4(a, "write");
|
|
6024
6414
|
const changes = fixCss(target, ds.index, write);
|
|
6025
6415
|
const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
|
|
6026
6416
|
for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
|
|
6027
6417
|
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.");
|
|
6028
6418
|
return 0;
|
|
6029
6419
|
}
|
|
6030
|
-
const baselinePath =
|
|
6420
|
+
const baselinePath = flag4(a, "baseline");
|
|
6031
6421
|
let baseline;
|
|
6032
6422
|
if (baselinePath) {
|
|
6033
6423
|
if (!existsSync6(baselinePath)) throw new UsageError(`Baseline not found: ${baselinePath}`);
|
|
6034
|
-
baseline = JSON.parse(await
|
|
6424
|
+
baseline = JSON.parse(await readFile7(baselinePath, "utf8"));
|
|
6035
6425
|
}
|
|
6036
|
-
const label =
|
|
6037
|
-
const allowlistPath =
|
|
6426
|
+
const label = flag4(a, "label");
|
|
6427
|
+
const allowlistPath = flag4(a, "allowlist");
|
|
6038
6428
|
if (allowlistPath && !existsSync6(allowlistPath)) throw new UsageError(`Allowlist not found: ${allowlistPath}`);
|
|
6039
6429
|
let allowlist;
|
|
6040
6430
|
try {
|
|
@@ -6044,8 +6434,8 @@ async function audit(a, root) {
|
|
|
6044
6434
|
}
|
|
6045
6435
|
const report = runAudit(target, { ds: dsRoot, version: VERSION, ...label ? { label } : {}, ...baseline ? { baseline } : {}, ...allowlist ? { allowlist } : {} });
|
|
6046
6436
|
const md = auditMarkdown(report);
|
|
6047
|
-
const jsonOut =
|
|
6048
|
-
const mdOut =
|
|
6437
|
+
const jsonOut = flag4(a, "json");
|
|
6438
|
+
const mdOut = flag4(a, "md");
|
|
6049
6439
|
if (jsonOut && jsonOut !== "true") await writeFile5(jsonOut, `${JSON.stringify(report, null, 2)}
|
|
6050
6440
|
`);
|
|
6051
6441
|
if (mdOut && mdOut !== "true") await writeFile5(mdOut, `${md}
|
|
@@ -6085,7 +6475,7 @@ async function audit(a, root) {
|
|
|
6085
6475
|
for (const f of report.files) for (const e of f.ignoreErrors ?? []) process.stderr.write(`${report.target.path}/${f.path}:${e.line}: ${e.message}
|
|
6086
6476
|
`);
|
|
6087
6477
|
let code = 0;
|
|
6088
|
-
const failUnder =
|
|
6478
|
+
const failUnder = flag4(a, "fail-under");
|
|
6089
6479
|
if (failUnder !== void 0) {
|
|
6090
6480
|
const min = Number(failUnder);
|
|
6091
6481
|
if (!Number.isFinite(min)) throw new UsageError("--fail-under needs a number, e.g. --fail-under 80");
|
|
@@ -6096,7 +6486,7 @@ async function audit(a, root) {
|
|
|
6096
6486
|
code = 1;
|
|
6097
6487
|
}
|
|
6098
6488
|
}
|
|
6099
|
-
if (
|
|
6489
|
+
if (has4(a, "no-regress") && report.baseline) {
|
|
6100
6490
|
const b = report.baseline;
|
|
6101
6491
|
if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
|
|
6102
6492
|
process.stderr.write("bridge audit: adoption regressed since the baseline\n");
|
|
@@ -6108,9 +6498,9 @@ async function audit(a, root) {
|
|
|
6108
6498
|
async function docsCmd(a, root) {
|
|
6109
6499
|
const [, sub, name] = a._;
|
|
6110
6500
|
if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
|
|
6111
|
-
const ctx = await
|
|
6501
|
+
const ctx = await BridgeContext5.create({ root });
|
|
6112
6502
|
const d = await ops.draftDocsOp(ctx, { name });
|
|
6113
|
-
if (
|
|
6503
|
+
if (has4(a, "json")) {
|
|
6114
6504
|
print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
|
|
6115
6505
|
return 0;
|
|
6116
6506
|
}
|
|
@@ -6141,12 +6531,15 @@ async function main() {
|
|
|
6141
6531
|
const argv = process.argv.slice(2);
|
|
6142
6532
|
const a = parse(argv);
|
|
6143
6533
|
const cmd = a._[0];
|
|
6144
|
-
const root =
|
|
6534
|
+
const root = resolve4(flag4(a, "root") ?? process.cwd());
|
|
6145
6535
|
if (cmd === "request") return runRequest(a, root);
|
|
6146
6536
|
if (cmd === "explore") return runExplore(a, root);
|
|
6147
|
-
if (
|
|
6537
|
+
if (cmd === "branch") return runBranch(a, root);
|
|
6538
|
+
if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
|
|
6539
|
+
if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
|
|
6540
|
+
if (!cmd || has4(a, "help") || cmd === "help") {
|
|
6148
6541
|
print(HELP);
|
|
6149
|
-
return cmd ||
|
|
6542
|
+
return cmd || has4(a, "help") ? 0 : 2;
|
|
6150
6543
|
}
|
|
6151
6544
|
if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
|
|
6152
6545
|
switch (cmd) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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.4.0",
|
|
47
|
+
"@bridge-ds/visual": "^0.3.1",
|
|
48
48
|
"typescript": "5.9.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|