basuicn 0.2.0 → 0.2.3

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/ui-cli.cjs CHANGED
@@ -27,7 +27,7 @@ var import_fs = __toESM(require("fs"), 1);
27
27
  var import_path = __toESM(require("path"), 1);
28
28
  var import_child_process = require("child_process");
29
29
  var import_readline = __toESM(require("readline"), 1);
30
- var VERSION = "0.2.0";
30
+ var VERSION = "0.2.3";
31
31
  var REGISTRY_LOCAL = "./registry.json";
32
32
  var REGISTRY_REMOTE = "https://raw.githubusercontent.com/Basuicn/basuicn-core/main/registry.json";
33
33
  var c = {
@@ -47,6 +47,27 @@ var ok = (msg) => console.log(`${c.green}\u2714${c.reset} ${msg}`);
47
47
  var warn = (msg) => console.warn(`${c.yellow}\u26A0${c.reset} ${msg}`);
48
48
  var error = (msg) => console.error(`${c.red}\u2716${c.reset} ${msg}`);
49
49
  var getTargetProjectDir = () => process.cwd();
50
+ var detectFramework = (cwd) => {
51
+ const hasNextConfig = import_fs.default.existsSync(import_path.default.join(cwd, "next.config.js")) || import_fs.default.existsSync(import_path.default.join(cwd, "next.config.ts")) || import_fs.default.existsSync(import_path.default.join(cwd, "next.config.mjs"));
52
+ const hasNextDep = (() => {
53
+ const pkgPath = import_path.default.join(cwd, "package.json");
54
+ if (!import_fs.default.existsSync(pkgPath)) return false;
55
+ try {
56
+ const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
57
+ const all = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
58
+ return !!all["next"];
59
+ } catch {
60
+ return false;
61
+ }
62
+ })();
63
+ if (hasNextConfig || hasNextDep) {
64
+ const hasAppDir = import_fs.default.existsSync(import_path.default.join(cwd, "src/app")) || import_fs.default.existsSync(import_path.default.join(cwd, "app"));
65
+ const hasPagesDir = import_fs.default.existsSync(import_path.default.join(cwd, "src/pages")) || import_fs.default.existsSync(import_path.default.join(cwd, "pages"));
66
+ if (hasPagesDir && !hasAppDir) return "nextjs-pages";
67
+ return "nextjs-app";
68
+ }
69
+ return "vite";
70
+ };
50
71
  var ask = (question) => {
51
72
  const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
52
73
  return new Promise((resolve) => {
@@ -130,6 +151,19 @@ var RUNTIME_PACKAGES = [
130
151
  "tailwindcss-animate",
131
152
  "lucide-react"
132
153
  ];
154
+ var NEXTJS_DEV_PACKAGES = [
155
+ "tailwindcss",
156
+ "@tailwindcss/postcss",
157
+ "@types/node"
158
+ ];
159
+ var POSTCSS_CONFIG_TEMPLATE = `const config = {
160
+ plugins: {
161
+ '@tailwindcss/postcss': {},
162
+ },
163
+ };
164
+
165
+ export default config;
166
+ `;
133
167
  var VITE_CONFIG_TEMPLATE = `import { defineConfig } from 'vite';
134
168
  import tailwindcss from '@tailwindcss/vite';
135
169
  import react from '@vitejs/plugin-react';
@@ -237,6 +271,22 @@ var setupViteConfig = (cwd) => {
237
271
  import_fs.default.writeFileSync(existingPath, content);
238
272
  ok(`Updated ${import_path.default.basename(existingPath)} with Tailwind + path aliases.`);
239
273
  };
274
+ var setupNextConfig = (cwd) => {
275
+ installNpmPackages(NEXTJS_DEV_PACKAGES, cwd, true);
276
+ const postcssCandidates = ["postcss.config.mjs", "postcss.config.js", "postcss.config.cjs"];
277
+ const existingPostcss = postcssCandidates.map((f) => import_path.default.join(cwd, f)).find((p) => import_fs.default.existsSync(p));
278
+ if (!existingPostcss) {
279
+ import_fs.default.writeFileSync(import_path.default.join(cwd, "postcss.config.mjs"), POSTCSS_CONFIG_TEMPLATE);
280
+ ok("Created postcss.config.mjs with @tailwindcss/postcss.");
281
+ return;
282
+ }
283
+ const content = import_fs.default.readFileSync(existingPostcss, "utf-8");
284
+ if (!content.includes("@tailwindcss/postcss") && !content.includes("tailwindcss")) {
285
+ warn(`${import_path.default.basename(existingPostcss)} found but missing Tailwind plugin \u2014 add '@tailwindcss/postcss': {} to plugins manually.`);
286
+ } else {
287
+ ok(`${import_path.default.basename(existingPostcss)} already configured \u2014 skipping.`);
288
+ }
289
+ };
240
290
  var setupTsConfig = (cwd) => {
241
291
  const candidates = ["tsconfig.app.json", "tsconfig.json"];
242
292
  for (const candidate of candidates) {
@@ -288,6 +338,154 @@ var ensureCore = (registry, cwd, options = {}) => {
288
338
  ok(`${import_fs.default.existsSync(targetPath) ? "Updated" : "Created"} core file: ${file.path}`);
289
339
  }
290
340
  };
341
+ var NEXT_APP_LAYOUT_CANDIDATES = [
342
+ "src/app/layout.tsx",
343
+ "src/app/layout.jsx",
344
+ "app/layout.tsx",
345
+ "app/layout.jsx"
346
+ ];
347
+ var NEXT_PAGES_APP_CANDIDATES = [
348
+ "src/pages/_app.tsx",
349
+ "src/pages/_app.jsx",
350
+ "pages/_app.tsx",
351
+ "pages/_app.jsx"
352
+ ];
353
+ var findNextLayoutFile = (cwd) => {
354
+ for (const c2 of NEXT_APP_LAYOUT_CANDIDATES) {
355
+ const p = import_path.default.join(cwd, c2);
356
+ if (import_fs.default.existsSync(p)) return p;
357
+ }
358
+ return null;
359
+ };
360
+ var findNextPagesAppFile = (cwd) => {
361
+ for (const c2 of NEXT_PAGES_APP_CANDIDATES) {
362
+ const p = import_path.default.join(cwd, c2);
363
+ if (import_fs.default.existsSync(p)) return p;
364
+ }
365
+ return null;
366
+ };
367
+ var patchNextLayout = (cwd) => {
368
+ const layoutPath = findNextLayoutFile(cwd);
369
+ if (!layoutPath) {
370
+ warn("Could not find app/layout.tsx \u2014 add ThemeProvider and CSS import manually.");
371
+ return;
372
+ }
373
+ let content = import_fs.default.readFileSync(layoutPath, "utf-8");
374
+ let changed = false;
375
+ const cssImport = "import '@/styles/index.css';";
376
+ if (!content.includes("styles/index.css") && !content.includes("index.css")) {
377
+ const firstImport = content.match(/^import\s/m);
378
+ if (firstImport?.index !== void 0) {
379
+ content = content.slice(0, firstImport.index) + cssImport + "\n" + content.slice(firstImport.index);
380
+ } else {
381
+ content = cssImport + "\n" + content;
382
+ }
383
+ changed = true;
384
+ }
385
+ if (!content.includes("ThemeProvider")) {
386
+ content = insertImport(content, "import { ThemeProvider } from '@/lib/theme/ThemeProvider';");
387
+ const wrapped = content.replace(/\{children\}/, "<ThemeProvider>{children}</ThemeProvider>");
388
+ if (wrapped !== content) {
389
+ content = wrapped;
390
+ } else {
391
+ warn("Could not locate {children} in layout.tsx \u2014 add <ThemeProvider> wrapper manually.");
392
+ }
393
+ changed = true;
394
+ }
395
+ if (changed) {
396
+ import_fs.default.writeFileSync(layoutPath, content);
397
+ ok(`Patched ${import_path.default.relative(cwd, layoutPath)}.`);
398
+ } else {
399
+ ok(`${import_path.default.relative(cwd, layoutPath)} already configured \u2014 skipping.`);
400
+ }
401
+ };
402
+ var patchNextPagesApp = (cwd) => {
403
+ const appPath = findNextPagesAppFile(cwd);
404
+ if (!appPath) {
405
+ warn("Could not find pages/_app.tsx \u2014 add ThemeProvider and CSS import manually.");
406
+ return;
407
+ }
408
+ let content = import_fs.default.readFileSync(appPath, "utf-8");
409
+ let changed = false;
410
+ const cssImport = "import '@/styles/index.css';";
411
+ if (!content.includes("styles/index.css") && !content.includes("index.css")) {
412
+ content = insertImport(content, cssImport);
413
+ changed = true;
414
+ }
415
+ if (!content.includes("ThemeProvider")) {
416
+ content = insertImport(content, "import { ThemeProvider } from '@/lib/theme/ThemeProvider';");
417
+ const wrapped = content.replace(/(<Component\s[^/]*\/\s*>)/, "<ThemeProvider>\n $1\n </ThemeProvider>");
418
+ if (wrapped !== content) {
419
+ content = wrapped;
420
+ } else {
421
+ warn("Could not locate <Component .../> in _app.tsx \u2014 add <ThemeProvider> wrapper manually.");
422
+ }
423
+ changed = true;
424
+ }
425
+ if (changed) {
426
+ import_fs.default.writeFileSync(appPath, content);
427
+ ok(`Patched ${import_path.default.relative(cwd, appPath)}.`);
428
+ } else {
429
+ ok(`${import_path.default.relative(cwd, appPath)} already configured \u2014 skipping.`);
430
+ }
431
+ };
432
+ var patchNextLayoutComponent = (cwd, patch) => {
433
+ const layoutPath = findNextLayoutFile(cwd);
434
+ if (!layoutPath) {
435
+ warn(`Could not find Next.js layout \u2014 add ${patch.jsx} manually.`);
436
+ return;
437
+ }
438
+ let content = import_fs.default.readFileSync(layoutPath, "utf-8");
439
+ const tagName = patch.jsx.match(/<(\w+)/)?.[1];
440
+ if (tagName && content.includes(`<${tagName}`)) return;
441
+ content = insertImport(content, patch.import);
442
+ const updated = content.replace(/<\/body>/, ` ${patch.jsx}
443
+ </body>`);
444
+ if (updated !== content) {
445
+ import_fs.default.writeFileSync(layoutPath, updated);
446
+ ok(`Added <${tagName}> to ${import_path.default.relative(cwd, layoutPath)}.`);
447
+ } else {
448
+ warn(`Could not auto-add <${tagName}> to layout.tsx \u2014 add it manually.`);
449
+ }
450
+ };
451
+ var patchNextPagesAppComponent = (cwd, patch) => {
452
+ const appPath = findNextPagesAppFile(cwd);
453
+ if (!appPath) {
454
+ warn(`Could not find pages/_app.tsx \u2014 add ${patch.jsx} manually.`);
455
+ return;
456
+ }
457
+ let content = import_fs.default.readFileSync(appPath, "utf-8");
458
+ const tagName = patch.jsx.match(/<(\w+)/)?.[1];
459
+ if (tagName && content.includes(`<${tagName}`)) return;
460
+ content = insertImport(content, patch.import);
461
+ let updated = content.replace(
462
+ /(<Component\s[^/]*\/\s*>)(\s*\n\s*<\/ThemeProvider>)/,
463
+ `$1
464
+ ${patch.jsx}$2`
465
+ );
466
+ if (updated === content) {
467
+ updated = content.replace(/(<Component\s[^/]*\/\s*>)/, `$1
468
+ ${patch.jsx}`);
469
+ }
470
+ if (updated !== content) {
471
+ import_fs.default.writeFileSync(appPath, updated);
472
+ ok(`Added <${tagName}> to ${import_path.default.relative(cwd, appPath)}.`);
473
+ } else {
474
+ warn(`Could not auto-add <${tagName}> to _app.tsx \u2014 add it manually.`);
475
+ }
476
+ };
477
+ var patchEntryFile = (cwd, framework) => {
478
+ if (framework === "nextjs-app") patchNextLayout(cwd);
479
+ else if (framework === "nextjs-pages") patchNextPagesApp(cwd);
480
+ else patchMainTsx(cwd);
481
+ };
482
+ var patchEntryComponentFile = (cwd, componentName, framework) => {
483
+ const patch = MAIN_PATCH_COMPONENTS[componentName];
484
+ if (!patch) return;
485
+ if (framework === "nextjs-app") patchNextLayoutComponent(cwd, patch);
486
+ else if (framework === "nextjs-pages") patchNextPagesAppComponent(cwd, patch);
487
+ else patchMainTsxComponent(cwd, componentName);
488
+ };
291
489
  var MAIN_PATCH_COMPONENTS = {
292
490
  toast: {
293
491
  import: "import { Toaster } from '@/components/ui/toast/Toaster';",
@@ -400,7 +598,13 @@ var addComponent = (name, registry, cwd, options, added = /* @__PURE__ */ new Se
400
598
  warn(`Skipped (exists): ${file.path} \u2014 use ${c.cyan}--force${c.reset} to overwrite`);
401
599
  continue;
402
600
  }
403
- import_fs.default.writeFileSync(targetPath, file.content);
601
+ let content = file.content;
602
+ if (options.framework === "nextjs-app" && file.path.endsWith(".tsx")) {
603
+ if (!content.startsWith("'use client'") && !content.startsWith('"use client"')) {
604
+ content = "'use client';\n" + content;
605
+ }
606
+ }
607
+ import_fs.default.writeFileSync(targetPath, content);
404
608
  ok(`Created: ${file.path}`);
405
609
  }
406
610
  };
@@ -468,13 +672,21 @@ var HELP_COMMANDS = {
468
672
  ${c.bold}basuicn init${c.reset}
469
673
 
470
674
  Initialize your project for basuicn components.
675
+ Auto-detects Vite or Next.js (App Router / Pages Router).
471
676
 
472
- ${c.bold}What it does:${c.reset}
677
+ ${c.bold}What it does (Vite):${c.reset}
473
678
  1. Installs runtime dependencies (@base-ui/react, tailwind-variants, etc.)
474
679
  2. Sets up vite.config.ts with Tailwind CSS + path aliases
475
680
  3. Patches tsconfig.json with path aliases (@/*, @lib/*, etc.)
476
681
  4. Copies core files (cn.ts, themes.ts, ThemeProvider.tsx, index.css)
477
- 5. Wraps your <App /> with <ThemeProvider> in the main entry
682
+ 5. Wraps your <App /> with <ThemeProvider> in src/main.tsx
683
+
684
+ ${c.bold}What it does (Next.js):${c.reset}
685
+ 1. Installs runtime dependencies (@base-ui/react, tailwind-variants, etc.)
686
+ 2. Sets up postcss.config.mjs with @tailwindcss/postcss
687
+ 3. Patches tsconfig.json with path aliases (@/*, @lib/*, etc.)
688
+ 4. Copies core files (cn.ts, themes.ts, ThemeProvider.tsx, index.css)
689
+ 5. Wraps {children} with <ThemeProvider> in app/layout.tsx (or pages/_app.tsx)
478
690
 
479
691
  ${c.bold}Usage:${c.reset}
480
692
  ${c.dim}$${c.reset} npx basuicn init
@@ -584,11 +796,17 @@ var main = async () => {
584
796
  switch (command) {
585
797
  case "init": {
586
798
  log("Initializing project...");
587
- setupViteConfig(cwd);
799
+ const framework = detectFramework(cwd);
800
+ if (framework === "vite") {
801
+ setupViteConfig(cwd);
802
+ } else {
803
+ log(`Detected Next.js (${framework === "nextjs-app" ? "App Router" : "Pages Router"}).`);
804
+ setupNextConfig(cwd);
805
+ }
588
806
  setupTsConfig(cwd);
589
807
  installNpmPackages(RUNTIME_PACKAGES, cwd);
590
808
  ensureCore(registry, cwd, { force: true });
591
- patchMainTsx(cwd);
809
+ patchEntryFile(cwd, framework);
592
810
  console.log("");
593
811
  ok(`${c.bold}Initialization complete!${c.reset} Run ${c.cyan}npx basuicn add <component>${c.reset} to get started.`);
594
812
  break;
@@ -619,18 +837,24 @@ ${c.bold}Available components (${all.length}):${c.reset}`);
619
837
  names = answer === "all" ? all : answer.split(/[\s,]+/).filter(Boolean);
620
838
  }
621
839
  const cnPath = import_path.default.join(cwd, "src/lib/utils/cn.ts");
840
+ const framework = detectFramework(cwd);
622
841
  if (!import_fs.default.existsSync(cnPath)) {
623
842
  log("Project not initialized \u2014 running init first...");
624
- setupViteConfig(cwd);
843
+ if (framework === "vite") {
844
+ setupViteConfig(cwd);
845
+ } else {
846
+ log(`Detected Next.js (${framework === "nextjs-app" ? "App Router" : "Pages Router"}).`);
847
+ setupNextConfig(cwd);
848
+ }
625
849
  setupTsConfig(cwd);
626
850
  installNpmPackages(RUNTIME_PACKAGES, cwd);
627
851
  ensureCore(registry, cwd, { force: true });
628
- patchMainTsx(cwd);
852
+ patchEntryFile(cwd, framework);
629
853
  console.log("");
630
854
  }
631
855
  for (const name of names) {
632
- addComponent(name, registry, cwd, { force: isForce });
633
- patchMainTsxComponent(cwd, name);
856
+ addComponent(name, registry, cwd, { force: isForce, framework });
857
+ patchEntryComponentFile(cwd, name, framework);
634
858
  }
635
859
  console.log("");
636
860
  ok(`${c.bold}Done!${c.reset} Added ${names.length} component(s).`);
@@ -762,6 +986,11 @@ ${c.bold}Project Health Check${c.reset}
762
986
  issues++;
763
987
  }
764
988
  };
