@bridge-ds/cli 0.5.0 → 0.6.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 +193 -92
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2158,57 +2158,6 @@ function planCssFixes(text, lang, ds) {
|
|
|
2158
2158
|
return { edits: edits.map(({ start: _s, end: _e, ...e }) => e), output };
|
|
2159
2159
|
}
|
|
2160
2160
|
|
|
2161
|
-
// ../core/src/node/git-local.ts
|
|
2162
|
-
import { execFile } from "node:child_process";
|
|
2163
|
-
|
|
2164
|
-
// ../core/src/governance.ts
|
|
2165
|
-
var DEFAULT_PROTECTED_BRANCHES = ["main", "master"];
|
|
2166
|
-
var ProtectedBranchError = class extends Error {
|
|
2167
|
-
constructor(message) {
|
|
2168
|
-
super(message);
|
|
2169
|
-
this.name = "ProtectedBranchError";
|
|
2170
|
-
}
|
|
2171
|
-
};
|
|
2172
|
-
function assertSafeBranch(branch, rules = {}) {
|
|
2173
|
-
const name = branch.replace(/^refs\/heads\//, "").trim();
|
|
2174
|
-
const blocked = /* @__PURE__ */ new Set([...rules.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES, ...rules.base ? [rules.base] : []]);
|
|
2175
|
-
if (!name || blocked.has(name)) {
|
|
2176
|
-
throw new ProtectedBranchError(`Refusing to write to protected branch "${name}". Bridge only creates new branches and pull requests.`);
|
|
2177
|
-
}
|
|
2178
|
-
if (rules.branchPrefix && !name.startsWith(rules.branchPrefix)) {
|
|
2179
|
-
throw new ProtectedBranchError(`Branch "${name}" must start with "${rules.branchPrefix}".`);
|
|
2180
|
-
}
|
|
2181
|
-
}
|
|
2182
|
-
|
|
2183
|
-
// ../core/src/node/git-local.ts
|
|
2184
|
-
function git(root, args, opts = {}) {
|
|
2185
|
-
return new Promise((resolve5, reject) => {
|
|
2186
|
-
const child = execFile("git", args, { cwd: root, env: { ...process.env, ...opts.env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
2187
|
-
if (err) reject(new Error(`git ${args.join(" ")}: ${stderr || err.message}`.trim()));
|
|
2188
|
-
else resolve5(stdout);
|
|
2189
|
-
});
|
|
2190
|
-
if (opts.input !== void 0) child.stdin.end(opts.input);
|
|
2191
|
-
});
|
|
2192
|
-
}
|
|
2193
|
-
async function showFile(root, ref, path) {
|
|
2194
|
-
try {
|
|
2195
|
-
return await git(root, ["show", `${ref}:${path}`]);
|
|
2196
|
-
} catch {
|
|
2197
|
-
return null;
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
async function listFilesAt(root, ref) {
|
|
2201
|
-
return (await git(root, ["ls-tree", "-r", "--name-only", ref])).split("\n").filter(Boolean);
|
|
2202
|
-
}
|
|
2203
|
-
async function originRepo(root) {
|
|
2204
|
-
try {
|
|
2205
|
-
const url = (await git(root, ["remote", "get-url", "origin"])).trim();
|
|
2206
|
-
return /github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?$/.exec(url)?.[1];
|
|
2207
|
-
} catch {
|
|
2208
|
-
return void 0;
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
|
|
2212
2161
|
// ../core/src/previews.ts
|
|
2213
2162
|
var PREVIEW_APP_LABELS = { docs: "Docs", storybook: "Storybook", admin: "Admin demo" };
|
|
2214
2163
|
|
|
@@ -2766,6 +2715,57 @@ function loadConfig(source) {
|
|
|
2766
2715
|
return defineConfig(d);
|
|
2767
2716
|
}
|
|
2768
2717
|
|
|
2718
|
+
// ../core/src/node/git-local.ts
|
|
2719
|
+
import { execFile } from "node:child_process";
|
|
2720
|
+
|
|
2721
|
+
// ../core/src/governance.ts
|
|
2722
|
+
var DEFAULT_PROTECTED_BRANCHES = ["main", "master"];
|
|
2723
|
+
var ProtectedBranchError = class extends Error {
|
|
2724
|
+
constructor(message) {
|
|
2725
|
+
super(message);
|
|
2726
|
+
this.name = "ProtectedBranchError";
|
|
2727
|
+
}
|
|
2728
|
+
};
|
|
2729
|
+
function assertSafeBranch(branch, rules = {}) {
|
|
2730
|
+
const name = branch.replace(/^refs\/heads\//, "").trim();
|
|
2731
|
+
const blocked = /* @__PURE__ */ new Set([...rules.protectedBranches ?? DEFAULT_PROTECTED_BRANCHES, ...rules.base ? [rules.base] : []]);
|
|
2732
|
+
if (!name || blocked.has(name)) {
|
|
2733
|
+
throw new ProtectedBranchError(`Refusing to write to protected branch "${name}". Bridge only creates new branches and pull requests.`);
|
|
2734
|
+
}
|
|
2735
|
+
if (rules.branchPrefix && !name.startsWith(rules.branchPrefix)) {
|
|
2736
|
+
throw new ProtectedBranchError(`Branch "${name}" must start with "${rules.branchPrefix}".`);
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
// ../core/src/node/git-local.ts
|
|
2741
|
+
function git(root, args, opts = {}) {
|
|
2742
|
+
return new Promise((resolve5, reject) => {
|
|
2743
|
+
const child = execFile("git", args, { cwd: root, env: { ...process.env, ...opts.env }, maxBuffer: 32 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
2744
|
+
if (err) reject(new Error(`git ${args.join(" ")}: ${stderr || err.message}`.trim()));
|
|
2745
|
+
else resolve5(stdout);
|
|
2746
|
+
});
|
|
2747
|
+
if (opts.input !== void 0) child.stdin.end(opts.input);
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
async function showFile(root, ref, path) {
|
|
2751
|
+
try {
|
|
2752
|
+
return await git(root, ["show", `${ref}:${path}`]);
|
|
2753
|
+
} catch {
|
|
2754
|
+
return null;
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
async function listFilesAt(root, ref) {
|
|
2758
|
+
return (await git(root, ["ls-tree", "-r", "--name-only", ref])).split("\n").filter(Boolean);
|
|
2759
|
+
}
|
|
2760
|
+
async function originRepo(root) {
|
|
2761
|
+
try {
|
|
2762
|
+
const url = (await git(root, ["remote", "get-url", "origin"])).trim();
|
|
2763
|
+
return /github\.com[:/]([\w.-]+\/[\w.-]+?)(\.git)?$/.exec(url)?.[1];
|
|
2764
|
+
} catch {
|
|
2765
|
+
return void 0;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
2769
|
// ../core/src/layered-review.ts
|
|
2770
2770
|
var same = (a, b) => JSON.stringify(a) === JSON.stringify(b);
|
|
2771
2771
|
function diffLayered(before, after) {
|
|
@@ -3773,6 +3773,17 @@ var GitHubClient = class _GitHubClient {
|
|
|
3773
3773
|
this.assertToken("Updating a branch from main");
|
|
3774
3774
|
await this.request("PUT", `${this.repoPath}/pulls/${n}/update-branch`, { expected_head_sha: expectedHeadSha });
|
|
3775
3775
|
}
|
|
3776
|
+
/**
|
|
3777
|
+
* Merges the branch `head` into the branch `base` (GitHub's merges API): a merge commit on `base`, or nothing when
|
|
3778
|
+
* `base` already has every commit of `head`. A conflict throws a GitHubError with status 409. Refuses protected
|
|
3779
|
+
* branches and, with a branch prefix, a `base` without it.
|
|
3780
|
+
*/
|
|
3781
|
+
async mergeIntoBranch(base, head, message) {
|
|
3782
|
+
this.assertToken("Merging a branch into another");
|
|
3783
|
+
this.assertWritableBranch(base);
|
|
3784
|
+
const r = await this.request("POST", `${this.repoPath}/merges`, { base, head, commit_message: message });
|
|
3785
|
+
return r?.sha ? { status: "merged", sha: r.sha } : { status: "up-to-date", sha: null };
|
|
3786
|
+
}
|
|
3776
3787
|
/** Posts a comment on an issue or pull request and returns its id and link. */
|
|
3777
3788
|
async createIssueComment(n, body) {
|
|
3778
3789
|
this.assertToken("Commenting");
|
|
@@ -5904,6 +5915,91 @@ ${r.content}` : String(r.said));
|
|
|
5904
5915
|
return 0;
|
|
5905
5916
|
}
|
|
5906
5917
|
|
|
5918
|
+
// src/pieces.ts
|
|
5919
|
+
import { BridgeContext as BridgeContext5, branches as branches2, pieces } from "@bridge-ds/mcp";
|
|
5920
|
+
var PIECES_HELP = `bridge pieces: a product branch and the library
|
|
5921
|
+
|
|
5922
|
+
Usage:
|
|
5923
|
+
bridge pieces <n> [--record] [--json] what the product branch adds that the library lacks (the audit
|
|
5924
|
+
on its diff against main); --record saves it in the branch,
|
|
5925
|
+
where Bridge Studio shows it
|
|
5926
|
+
bridge pieces status <n> [--json] each linked library branch: status, reviewers, preview, next step
|
|
5927
|
+
bridge pieces open <n> <piece> [--no-preview] [--dry-run] [--json]
|
|
5928
|
+
open the library side of a piece, linked both ways: a token
|
|
5929
|
+
change, a library exploration (variant) or a request (component)
|
|
5930
|
+
bridge pieces preview <n> <piece> the product branch takes the latest library branch (merged in)
|
|
5931
|
+
bridge pieces switch <n> once the library shipped: main into the product branch
|
|
5932
|
+
|
|
5933
|
+
GitHub token: GITHUB_TOKEN or BRIDGE_GITHUB_TOKEN, else \`gh auth token\`. Exit codes: 0 ok, 2 usage or setup error.`;
|
|
5934
|
+
var has4 = (a, k) => Boolean(a.flags[k]);
|
|
5935
|
+
async function context4(root, io) {
|
|
5936
|
+
const token = io.env.BRIDGE_GITHUB_TOKEN || io.env.GITHUB_TOKEN || io.ghToken();
|
|
5937
|
+
return BridgeContext5.create({ root, ...token ? { token } : {}, ...io.fetch ? { fetch: io.fetch } : {} });
|
|
5938
|
+
}
|
|
5939
|
+
function number2(a, at, usage) {
|
|
5940
|
+
const raw = a._[at];
|
|
5941
|
+
if (!raw) throw new RequestUsageError(`Usage: ${usage}`);
|
|
5942
|
+
return branches2.branchNumber(raw);
|
|
5943
|
+
}
|
|
5944
|
+
function piece(a, at, usage) {
|
|
5945
|
+
const raw = a._[at];
|
|
5946
|
+
if (!raw) throw new RequestUsageError(`Usage: ${usage}. \`bridge pieces <n>\` lists the piece ids.`);
|
|
5947
|
+
return raw;
|
|
5948
|
+
}
|
|
5949
|
+
async function runPieces(a, root, io = defaultIO()) {
|
|
5950
|
+
const sub = a._[1];
|
|
5951
|
+
if (!sub || sub === "help" || has4(a, "help")) {
|
|
5952
|
+
io.print(PIECES_HELP);
|
|
5953
|
+
return sub || has4(a, "help") ? 0 : 2;
|
|
5954
|
+
}
|
|
5955
|
+
const json = has4(a, "json");
|
|
5956
|
+
const ctx = await context4(root, io);
|
|
5957
|
+
const out = (r, text) => (json ? io.print(JSON.stringify(r, null, 2)) : text(), 0);
|
|
5958
|
+
switch (sub) {
|
|
5959
|
+
case "status": {
|
|
5960
|
+
const r = await pieces.productBranchOp(ctx, { pr: number2(a, 2, "bridge pieces status <n>") });
|
|
5961
|
+
return out(r, () => {
|
|
5962
|
+
io.print(`#${r.pr} ${r.title} (${r.product})`);
|
|
5963
|
+
io.print(r.said);
|
|
5964
|
+
for (const l of r.linked) io.print(` ${l.piece}: #${l.library.number} ${l.status}. ${l.preview}${l.action ? ` Next: ${l.action}.` : ""}`);
|
|
5965
|
+
for (const p of r.notLinked) io.print(` ${p.title} (${p.id}): no library branch yet. bridge pieces open ${r.pr} ${p.id}`);
|
|
5966
|
+
if (r.next) io.print(r.next);
|
|
5967
|
+
});
|
|
5968
|
+
}
|
|
5969
|
+
case "open": {
|
|
5970
|
+
const usage = "bridge pieces open <n> <piece>";
|
|
5971
|
+
const r = await pieces.openLibraryBranchOp(ctx, { pr: number2(a, 2, usage), piece: piece(a, 3, usage), usePreview: !has4(a, "no-preview"), dryRun: has4(a, "dry-run"), source: "cli" });
|
|
5972
|
+
return out(r, () => {
|
|
5973
|
+
io.print(`${r.status === "dry-run" ? "Would open" : r.status === "already-linked" ? "Already linked" : "Opened"} a ${r.opens}: ${r.title}, needed by ${r.neededBy}.`);
|
|
5974
|
+
if (r.library) io.print(` #${r.library.number} ${r.library.url}`);
|
|
5975
|
+
if (r.preview) io.print(` ${r.preview.said}`);
|
|
5976
|
+
for (const w of r.warnings ?? []) io.print(` ${w}`);
|
|
5977
|
+
io.print(`Next: ${r.next}`);
|
|
5978
|
+
});
|
|
5979
|
+
}
|
|
5980
|
+
case "preview": {
|
|
5981
|
+
const usage = "bridge pieces preview <n> <piece>";
|
|
5982
|
+
const r = await pieces.useLibraryPreviewOp(ctx, { pr: number2(a, 2, usage), piece: piece(a, 3, usage) });
|
|
5983
|
+
return out(r, () => io.print(r.said));
|
|
5984
|
+
}
|
|
5985
|
+
case "switch": {
|
|
5986
|
+
const r = await pieces.switchToPublishedOp(ctx, { pr: number2(a, 2, "bridge pieces switch <n>") });
|
|
5987
|
+
return out(r, () => io.print(r.said));
|
|
5988
|
+
}
|
|
5989
|
+
default: {
|
|
5990
|
+
const r = await pieces.missingPiecesOp(ctx, { pr: number2(a, 1, "bridge pieces <n> [--record]"), record: has4(a, "record") });
|
|
5991
|
+
return out(r, () => {
|
|
5992
|
+
io.print(`#${r.pr} ${r.branch} (${r.product}): ${r.pieces.length ? `${r.pieces.length} ${r.pieces.length === 1 ? "piece is" : "pieces are"} missing from the library` : "nothing is missing from the library"}, ${r.files} files read.`);
|
|
5993
|
+
for (const p of r.pieces) io.print(` ${p.id} ${p.title}: ${p.summary} Opens ${p.opens}.${"linked" in p ? ` Linked: #${p.linked}.` : ""}`);
|
|
5994
|
+
if (r.fixes.count) io.print(`${r.fixes.count} value${r.fixes.count === 1 ? "" : "s"} the library already has (use them instead):
|
|
5995
|
+
${r.fixes.sample.map((s) => ` ${s}`).join("\n")}`);
|
|
5996
|
+
if (r.skipped) io.print(r.skipped);
|
|
5997
|
+
io.print(`Next: ${r.next}`);
|
|
5998
|
+
});
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
|
|
5907
6003
|
// src/protection.ts
|
|
5908
6004
|
var UNKNOWN = { kind: "unknown", missing: ["review", "checks", "history", "deletion"] };
|
|
5909
6005
|
async function protectionReport(opts) {
|
|
@@ -5936,7 +6032,7 @@ async function rolesReport(opts) {
|
|
|
5936
6032
|
}
|
|
5937
6033
|
|
|
5938
6034
|
// src/index.ts
|
|
5939
|
-
import { brand, BridgeContext as
|
|
6035
|
+
import { brand, BridgeContext as BridgeContext6, CONFIG_FILE as CONFIG_FILE2, core, detectConfig, GitRefSource, history as hist, LocalSource, ops, optionsFromArgv, serveStdio, toConfigJson, VERSION } from "@bridge-ds/mcp";
|
|
5940
6036
|
var MCP_PACKAGE = "@bridge-ds/mcp";
|
|
5941
6037
|
var HELP = `bridge ${VERSION}: design system as code, for you and your Claude
|
|
5942
6038
|
|
|
@@ -6018,6 +6114,10 @@ Usage:
|
|
|
6018
6114
|
(\`bridge branch help\`): what needs me, send for review, review,
|
|
6019
6115
|
merge through the same gate (owner override with a reason), update
|
|
6020
6116
|
from main with token conflicts, try it in a product, try together
|
|
6117
|
+
bridge pieces <n> [--record] | status <n> | open <n> <piece> | preview <n> <piece> | switch <n>
|
|
6118
|
+
a product branch and the library (\`bridge pieces help\`): what it adds
|
|
6119
|
+
that the library lacks, the library branch each piece opens (linked
|
|
6120
|
+
both ways), its preview in the product branch, the published version
|
|
6021
6121
|
bridge products | bridge settings | bridge coverage [Component]
|
|
6022
6122
|
products (and this folder's), Studio settings safe to read, which
|
|
6023
6123
|
components are in Figma
|
|
@@ -6046,16 +6146,16 @@ function parse(argv) {
|
|
|
6046
6146
|
return out;
|
|
6047
6147
|
}
|
|
6048
6148
|
var flag4 = (a, k) => a.flags[k]?.[a.flags[k].length - 1];
|
|
6049
|
-
var
|
|
6149
|
+
var has5 = (a, k) => Boolean(a.flags[k]);
|
|
6050
6150
|
var print = (s) => process.stdout.write(`${s}
|
|
6051
6151
|
`);
|
|
6052
6152
|
var modeArg = (s) => s ? Object.fromEntries(s.split(",").map((kv) => kv.split("="))) : void 0;
|
|
6053
6153
|
async function init(a, root) {
|
|
6054
6154
|
const cfgPath = join6(root, CONFIG_FILE2);
|
|
6055
6155
|
let config;
|
|
6056
|
-
if (existsSync6(cfgPath) && !
|
|
6156
|
+
if (existsSync6(cfgPath) && !has5(a, "force")) {
|
|
6057
6157
|
print(`${CONFIG_FILE2} exists, keeping it (--force to regenerate).`);
|
|
6058
|
-
config = (await (await
|
|
6158
|
+
config = (await (await BridgeContext6.create({ root })).config()).config;
|
|
6059
6159
|
} else {
|
|
6060
6160
|
const src = new LocalSource(root);
|
|
6061
6161
|
const files = await src.list("");
|
|
@@ -6083,14 +6183,14 @@ async function init(a, root) {
|
|
|
6083
6183
|
print(` claude mcp add bridge -- ${desktop.command} ${desktop.args.join(" ")}`);
|
|
6084
6184
|
}
|
|
6085
6185
|
async function check(a, root) {
|
|
6086
|
-
const ctx = await
|
|
6186
|
+
const ctx = await BridgeContext6.create({ root });
|
|
6087
6187
|
const v = await ops.validateTokens(ctx, { includeWarnings: true });
|
|
6088
6188
|
const c = await ops.checkContrast(ctx, {});
|
|
6089
6189
|
const warnings = "warnings" in v ? v.warnings ?? [] : [];
|
|
6090
|
-
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 ||
|
|
6190
|
+
const failed = !v.valid || "failing" in c && (c.failing ?? 0) > 0 || has5(a, "strict") && warnings.length > 0;
|
|
6091
6191
|
const protection = await protectionOf(a, root, ctx);
|
|
6092
6192
|
const roles = await rolesOf(a, root, ctx);
|
|
6093
|
-
if (
|
|
6193
|
+
if (has5(a, "json")) {
|
|
6094
6194
|
print(JSON.stringify({ ok: !failed, validation: v, contrast: c, ...protection ? { protection: protection.state } : {}, ...roles?.teams.length ? { teams: roles.teams } : {} }, null, 2));
|
|
6095
6195
|
return failed ? 1 : 0;
|
|
6096
6196
|
}
|
|
@@ -6109,7 +6209,7 @@ async function check(a, root) {
|
|
|
6109
6209
|
return failed ? 1 : 0;
|
|
6110
6210
|
}
|
|
6111
6211
|
async function protectionOf(a, root, ctx) {
|
|
6112
|
-
if (
|
|
6212
|
+
if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6113
6213
|
const { config } = await ctx.config();
|
|
6114
6214
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6115
6215
|
if (!repo) return void 0;
|
|
@@ -6117,7 +6217,7 @@ async function protectionOf(a, root, ctx) {
|
|
|
6117
6217
|
return protectionReport({ repo, defaultBranch: config.repo?.defaultBranch ?? "main", token });
|
|
6118
6218
|
}
|
|
6119
6219
|
async function rolesOf(a, root, ctx) {
|
|
6120
|
-
if (
|
|
6220
|
+
if (has5(a, "offline") || process.env.BRIDGE_OFFLINE === "1") return void 0;
|
|
6121
6221
|
const { config } = await ctx.config();
|
|
6122
6222
|
const repo = config.repo ? `${config.repo.owner}/${config.repo.name}` : await originRepo(root);
|
|
6123
6223
|
if (!repo) return void 0;
|
|
@@ -6127,9 +6227,9 @@ async function rolesOf(a, root, ctx) {
|
|
|
6127
6227
|
async function tokens(a, root) {
|
|
6128
6228
|
const q = a._.slice(1).join(" ");
|
|
6129
6229
|
if (!q) throw new UsageError("bridge tokens <query>");
|
|
6130
|
-
const ctx = await
|
|
6230
|
+
const ctx = await BridgeContext6.create({ root });
|
|
6131
6231
|
const r = await ops.searchTokens(ctx, { query: q, limit: Number(flag4(a, "limit") ?? 20) });
|
|
6132
|
-
if (
|
|
6232
|
+
if (has5(a, "json")) return print(JSON.stringify(r, null, 2));
|
|
6133
6233
|
if (!r.results.length) return print(`No token matches "${q}".`);
|
|
6134
6234
|
const w = Math.max(...r.results.map((x) => x.path.length));
|
|
6135
6235
|
for (const t of r.results) print(`${t.path.padEnd(w)} ${String(t.value).padEnd(10)} ${(t.tier ?? "").padEnd(9)} ${t.description ?? ""}`);
|
|
@@ -6137,7 +6237,7 @@ async function tokens(a, root) {
|
|
|
6137
6237
|
async function diff(a, root) {
|
|
6138
6238
|
const base = a._[1];
|
|
6139
6239
|
if (!base) throw new UsageError("bridge diff <base> (a git ref: main, HEAD~1, a sha)");
|
|
6140
|
-
const ctx = await
|
|
6240
|
+
const ctx = await BridgeContext6.create({ root });
|
|
6141
6241
|
const { config } = await ctx.config();
|
|
6142
6242
|
const before = await core.loadTokenSet(new GitRefSource(root, base), config);
|
|
6143
6243
|
const after = await ctx.tokens();
|
|
@@ -6146,11 +6246,11 @@ async function diff(a, root) {
|
|
|
6146
6246
|
const delta2 = pairs2.length ? core.contrastDelta(core.runContrast(before, pairs2, config.contrast?.modes), core.runContrast(after, pairs2, config.contrast?.modes)) : [];
|
|
6147
6247
|
const tier = core.classify(changes, [], config);
|
|
6148
6248
|
const imp = core.impact(before, after, changes, 30);
|
|
6149
|
-
if (
|
|
6249
|
+
if (has5(a, "json")) {
|
|
6150
6250
|
print(JSON.stringify({ base, changes, contrast: delta2, tier, impact: imp }, null, 2));
|
|
6151
6251
|
return 0;
|
|
6152
6252
|
}
|
|
6153
|
-
if (
|
|
6253
|
+
if (has5(a, "md")) {
|
|
6154
6254
|
print(`## Token changes since \`${base}\`
|
|
6155
6255
|
|
|
6156
6256
|
${core.tokenTable(changes)}
|
|
@@ -6200,7 +6300,7 @@ async function propose(a, root) {
|
|
|
6200
6300
|
if (!edits.length) throw new UsageError("Give at least one --set path=value or --edits file.json");
|
|
6201
6301
|
const pr = flag4(a, "pr");
|
|
6202
6302
|
const token = pr ? process.env.BRIDGE_GITHUB_TOKEN || process.env.GITHUB_TOKEN || defaultIO().ghToken() : void 0;
|
|
6203
|
-
const ctx = await
|
|
6303
|
+
const ctx = await BridgeContext6.create({ root, ...token ? { token } : {} });
|
|
6204
6304
|
const summary = flag4(a, "summary");
|
|
6205
6305
|
const branch = flag4(a, "branch");
|
|
6206
6306
|
const jira = flag4(a, "jira");
|
|
@@ -6213,9 +6313,9 @@ async function propose(a, root) {
|
|
|
6213
6313
|
...pr ? { pr } : {},
|
|
6214
6314
|
...jira ? { jira } : {},
|
|
6215
6315
|
...product ? { product } : {},
|
|
6216
|
-
dryRun:
|
|
6316
|
+
dryRun: has5(a, "dry-run")
|
|
6217
6317
|
});
|
|
6218
|
-
if (
|
|
6318
|
+
if (has5(a, "json")) print(JSON.stringify(r, null, 2));
|
|
6219
6319
|
else {
|
|
6220
6320
|
print(`status: ${r.status}${r.why ? ` (${r.why})` : ""}`);
|
|
6221
6321
|
if (r.tier) print(`tier ${r.tier}, ${r.bump}`);
|
|
@@ -6230,10 +6330,10 @@ ${r.prBody}`);
|
|
|
6230
6330
|
}
|
|
6231
6331
|
async function brandCmd(a, root) {
|
|
6232
6332
|
const sub = a._[1];
|
|
6233
|
-
const ctx = await
|
|
6333
|
+
const ctx = await BridgeContext6.create({ root });
|
|
6234
6334
|
if (sub === "list") {
|
|
6235
6335
|
const r2 = await brand.listBrands(ctx);
|
|
6236
|
-
if (
|
|
6336
|
+
if (has5(a, "json")) print(JSON.stringify(r2.brands, null, 2));
|
|
6237
6337
|
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(", ")}` : ""}`);
|
|
6238
6338
|
return 0;
|
|
6239
6339
|
}
|
|
@@ -6253,9 +6353,9 @@ async function brandCmd(a, root) {
|
|
|
6253
6353
|
...opt("radius") ? { radius: opt("radius") } : {},
|
|
6254
6354
|
...Object.keys(fonts).length ? { fonts } : {},
|
|
6255
6355
|
...opt("notes") ? { notes: opt("notes") } : {},
|
|
6256
|
-
dryRun:
|
|
6356
|
+
dryRun: has5(a, "dry-run")
|
|
6257
6357
|
});
|
|
6258
|
-
if (
|
|
6358
|
+
if (has5(a, "json")) {
|
|
6259
6359
|
const { fileContents: _f, ...rest } = r;
|
|
6260
6360
|
print(JSON.stringify(rest, null, 2));
|
|
6261
6361
|
return r.status === "blocked" ? 1 : 0;
|
|
@@ -6291,9 +6391,9 @@ async function figmaReferences(a, root) {
|
|
|
6291
6391
|
out: resolve4(root, flag4(a, "out") ?? "figma-references"),
|
|
6292
6392
|
sha,
|
|
6293
6393
|
...only.length ? { only } : {},
|
|
6294
|
-
log:
|
|
6394
|
+
log: has5(a, "json") ? void 0 : (line) => print(line)
|
|
6295
6395
|
});
|
|
6296
|
-
if (
|
|
6396
|
+
if (has5(a, "json")) print(JSON.stringify({ manifest: r.manifestPath, captured: r.captured, skipped: r.skipped }, null, 2));
|
|
6297
6397
|
return 0;
|
|
6298
6398
|
}
|
|
6299
6399
|
async function impact(a, root) {
|
|
@@ -6318,15 +6418,15 @@ async function impact(a, root) {
|
|
|
6318
6418
|
...maxStories ? { maxStoriesPerComponent: maxStories } : {},
|
|
6319
6419
|
...imageBase !== void 0 ? { imageBase } : {},
|
|
6320
6420
|
...artifactUrl ? { artifactUrl } : {},
|
|
6321
|
-
plan:
|
|
6322
|
-
keepUnchanged:
|
|
6421
|
+
plan: has5(a, "plan"),
|
|
6422
|
+
keepUnchanged: has5(a, "keep-unchanged")
|
|
6323
6423
|
});
|
|
6324
|
-
if (
|
|
6424
|
+
if (has5(a, "json")) {
|
|
6325
6425
|
print(JSON.stringify({ summary: r.summary, manifest: r.manifestPath, comment: r.commentPath ?? null, ...r.manifest.summary, selection: r.manifest.selection }, null, 2));
|
|
6326
6426
|
return 0;
|
|
6327
6427
|
}
|
|
6328
6428
|
const sel = r.manifest.selection;
|
|
6329
|
-
if (
|
|
6429
|
+
if (has5(a, "plan")) {
|
|
6330
6430
|
print(`Plan ${r.manifest.base.ref} (${r.manifest.base.sha.slice(0, 7)}) -> ${r.manifest.head.ref} (${r.manifest.head.sha.slice(0, 7)})`);
|
|
6331
6431
|
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(", ")})` : ""}`);
|
|
6332
6432
|
print(` components: ${sel.components.map((c) => c.id).join(", ") || "none"}`);
|
|
@@ -6342,12 +6442,12 @@ async function impact(a, root) {
|
|
|
6342
6442
|
}
|
|
6343
6443
|
var day = (iso) => iso.slice(0, 10);
|
|
6344
6444
|
async function historyCmd(a, root) {
|
|
6345
|
-
const ctx = await
|
|
6445
|
+
const ctx = await BridgeContext6.create({ root });
|
|
6346
6446
|
const token = a._[1];
|
|
6347
6447
|
const limit = Number(flag4(a, "limit") ?? 20);
|
|
6348
6448
|
if (token) {
|
|
6349
6449
|
const r2 = await hist.getTokenHistory(ctx, { path: token, limit });
|
|
6350
|
-
if (
|
|
6450
|
+
if (has5(a, "json")) return print(JSON.stringify(r2, null, 2)), 0;
|
|
6351
6451
|
if (!r2.total) return print(`No recorded change of ${r2.path} in ${r2.source}.`), 0;
|
|
6352
6452
|
print(`${r2.path}: ${r2.total} change(s) (${r2.source})
|
|
6353
6453
|
`);
|
|
@@ -6359,8 +6459,8 @@ async function historyCmd(a, root) {
|
|
|
6359
6459
|
return 0;
|
|
6360
6460
|
}
|
|
6361
6461
|
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") } : {}, ...
|
|
6363
|
-
if (
|
|
6462
|
+
const r = await hist.getHistory(ctx, { limit, ...flag4(a, "author") ? { author: flag4(a, "author") } : {}, ...tier ? { tier: Number(tier) } : {}, ...flag4(a, "version") ? { version: flag4(a, "version") } : {}, ...has5(a, "breaking") ? { breaking: true } : {} });
|
|
6463
|
+
if (has5(a, "json")) return print(JSON.stringify(r, null, 2)), 0;
|
|
6364
6464
|
print(`${r.source}${r.currentVersion ? `, package version ${r.currentVersion}` : ""}`);
|
|
6365
6465
|
if (!r.total) return print(r.note ?? "No token change matches."), 0;
|
|
6366
6466
|
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" : ""}`);
|
|
@@ -6378,10 +6478,10 @@ ${r.truncated}`);
|
|
|
6378
6478
|
async function revertCmd(a, root) {
|
|
6379
6479
|
const target = a._[1];
|
|
6380
6480
|
if (!target) throw new UsageError("bridge revert <pr|commit|id> [--dry-run] (ids from `bridge history`)");
|
|
6381
|
-
const ctx = await
|
|
6481
|
+
const ctx = await BridgeContext6.create({ root });
|
|
6382
6482
|
const resolutions = Object.fromEntries((a.flags["use-before"] ?? []).map((k) => [k, "use-before"]));
|
|
6383
|
-
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts:
|
|
6384
|
-
if (
|
|
6483
|
+
const r = await hist.proposeRevert(ctx, { target, resolutions, restoreConflicts: has5(a, "restore-conflicts"), dryRun: has5(a, "dry-run") });
|
|
6484
|
+
if (has5(a, "json")) {
|
|
6385
6485
|
print(JSON.stringify(r, null, 2));
|
|
6386
6486
|
return r.status === "blocked" ? 1 : 0;
|
|
6387
6487
|
}
|
|
@@ -6409,8 +6509,8 @@ async function audit(a, root) {
|
|
|
6409
6509
|
const dsRoot = flag4(a, "ds") ? resolve4(flag4(a, "ds")) : existsSync6(join6(root, CONFIG_FILE2)) ? root : findDesignSystemRoot(root);
|
|
6410
6510
|
if (!dsRoot) throw new UsageError(`No ${CONFIG_FILE2} found: run bridge audit from the design system repo or pass --ds <dir>.`);
|
|
6411
6511
|
const ds = loadDesignSystem(dsRoot);
|
|
6412
|
-
if (
|
|
6413
|
-
const write =
|
|
6512
|
+
if (has5(a, "fix")) {
|
|
6513
|
+
const write = has5(a, "write");
|
|
6414
6514
|
const changes = fixCss(target, ds.index, write);
|
|
6415
6515
|
const n = changes.reduce((acc, c) => acc + c.edits.length, 0);
|
|
6416
6516
|
for (const c of changes) for (const e of c.edits) print(`${c.file}:${e.line}:${e.column} ${e.from} -> ${e.to}`);
|
|
@@ -6486,7 +6586,7 @@ async function audit(a, root) {
|
|
|
6486
6586
|
code = 1;
|
|
6487
6587
|
}
|
|
6488
6588
|
}
|
|
6489
|
-
if (
|
|
6589
|
+
if (has5(a, "no-regress") && report.baseline) {
|
|
6490
6590
|
const b = report.baseline;
|
|
6491
6591
|
if ((b.tokenAdoption.delta ?? 0) < 0 || (b.componentAdoption.delta ?? 0) < 0 || b.hardcoded.delta > 0) {
|
|
6492
6592
|
process.stderr.write("bridge audit: adoption regressed since the baseline\n");
|
|
@@ -6498,9 +6598,9 @@ async function audit(a, root) {
|
|
|
6498
6598
|
async function docsCmd(a, root) {
|
|
6499
6599
|
const [, sub, name] = a._;
|
|
6500
6600
|
if (sub !== "draft" || !name) throw new UsageError("Usage: bridge docs draft <Component> [--json]");
|
|
6501
|
-
const ctx = await
|
|
6601
|
+
const ctx = await BridgeContext6.create({ root });
|
|
6502
6602
|
const d = await ops.draftDocsOp(ctx, { name });
|
|
6503
|
-
if (
|
|
6603
|
+
if (has5(a, "json")) {
|
|
6504
6604
|
print(JSON.stringify({ component: d.component, path: d.path, exists: d.exists, prefill: d.prefill, prompt: d.prompt }, null, 2));
|
|
6505
6605
|
return 0;
|
|
6506
6606
|
}
|
|
@@ -6535,11 +6635,12 @@ async function main() {
|
|
|
6535
6635
|
if (cmd === "request") return runRequest(a, root);
|
|
6536
6636
|
if (cmd === "explore") return runExplore(a, root);
|
|
6537
6637
|
if (cmd === "branch") return runBranch(a, root);
|
|
6638
|
+
if (cmd === "pieces") return runPieces(a, root);
|
|
6538
6639
|
if (cmd === "products" || cmd === "settings" || cmd === "coverage") return runStudioRead(cmd, a, root);
|
|
6539
6640
|
if (cmd === "docs" && a._[1] === "save") return runDocsSave(a, root);
|
|
6540
|
-
if (!cmd ||
|
|
6641
|
+
if (!cmd || has5(a, "help") || cmd === "help") {
|
|
6541
6642
|
print(HELP);
|
|
6542
|
-
return cmd ||
|
|
6643
|
+
return cmd || has5(a, "help") ? 0 : 2;
|
|
6543
6644
|
}
|
|
6544
6645
|
if (cmd === "--version" || cmd === "version") return print(VERSION), 0;
|
|
6545
6646
|
switch (cmd) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bridge-ds/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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.5.0",
|
|
47
|
+
"@bridge-ds/visual": "^0.3.3",
|
|
48
48
|
"typescript": "5.9.3"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|