@collectui/bdui 0.1.9 → 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 +22 -6
- package/src/hints.js +22 -0
- package/src/init.js +111 -29
- package/src/mcp.js +36 -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,11 +146,20 @@ 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)
|
|
149
165
|
if (nextImports.size && !project.deps?.next) {
|
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,6 +1,7 @@
|
|
|
1
1
|
// `bdui init` — 对齐 boardui init:装 foundations(cx/theme/typography/globals)
|
|
2
2
|
// + agent 规则(AGENTS.md + .cursor/rules),内容全部来自 registry(与官方同源)
|
|
3
3
|
|
|
4
|
+
import { execSync } from "child_process";
|
|
4
5
|
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "fs";
|
|
5
6
|
import ora from "ora";
|
|
6
7
|
import path from "path";
|
|
@@ -32,21 +33,27 @@ function frameworkSetup(project, cwd) {
|
|
|
32
33
|
// Next.js:官方模板自带 @/ alias 与 @types/node;仅补缺失的 tailwind 管道。
|
|
33
34
|
// postcss 配置是硬前提:模板没带 Tailwind 时 postcss.config 不存在,
|
|
34
35
|
// @tailwindcss/postcss 装了也不接管,组件样式静默失效(next build 只给
|
|
35
|
-
// "Unknown at rule: @apply" 警告仍报成功)——缺配置就补 v4
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"postcss
|
|
41
|
-
"postcss.config.ts",
|
|
42
|
-
].some((f) => existsSync(path.join(cwd, f)));
|
|
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");
|
|
43
42
|
const writes = [];
|
|
44
|
-
if (!
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
+
}
|
|
50
57
|
return { pkgs, notes, writes };
|
|
51
58
|
}
|
|
52
59
|
const viteConfig = [
|
|
@@ -85,9 +92,9 @@ export async function cmdInit(flags) {
|
|
|
85
92
|
throw new Error("No package.json found — run inside your app project.");
|
|
86
93
|
|
|
87
94
|
// alias 不在此行展示:tsconfig 的 @/ 别名与实际落位(components/base/<group>)
|
|
88
|
-
// 口径不同,曾让客户误读(
|
|
95
|
+
// 口径不同,曾让客户误读(盲测 #6);落位在 ✓ 行明示。src= 是内部术语,给人话(r2 P10)
|
|
89
96
|
console.log(
|
|
90
|
-
`${c.cyan("init")} detected ${project.packageManager},
|
|
97
|
+
`${c.cyan("init")} detected ${project.packageManager}, source root: ${project.usesSrcDir ? "src/" : "project root"}`
|
|
91
98
|
);
|
|
92
99
|
|
|
93
100
|
// npm 依赖:foundation 用到的 + 可选 pro 依赖(官方 --pro 同款集)+ 框架适配
|
|
@@ -113,7 +120,7 @@ export async function cmdInit(flags) {
|
|
|
113
120
|
deps.set(pkgName, dep);
|
|
114
121
|
}
|
|
115
122
|
}
|
|
116
|
-
wrote += await writeItemFiles(item, outRoot, flags);
|
|
123
|
+
wrote += (await writeItemFiles(item, outRoot, flags)).wrote;
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
// 框架管道配置(postcss.config.mjs 等):样式静默失效的根因修复(盲测 #1)
|
|
@@ -139,9 +146,23 @@ export async function cmdInit(flags) {
|
|
|
139
146
|
` ${c.green("add")} .gitignore += .mcp.json (license key leak guard)`
|
|
140
147
|
);
|
|
141
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 {}
|
|
142
161
|
}
|
|
143
162
|
|
|
144
|
-
//
|
|
163
|
+
// 样式入口接线:优先「替换」模板旧 globals 导入而非并排追加——两份
|
|
164
|
+
// @import \"tailwindcss\" = Tailwind 双编译(~75KB 冗余 CSS)+旧 body Arial
|
|
165
|
+
// 覆盖 BoardUI 排版(盲测 r2 P3);旧文件非 Tailwind 入口才走追加(盲测 #7)
|
|
145
166
|
const layoutFile = [
|
|
146
167
|
"app/layout.tsx",
|
|
147
168
|
"app/layout.ts",
|
|
@@ -151,23 +172,79 @@ export async function cmdInit(flags) {
|
|
|
151
172
|
.map((f) => path.join(project.sourceRoot, f))
|
|
152
173
|
.find((p) => existsSync(p));
|
|
153
174
|
let stylesWired = false;
|
|
175
|
+
let fontsHint = "";
|
|
154
176
|
if (layoutFile) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
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");
|
|
161
210
|
let at = 0;
|
|
162
211
|
for (let i = 0; i < lines.length; i++)
|
|
163
212
|
if (/^import[\s{"']/.test(lines[i])) at = i + 1;
|
|
164
|
-
lines.splice(
|
|
165
|
-
|
|
166
|
-
|
|
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;
|
|
167
243
|
console.log(
|
|
168
|
-
` ${c.green("add")} ${
|
|
244
|
+
` ${c.green("add")} ${rel}: Inter + JetBrains Mono via next/font (--font-inter / --font-mono-source)`
|
|
169
245
|
);
|
|
170
246
|
}
|
|
247
|
+
writeFileSync(layoutFile, content);
|
|
171
248
|
}
|
|
172
249
|
|
|
173
250
|
// agent 规则(AGENTS.md + .cursor/rules/boardui.mdc)
|
|
@@ -190,13 +267,18 @@ export async function cmdInit(flags) {
|
|
|
190
267
|
throw e;
|
|
191
268
|
}
|
|
192
269
|
}
|
|
270
|
+
// relative 同目录返回 ""/拼接句号曾显示成 "written to .."(盲测 r2 P10)
|
|
271
|
+
const dest = path.relative(cwd, outRoot);
|
|
193
272
|
console.log(
|
|
194
273
|
c.green(
|
|
195
|
-
`\n✓ ${wrote} foundation file(s) written to ${
|
|
274
|
+
`\n✓ ${wrote} foundation file(s) written to ${
|
|
275
|
+
dest === "" || dest === "." ? "the project root" : dest
|
|
276
|
+
}.`
|
|
196
277
|
)
|
|
197
278
|
);
|
|
198
279
|
for (const note of setup.notes) console.log(c.yellow(`\n${note}\n`));
|
|
199
280
|
for (const line of ruleLines) console.log(line);
|
|
281
|
+
if (fontsHint) console.log(c.yellow(` warn fonts: ${fontsHint}`));
|
|
200
282
|
console.log(
|
|
201
283
|
stylesWired
|
|
202
284
|
? ` ${c.dim('Note: BoardUI styles wired into your root layout (import "@/styles/globals.css").')}`
|
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
|
|
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
|
}
|