shadcn-nextjs-page-generator 1.0.4 → 1.0.6

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 CHANGED
@@ -12,11 +12,13 @@
12
12
  ✨ **Interactive CLI** - User-friendly prompts guide you through the generation process
13
13
  🏗️ **Flexible Architecture** - Choose between DDD (Domain-Driven Design) or Simplified structure
14
14
  🎨 **shadcn/ui Components** - Beautiful, accessible components built with Radix UI
15
+ � **Auto-Install** - Automatically detects and installs missing shadcn components
15
16
  💨 **Tailwind CSS v4** - Latest Tailwind with modern CSS-first syntax
16
17
  🎭 **Framer Motion** - Smooth animations with configurable intensity
17
18
  📊 **Complete CRUD** - Tables with search, filter, sort, pagination out of the box
18
19
  🔄 **Multiple Data Fetching** - Support for Mock data, TanStack Query, or standard fetch
19
20
  ⚡ **TypeScript First** - Full type safety and IntelliSense support
21
+ ♻️ **Smart Overwrite** - Regenerate files safely with automatic overwrite detection
20
22
 
21
23
  ## Quick Start
22
24
 
@@ -98,6 +100,20 @@ npx shadcn-nextjs-page-generator
98
100
 
99
101
  ### 3. Install Dependencies (if needed)
100
102
 
103
+ The CLI automatically checks and installs missing shadcn/ui components! You'll see:
104
+
105
+ ```
106
+ 🔍 Checking shadcn components...
107
+ ✓ button already installed
108
+ ✓ table already installed
109
+ - card missing, installing...
110
+ - pagination missing, installing...
111
+
112
+ ✓ Installed 2 component(s): card, pagination
113
+ ```
114
+
115
+ If you prefer to install manually or need additional dependencies:
116
+
101
117
  ```bash
102
118
  # If you selected TanStack Query
103
119
  npm install @tanstack/react-query
@@ -106,6 +122,8 @@ npm install @tanstack/react-query
106
122
  npm install framer-motion
107
123
  ```
108
124
 
125
+ > **Note:** Auto-install requires shadcn/ui to be initialized in your project (`npx shadcn@latest init`)
126
+
109
127
  ### 4. Navigate to Your Page
110
128
 
111
129
  ```bash
@@ -301,8 +319,9 @@ const containerVariants = {
301
319
 
302
320
  ## shadcn/ui Components Used
303
321
 
304
- The generated code uses these shadcn/ui components:
322
+ The generator automatically detects and installs the required shadcn/ui components based on your configuration:
305
323
 
324
+ **Always installed:**
306
325
  - `button`
307
326
  - `table`
308
327
  - `card`
@@ -311,14 +330,27 @@ The generated code uses these shadcn/ui components:
311
330
  - `badge`
312
331
  - `pagination`
313
332
  - `dropdown-menu`
314
- - `popover`
315
- - `calendar` (if date filters)
316
- - `checkbox` (if row selection)
317
333
 
318
- Make sure to install them:
334
+ **Conditionally installed:**
335
+ - `popover` + `calendar` (if date filters enabled)
336
+ - `checkbox` (if row selection enabled)
337
+
338
+ ### Auto-Install Feature
339
+
340
+ The CLI automatically checks for missing components and installs them using:
341
+
342
+ ```bash
343
+ npx shadcn@latest add <component> --yes --overwrite
344
+ ```
345
+
346
+ This happens before generating your files, ensuring all required components are available.
347
+
348
+ ### Manual Installation
349
+
350
+ If you prefer to install components manually:
319
351
 
320
352
  ```bash
