basuicn 0.1.5 → 0.1.7
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/README.md +96 -96
- package/README_CLI.md +44 -44
- package/dist/ui-cli.cjs +464 -146
- package/package.json +102 -102
- package/registry.json +78 -17
- package/scripts/build-cli.mjs +13 -0
- package/scripts/generate-theme-css.ts +74 -74
- package/scripts/ui-cli.ts +966 -966
- package/dist/assets/index-1YAQdTE0.css +0 -2
- package/dist/assets/index-BsQ6nn74.js +0 -237
- package/dist/favicon.svg +0 -1
- package/dist/icons.svg +0 -24
- package/dist/index.html +0 -13
- package/dist/ui-cli.js +0 -124
package/dist/ui-cli.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
2
3
|
var __create = Object.create;
|
|
3
4
|
var __defProp = Object.defineProperty;
|
|
4
5
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -26,12 +27,42 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
27
|
var import_fs = __toESM(require("fs"), 1);
|
|
27
28
|
var import_path = __toESM(require("path"), 1);
|
|
28
29
|
var import_child_process = require("child_process");
|
|
30
|
+
var import_readline = __toESM(require("readline"), 1);
|
|
31
|
+
var VERSION = "0.1.6";
|
|
29
32
|
var REGISTRY_LOCAL = "./registry.json";
|
|
30
|
-
var REGISTRY_REMOTE = "https://raw.githubusercontent.com/
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
var REGISTRY_REMOTE = "https://raw.githubusercontent.com/Basuicn/basuicn-core/main/registry.json";
|
|
34
|
+
var c = {
|
|
35
|
+
reset: "\x1B[0m",
|
|
36
|
+
bold: "\x1B[1m",
|
|
37
|
+
dim: "\x1B[2m",
|
|
38
|
+
green: "\x1B[32m",
|
|
39
|
+
yellow: "\x1B[33m",
|
|
40
|
+
red: "\x1B[31m",
|
|
41
|
+
cyan: "\x1B[36m",
|
|
42
|
+
magenta: "\x1B[35m",
|
|
43
|
+
blue: "\x1B[34m",
|
|
44
|
+
gray: "\x1B[90m"
|
|
45
|
+
};
|
|
46
|
+
var log = (msg) => console.log(`${c.cyan}\u25B8${c.reset} ${msg}`);
|
|
47
|
+
var ok = (msg) => console.log(`${c.green}\u2714${c.reset} ${msg}`);
|
|
48
|
+
var warn = (msg) => console.warn(`${c.yellow}\u26A0${c.reset} ${msg}`);
|
|
49
|
+
var error = (msg) => console.error(`${c.red}\u2716${c.reset} ${msg}`);
|
|
34
50
|
var getTargetProjectDir = () => process.cwd();
|
|
51
|
+
var ask = (question) => {
|
|
52
|
+
const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
rl.question(`${c.cyan}?${c.reset} ${question} `, (answer) => {
|
|
55
|
+
rl.close();
|
|
56
|
+
resolve(answer.trim());
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
var confirm = async (question, defaultYes = true) => {
|
|
61
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
62
|
+
const answer = await ask(`${question} ${c.dim}(${hint})${c.reset}`);
|
|
63
|
+
if (!answer) return defaultYes;
|
|
64
|
+
return answer.toLowerCase().startsWith("y");
|
|
65
|
+
};
|
|
35
66
|
var validateRegistry = (data) => {
|
|
36
67
|
if (!data || typeof data !== "object") return false;
|
|
37
68
|
const reg = data;
|
|
@@ -78,7 +109,7 @@ var installNpmPackages = (packages, cwd, dev = false) => {
|
|
|
78
109
|
toInstall = packages.filter((p) => !allDeps[p]);
|
|
79
110
|
}
|
|
80
111
|
if (toInstall.length === 0) return;
|
|
81
|
-
log(`Installing: ${toInstall.join(", ")}...`);
|
|
112
|
+
log(`Installing: ${c.bold}${toInstall.join(", ")}${c.reset}...`);
|
|
82
113
|
const flag = dev ? "--save-dev" : "--save";
|
|
83
114
|
try {
|
|
84
115
|
(0, import_child_process.execSync)(`npm install ${toInstall.join(" ")} ${flag}`, { stdio: "inherit", cwd });
|
|
@@ -86,22 +117,27 @@ var installNpmPackages = (packages, cwd, dev = false) => {
|
|
|
86
117
|
error(`Failed to install packages: ${toInstall.join(", ")}. ${err instanceof Error ? err.message : ""}`);
|
|
87
118
|
}
|
|
88
119
|
};
|
|
89
|
-
var VITE_DEV_PACKAGES = [
|
|
90
|
-
|
|
120
|
+
var VITE_DEV_PACKAGES = [
|
|
121
|
+
"tailwindcss",
|
|
122
|
+
"@tailwindcss/vite",
|
|
123
|
+
"@vitejs/plugin-react",
|
|
124
|
+
"@types/node"
|
|
125
|
+
];
|
|
126
|
+
var RUNTIME_PACKAGES = [
|
|
127
|
+
"@base-ui/react",
|
|
128
|
+
"tailwind-variants",
|
|
129
|
+
"clsx",
|
|
130
|
+
"tailwind-merge",
|
|
131
|
+
"tailwindcss-animate",
|
|
132
|
+
"lucide-react"
|
|
133
|
+
];
|
|
91
134
|
var VITE_CONFIG_TEMPLATE = `import { defineConfig } from 'vite';
|
|
92
135
|
import tailwindcss from '@tailwindcss/vite';
|
|
93
136
|
import react from '@vitejs/plugin-react';
|
|
94
|
-
import babel from 'vite-plugin-babel';
|
|
95
|
-
import { reactCompilerPreset } from 'babel-plugin-react-compiler';
|
|
96
137
|
import path from 'path';
|
|
97
138
|
|
|
98
|
-
// https://vite.dev/config/
|
|
99
139
|
export default defineConfig({
|
|
100
|
-
plugins: [
|
|
101
|
-
tailwindcss(),
|
|
102
|
-
react(),
|
|
103
|
-
babel({ presets: [reactCompilerPreset()] }),
|
|
104
|
-
],
|
|
140
|
+
plugins: [tailwindcss(), react()],
|
|
105
141
|
resolve: {
|
|
106
142
|
alias: {
|
|
107
143
|
'@': path.resolve(__dirname, './src'),
|
|
@@ -128,7 +164,7 @@ var setupViteConfig = (cwd) => {
|
|
|
128
164
|
const configJs = import_path.default.join(cwd, "vite.config.js");
|
|
129
165
|
if (!import_fs.default.existsSync(configTs) && !import_fs.default.existsSync(configJs)) {
|
|
130
166
|
import_fs.default.writeFileSync(configTs, VITE_CONFIG_TEMPLATE);
|
|
131
|
-
|
|
167
|
+
ok("Created vite.config.ts.");
|
|
132
168
|
return;
|
|
133
169
|
}
|
|
134
170
|
const existingPath = import_fs.default.existsSync(configTs) ? configTs : configJs;
|
|
@@ -136,16 +172,13 @@ var setupViteConfig = (cwd) => {
|
|
|
136
172
|
const missingImports = [];
|
|
137
173
|
if (!content.includes("@tailwindcss/vite")) missingImports.push("import tailwindcss from '@tailwindcss/vite';");
|
|
138
174
|
if (!content.includes("@vitejs/plugin-react")) missingImports.push("import react from '@vitejs/plugin-react';");
|
|
139
|
-
if (!content.includes("vite-plugin-babel")) missingImports.push("import babel from 'vite-plugin-babel';");
|
|
140
|
-
if (!content.includes("babel-plugin-react-compiler")) missingImports.push("import { reactCompilerPreset } from 'babel-plugin-react-compiler';");
|
|
141
175
|
if (!content.includes("from 'path'") && !content.includes('from "path"')) missingImports.push("import path from 'path';");
|
|
142
176
|
const missingPlugins = [];
|
|
143
177
|
if (!content.includes("tailwindcss()")) missingPlugins.push("tailwindcss()");
|
|
144
178
|
if (!content.includes("react()") && !content.includes("react({")) missingPlugins.push("react()");
|
|
145
|
-
|
|
146
|
-
const hasAlias = content.includes("alias:") || content.includes("alias(") || content.includes("'@'") || content.includes('"@"');
|
|
179
|
+
const hasAlias = content.includes("alias:") || content.includes("'@'") || content.includes('"@"');
|
|
147
180
|
if (missingImports.length === 0 && missingPlugins.length === 0 && hasAlias) {
|
|
148
|
-
|
|
181
|
+
ok("vite.config already configured \u2014 skipping.");
|
|
149
182
|
return;
|
|
150
183
|
}
|
|
151
184
|
if (missingImports.length > 0) {
|
|
@@ -203,29 +236,7 @@ var setupViteConfig = (cwd) => {
|
|
|
203
236
|
}
|
|
204
237
|
}
|
|
205
238
|
import_fs.default.writeFileSync(existingPath, content);
|
|
206
|
-
|
|
207
|
-
};
|
|
208
|
-
var ensureTailwindCss = (cwd) => {
|
|
209
|
-
const candidates = ["src/index.css", "src/App.css", "src/main.css"];
|
|
210
|
-
for (const cssFile of candidates) {
|
|
211
|
-
const cssPath = import_path.default.join(cwd, cssFile);
|
|
212
|
-
if (import_fs.default.existsSync(cssPath)) {
|
|
213
|
-
const content = import_fs.default.readFileSync(cssPath, "utf-8");
|
|
214
|
-
if (!content.includes('@import "tailwindcss"') && !content.includes("@import 'tailwindcss'")) {
|
|
215
|
-
import_fs.default.writeFileSync(cssPath, `@import "tailwindcss";
|
|
216
|
-
|
|
217
|
-
${content}`);
|
|
218
|
-
log(`Added @import "tailwindcss" to ${cssFile}`);
|
|
219
|
-
} else {
|
|
220
|
-
log(`${cssFile} already imports Tailwind \u2014 skipping.`);
|
|
221
|
-
}
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
const srcDir = import_path.default.join(cwd, "src");
|
|
226
|
-
if (!import_fs.default.existsSync(srcDir)) import_fs.default.mkdirSync(srcDir, { recursive: true });
|
|
227
|
-
import_fs.default.writeFileSync(import_path.default.join(srcDir, "index.css"), '@import "tailwindcss";\n');
|
|
228
|
-
log('Created src/index.css with @import "tailwindcss"');
|
|
239
|
+
ok(`Updated ${import_path.default.basename(existingPath)} with Tailwind + path aliases.`);
|
|
229
240
|
};
|
|
230
241
|
var setupTsConfig = (cwd) => {
|
|
231
242
|
const candidates = ["tsconfig.app.json", "tsconfig.json"];
|
|
@@ -234,7 +245,7 @@ var setupTsConfig = (cwd) => {
|
|
|
234
245
|
if (!import_fs.default.existsSync(configPath)) continue;
|
|
235
246
|
const raw = import_fs.default.readFileSync(configPath, "utf-8");
|
|
236
247
|
if (raw.includes('"@/*"') || raw.includes("'@/*'")) {
|
|
237
|
-
|
|
248
|
+
ok(`${candidate} already has path aliases \u2014 skipping.`);
|
|
238
249
|
return;
|
|
239
250
|
}
|
|
240
251
|
try {
|
|
@@ -244,7 +255,7 @@ var setupTsConfig = (cwd) => {
|
|
|
244
255
|
parsed.compilerOptions.baseUrl = ".";
|
|
245
256
|
parsed.compilerOptions.paths = TSCONFIG_PATHS;
|
|
246
257
|
import_fs.default.writeFileSync(configPath, JSON.stringify(parsed, null, 2));
|
|
247
|
-
|
|
258
|
+
ok(`Added path aliases to ${candidate}.`);
|
|
248
259
|
} catch (err) {
|
|
249
260
|
warn(`Could not auto-patch ${candidate}: ${err instanceof Error ? err.message : err}`);
|
|
250
261
|
warn("Add these to compilerOptions manually:");
|
|
@@ -260,33 +271,119 @@ var setupTsConfig = (cwd) => {
|
|
|
260
271
|
}
|
|
261
272
|
const newConfig = { compilerOptions: { baseUrl: ".", paths: TSCONFIG_PATHS } };
|
|
262
273
|
import_fs.default.writeFileSync(import_path.default.join(cwd, "tsconfig.json"), JSON.stringify(newConfig, null, 2));
|
|
263
|
-
|
|
274
|
+
ok("Created tsconfig.json with path aliases.");
|
|
264
275
|
};
|
|
265
|
-
var ensureCore = (registry, cwd) => {
|
|
276
|
+
var ensureCore = (registry, cwd, options = {}) => {
|
|
266
277
|
const core = registry.core;
|
|
267
278
|
if (!core) return;
|
|
268
279
|
installNpmPackages(core.dependencies, cwd);
|
|
269
280
|
for (const file of core.files) {
|
|
270
281
|
const targetPath = import_path.default.join(cwd, file.path);
|
|
271
282
|
const targetDir = import_path.default.dirname(targetPath);
|
|
272
|
-
if (!import_fs.default.existsSync(targetDir)) {
|
|
273
|
-
|
|
283
|
+
if (!import_fs.default.existsSync(targetDir)) import_fs.default.mkdirSync(targetDir, { recursive: true });
|
|
284
|
+
if (import_fs.default.existsSync(targetPath) && !options.force) {
|
|
285
|
+
log(`Core file exists (skipping): ${c.dim}${file.path}${c.reset}`);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
import_fs.default.writeFileSync(targetPath, file.content);
|
|
289
|
+
ok(`${import_fs.default.existsSync(targetPath) ? "Updated" : "Created"} core file: ${file.path}`);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
var MAIN_PATCH_COMPONENTS = {
|
|
293
|
+
toast: {
|
|
294
|
+
import: "import { Toaster } from '@/components/ui/toast/Toaster';",
|
|
295
|
+
jsx: '<Toaster position="top-center" expand={true} richColors />'
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
var MAIN_CANDIDATES = ["src/main.tsx", "src/main.jsx", "src/index.tsx", "src/index.jsx"];
|
|
299
|
+
var findMainFile = (cwd) => {
|
|
300
|
+
for (const c2 of MAIN_CANDIDATES) {
|
|
301
|
+
const p = import_path.default.join(cwd, c2);
|
|
302
|
+
if (import_fs.default.existsSync(p)) return p;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
};
|
|
306
|
+
var insertImport = (content, importLine) => {
|
|
307
|
+
if (content.includes(importLine)) return content;
|
|
308
|
+
const allImports = [...content.matchAll(/^import\s.+$/gm)];
|
|
309
|
+
if (allImports.length > 0) {
|
|
310
|
+
const last = allImports[allImports.length - 1];
|
|
311
|
+
const pos = last.index + last[0].length;
|
|
312
|
+
return content.slice(0, pos) + "\n" + importLine + content.slice(pos);
|
|
313
|
+
}
|
|
314
|
+
return importLine + "\n" + content;
|
|
315
|
+
};
|
|
316
|
+
var patchMainTsx = (cwd) => {
|
|
317
|
+
const mainPath = findMainFile(cwd);
|
|
318
|
+
if (!mainPath) {
|
|
319
|
+
warn("Could not find entry file (src/main.tsx). Skipping main entry setup.");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
let content = import_fs.default.readFileSync(mainPath, "utf-8");
|
|
323
|
+
let changed = false;
|
|
324
|
+
const cssImportLine = "import './styles/index.css';";
|
|
325
|
+
const hasCssImport = content.includes("styles/index.css") || content.includes("index.css");
|
|
326
|
+
if (!hasCssImport) {
|
|
327
|
+
const firstImport = content.match(/^import\s/m);
|
|
328
|
+
if (firstImport?.index !== void 0) {
|
|
329
|
+
content = content.slice(0, firstImport.index) + cssImportLine + "\n" + content.slice(firstImport.index);
|
|
330
|
+
} else {
|
|
331
|
+
content = cssImportLine + "\n" + content;
|
|
274
332
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
333
|
+
changed = true;
|
|
334
|
+
} else if (!content.includes("styles/index.css")) {
|
|
335
|
+
content = insertImport(content, cssImportLine);
|
|
336
|
+
changed = true;
|
|
337
|
+
}
|
|
338
|
+
if (!content.includes("ThemeProvider")) {
|
|
339
|
+
content = insertImport(content, "import { ThemeProvider } from '@/lib/theme/ThemeProvider';");
|
|
340
|
+
const wrapped = content.replace(/(<App\s*\/>)/g, "<ThemeProvider>\n $1\n </ThemeProvider>");
|
|
341
|
+
if (wrapped === content) {
|
|
342
|
+
warn("Could not locate <App /> in entry file \u2014 add <ThemeProvider> wrapper manually.");
|
|
343
|
+
} else {
|
|
344
|
+
content = wrapped;
|
|
278
345
|
}
|
|
346
|
+
changed = true;
|
|
279
347
|
}
|
|
348
|
+
if (changed) {
|
|
349
|
+
import_fs.default.writeFileSync(mainPath, content);
|
|
350
|
+
ok(`Patched ${import_path.default.relative(cwd, mainPath)}.`);
|
|
351
|
+
} else {
|
|
352
|
+
ok(`${import_path.default.relative(cwd, mainPath)} already configured \u2014 skipping.`);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
var patchMainTsxComponent = (cwd, componentName) => {
|
|
356
|
+
const patch = MAIN_PATCH_COMPONENTS[componentName];
|
|
357
|
+
if (!patch) return;
|
|
358
|
+
const mainPath = findMainFile(cwd);
|
|
359
|
+
if (!mainPath) return;
|
|
360
|
+
let content = import_fs.default.readFileSync(mainPath, "utf-8");
|
|
361
|
+
const tagName = patch.jsx.match(/<(\w+)/)?.[1];
|
|
362
|
+
if (tagName && content.includes(`<${tagName}`)) return;
|
|
363
|
+
content = insertImport(content, patch.import);
|
|
364
|
+
const withProvider = content.replace(
|
|
365
|
+
/(<App\s*\/>)(\s*\n\s*<\/ThemeProvider>)/,
|
|
366
|
+
`$1
|
|
367
|
+
${patch.jsx}$2`
|
|
368
|
+
);
|
|
369
|
+
if (withProvider !== content) {
|
|
370
|
+
import_fs.default.writeFileSync(mainPath, withProvider);
|
|
371
|
+
} else {
|
|
372
|
+
const fallback = content.replace(/(<App\s*\/>)/, `$1
|
|
373
|
+
${patch.jsx}`);
|
|
374
|
+
if (fallback !== content) import_fs.default.writeFileSync(mainPath, fallback);
|
|
375
|
+
}
|
|
376
|
+
ok(`Added <${tagName}> to ${import_path.default.relative(cwd, mainPath)}.`);
|
|
280
377
|
};
|
|
281
378
|
var addComponent = (name, registry, cwd, options, added = /* @__PURE__ */ new Set()) => {
|
|
282
379
|
if (added.has(name)) return;
|
|
283
380
|
added.add(name);
|
|
284
381
|
const component = registry.components[name];
|
|
285
382
|
if (!component) {
|
|
286
|
-
error(`Component "${name}" not found. Run 'list' to see available components.`);
|
|
383
|
+
error(`Component "${name}" not found. Run '${c.cyan}basuicn list${c.reset}' to see available components.`);
|
|
287
384
|
return;
|
|
288
385
|
}
|
|
289
|
-
log(`Adding: ${name}...`);
|
|
386
|
+
log(`Adding: ${c.bold}${name}${c.reset}...`);
|
|
290
387
|
ensureCore(registry, cwd);
|
|
291
388
|
installNpmPackages(component.dependencies, cwd);
|
|
292
389
|
if (component.internalDependencies) {
|
|
@@ -299,15 +396,13 @@ var addComponent = (name, registry, cwd, options, added = /* @__PURE__ */ new Se
|
|
|
299
396
|
for (const file of component.files) {
|
|
300
397
|
const targetPath = import_path.default.join(cwd, file.path);
|
|
301
398
|
const targetDir = import_path.default.dirname(targetPath);
|
|
302
|
-
if (!import_fs.default.existsSync(targetDir)) {
|
|
303
|
-
import_fs.default.mkdirSync(targetDir, { recursive: true });
|
|
304
|
-
}
|
|
399
|
+
if (!import_fs.default.existsSync(targetDir)) import_fs.default.mkdirSync(targetDir, { recursive: true });
|
|
305
400
|
if (import_fs.default.existsSync(targetPath) && !options.force) {
|
|
306
|
-
warn(`Skipped (exists): ${file.path} \u2014 use --force to overwrite`);
|
|
401
|
+
warn(`Skipped (exists): ${file.path} \u2014 use ${c.cyan}--force${c.reset} to overwrite`);
|
|
307
402
|
continue;
|
|
308
403
|
}
|
|
309
404
|
import_fs.default.writeFileSync(targetPath, file.content);
|
|
310
|
-
|
|
405
|
+
ok(`Created: ${file.path}`);
|
|
311
406
|
}
|
|
312
407
|
};
|
|
313
408
|
var removeComponent = (name, registry, cwd) => {
|
|
@@ -316,12 +411,12 @@ var removeComponent = (name, registry, cwd) => {
|
|
|
316
411
|
error(`Component "${name}" not found.`);
|
|
317
412
|
return;
|
|
318
413
|
}
|
|
319
|
-
log(`Removing: ${name}...`);
|
|
414
|
+
log(`Removing: ${c.bold}${name}${c.reset}...`);
|
|
320
415
|
for (const file of component.files) {
|
|
321
416
|
const targetPath = import_path.default.join(cwd, file.path);
|
|
322
417
|
if (import_fs.default.existsSync(targetPath)) {
|
|
323
418
|
import_fs.default.unlinkSync(targetPath);
|
|
324
|
-
|
|
419
|
+
ok(`Deleted: ${file.path}`);
|
|
325
420
|
}
|
|
326
421
|
}
|
|
327
422
|
for (const file of component.files) {
|
|
@@ -329,107 +424,304 @@ var removeComponent = (name, registry, cwd) => {
|
|
|
329
424
|
try {
|
|
330
425
|
if (import_fs.default.existsSync(targetDir) && import_fs.default.readdirSync(targetDir).length === 0) {
|
|
331
426
|
import_fs.default.rmdirSync(targetDir);
|
|
332
|
-
|
|
427
|
+
ok(`Removed empty dir: ${import_path.default.relative(cwd, targetDir)}`);
|
|
333
428
|
}
|
|
334
429
|
} catch (err) {
|
|
335
430
|
warn(`Could not remove directory: ${err instanceof Error ? err.message : err}`);
|
|
336
431
|
}
|
|
337
432
|
}
|
|
338
433
|
};
|
|
434
|
+
var HELP_MAIN = `
|
|
435
|
+
${c.bold}${c.cyan}basuicn${c.reset} ${c.dim}v${VERSION}${c.reset} \u2014 Modern React UI Component CLI
|
|
436
|
+
|
|
437
|
+
${c.bold}USAGE${c.reset}
|
|
438
|
+
${c.cyan}npx basuicn${c.reset} ${c.green}<command>${c.reset} ${c.dim}[options]${c.reset}
|
|
439
|
+
|
|
440
|
+
${c.bold}COMMANDS${c.reset}
|
|
441
|
+
${c.green}init${c.reset} Initialize project: install deps, copy core files, patch entry
|
|
442
|
+
${c.green}add${c.reset} ${c.dim}<name...>${c.reset} Add component(s) to your project
|
|
443
|
+
${c.green}update${c.reset} ${c.dim}<name...>${c.reset} Update component(s) to latest registry version
|
|
444
|
+
${c.green}diff${c.reset} ${c.dim}<name...>${c.reset} Show diff between local and registry version
|
|
445
|
+
${c.green}remove${c.reset} ${c.dim}<name...>${c.reset} Remove component(s) from your project
|
|
446
|
+
${c.green}list${c.reset} List all available components
|
|
447
|
+
${c.green}doctor${c.reset} Check project health and configuration
|
|
448
|
+
|
|
449
|
+
${c.bold}OPTIONS${c.reset}
|
|
450
|
+
${c.cyan}--force${c.reset} Overwrite existing files when adding/updating
|
|
451
|
+
${c.cyan}--local${c.reset} Use local registry.json instead of remote
|
|
452
|
+
${c.cyan}--help, -h${c.reset} Show help (use with a command for detailed help)
|
|
453
|
+
${c.cyan}--version, -v${c.reset} Show version
|
|
454
|
+
|
|
455
|
+
${c.bold}QUICK START${c.reset}
|
|
456
|
+
${c.dim}$${c.reset} npx basuicn init
|
|
457
|
+
${c.dim}$${c.reset} npx basuicn add button input card
|
|
458
|
+
${c.dim}$${c.reset} npx basuicn add toast
|
|
459
|
+
|
|
460
|
+
${c.bold}EXAMPLES${c.reset}
|
|
461
|
+
${c.dim}$${c.reset} npx basuicn add dialog --force ${c.dim}# Overwrite existing dialog${c.reset}
|
|
462
|
+
${c.dim}$${c.reset} npx basuicn diff button ${c.dim}# See what changed since last update${c.reset}
|
|
463
|
+
${c.dim}$${c.reset} npx basuicn doctor ${c.dim}# Diagnose missing deps/config${c.reset}
|
|
464
|
+
|
|
465
|
+
${c.dim}Documentation: https://github.com/Basuicn/basuicn-core${c.reset}
|
|
466
|
+
`;
|
|
467
|
+
var HELP_COMMANDS = {
|
|
468
|
+
init: `
|
|
469
|
+
${c.bold}basuicn init${c.reset}
|
|
470
|
+
|
|
471
|
+
Initialize your project for basuicn components.
|
|
472
|
+
|
|
473
|
+
${c.bold}What it does:${c.reset}
|
|
474
|
+
1. Installs runtime dependencies (@base-ui/react, tailwind-variants, etc.)
|
|
475
|
+
2. Sets up vite.config.ts with Tailwind CSS + path aliases
|
|
476
|
+
3. Patches tsconfig.json with path aliases (@/*, @lib/*, etc.)
|
|
477
|
+
4. Copies core files (cn.ts, themes.ts, ThemeProvider.tsx, index.css)
|
|
478
|
+
5. Wraps your <App /> with <ThemeProvider> in the main entry
|
|
479
|
+
|
|
480
|
+
${c.bold}Usage:${c.reset}
|
|
481
|
+
${c.dim}$${c.reset} npx basuicn init
|
|
482
|
+
${c.dim}$${c.reset} npx basuicn init --local ${c.dim}# Use local registry${c.reset}
|
|
483
|
+
`,
|
|
484
|
+
add: `
|
|
485
|
+
${c.bold}basuicn add${c.reset} ${c.dim}<name...>${c.reset}
|
|
486
|
+
|
|
487
|
+
Add one or more components to your project.
|
|
488
|
+
|
|
489
|
+
${c.bold}Options:${c.reset}
|
|
490
|
+
${c.cyan}--force${c.reset} Overwrite existing component files
|
|
491
|
+
|
|
492
|
+
${c.bold}Features:${c.reset}
|
|
493
|
+
\u2022 Auto-runs init if project hasn't been set up
|
|
494
|
+
\u2022 Resolves internal dependencies (e.g., dialog depends on button)
|
|
495
|
+
\u2022 Installs required npm packages automatically
|
|
496
|
+
\u2022 Patches main entry for components that need it (e.g., toast)
|
|
497
|
+
|
|
498
|
+
${c.bold}Usage:${c.reset}
|
|
499
|
+
${c.dim}$${c.reset} npx basuicn add button
|
|
500
|
+
${c.dim}$${c.reset} npx basuicn add button input card dialog
|
|
501
|
+
${c.dim}$${c.reset} npx basuicn add toast --force
|
|
502
|
+
|
|
503
|
+
${c.bold}Interactive:${c.reset}
|
|
504
|
+
${c.dim}$${c.reset} npx basuicn add ${c.dim}# Prompts to select components${c.reset}
|
|
505
|
+
`,
|
|
506
|
+
update: `
|
|
507
|
+
${c.bold}basuicn update${c.reset} ${c.dim}<name...>${c.reset}
|
|
508
|
+
|
|
509
|
+
Update component(s) to the latest registry version.
|
|
510
|
+
Equivalent to ${c.cyan}add --force${c.reset}.
|
|
511
|
+
|
|
512
|
+
${c.bold}Usage:${c.reset}
|
|
513
|
+
${c.dim}$${c.reset} npx basuicn update button
|
|
514
|
+
${c.dim}$${c.reset} npx basuicn update button card dialog
|
|
515
|
+
`,
|
|
516
|
+
remove: `
|
|
517
|
+
${c.bold}basuicn remove${c.reset} ${c.dim}<name...>${c.reset}
|
|
518
|
+
|
|
519
|
+
Remove component(s) from your project.
|
|
520
|
+
Deletes component files and cleans up empty directories.
|
|
521
|
+
|
|
522
|
+
${c.bold}Usage:${c.reset}
|
|
523
|
+
${c.dim}$${c.reset} npx basuicn remove button
|
|
524
|
+
${c.dim}$${c.reset} npx basuicn remove dialog drawer sheet
|
|
525
|
+
`,
|
|
526
|
+
diff: `
|
|
527
|
+
${c.bold}basuicn diff${c.reset} ${c.dim}<name...>${c.reset}
|
|
528
|
+
|
|
529
|
+
Show differences between your local component files and the registry version.
|
|
530
|
+
Useful to see what has changed before running update.
|
|
531
|
+
|
|
532
|
+
${c.bold}Usage:${c.reset}
|
|
533
|
+
${c.dim}$${c.reset} npx basuicn diff button
|
|
534
|
+
${c.dim}$${c.reset} npx basuicn diff button card
|
|
535
|
+
`,
|
|
536
|
+
list: `
|
|
537
|
+
${c.bold}basuicn list${c.reset}
|
|
538
|
+
|
|
539
|
+
Show all available components in the registry.
|
|
540
|
+
Displays internal dependencies for each component.
|
|
541
|
+
|
|
542
|
+
${c.bold}Usage:${c.reset}
|
|
543
|
+
${c.dim}$${c.reset} npx basuicn list
|
|
544
|
+
`,
|
|
545
|
+
doctor: `
|
|
546
|
+
${c.bold}basuicn doctor${c.reset}
|
|
547
|
+
|
|
548
|
+
Run a health check on your project configuration.
|
|
549
|
+
|
|
550
|
+
${c.bold}Checks:${c.reset}
|
|
551
|
+
\u2022 Core files exist (cn.ts, themes.ts, ThemeProvider.tsx, index.css)
|
|
552
|
+
\u2022 ThemeProvider + CSS import in main entry
|
|
553
|
+
\u2022 Runtime packages installed
|
|
554
|
+
\u2022 Dev packages installed
|
|
555
|
+
\u2022 Tailwind CSS configured
|
|
556
|
+
\u2022 TypeScript path aliases
|
|
557
|
+
\u2022 Vite config present
|
|
558
|
+
|
|
559
|
+
${c.bold}Usage:${c.reset}
|
|
560
|
+
${c.dim}$${c.reset} npx basuicn doctor
|
|
561
|
+
`
|
|
562
|
+
};
|
|
339
563
|
var main = async () => {
|
|
340
564
|
const args = process.argv.slice(2);
|
|
565
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
566
|
+
console.log(`basuicn v${VERSION}`);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
341
569
|
const isLocal = args.includes("--local");
|
|
342
570
|
const isForce = args.includes("--force");
|
|
343
|
-
const
|
|
571
|
+
const isHelp = args.includes("--help") || args.includes("-h");
|
|
572
|
+
const filteredArgs = args.filter((a) => !a.startsWith("--") && a !== "-h" && a !== "-v");
|
|
344
573
|
const command = filteredArgs[0];
|
|
345
574
|
const componentNames = filteredArgs.slice(1);
|
|
575
|
+
if (isHelp && command && HELP_COMMANDS[command]) {
|
|
576
|
+
console.log(HELP_COMMANDS[command]);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (isHelp || !command) {
|
|
580
|
+
console.log(HELP_MAIN);
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
346
583
|
const cwd = getTargetProjectDir();
|
|
347
584
|
const registry = await getRegistry(isLocal);
|
|
348
585
|
switch (command) {
|
|
586
|
+
case "init": {
|
|
587
|
+
log("Initializing project...");
|
|
588
|
+
setupViteConfig(cwd);
|
|
589
|
+
setupTsConfig(cwd);
|
|
590
|
+
installNpmPackages(RUNTIME_PACKAGES, cwd);
|
|
591
|
+
ensureCore(registry, cwd, { force: true });
|
|
592
|
+
patchMainTsx(cwd);
|
|
593
|
+
console.log("");
|
|
594
|
+
ok(`${c.bold}Initialization complete!${c.reset} Run ${c.cyan}npx basuicn add <component>${c.reset} to get started.`);
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
349
597
|
case "add": {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
598
|
+
let names = componentNames;
|
|
599
|
+
if (names.length === 0) {
|
|
600
|
+
const all = Object.keys(registry.components).sort();
|
|
601
|
+
console.log(`
|
|
602
|
+
${c.bold}Available components (${all.length}):${c.reset}`);
|
|
603
|
+
const categories = {};
|
|
604
|
+
for (const name of all) {
|
|
605
|
+
const prefix = name.includes("-") ? name.split("-")[0] : "general";
|
|
606
|
+
if (!categories[prefix]) categories[prefix] = [];
|
|
607
|
+
categories[prefix].push(name);
|
|
608
|
+
}
|
|
609
|
+
const cols = 4;
|
|
610
|
+
for (let i = 0; i < all.length; i += cols) {
|
|
611
|
+
const row = all.slice(i, i + cols).map((n) => n.padEnd(20)).join("");
|
|
612
|
+
console.log(` ${c.dim}${row}${c.reset}`);
|
|
613
|
+
}
|
|
614
|
+
console.log("");
|
|
615
|
+
const answer = await ask(`Which components to add? ${c.dim}(space-separated, or "all")${c.reset}`);
|
|
616
|
+
if (!answer) {
|
|
617
|
+
log("No components selected.");
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
names = answer === "all" ? all : answer.split(/[\s,]+/).filter(Boolean);
|
|
353
621
|
}
|
|
354
622
|
const cnPath = import_path.default.join(cwd, "src/lib/utils/cn.ts");
|
|
355
623
|
if (!import_fs.default.existsSync(cnPath)) {
|
|
356
624
|
log("Project not initialized \u2014 running init first...");
|
|
357
625
|
setupViteConfig(cwd);
|
|
358
626
|
setupTsConfig(cwd);
|
|
359
|
-
ensureTailwindCss(cwd);
|
|
360
627
|
installNpmPackages(RUNTIME_PACKAGES, cwd);
|
|
628
|
+
ensureCore(registry, cwd, { force: true });
|
|
629
|
+
patchMainTsx(cwd);
|
|
630
|
+
console.log("");
|
|
361
631
|
}
|
|
362
|
-
for (const name of
|
|
632
|
+
for (const name of names) {
|
|
363
633
|
addComponent(name, registry, cwd, { force: isForce });
|
|
634
|
+
patchMainTsxComponent(cwd, name);
|
|
364
635
|
}
|
|
365
|
-
log("
|
|
636
|
+
console.log("");
|
|
637
|
+
ok(`${c.bold}Done!${c.reset} Added ${names.length} component(s).`);
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
case "update": {
|
|
641
|
+
if (componentNames.length === 0) {
|
|
642
|
+
error(`Usage: ${c.cyan}npx basuicn update <component-name> [...]${c.reset}`);
|
|
643
|
+
console.log(` Run ${c.cyan}npx basuicn update --help${c.reset} for details.`);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
for (const name of componentNames) {
|
|
647
|
+
log(`Updating: ${c.bold}${name}${c.reset}...`);
|
|
648
|
+
addComponent(name, registry, cwd, { force: true });
|
|
649
|
+
}
|
|
650
|
+
console.log("");
|
|
651
|
+
ok(`${c.bold}Update complete.${c.reset}`);
|
|
366
652
|
break;
|
|
367
653
|
}
|
|
368
654
|
case "remove": {
|
|
369
655
|
if (componentNames.length === 0) {
|
|
370
|
-
error(
|
|
656
|
+
error(`Usage: ${c.cyan}npx basuicn remove <component-name>${c.reset}`);
|
|
371
657
|
return;
|
|
372
658
|
}
|
|
659
|
+
if (!isForce) {
|
|
660
|
+
const yes = await confirm(`Remove ${componentNames.join(", ")}?`);
|
|
661
|
+
if (!yes) {
|
|
662
|
+
log("Cancelled.");
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
373
666
|
for (const name of componentNames) {
|
|
374
667
|
removeComponent(name, registry, cwd);
|
|
375
668
|
}
|
|
376
|
-
log("
|
|
669
|
+
console.log("");
|
|
670
|
+
ok(`${c.bold}Done!${c.reset}`);
|
|
377
671
|
break;
|
|
378
672
|
}
|
|
379
673
|
case "list": {
|
|
380
674
|
const components = Object.keys(registry.components).sort();
|
|
381
|
-
log(`
|
|
675
|
+
console.log(`
|
|
676
|
+
${c.bold}Available components (${components.length}):${c.reset}
|
|
677
|
+
`);
|
|
678
|
+
const installed = [];
|
|
679
|
+
const available = [];
|
|
382
680
|
for (const k of components) {
|
|
383
|
-
const
|
|
384
|
-
const
|
|
385
|
-
|
|
681
|
+
const comp = registry.components[k];
|
|
682
|
+
const firstFile = comp.files[0];
|
|
683
|
+
const isInstalled = firstFile && import_fs.default.existsSync(import_path.default.join(cwd, firstFile.path));
|
|
684
|
+
if (isInstalled) installed.push(k);
|
|
685
|
+
else available.push(k);
|
|
386
686
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
log("Initialization complete.");
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
case "tailwind": {
|
|
399
|
-
console.log("\n--- Copy to tailwind.config.ts / tailwind.config.js ---\n");
|
|
400
|
-
console.log("// See README_CLI.md for full theme config");
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
case "update": {
|
|
404
|
-
if (componentNames.length === 0) {
|
|
405
|
-
error("Usage: npx basuicn update <component-name> [...]");
|
|
406
|
-
return;
|
|
687
|
+
if (installed.length > 0) {
|
|
688
|
+
console.log(` ${c.green}Installed (${installed.length}):${c.reset}`);
|
|
689
|
+
for (const k of installed) {
|
|
690
|
+
const deps = registry.components[k].internalDependencies?.filter(Boolean);
|
|
691
|
+
const depStr = deps?.length ? ` ${c.dim}\u2192 ${deps.join(", ")}${c.reset}` : "";
|
|
692
|
+
console.log(` ${c.green}\u25CF${c.reset} ${k}${depStr}`);
|
|
693
|
+
}
|
|
694
|
+
console.log("");
|
|
407
695
|
}
|
|
408
|
-
|
|
409
|
-
log(`
|
|
410
|
-
|
|
696
|
+
if (available.length > 0) {
|
|
697
|
+
console.log(` ${c.dim}Available (${available.length}):${c.reset}`);
|
|
698
|
+
for (const k of available) {
|
|
699
|
+
const deps = registry.components[k].internalDependencies?.filter(Boolean);
|
|
700
|
+
const depStr = deps?.length ? ` ${c.dim}\u2192 ${deps.join(", ")}${c.reset}` : "";
|
|
701
|
+
console.log(` ${c.dim}\u25CB${c.reset} ${k}${depStr}`);
|
|
702
|
+
}
|
|
411
703
|
}
|
|
412
|
-
log("
|
|
704
|
+
console.log("");
|
|
413
705
|
break;
|
|
414
706
|
}
|
|
415
707
|
case "diff": {
|
|
416
708
|
if (componentNames.length === 0) {
|
|
417
|
-
error(
|
|
709
|
+
error(`Usage: ${c.cyan}npx basuicn diff <component-name>${c.reset}`);
|
|
418
710
|
return;
|
|
419
711
|
}
|
|
420
712
|
for (const name of componentNames) {
|
|
421
713
|
const component = registry.components[name];
|
|
422
714
|
if (!component) {
|
|
423
|
-
error(`Component "${name}" not found
|
|
715
|
+
error(`Component "${name}" not found.`);
|
|
424
716
|
continue;
|
|
425
717
|
}
|
|
426
718
|
let hasDiff = false;
|
|
427
719
|
console.log(`
|
|
428
|
-
[diff] ${name}`);
|
|
720
|
+
${c.bold}[diff] ${name}${c.reset}`);
|
|
429
721
|
for (const file of component.files) {
|
|
430
722
|
const targetPath = import_path.default.join(cwd, file.path);
|
|
431
723
|
if (!import_fs.default.existsSync(targetPath)) {
|
|
432
|
-
console.log(` + [new file] ${file.path}`);
|
|
724
|
+
console.log(` ${c.green}+ [new file]${c.reset} ${file.path}`);
|
|
433
725
|
hasDiff = true;
|
|
434
726
|
continue;
|
|
435
727
|
}
|
|
@@ -437,94 +729,120 @@ var main = async () => {
|
|
|
437
729
|
if (localContent === file.content) continue;
|
|
438
730
|
hasDiff = true;
|
|
439
731
|
console.log(`
|
|
440
|
-
|
|
732
|
+
${c.yellow}~${c.reset} ${file.path}`);
|
|
441
733
|
const localLines = localContent.split("\n");
|
|
442
734
|
const remoteLines = file.content.split("\n");
|
|
443
735
|
const maxLen = Math.max(localLines.length, remoteLines.length);
|
|
444
736
|
let shownLines = 0;
|
|
445
737
|
for (let i = 0; i < maxLen; i++) {
|
|
446
738
|
if (localLines[i] !== remoteLines[i]) {
|
|
447
|
-
if (localLines[i] !== void 0) console.log(` - ${localLines[i]}`);
|
|
448
|
-
if (remoteLines[i] !== void 0) console.log(` + ${remoteLines[i]}`);
|
|
739
|
+
if (localLines[i] !== void 0) console.log(` ${c.red}- ${localLines[i]}${c.reset}`);
|
|
740
|
+
if (remoteLines[i] !== void 0) console.log(` ${c.green}+ ${remoteLines[i]}${c.reset}`);
|
|
449
741
|
shownLines++;
|
|
450
742
|
if (shownLines >= 20) {
|
|
451
743
|
const remaining = maxLen - i - 1;
|
|
452
|
-
if (remaining > 0) console.log(` ... and ${remaining} more lines`);
|
|
744
|
+
if (remaining > 0) console.log(` ${c.dim}... and ${remaining} more lines${c.reset}`);
|
|
453
745
|
break;
|
|
454
746
|
}
|
|
455
747
|
}
|
|
456
748
|
}
|
|
457
749
|
}
|
|
458
|
-
if (!hasDiff)
|
|
750
|
+
if (!hasDiff) ok(`${name}: already up to date.`);
|
|
459
751
|
}
|
|
460
752
|
break;
|
|
461
753
|
}
|
|
462
754
|
case "doctor": {
|
|
463
|
-
log(
|
|
755
|
+
console.log(`
|
|
756
|
+
${c.bold}Project Health Check${c.reset}
|
|
757
|
+
`);
|
|
464
758
|
let issues = 0;
|
|
465
|
-
const check = (
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
if (fix) console.log(` \u2192 ${fix}`);
|
|
759
|
+
const check = (passed, msg, fix) => {
|
|
760
|
+
console.log(` ${passed ? `${c.green}\u2714${c.reset}` : `${c.red}\u2716${c.reset}`} ${msg}`);
|
|
761
|
+
if (!passed) {
|
|
762
|
+
if (fix) console.log(` ${c.dim}\u2192 ${fix}${c.reset}`);
|
|
470
763
|
issues++;
|
|
471
764
|
}
|
|
472
765
|
};
|
|
473
|
-
|
|
474
|
-
|
|
766
|
+
check(
|
|
767
|
+
import_fs.default.existsSync(import_path.default.join(cwd, "src/lib/utils/cn.ts")),
|
|
768
|
+
"src/lib/utils/cn.ts",
|
|
769
|
+
"run: npx basuicn init"
|
|
770
|
+
);
|
|
771
|
+
check(
|
|
772
|
+
import_fs.default.existsSync(import_path.default.join(cwd, "src/lib/theme/themes.ts")),
|
|
773
|
+
"src/lib/theme/themes.ts",
|
|
774
|
+
"run: npx basuicn init"
|
|
775
|
+
);
|
|
776
|
+
check(
|
|
777
|
+
import_fs.default.existsSync(import_path.default.join(cwd, "src/lib/theme/ThemeProvider.tsx")),
|
|
778
|
+
"src/lib/theme/ThemeProvider.tsx",
|
|
779
|
+
"run: npx basuicn init"
|
|
780
|
+
);
|
|
781
|
+
check(
|
|
782
|
+
import_fs.default.existsSync(import_path.default.join(cwd, "src/styles/index.css")),
|
|
783
|
+
"src/styles/index.css (theme variables)",
|
|
784
|
+
"run: npx basuicn init"
|
|
785
|
+
);
|
|
786
|
+
const mainPath = findMainFile(cwd);
|
|
787
|
+
if (mainPath) {
|
|
788
|
+
const mainContent = import_fs.default.readFileSync(mainPath, "utf-8");
|
|
789
|
+
check(
|
|
790
|
+
mainContent.includes("ThemeProvider"),
|
|
791
|
+
"ThemeProvider in main entry",
|
|
792
|
+
"run: npx basuicn init"
|
|
793
|
+
);
|
|
794
|
+
check(
|
|
795
|
+
mainContent.includes("styles/index.css") || mainContent.includes("index.css"),
|
|
796
|
+
"CSS import in main entry",
|
|
797
|
+
"run: npx basuicn init"
|
|
798
|
+
);
|
|
799
|
+
} else {
|
|
800
|
+
check(false, "main entry file (src/main.tsx)", "create src/main.tsx");
|
|
801
|
+
}
|
|
475
802
|
const pkgPath = import_path.default.join(cwd, "package.json");
|
|
476
803
|
if (import_fs.default.existsSync(pkgPath)) {
|
|
477
804
|
const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
478
805
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
479
806
|
for (const dep of RUNTIME_PACKAGES) {
|
|
480
|
-
check(!!allDeps[dep], `
|
|
807
|
+
check(!!allDeps[dep], `package: ${dep}`, `run: npm install ${dep}`);
|
|
808
|
+
}
|
|
809
|
+
for (const dep of VITE_DEV_PACKAGES) {
|
|
810
|
+
check(!!allDeps[dep], `package (dev): ${dep}`, `run: npm install -D ${dep}`);
|
|
481
811
|
}
|
|
482
812
|
} else {
|
|
483
813
|
check(false, "package.json found", "run: npm init -y");
|
|
484
814
|
}
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
815
|
+
const hasTailwindInCss = (() => {
|
|
816
|
+
const candidates = ["src/styles/index.css", "src/index.css", "src/App.css"];
|
|
817
|
+
return candidates.some((f) => {
|
|
818
|
+
const p = import_path.default.join(cwd, f);
|
|
819
|
+
if (!import_fs.default.existsSync(p)) return false;
|
|
820
|
+
const content = import_fs.default.readFileSync(p, "utf-8");
|
|
821
|
+
return content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'");
|
|
822
|
+
});
|
|
823
|
+
})();
|
|
824
|
+
check(hasTailwindInCss, '@import "tailwindcss" in CSS', "run: npx basuicn init");
|
|
493
825
|
const tsCandidates = ["tsconfig.app.json", "tsconfig.json"];
|
|
494
826
|
const hasAlias = tsCandidates.some((f) => {
|
|
495
827
|
const p = import_path.default.join(cwd, f);
|
|
496
828
|
if (!import_fs.default.existsSync(p)) return false;
|
|
497
|
-
const
|
|
498
|
-
return
|
|
829
|
+
const content = import_fs.default.readFileSync(p, "utf-8");
|
|
830
|
+
return content.includes('"@/*"') || content.includes("'@/*'");
|
|
499
831
|
});
|
|
500
832
|
check(hasAlias, "TypeScript path aliases (@/*)", "run: npx basuicn init");
|
|
501
833
|
const hasViteConfig = import_fs.default.existsSync(import_path.default.join(cwd, "vite.config.ts")) || import_fs.default.existsSync(import_path.default.join(cwd, "vite.config.js"));
|
|
502
834
|
check(hasViteConfig, "vite.config.ts / vite.config.js", "run: npx basuicn init");
|
|
503
835
|
console.log("");
|
|
504
836
|
if (issues === 0) {
|
|
505
|
-
|
|
837
|
+
ok(`${c.bold}All checks passed!${c.reset} Project is healthy.`);
|
|
506
838
|
} else {
|
|
507
|
-
warn(`${issues} issue(s) found. Run
|
|
839
|
+
warn(`${c.bold}${issues} issue(s) found.${c.reset} Run ${c.cyan}npx basuicn init${c.reset} to fix most issues.`);
|
|
508
840
|
}
|
|
509
841
|
break;
|
|
510
842
|
}
|
|
511
843
|
default: {
|
|
512
|
-
|
|
513
|
-
basuicn
|
|
514
|
-
|
|
515
|
-
Commands:
|
|
516
|
-
init Initialize project (install core deps + files)
|
|
517
|
-
add <name> [--force] Add component(s) to your project
|
|
518
|
-
update <name> Update component(s) to the latest registry version
|
|
519
|
-
diff <name> Show diff between local and registry version
|
|
520
|
-
remove <name> Remove component(s) from your project
|
|
521
|
-
list List all available components
|
|
522
|
-
doctor Check project health and configuration
|
|
523
|
-
tailwind Show Tailwind config instructions
|
|
524
|
-
|
|
525
|
-
Options:
|
|
526
|
-
--local Use local registry.json instead of remote
|
|
527
|
-
--force Overwrite existing files when adding
|
|
844
|
+
error(`Unknown command: "${command}"`);
|
|
845
|
+
console.log(` Run ${c.cyan}npx basuicn --help${c.reset} to see available commands.
|
|
528
846
|
`);
|
|
529
847
|
}
|
|
530
848
|
}
|