989
+ const docFramework = detectFramework(cwd);
990
+ if (docFramework !== "vite") {
991
+ console.log(` ${c.cyan}\u2139${c.reset} Framework: Next.js (${docFramework === "nextjs-app" ? "App Router" : "Pages Router"})
992
+ `);
993
+ }
765
994
  check(
766
995
  import_fs.default.existsSync(import_path.default.join(cwd, "src/lib/utils/cn.ts")),
767
996
  "src/lib/utils/cn.ts",
@@ -782,31 +1011,49 @@ ${c.bold}Project Health Check${c.reset}
782
1011
  "src/styles/index.css (theme variables)",
783
1012
  "run: npx basuicn init"
784
1013
  );
785
- const mainPath = findMainFile(cwd);
786
- if (mainPath) {
787
- const mainContent = import_fs.default.readFileSync(mainPath, "utf-8");
788
- check(
789
- mainContent.includes("ThemeProvider"),
790
- "ThemeProvider in main entry",
791
- "run: npx basuicn init"
792
- );
793
- check(
794
- mainContent.includes("styles/index.css") || mainContent.includes("index.css"),
795
- "CSS import in main entry",
796
- "run: npx basuicn init"
797
- );
1014
+ if (docFramework === "nextjs-app") {
1015
+ const layoutPath = findNextLayoutFile(cwd);
1016
+ if (layoutPath) {
1017
+ const layoutContent = import_fs.default.readFileSync(layoutPath, "utf-8");
1018
+ check(layoutContent.includes("ThemeProvider"), "ThemeProvider in app/layout.tsx", "run: npx basuicn init");
1019
+ check(layoutContent.includes("styles/index.css") || layoutContent.includes("index.css"), "CSS import in app/layout.tsx", "run: npx basuicn init");
1020
+ } else {
1021
+ check(false, "app/layout.tsx", "create src/app/layout.tsx");
1022
+ }
1023
+ } else if (docFramework === "nextjs-pages") {
1024
+ const pagesAppPath = findNextPagesAppFile(cwd);
1025
+ if (pagesAppPath) {
1026
+ const pagesAppContent = import_fs.default.readFileSync(pagesAppPath, "utf-8");
1027
+ check(pagesAppContent.includes("ThemeProvider"), "ThemeProvider in pages/_app.tsx", "run: npx basuicn init");
1028
+ check(pagesAppContent.includes("styles/index.css") || pagesAppContent.includes("index.css"), "CSS import in pages/_app.tsx", "run: npx basuicn init");
1029
+ } else {
1030
+ check(false, "pages/_app.tsx", "create pages/_app.tsx");
1031
+ }
798
1032
  } else {
799
- check(false, "main entry file (src/main.tsx)", "create src/main.tsx");
1033
+ const mainPath = findMainFile(cwd);
1034
+ if (mainPath) {
1035
+ const mainContent = import_fs.default.readFileSync(mainPath, "utf-8");
1036
+ check(mainContent.includes("ThemeProvider"), "ThemeProvider in main entry", "run: npx basuicn init");
1037
+ check(mainContent.includes("styles/index.css") || mainContent.includes("index.css"), "CSS import in main entry", "run: npx basuicn init");
1038
+ } else {
1039
+ check(false, "main entry file (src/main.tsx)", "create src/main.tsx");
1040
+ }
800
1041
  }
801
1042
  const pkgPath = import_path.default.join(cwd, "package.json");
802
1043
  if (import_fs.default.existsSync(pkgPath)) {
803
1044
  const pkg = JSON.parse(import_fs.default.readFileSync(pkgPath, "utf-8"));
804
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
1045
+ const allDeps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
805
1046
  for (const dep of RUNTIME_PACKAGES) {
806
1047
  check(!!allDeps[dep], `package: ${dep}`, `run: npm install ${dep}`);
807
1048
  }
808
- for (const dep of VITE_DEV_PACKAGES) {
809
- check(!!allDeps[dep], `package (dev): ${dep}`, `run: npm install -D ${dep}`);
1049
+ if (docFramework === "vite") {
1050
+ for (const dep of VITE_DEV_PACKAGES) {
1051
+ check(!!allDeps[dep], `package (dev): ${dep}`, `run: npm install -D ${dep}`);
1052
+ }
1053
+ } else {
1054
+ for (const dep of NEXTJS_DEV_PACKAGES) {
1055
+ check(!!allDeps[dep], `package (dev): ${dep}`, `run: npm install -D ${dep}`);
1056
+ }
810
1057
  }
811
1058
  } else {
812
1059
  check(false, "package.json found", "run: npm init -y");
@@ -829,8 +1076,13 @@ ${c.bold}Project Health Check${c.reset}
829
1076
  return content.includes('"@/*"') || content.includes("'@/*'");
830
1077
  });
831
1078
  check(hasAlias, "TypeScript path aliases (@/*)", "run: npx basuicn init");
832
- 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"));
833
- check(hasViteConfig, "vite.config.ts / vite.config.js", "run: npx basuicn init");
1079
+ if (docFramework === "vite") {
1080
+ 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"));
1081
+ check(hasViteConfig, "vite.config.ts / vite.config.js", "run: npx basuicn init");
1082
+ } else {
1083
+ const hasPostcss = ["postcss.config.mjs", "postcss.config.js", "postcss.config.cjs"].some((f) => import_fs.default.existsSync(import_path.default.join(cwd, f)));
1084
+ check(hasPostcss, "postcss.config.mjs (Tailwind CSS for Next.js)", "run: npx basuicn init");
1085
+ }
834
1086
  console.log("");
835
1087
  if (issues === 0) {
836
1088
  ok(`${c.bold}All checks passed!${c.reset} Project is healthy.`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "basuicn",
3
3
  "private": false,
4
- "version": "0.2.0",
4
+ "version": "0.2.3",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "basuicn": "./dist/ui-cli.cjs"