321
- npx shadcn-ui@latest add button table card input select badge pagination dropdown-menu popover calendar checkbox
353
+ npx shadcn@latest add button table card input select badge pagination dropdown-menu popover calendar checkbox
322
354
  ```
323
355
 
324
356
  ## FAQ
package/dist/index.cjs CHANGED
@@ -377,7 +377,7 @@ ${message}
377
377
  };
378
378
 
379
379
  // src/generators/ddd-generator.ts
380
- var import_path2 = __toESM(require("path"), 1);
380
+ var import_path3 = __toESM(require("path"), 1);
381
381
 
382
382
  // src/utils/file-system.ts
383
383
  var import_fs_extra = __toESM(require("fs-extra"), 1);
@@ -385,22 +385,134 @@ var import_path = __toESM(require("path"), 1);
385
385
  async function ensureDir(dirPath) {
386
386
  await import_fs_extra.default.ensureDir(dirPath);
387
387
  }
388
- async function writeFile(filePath, content) {
388
+ async function writeFile(filePath, content, shouldOverwrite = true) {
389
389
  const dir = import_path.default.dirname(filePath);
390
390
  await ensureDir(dir);
391
- await import_fs_extra.default.writeFile(filePath, content, "utf-8");
391
+ const writeOptions = shouldOverwrite ? { encoding: "utf-8", flag: "w" } : { encoding: "utf-8", flag: "wx" };
392
+ await import_fs_extra.default.writeFile(filePath, content, writeOptions);
393
+ }
394
+ async function exists(pathToCheck) {
395
+ try {
396
+ await import_fs_extra.default.access(pathToCheck);
397
+ return true;
398
+ } catch {
399
+ return false;
400
+ }
392
401
  }
393
402
  async function createDirectories(dirs) {
394
403
  for (const dir of dirs) {
395
404
  await ensureDir(dir);
396
405
  }
397
406
  }
398
- async function writeFiles(files) {
407
+ async function writeFiles(files, shouldOverwrite = true) {
399
408
  for (const file of files) {
400
- await writeFile(file.path, file.content);
401
- logger.dim(` Created: ${file.path}`);
409
+ const fileExists = await exists(file.path);
410
+ await writeFile(file.path, file.content, shouldOverwrite);
411
+ if (fileExists) {
412
+ logger.dim(` Updated: ${file.path}`);
413
+ } else {
414
+ logger.dim(` Created: ${file.path}`);
415
+ }
402
416
  }
403
417
  }
418
+ async function checkExistingFiles(filePaths) {
419
+ const existingFiles = [];
420
+ for (const filePath of filePaths) {
421
+ if (await exists(filePath)) {
422
+ existingFiles.push(filePath);
423
+ }
424
+ }
425
+ return existingFiles;
426
+ }
427
+
428
+ // src/utils/shadcn-installer.ts
429
+ var import_child_process = require("child_process");
430
+ var import_fs = __toESM(require("fs"), 1);
431
+ var import_path2 = __toESM(require("path"), 1);
432
+ var COMPONENT_MAP = {
433
+ button: "button",
434
+ table: "table",
435
+ select: "select",
436
+ input: "input",
437
+ badge: "badge",
438
+ card: "card",
439
+ checkbox: "checkbox",
440
+ calendar: "calendar",
441
+ popover: "popover",
442
+ pagination: "pagination",
443
+ dropdown: "dropdown-menu"
444
+ };
445
+ function isComponentInstalled(componentName, cwd) {
446
+ const componentPath = import_path2.default.join(cwd, "components", "ui", `${componentName}.tsx`);
447
+ return import_fs.default.existsSync(componentPath);
448
+ }
449
+ function installComponent(componentName, cwd) {
450
+ try {
451
+ logger.info(`Installing ${componentName} component...`);
452
+ (0, import_child_process.execSync)(`npx shadcn@latest add ${componentName} --yes --overwrite`, {
453
+ cwd,
454
+ stdio: "inherit"
455
+ });
456
+ return true;
457
+ } catch (error) {
458
+ logger.error(`Failed to install ${componentName}: ${error}`);
459
+ return false;
460
+ }
461
+ }
462
+ function detectRequiredComponents(config) {
463
+ const required = /* @__PURE__ */ new Set();
464
+ required.add(COMPONENT_MAP.button);
465
+ required.add(COMPONENT_MAP.table);
466
+ required.add(COMPONENT_MAP.select);
467
+ required.add(COMPONENT_MAP.input);
468
+ required.add(COMPONENT_MAP.badge);
469
+ required.add(COMPONENT_MAP.card);
470
+ required.add(COMPONENT_MAP.pagination);
471
+ required.add(COMPONENT_MAP.dropdown);
472
+ if (config.includeRowSelection) {
473
+ required.add(COMPONENT_MAP.checkbox);
474
+ }
475
+ if (config.filters.some((f) => f.type === "date")) {
476
+ required.add(COMPONENT_MAP.calendar);
477
+ required.add(COMPONENT_MAP.popover);
478
+ }
479
+ return Array.from(required);
480
+ }
481
+ async function autoInstallComponents(config, cwd = process.cwd()) {
482
+ const result = {
483
+ installed: [],
484
+ skipped: [],
485
+ failed: []
486
+ };
487
+ const required = detectRequiredComponents(config);
488
+ logger.info(`Checking ${required.length} shadcn components...`);
489
+ for (const component of required) {
490
+ if (isComponentInstalled(component, cwd)) {
491
+ result.skipped.push(component);
492
+ logger.dim(` \u2713 ${component} already installed`);
493
+ } else {
494
+ logger.dim(` - ${component} missing, installing...`);
495
+ const success = installComponent(component, cwd);
496
+ if (success) {
497
+ result.installed.push(component);
498
+ } else {
499
+ result.failed.push(component);
500
+ }
501
+ }
502
+ }
503
+ return result;
504
+ }
505
+ function checkShadcnCli() {
506
+ try {
507
+ (0, import_child_process.execSync)("npx shadcn@latest --version", { stdio: "ignore" });
508
+ return true;
509
+ } catch {
510
+ return false;
511
+ }
512
+ }
513
+ function isShadcnInitialized(cwd = process.cwd()) {
514
+ return import_fs.default.existsSync(import_path2.default.join(cwd, "components.json"));
515
+ }
404
516
 
405
517
  // src/templates/ddd/entity.ts
406
518
  function generateEntity(config) {
@@ -633,7 +745,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
633
745
  import { Calendar as CalendarIcon } from 'lucide-react';
634
746
  import { format } from 'date-fns';` : ""}
