@collectui/bdui 0.1.8 → 0.1.10
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/package.json +1 -1
- package/src/add.js +24 -7
- package/src/hints.js +22 -0
- package/src/init.js +169 -9
- package/src/list.js +4 -2
- package/src/mcp.js +41 -9
- package/src/registry.js +4 -1
- package/src/write.js +28 -1
package/package.json
CHANGED
package/src/add.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { spawn } from "child_process";
|
|
4
4
|
import ora from "ora";
|
|
5
5
|
import c from "./color.js";
|
|
6
|
+
import { itemHints } from "./hints.js";
|
|
6
7
|
import { detectProject, installCommand } from "./project.js";
|
|
7
8
|
import { fetchIndex, fetchItem } from "./registry.js";
|
|
8
9
|
import { writeItemFiles } from "./write.js";
|
|
@@ -95,6 +96,7 @@ export async function cmdAdd(names, flags) {
|
|
|
95
96
|
const deps = new Map(); // pkgName → 带 版本的 spec(去重,优先带版本形式)
|
|
96
97
|
const nextImports = new Set();
|
|
97
98
|
let wrote = 0;
|
|
99
|
+
let skippedExisting = 0;
|
|
98
100
|
writeItemFiles.lastWroteStyles = false;
|
|
99
101
|
|
|
100
102
|
for (const item of resolved.values()) {
|
|
@@ -110,7 +112,12 @@ export async function cmdAdd(names, flags) {
|
|
|
110
112
|
for (const m of (f.content ?? "").matchAll(/from\s+["'](next\/[a-z-]+)/g))
|
|
111
113
|
nextImports.add(m[1]);
|
|
112
114
|
}
|
|
113
|
-
|
|
115
|
+
const r = await writeItemFiles(item, outRoot, flags);
|
|
116
|
+
wrote += r.wrote;
|
|
117
|
+
skippedExisting += r.skippedExisting;
|
|
118
|
+
// 源码特征提示(不写死:特征随官方源码演进,官方修好提示自动消失)
|
|
119
|
+
for (const h of itemHints(item))
|
|
120
|
+
console.log(c.yellow(` ${item.name}: ${h}`));
|
|
114
121
|
}
|
|
115
122
|
|
|
116
123
|
if (deps.size) {
|
|
@@ -139,13 +146,23 @@ export async function cmdAdd(names, flags) {
|
|
|
139
146
|
}
|
|
140
147
|
console.log(c.green(`\n✓ ${wrote} file(s) written.`));
|
|
141
148
|
if (wrote === 0) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
149
|
+
if (skippedExisting > 0) {
|
|
150
|
+
// 全部跳过=已装过,不是上游坏了(盲测 r2 P7 误报「may be broken」)
|
|
151
|
+
console.log(
|
|
152
|
+
c.yellow(
|
|
153
|
+
` already installed (${skippedExisting} file(s) exist) — pass --overwrite to update`
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
console.log(
|
|
158
|
+
c.yellow(
|
|
159
|
+
" warn nothing was written — every file was skipped, or the registry entry ships no component source (upstream entry may be broken)"
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
}
|
|
147
163
|
}
|
|
148
|
-
|
|
164
|
+
// Next 项目里 next/link 是一等公民,提示反而是噪音(盲测 #10)
|
|
165
|
+
if (nextImports.size && !project.deps?.next) {
|
|
149
166
|
console.log(
|
|
150
167
|
c.yellow(
|
|
151
168
|
`\nNote: ${[...nextImports].sort().join(", ")} imported by installed files — these only compile in a Next.js project.`
|
package/src/hints.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// 条目提示(2026-10-26 盲测 r2 P2)——基于内容特征检测,不写死条目名:
|
|
2
|
+
// 官方源码随时会变,特征还在才提示,官方修好提示自动消失(用户铁律:
|
|
3
|
+
// 官方源码透传不改写,上游问题顶多提示,且提示不能写死)。
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 按条目当前源码特征给出安装/使用提示;无特征命中返回 []。
|
|
7
|
+
* @param {object} item registry 条目(files[].content)
|
|
8
|
+
* @returns {string[]}
|
|
9
|
+
*/
|
|
10
|
+
export function itemHints(item) {
|
|
11
|
+
const hints = [];
|
|
12
|
+
const src = (item.files ?? []).map((f) => f.content ?? "").join("\n");
|
|
13
|
+
|
|
14
|
+
// render 期读当前时间(new Date() / react-aria .toDate()):Next.js 16
|
|
15
|
+
// Cache Components 预渲染会炸,官方目前 calendar 系如此
|
|
16
|
+
if (/\bnew Date\(\)|\.toDate\(/.test(src)) {
|
|
17
|
+
hints.push(
|
|
18
|
+
"Note: this item reads the current time while rendering (per current registry source). On Next.js 16 Cache Components, wrap it in <Suspense> or `next build` prerendering fails."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return hints;
|
|
22
|
+
}
|
package/src/init.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// `bdui init` — 对齐 boardui init:装 foundations(cx/theme/typography/globals)
|
|
2
2
|
// + agent 规则(AGENTS.md + .cursor/rules),内容全部来自 registry(与官方同源)
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
5
6
|
import ora from "ora";
|
|
6
7
|
import path from "path";
|
|
7
8
|
import { installPackages, pkgBaseName } from "./add.js";
|
|
@@ -29,9 +30,31 @@ function frameworkSetup(project, cwd) {
|
|
|
29
30
|
|
|
30
31
|
if (!has("tailwindcss")) pkgs.push("tailwindcss");
|
|
31
32
|
if (project.deps?.next) {
|
|
32
|
-
// Next.js:官方模板自带 @/ alias 与 @types/node;仅补缺失的 tailwind
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
// Next.js:官方模板自带 @/ alias 与 @types/node;仅补缺失的 tailwind 管道。
|
|
34
|
+
// postcss 配置是硬前提:模板没带 Tailwind 时 postcss.config 不存在,
|
|
35
|
+
// @tailwindcss/postcss 装了也不接管,组件样式静默失效(next build 只给
|
|
36
|
+
// "Unknown at rule: @apply" 警告仍报成功)——缺配置就补 v4 标准形态。
|
|
37
|
+
// 反向同理(CNA 16 默认 @tailwindcss/turbopack 管道):已走 turbopack
|
|
38
|
+
// 就不再注入 postcss,避免双管道并存困惑(盲测 r2 P13)
|
|
39
|
+
const hasTurbopackTw = has("@tailwindcss/turbopack");
|
|
40
|
+
if (!(hasTurbopackTw || has("@tailwindcss/postcss")))
|
|
41
|
+
pkgs.push("@tailwindcss/postcss");
|
|
42
|
+
const writes = [];
|
|
43
|
+
if (!hasTurbopackTw) {
|
|
44
|
+
const hasPostcssCfg = [
|
|
45
|
+
"postcss.config.mjs",
|
|
46
|
+
"postcss.config.js",
|
|
47
|
+
"postcss.config.cjs",
|
|
48
|
+
"postcss.config.ts",
|
|
49
|
+
].some((f) => existsSync(path.join(cwd, f)));
|
|
50
|
+
if (!hasPostcssCfg)
|
|
51
|
+
writes.push({
|
|
52
|
+
file: "postcss.config.mjs",
|
|
53
|
+
content:
|
|
54
|
+
'const config = {\n plugins: { "@tailwindcss/postcss": {} },\n};\n\nexport default config;\n',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return { pkgs, notes, writes };
|
|
35
58
|
}
|
|
36
59
|
const viteConfig = [
|
|
37
60
|
"vite.config.ts",
|
|
@@ -59,7 +82,7 @@ function frameworkSetup(project, cwd) {
|
|
|
59
82
|
].join("\n")
|
|
60
83
|
);
|
|
61
84
|
}
|
|
62
|
-
return { pkgs, notes };
|
|
85
|
+
return { pkgs, notes, writes: [] };
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
export async function cmdInit(flags) {
|
|
@@ -68,8 +91,10 @@ export async function cmdInit(flags) {
|
|
|
68
91
|
if (!project.pkg)
|
|
69
92
|
throw new Error("No package.json found — run inside your app project.");
|
|
70
93
|
|
|
94
|
+
// alias 不在此行展示:tsconfig 的 @/ 别名与实际落位(components/base/<group>)
|
|
95
|
+
// 口径不同,曾让客户误读(盲测 #6);落位在 ✓ 行明示。src= 是内部术语,给人话(r2 P10)
|
|
71
96
|
console.log(
|
|
72
|
-
`${c.cyan("init")} detected ${project.packageManager},
|
|
97
|
+
`${c.cyan("init")} detected ${project.packageManager}, source root: ${project.usesSrcDir ? "src/" : "project root"}`
|
|
73
98
|
);
|
|
74
99
|
|
|
75
100
|
// npm 依赖:foundation 用到的 + 可选 pro 依赖(官方 --pro 同款集)+ 框架适配
|
|
@@ -95,7 +120,131 @@ export async function cmdInit(flags) {
|
|
|
95
120
|
deps.set(pkgName, dep);
|
|
96
121
|
}
|
|
97
122
|
}
|
|
98
|
-
wrote += await writeItemFiles(item, outRoot, flags);
|
|
123
|
+
wrote += (await writeItemFiles(item, outRoot, flags)).wrote;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 框架管道配置(postcss.config.mjs 等):样式静默失效的根因修复(盲测 #1)
|
|
127
|
+
for (const w of setup.writes ?? []) {
|
|
128
|
+
const target = path.join(cwd, w.file);
|
|
129
|
+
if (existsSync(target)) continue;
|
|
130
|
+
writeFileSync(target, w.content);
|
|
131
|
+
console.log(
|
|
132
|
+
` ${c.green("add")} ${w.file} (Tailwind v4 PostCSS pipeline)`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// .mcp.json 常嵌 license key(MCP 官方形态)——git 仓库防一次 commit 泄漏(盲测 #3)
|
|
137
|
+
const gitignore = path.join(cwd, ".gitignore");
|
|
138
|
+
if (existsSync(path.join(cwd, ".git")) || existsSync(gitignore)) {
|
|
139
|
+
const cur = existsSync(gitignore) ? readFileSync(gitignore, "utf8") : "";
|
|
140
|
+
if (!/^\s*\.mcp\.json\s*$/m.test(cur)) {
|
|
141
|
+
appendFileSync(
|
|
142
|
+
gitignore,
|
|
143
|
+
`${cur && !cur.endsWith("\n") ? "\n" : ""}\n# BoardUI MCP config (contains your license key)\n.mcp.json\n`
|
|
144
|
+
);
|
|
145
|
+
console.log(
|
|
146
|
+
` ${c.green("add")} .gitignore += .mcp.json (license key leak guard)`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
// 已跟踪的 .mcp.json:gitignore 挡不住,防"已安全"错觉(盲测 r2 P8)
|
|
150
|
+
try {
|
|
151
|
+
execSync("git ls-files --error-unmatch .mcp.json", {
|
|
152
|
+
cwd,
|
|
153
|
+
stdio: "ignore",
|
|
154
|
+
});
|
|
155
|
+
console.log(
|
|
156
|
+
c.yellow(
|
|
157
|
+
" warn .mcp.json is already tracked by git — .gitignore does not untrack it. Run: git rm --cached .mcp.json (and rotate the key if it was ever pushed)"
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
} catch {}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 样式入口接线:优先「替换」模板旧 globals 导入而非并排追加——两份
|
|
164
|
+
// @import \"tailwindcss\" = Tailwind 双编译(~75KB 冗余 CSS)+旧 body Arial
|
|
165
|
+
// 覆盖 BoardUI 排版(盲测 r2 P3);旧文件非 Tailwind 入口才走追加(盲测 #7)
|
|
166
|
+
const layoutFile = [
|
|
167
|
+
"app/layout.tsx",
|
|
168
|
+
"app/layout.ts",
|
|
169
|
+
"app/layout.jsx",
|
|
170
|
+
"app/layout.js",
|
|
171
|
+
]
|
|
172
|
+
.map((f) => path.join(project.sourceRoot, f))
|
|
173
|
+
.find((p) => existsSync(p));
|
|
174
|
+
let stylesWired = false;
|
|
175
|
+
let fontsHint = "";
|
|
176
|
+
if (layoutFile) {
|
|
177
|
+
const rel = path.relative(cwd, layoutFile);
|
|
178
|
+
let content = readFileSync(layoutFile, "utf8");
|
|
179
|
+
if (!content.includes("@/styles/globals.css")) {
|
|
180
|
+
const lines = content.split("\n");
|
|
181
|
+
const oldIdx = lines.findIndex((l) =>
|
|
182
|
+
/^\s*import\s+["']\.\/globals\.css["'];?\s*$/.test(l)
|
|
183
|
+
);
|
|
184
|
+
const oldGlobals = path.join(project.sourceRoot, "app/globals.css");
|
|
185
|
+
const oldIsTw =
|
|
186
|
+
existsSync(oldGlobals) &&
|
|
187
|
+
readFileSync(oldGlobals, "utf8").includes('@import "tailwindcss"');
|
|
188
|
+
if (oldIdx !== -1 && oldIsTw) {
|
|
189
|
+
lines[oldIdx] = 'import "@/styles/globals.css";';
|
|
190
|
+
console.log(
|
|
191
|
+
` ${c.green("add")} ${rel}: replaced "./globals.css" → "@/styles/globals.css" (old file was a second Tailwind entry; merge custom styles into BoardUI's if needed)`
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
// 插到最后一个顶层 import 之后;没有 import 则插到文件头
|
|
195
|
+
let at = 0;
|
|
196
|
+
for (let i = 0; i < lines.length; i++)
|
|
197
|
+
if (/^import[\s{"']/.test(lines[i])) at = i + 1;
|
|
198
|
+
lines.splice(at, 0, 'import "@/styles/globals.css";');
|
|
199
|
+
console.log(
|
|
200
|
+
` ${c.green("add")} ${rel}: import "@/styles/globals.css"`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
content = lines.join("\n");
|
|
204
|
+
}
|
|
205
|
+
stylesWired = true;
|
|
206
|
+
// 字体接线(r2 P4):theme.css 期望 --font-inter/--font-mono-source 由
|
|
207
|
+
// next/font 暴露,模板默认 Geist 不定义它们,注入 Inter + JetBrains Mono
|
|
208
|
+
if (!content.includes("--font-inter")) {
|
|
209
|
+
const lines = content.split("\n");
|
|
210
|
+
let at = 0;
|
|
211
|
+
for (let i = 0; i < lines.length; i++)
|
|
212
|
+
if (/^import[\s{"']/.test(lines[i])) at = i + 1;
|
|
213
|
+
lines.splice(
|
|
214
|
+
at,
|
|
215
|
+
0,
|
|
216
|
+
'import { Inter, JetBrains_Mono } from "next/font/google";',
|
|
217
|
+
'const bduiInter = Inter({ subsets: ["latin"], variable: "--font-inter" });',
|
|
218
|
+
'const bduiMono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-mono-source" });'
|
|
219
|
+
);
|
|
220
|
+
let next = lines.join("\n");
|
|
221
|
+
const vars = "${bduiInter.variable} ${bduiMono.variable}";
|
|
222
|
+
const tag = next.match(/<html[^>]*>/);
|
|
223
|
+
if (!tag) {
|
|
224
|
+
fontsHint =
|
|
225
|
+
"could not find <html> in the layout — attach bduiInter/bduiMono variable classNames manually";
|
|
226
|
+
} else if (tag[0].includes("className={`")) {
|
|
227
|
+
next = next.replace("className={`", `className={\`${vars} `);
|
|
228
|
+
} else {
|
|
229
|
+
const cm = tag[0].match(/className="([^"]*)"/);
|
|
230
|
+
if (cm) {
|
|
231
|
+
next = next.replace(
|
|
232
|
+
`className="${cm[1]}"`,
|
|
233
|
+
"className={`" + (cm[1] ? `${cm[1]} ` : "") + vars + "`}"
|
|
234
|
+
);
|
|
235
|
+
} else {
|
|
236
|
+
next = next.replace(
|
|
237
|
+
tag[0],
|
|
238
|
+
tag[0].replace(/>$/, ` className={\`${vars}\`}>`)
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
content = next;
|
|
243
|
+
console.log(
|
|
244
|
+
` ${c.green("add")} ${rel}: Inter + JetBrains Mono via next/font (--font-inter / --font-mono-source)`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
writeFileSync(layoutFile, content);
|
|
99
248
|
}
|
|
100
249
|
|
|
101
250
|
// agent 规则(AGENTS.md + .cursor/rules/boardui.mdc)
|
|
@@ -118,11 +267,22 @@ export async function cmdInit(flags) {
|
|
|
118
267
|
throw e;
|
|
119
268
|
}
|
|
120
269
|
}
|
|
121
|
-
|
|
270
|
+
// relative 同目录返回 ""/拼接句号曾显示成 "written to .."(盲测 r2 P10)
|
|
271
|
+
const dest = path.relative(cwd, outRoot);
|
|
272
|
+
console.log(
|
|
273
|
+
c.green(
|
|
274
|
+
`\n✓ ${wrote} foundation file(s) written to ${
|
|
275
|
+
dest === "" || dest === "." ? "the project root" : dest
|
|
276
|
+
}.`
|
|
277
|
+
)
|
|
278
|
+
);
|
|
122
279
|
for (const note of setup.notes) console.log(c.yellow(`\n${note}\n`));
|
|
123
280
|
for (const line of ruleLines) console.log(line);
|
|
281
|
+
if (fontsHint) console.log(c.yellow(` warn fonts: ${fontsHint}`));
|
|
124
282
|
console.log(
|
|
125
|
-
|
|
283
|
+
stylesWired
|
|
284
|
+
? ` ${c.dim('Note: BoardUI styles wired into your root layout (import "@/styles/globals.css").')}`
|
|
285
|
+
: ` ${c.dim('Note: import the styles in your root layout or global css: import "@/styles/globals.css";')}`
|
|
126
286
|
);
|
|
127
287
|
console.log(
|
|
128
288
|
c.green(
|
package/src/list.js
CHANGED
|
@@ -17,9 +17,11 @@ export async function cmdList() {
|
|
|
17
17
|
};
|
|
18
18
|
for (const [key, group] of groups) {
|
|
19
19
|
console.log(c.bold(labels[key] ?? key));
|
|
20
|
-
|
|
20
|
+
// Pro 标记:混排时无从分辨 Pro/免费(盲测 #5),对齐 MCP list 的 (pro) 口径
|
|
21
|
+
const label = (i) => (i.pro ? `${i.name} (pro)` : i.name);
|
|
22
|
+
const width = Math.max(...group.map((i) => label(i).length)) + 2;
|
|
21
23
|
for (const item of group) {
|
|
22
|
-
const name = item.
|
|
24
|
+
const name = label(item).padEnd(width);
|
|
23
25
|
console.log(` ${name}${item.description ?? ""}`);
|
|
24
26
|
}
|
|
25
27
|
console.log();
|
package/src/mcp.js
CHANGED
|
@@ -8,6 +8,7 @@ import path from "path";
|
|
|
8
8
|
import { createInterface } from "readline";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { cmdAdd } from "./add.js";
|
|
11
|
+
import { itemHints } from "./hints.js";
|
|
11
12
|
import { detectProject } from "./project.js";
|
|
12
13
|
import { fetchIndex, fetchItem } from "./registry.js";
|
|
13
14
|
|
|
@@ -184,16 +185,40 @@ async function dispatch(name, args, captured, serverKey) {
|
|
|
184
185
|
`internal dependencies: ${(item.registryDependencies ?? []).join(", ") || "none"}`,
|
|
185
186
|
`files: ${(item.files ?? []).map((f) => f.path).join(", ")}`,
|
|
186
187
|
];
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
188
|
+
// 全量源码输出(盲测 r2 P6:工具描述承诺 every file 却只回第一个)。
|
|
189
|
+
// 超大组件(整页模板)按文件逐个给,累计超限后剩余只列路径——
|
|
190
|
+
// 需要时 agent 可 install_components 落盘后再读文件。
|
|
191
|
+
const SRC_BUDGET = 150_000;
|
|
192
|
+
let used = 0;
|
|
193
|
+
for (const f of item.files ?? []) {
|
|
194
|
+
if (!f.content) continue;
|
|
195
|
+
if (used + f.content.length > SRC_BUDGET) {
|
|
196
|
+
lines.push(
|
|
197
|
+
"",
|
|
198
|
+
`(remaining files omitted for size: ${
|
|
199
|
+
(item.files ?? [])
|
|
200
|
+
.filter(
|
|
201
|
+
(x) =>
|
|
202
|
+
x.content &&
|
|
203
|
+
x !== f &&
|
|
204
|
+
used + x.content?.length > SRC_BUDGET
|
|
205
|
+
)
|
|
206
|
+
.map((x) => x.path)
|
|
207
|
+
.join(", ") || f.path
|
|
208
|
+
} — install_components writes them to disk)`
|
|
209
|
+
);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
used += f.content.length;
|
|
213
|
+
const lang = (f.path ?? "").endsWith(".ts")
|
|
214
|
+
? "ts"
|
|
215
|
+
: (f.path ?? "").endsWith(".css")
|
|
216
|
+
? "css"
|
|
217
|
+
: "tsx";
|
|
218
|
+
lines.push("", `## Source: ${f.path}`, "```" + lang, f.content, "```");
|
|
196
219
|
}
|
|
220
|
+
// 源码特征提示(内容检测,不写死;官方修好自动消失)
|
|
221
|
+
for (const h of itemHints(item)) lines.push("", `> ${h}`);
|
|
197
222
|
return lines.filter(Boolean).join("\n");
|
|
198
223
|
}
|
|
199
224
|
|
|
@@ -219,6 +244,8 @@ async function dispatch(name, args, captured, serverKey) {
|
|
|
219
244
|
}
|
|
220
245
|
if (!(meta.example || meta.snippets?.length))
|
|
221
246
|
lines.push("", "No usage examples recorded for this item.");
|
|
247
|
+
// 源码特征提示(不写死条目名:特征随官方源码演进,官方修好自动消失)
|
|
248
|
+
for (const h of itemHints(item)) lines.push("", `> ${h}`);
|
|
222
249
|
return lines.filter((x) => x !== undefined).join("\n");
|
|
223
250
|
}
|
|
224
251
|
|
|
@@ -343,6 +370,11 @@ async function call(name, args, serverKey) {
|
|
|
343
370
|
|
|
344
371
|
export async function cmdMcp(options = {}) {
|
|
345
372
|
const serverKey = options.key ?? undefined; // `bdui mcp --key <key>`:整会话兜底,见 loadItem 注释
|
|
373
|
+
// stderr 提示(stdio 协议,stdout 必须干净):key 常嵌 .mcp.json,防 commit 泄漏(盲测 #3)
|
|
374
|
+
if (serverKey)
|
|
375
|
+
console.error(
|
|
376
|
+
"[bdui] tip: if this key is embedded in .mcp.json, keep that file out of git (add it to .gitignore)."
|
|
377
|
+
);
|
|
346
378
|
const rl = createInterface({ input: process.stdin });
|
|
347
379
|
const write = (obj) => process.stdout.write(JSON.stringify(obj) + "\n");
|
|
348
380
|
|
package/src/registry.js
CHANGED
|
@@ -57,6 +57,9 @@ export async function fetchItem(name, key) {
|
|
|
57
57
|
} catch {}
|
|
58
58
|
throw new Error(message);
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
const item = await response.json();
|
|
61
|
+
// pro 通道拿到的条目缺 pro:true(服务端不回显)——补上,落盘输出标 (pro)(盲测 r2 P13)
|
|
62
|
+
item.pro = true;
|
|
63
|
+
return item;
|
|
61
64
|
}
|
|
62
65
|
}
|
package/src/write.js
CHANGED
|
@@ -27,6 +27,7 @@ async function confirmOverwrite(target, flags, state) {
|
|
|
27
27
|
|
|
28
28
|
export async function writeItemFiles(item, outRoot, flags = {}) {
|
|
29
29
|
let wrote = 0;
|
|
30
|
+
let skippedExisting = 0;
|
|
30
31
|
let wroteStyles = false;
|
|
31
32
|
const state = { all: false };
|
|
32
33
|
for (const file of item.files ?? []) {
|
|
@@ -39,6 +40,7 @@ export async function writeItemFiles(item, outRoot, flags = {}) {
|
|
|
39
40
|
}
|
|
40
41
|
const target = path.join(outRoot, rel);
|
|
41
42
|
if (existsSync(target) && !(await confirmOverwrite(target, flags, state))) {
|
|
43
|
+
skippedExisting++;
|
|
42
44
|
console.log(
|
|
43
45
|
` ${pc.yellow("skip")} ${path.relative(process.cwd(), target)} (exists)`
|
|
44
46
|
);
|
|
@@ -52,7 +54,32 @@ export async function writeItemFiles(item, outRoot, flags = {}) {
|
|
|
52
54
|
` ${pc.green("add")} ${path.relative(process.cwd(), target)}${item.pro ? pc.cyan(" (pro)") : ""}`
|
|
53
55
|
);
|
|
54
56
|
}
|
|
57
|
+
// 二进制 assets(base64):Pro 模板的头像/logo 等图片资源(盲测 r2 P1)
|
|
58
|
+
for (const asset of item.assets ?? []) {
|
|
59
|
+
const rel = path.normalize(asset.path ?? "");
|
|
60
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
61
|
+
console.log(` ${pc.red("skip")} unsafe path in asset: ${asset.path}`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const target = path.join(outRoot, "public", rel);
|
|
65
|
+
if (existsSync(target) && !(await confirmOverwrite(target, flags, state))) {
|
|
66
|
+
skippedExisting++;
|
|
67
|
+
console.log(
|
|
68
|
+
` ${pc.yellow("skip")} ${path.relative(process.cwd(), target)} (exists)`
|
|
69
|
+
);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
mkdirSync(path.dirname(target), { recursive: true });
|
|
73
|
+
writeFileSync(target, Buffer.from(asset.content ?? "", "base64"));
|
|
74
|
+
wrote++;
|
|
75
|
+
console.log(
|
|
76
|
+
` ${pc.green("add")} ${path.relative(
|
|
77
|
+
process.cwd(),
|
|
78
|
+
target
|
|
79
|
+
)}${item.pro ? pc.cyan(" (pro)") : ""} (asset)`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
55
82
|
writeItemFiles.lastWroteStyles =
|
|
56
83
|
writeItemFiles.lastWroteStyles || wroteStyles;
|
|
57
|
-
return wrote;
|
|
84
|
+
return { wrote, skippedExisting };
|
|
58
85
|
}
|