635
747
  import { Badge } from '@/components/ui/badge';
636
- ${includeStats ? `import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';` : ""}
748
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
637
749
  import {
638
750
  Search,
639
751
  Plus,
@@ -1144,10 +1256,10 @@ import { motion } from 'framer-motion';
1144
1256
  export default function Template({ children }: { children: React.ReactNode }) {
1145
1257
  return (
1146
1258
  <motion.div
1147
- initial=${animConfig.initial}
1148
- animate=${animConfig.animate}
1259
+ initial={${animConfig.initial}}
1260
+ animate={${animConfig.animate}}
1149
1261
  exit={{ opacity: 0, y: -20 }}
1150
- transition=${animConfig.transition}
1262
+ transition={${animConfig.transition}}
1151
1263
  >
1152
1264
  {children}
1153
1265
  </motion.div>
@@ -1165,48 +1277,61 @@ var DDDGenerator = class {
1165
1277
  const files = [];
1166
1278
  const cwd = process.cwd();
1167
1279
  const { moduleName, routePath } = this.config;
1168
- const moduleDir = import_path2.default.join(cwd, "modules", moduleName);
1169
- const appDir = import_path2.default.join(cwd, "app", "(dashboard)", routePath);
1280
+ await this.checkAndInstallComponents(cwd);
1281
+ const moduleDir = import_path3.default.join(cwd, "modules", moduleName);
1282
+ const appDir = import_path3.default.join(cwd, "app", "(dashboard)", routePath);
1170
1283
  const dirs = [
1171
- import_path2.default.join(moduleDir, "domain", "entities"),
1172
- import_path2.default.join(moduleDir, "domain", "repositories"),
1173
- import_path2.default.join(moduleDir, "application", "use-cases"),
1174
- import_path2.default.join(moduleDir, "infrastructure", "repositories"),
1175
- import_path2.default.join(moduleDir, "presentation", "components"),
1284
+ import_path3.default.join(moduleDir, "domain", "entities"),
1285
+ import_path3.default.join(moduleDir, "domain", "repositories"),
1286
+ import_path3.default.join(moduleDir, "application", "use-cases"),
1287
+ import_path3.default.join(moduleDir, "infrastructure", "repositories"),
1288
+ import_path3.default.join(moduleDir, "presentation", "components"),
1176
1289
  appDir
1177
1290
  ];
1178
1291
  await createDirectories(dirs);
1179
1292
  files.push({
1180
- path: import_path2.default.join(moduleDir, "domain", "entities", `${moduleName}.entity.ts`),
1293
+ path: import_path3.default.join(moduleDir, "domain", "entities", `${moduleName}.entity.ts`),
1181
1294
  content: generateEntity(this.config)
1182
1295
  });
1183
1296
  files.push({
1184
- path: import_path2.default.join(moduleDir, "domain", "repositories", `${moduleName}.repository.interface.ts`),
1297
+ path: import_path3.default.join(moduleDir, "domain", "repositories", `${moduleName}.repository.interface.ts`),
1185
1298
  content: generateRepositoryInterface(this.config)
1186
1299
  });
1187
1300
  files.push({
1188
- path: import_path2.default.join(moduleDir, "infrastructure", "repositories", `${moduleName}.repository.ts`),
1301
+ path: import_path3.default.join(moduleDir, "infrastructure", "repositories", `${moduleName}.repository.ts`),
1189
1302
  content: generateRepositoryImpl(this.config)
1190
1303
  });
1191
1304
  files.push({
1192
- path: import_path2.default.join(moduleDir, "application", "use-cases", `get-${moduleName}s.use-case.ts`),
1305
+ path: import_path3.default.join(moduleDir, "application", "use-cases", `get-${moduleName}s.use-case.ts`),
1193
1306
  content: generateUseCase(this.config)
1194
1307
  });
1195
1308
  files.push({
1196
- path: import_path2.default.join(moduleDir, "presentation", "components", `${moduleName}-list.tsx`),
1309
+ path: import_path3.default.join(moduleDir, "presentation", "components", `${moduleName}-list.tsx`),
1197
1310
  content: generateComponent(this.config)
1198
1311
  });
1199
1312
  files.push({
1200
- path: import_path2.default.join(appDir, "page.tsx"),
1313
+ path: import_path3.default.join(appDir, "page.tsx"),
1201
1314
  content: generatePage(this.config)
1202
1315
  });
1203
1316
  if (this.config.animations.pageTransitions) {
1204
1317
  files.push({
1205
- path: import_path2.default.join(appDir, "template.tsx"),
1318
+ path: import_path3.default.join(appDir, "template.tsx"),
1206
1319
  content: generateTemplate(this.config)
1207
1320
  });
1208
1321
  }
1209
- await writeFiles(files);
1322
+ const filePaths = files.map((f) => f.path);
1323
+ const existingFiles = await checkExistingFiles(filePaths);
1324
+ if (existingFiles.length > 0) {
1325
+ console.log("");
1326
+ logger.warning(`Found ${existingFiles.length} existing file(s):`);
1327
+ existingFiles.forEach((file) => {
1328
+ const relativePath = import_path3.default.relative(cwd, file);
1329
+ logger.dim(` - ${relativePath}`);
1330
+ });
1331
+ console.log("");
1332
+ logger.info("Overwriting existing files...");
1333
+ }
1334
+ await writeFiles(files, true);
1210
1335
  const instructions = this.generateInstructions();
1211
1336
  return { files, instructions };
1212
1337
  }
@@ -1230,10 +1355,33 @@ var DDDGenerator = class {
1230
1355
  }
1231
1356
  return instructions;
1232
1357
  }
1358
+ async checkAndInstallComponents(cwd) {
1359
+ if (!isShadcnInitialized(cwd)) {
1360
+ logger.warning("shadcn/ui is not initialized in this project.");
1361
+ logger.info("Please run: npx shadcn@latest init");
1362
+ return;
1363
+ }
1364
+ if (!checkShadcnCli()) {
1365
+ logger.warning("shadcn CLI not available. Skipping component auto-install.");
1366
+ return;
1367
+ }
1368
+ console.log("");
1369
+ logger.info("\u{1F50D} Checking shadcn components...");
1370
+ const result = await autoInstallComponents(this.config, cwd);
1371
+ if (result.installed.length > 0) {
1372
+ console.log("");
1373
+ logger.success(`\u2713 Installed ${result.installed.length} component(s): ${result.installed.join(", ")}`);
1374
+ }
1375
+ if (result.failed.length > 0) {
1376
+ console.log("");
1377
+ logger.error(`\u2717 Failed to install ${result.failed.length} component(s): ${result.failed.join(", ")}`);
1378
+ }
1379
+ console.log("");
1380
+ }
1233
1381
  };
1234
1382
 
1235
1383
  // src/generators/simplified-generator.ts
1236
- var import_path3 = __toESM(require("path"), 1);
1384
+ var import_path4 = __toESM(require("path"), 1);
1237
1385
 
1238
1386
  // src/templates/simplified/component.ts
1239
1387
  function generateSimplifiedComponent(config) {
@@ -1360,7 +1508,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
1360
1508
  import { Calendar as CalendarIcon } from 'lucide-react';
1361
1509
  import { format } from 'date-fns';` : ""}
1362
1510
  import { Badge } from '@/components/ui/badge';
1363
- ${includeStats ? `import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';` : ""}
1511
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
1364
1512
  import {
1365
1513
  Search,
1366
1514
  Plus,
@@ -1794,24 +1942,37 @@ var SimplifiedGenerator = class {
1794
1942
  const files = [];
1795
1943
  const cwd = process.cwd();
1796
1944
  const { moduleName, routePath } = this.config;
1797
- const componentDir = import_path3.default.join(cwd, "components", moduleName);
1798
- const appDir = import_path3.default.join(cwd, "app", "(dashboard)", routePath);
1945
+ await this.checkAndInstallComponents(cwd);
1946
+ const componentDir = import_path4.default.join(cwd, "components", moduleName);
1947
+ const appDir = import_path4.default.join(cwd, "app", "(dashboard)", routePath);
1799
1948
  await createDirectories([componentDir, appDir]);
1800
1949
  files.push({
1801
- path: import_path3.default.join(componentDir, `${moduleName}-list.tsx`),
1950
+ path: import_path4.default.join(componentDir, `${moduleName}-list.tsx`),
1802
1951
  content: generateSimplifiedComponent(this.config)
1803
1952
  });
1804
1953
  files.push({
1805
- path: import_path3.default.join(appDir, "page.tsx"),
1954
+ path: import_path4.default.join(appDir, "page.tsx"),
1806
1955
  content: generateSimplifiedPage(this.config)
1807
1956
  });
1808
1957
  if (this.config.animations.pageTransitions) {
1809
1958
  files.push({
1810
- path: import_path3.default.join(appDir, "template.tsx"),
1959
+ path: import_path4.default.join(appDir, "template.tsx"),
1811
1960
  content: generateTemplate(this.config)
1812
1961
  });
1813
1962
  }
1814
- await writeFiles(files);
1963
+ const filePaths = files.map((f) => f.path);
1964
+ const existingFiles = await checkExistingFiles(filePaths);
1965
+ if (existingFiles.length > 0) {
1966
+ console.log("");
1967
+ logger.warning(`Found ${existingFiles.length} existing file(s):`);
1968
+ existingFiles.forEach((file) => {
1969
+ const relativePath = import_path4.default.relative(cwd, file);
1970
+ logger.dim(` - ${relativePath}`);
1971
+ });
1972
+ console.log("");
1973
+ logger.info("Overwriting existing files...");
1974
+ }
1975
+ await writeFiles(files, true);
1815
1976
  const instructions = this.generateInstructions();
1816
1977
  return { files, instructions };
1817
1978
  }
@@ -1835,6 +1996,29 @@ var SimplifiedGenerator = class {
1835
1996
  }
1836
1997
  return instructions;
1837
1998
  }
1999
+ async checkAndInstallComponents(cwd) {
2000
+ if (!isShadcnInitialized(cwd)) {
2001
+ logger.warning("shadcn/ui is not initialized in this project.");
2002
+ logger.info("Please run: npx shadcn@latest init");
2003
+ return;
2004
+ }
2005
+ if (!checkShadcnCli()) {
2006
+ logger.warning("shadcn CLI not available. Skipping component auto-install.");
2007
+ return;
2008
+ }
2009
+ console.log("");
2010
+ logger.info("\u{1F50D} Checking shadcn components...");
2011
+ const result = await autoInstallComponents(this.config, cwd);
2012
+ if (result.installed.length > 0) {
2013
+ console.log("");
2014
+ logger.success(`\u2713 Installed ${result.installed.length} component(s): ${result.installed.join(", ")}`);
2015
+ }
2016
+ if (result.failed.length > 0) {
2017
+ console.log("");
2018
+ logger.error(`\u2717 Failed to install ${result.failed.length} component(s): ${result.failed.join(", ")}`);
2019
+ }
2020
+ console.log("");
2021
+ }
1838
2022
  };
1839
2023
 
1840
2024
  // src/generators/index.ts