speedrun-cli 2.7.9 → 2.7.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/CHANGELOG.md CHANGED
@@ -5,7 +5,7 @@ All notable changes to create-nestjs-auth will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [2.8.0] - 2026-08-22
8
+ ## [2.7.10] - 2026-08-22
9
9
 
10
10
  ### Added
11
11
  - **Auto-Scaffold Missing Base Architecture (`ensureBaseArchitecture`)** — When generating modules via `speedrun-cli g [module]`, `src/common/base` is automatically scaffolded if missing from target project, resolving TS2307 & TS4112 compilation errors.
@@ -18,38 +18,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
18
  - **Specific Field Editing Sub-menu** — Interactive sub-menu allows modifying specific field properties (Field Name, Field Type, Optional Status, or All) instead of forcing full re-entry.
19
19
  - **Advanced Relationship Builder** — Interactively add relations (`Many-to-One`, `One-to-Many`) pointing to target modules and foreign keys, automatically injecting attributes into Prisma, TypeORM, Mongoose, and Drizzle schemas.
20
20
  - **Auth & Roles Guard Protection Prompt** — Optional step to protect write operations (`POST`, `PUT`, `DELETE`) with `@UseGuards()` and role-based decorators (`ADMIN`, `USER`, `MANAGER`).
21
-
22
- ### Changed
23
- - **Standardized Import Statements** — Controller & Service templates now fetch `BaseController`, `BaseService`, `IBaseRepository`, `ApiResponseDto`, `ApiResponseSchema`, `PaginatedResponseDto`, `PaginatedResponseSchema`, `PaginationQueryDto` cleanly from the single barrel export `../../common/base`.
24
-
25
- ---
26
-
27
- ## [2.7.1] - 2026-08-22
21
+ - **Optional `status` Field Prompt** — Added interactive prompt `Include default 'status' field (e.g. ACTIVE)? (Y/n)` during module generation to include or omit status field.
28
22
 
29
23
  ### Fixed
30
- - **Strict Input Validation for CLI Confirm Prompts** — Implemented `StrictConfirmPrompt` across all CLI interactive prompts. Previously, typing invalid characters (e.g. `'t'`) on boolean confirm prompts would silently default to `'no'`. Now, only valid inputs (`'y'`, `'n'`, `'yes'`, `'no'`, or pressing Enter for default) are accepted, and typing invalid characters displays `>> Invalid input. Please enter 'y' or 'n'.` and re-prompts the user.
31
-
32
- ---
33
-
34
- ## [2.7.0] - 2026-08-22
35
-
36
- ### Added
37
- - **Scaffolding Cleanup** — Static `src/modules/products` directory has been completely removed from all initial project templates (`templates/base-crud*`). Scaffolding now creates zero CRUD modules by default.
38
- - **Interactive Field Builder Loop** — When generating a module (via guided setup or `speedrun-cli g [module_name]`), users can interactively define custom fields (`fieldName`, `fieldType`: `String`/`Number`/`Boolean`/`Date`, `isOptional`).
39
- - **Dynamic ORM Schema Synchronization** — Automatically syncs definitions to the project's detected ORM:
40
- - **Prisma:** Appends `model` with appropriate data types (`String`, `Float`/`Int`, `Boolean`, `DateTime`) and optional modifiers (`?`) to `prisma/schema.prisma`.
41
- - **TypeORM:** Generates `@Entity()` class in `src/modules/[kebab]/entities/[singular].entity.ts`.
42
- - **Mongoose:** Generates `@Schema()` class in `src/modules/[kebab]/schemas/[singular].schema.ts`.
43
- - **Drizzle:** Generates `pgTable` definition in `src/modules/[kebab]/schema/[kebab].schema.ts`.
44
- - **Auto-Generated Starter Seed Template** — Creates starter seed snippet populated with dummy data based on defined fields in `prisma/seeds/[kebab].seed.ts` (Prisma) or `src/database/seeds/[kebab].seed.ts` (TypeORM/Mongoose/Drizzle).
45
- - **Dynamic Post-Setup Summary Log** — Displays clear post-install next steps dynamically tailored for each ORM (migration & seed commands).
24
+ - **Runtime Error `TypeError: manageFields is not a function` Fixed** — Exported `fieldManager` in `src/index.js` entry point so `manageFields` is properly destructured in `bin/cli.js` when executing `speedrun-cli field [module]`.
25
+ - **Module Field Regeneration** — Exported `regenerateModuleComponents` in `src/moduleGenerator.js` to enable automatic DTO and ORM schema updates when field definitions are edited via `speedrun-cli field`.
26
+ - **Automated Migration Flow for CRUD Modules & JWT Setup** — Reordered post-setup steps in `handlePostSetup` so CRUD module generation occurs *before* database migrations/seeding. Any generated CRUD modules and schema updates are now automatically included when running `prisma migrate dev` / `schema:sync` / `db:push` and seeding during CLI setup.
27
+ - **Strict Input Validation for CLI Confirm Prompts** — Enforced strict `y`/`n` input validation across all CLI interactive prompts.
46
28
 
47
29
  ### Changed
48
- - **Interactive Setup Prompt Sequence** updated:
49
- 1. `Enable Base CRUD Architecture?` (Default: `Yes`)
50
- 2. `Do you want to generate your first CRUD module now?` (Default: `Yes`)
51
- 3. Shared Module Generator execution for module name, CRUD mode, and custom field loop.
52
- - **Controller Service Constructor Visibility** fixed to `protected readonly service: [Name]Service` so generated controllers safely extend `BaseController`.
30
+ - **Standardized Import Statements** — Controller & Service templates now fetch `BaseController`, `BaseService`, `IBaseRepository`, `ApiResponseDto`, `ApiResponseSchema`, `PaginatedResponseDto`, `PaginatedResponseSchema`, `PaginationQueryDto` cleanly from the single barrel export `../../common/base`.
53
31
 
54
32
  ---
55
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speedrun-cli",
3
- "version": "2.7.9",
3
+ "version": "2.7.10",
4
4
  "description": "CLI tool to scaffold a production-ready NestJS authentication system with JWT, refresh tokens, and RBAC",
5
5
  "keywords": [
6
6
  "nestjs",
package/src/index.js CHANGED
@@ -1,13 +1,22 @@
1
- /**
2
- * Module exports for CLI source files
3
- * @module src
4
- */
5
-
6
- module.exports = {
7
- ...require('./constants'),
8
- ...require('./utils'),
9
- ...require('./prompts'),
10
- ...require('./generator'),
11
- ...require('./postSetup'),
12
- ...require('./moduleGenerator'),
13
- };
1
+ /**
2
+ * Module exports for CLI source files
3
+ * @module src
4
+ */
5
+
6
+ const constants = require('./constants');
7
+ const utils = require('./utils');
8
+ const prompts = require('./prompts');
9
+ const generator = require('./generator');
10
+ const postSetup = require('./postSetup');
11
+ const moduleGenerator = require('./moduleGenerator');
12
+ const fieldManager = require('./fieldManager');
13
+
14
+ module.exports = {
15
+ ...constants,
16
+ ...utils,
17
+ ...prompts,
18
+ ...generator,
19
+ ...postSetup,
20
+ ...moduleGenerator,
21
+ ...fieldManager,
22
+ };
@@ -444,7 +444,15 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
444
444
  }
445
445
  }
446
446
 
447
- // 4. Role & Auth Guard Protection Prompt
447
+ // 4. Status Field Prompt
448
+ const { includeStatus } = await inquirer.prompt([{
449
+ type: 'confirm',
450
+ name: 'includeStatus',
451
+ message: "Include default 'status' field (e.g. ACTIVE)?",
452
+ default: true,
453
+ }]);
454
+
455
+ // 5. Role & Auth Guard Protection Prompt
448
456
  const { protectWriteOps } = await inquirer.prompt([{
449
457
  type: 'confirm',
450
458
  name: 'protectWriteOps',
@@ -473,6 +481,7 @@ async function promptForModuleOptions(providedModuleName, detectedOrm = 'prisma'
473
481
  primaryKey,
474
482
  fields,
475
483
  relations,
484
+ includeStatus,
476
485
  protectWriteOps,
477
486
  roles: selectedRoles,
478
487
  };
@@ -495,7 +504,7 @@ function getFieldExampleValue(field, pascalName) {
495
504
  /**
496
505
  * Dynamic ORM Schema Synchronization: Prisma
497
506
  */
498
- async function syncPrismaSchema(targetDir, singularPascal, kebabName, primaryKey, fields, relations) {
507
+ async function syncPrismaSchema(targetDir, singularPascal, kebabName, primaryKey, fields, relations, includeStatus = true) {
499
508
  try {
500
509
  const schemaPath = path.join(targetDir, 'prisma', 'schema.prisma');
501
510
  if (!(await fs.pathExists(schemaPath))) return;
@@ -524,12 +533,13 @@ async function syncPrismaSchema(targetDir, singularPascal, kebabName, primaryKey
524
533
  return ` ${targetCamel} ${targetPascal}? @relation(fields: [${r.fkField}], references: [id])\n ${r.fkField} String?`;
525
534
  });
526
535
 
536
+ const statusLine = includeStatus ? ' status String @default("ACTIVE")\n' : '';
537
+
527
538
  const modelDefinition = `
528
539
  model ${singularPascal} {
529
540
  ${primaryKey} String @id @default(uuid())
530
541
  ${fieldLines.join('\n')}
531
- ${relLines.length > 0 ? relLines.join('\n') + '\n' : ''} status String @default("ACTIVE")
532
- createdAt DateTime @default(now())
542
+ ${relLines.length > 0 ? relLines.join('\n') + '\n' : ''}${statusLine} createdAt DateTime @default(now())
533
543
  updatedAt DateTime @updatedAt
534
544
  deletedAt DateTime?
535
545
 
@@ -548,7 +558,7 @@ ${relLines.length > 0 ? relLines.join('\n') + '\n' : ''} status String @de
548
558
  /**
549
559
  * Dynamic ORM Schema Synchronization: TypeORM
550
560
  */
551
- async function syncTypeOrmSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, relations) {
561
+ async function syncTypeOrmSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, relations, includeStatus = true) {
552
562
  try {
553
563
  const entityDir = path.join(moduleDir, 'entities');
554
564
  await fs.ensureDir(entityDir);
@@ -582,6 +592,8 @@ async function syncTypeOrmSchema(moduleDir, singularPascal, kebabName, primaryKe
582
592
  const hasRelations = relations.length > 0;
583
593
  const imports = [`Entity`, `PrimaryGeneratedColumn`, `Column`, `CreateDateColumn`, `UpdateDateColumn`, `DeleteDateColumn`].concat(hasRelations ? [`ManyToOne`, `JoinColumn`] : []);
584
594
 
595
+ const statusLine = includeStatus ? ' @Column({ default: \'ACTIVE\' })\n status: string;\n\n' : '';
596
+
585
597
  const entityContent = `import { ${imports.join(', ')} } from 'typeorm';
586
598
 
587
599
  @Entity('${toSnakeCase(kebabName)}')
@@ -591,10 +603,7 @@ export class ${singularPascal} {
591
603
 
592
604
  ${fieldLines.join('\n\n')}
593
605
 
594
- ${relLines.length > 0 ? relLines.join('\n\n') + '\n\n' : ''} @Column({ default: 'ACTIVE' })
595
- status: string;
596
-
597
- @CreateDateColumn()
606
+ ${relLines.length > 0 ? relLines.join('\n\n') + '\n\n' : ''}${statusLine} @CreateDateColumn()
598
607
  createdAt: Date;
599
608
 
600
609
  @UpdateDateColumn()
@@ -615,7 +624,7 @@ ${relLines.length > 0 ? relLines.join('\n\n') + '\n\n' : ''} @Column({ default:
615
624
  /**
616
625
  * Dynamic ORM Schema Synchronization: Mongoose
617
626
  */
618
- async function syncMongooseSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, relations) {
627
+ async function syncMongooseSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, relations, includeStatus = true) {
619
628
  try {
620
629
  const schemaDir = path.join(moduleDir, 'schemas');
621
630
  await fs.ensureDir(schemaDir);
@@ -647,6 +656,8 @@ async function syncMongooseSchema(moduleDir, singularPascal, kebabName, primaryK
647
656
  ? ` @Prop({ default: () => new Types.ObjectId().toString() })\n ${primaryKey}: string;\n\n`
648
657
  : '';
649
658
 
659
+ const statusLine = includeStatus ? ' @Prop({ default: \'ACTIVE\' })\n status: string;\n\n' : '';
660
+
650
661
  const schemaContent = `import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
651
662
  import { HydratedDocument, SchemaTypes, Types } from 'mongoose';
652
663
 
@@ -656,10 +667,7 @@ export type ${singularPascal}Document = HydratedDocument<${singularPascal}>;
656
667
  export class ${singularPascal} {
657
668
  ${pkLine}${fieldLines.join('\n\n')}
658
669
 
659
- ${relLines.length > 0 ? relLines.join('\n\n') + '\n\n' : ''} @Prop({ default: 'ACTIVE' })
660
- status: string;
661
-
662
- @Prop({ type: Date, default: null })
670
+ ${relLines.length > 0 ? relLines.join('\n\n') + '\n\n' : ''}${statusLine} @Prop({ type: Date, default: null })
663
671
  deletedAt?: Date | null;
664
672
  }
665
673
 
@@ -676,7 +684,7 @@ export const ${singularPascal}Schema = SchemaFactory.createForClass(${singularPa
676
684
  /**
677
685
  * Dynamic ORM Schema Synchronization: Drizzle
678
686
  */
679
- async function syncDrizzleSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, relations) {
687
+ async function syncDrizzleSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, relations, includeStatus = true) {
680
688
  try {
681
689
  const schemaDir = path.join(moduleDir, 'schema');
682
690
  await fs.ensureDir(schemaDir);
@@ -709,13 +717,14 @@ async function syncDrizzleSchema(moduleDir, singularPascal, kebabName, primaryKe
709
717
  return ` ${r.fkField}: varchar('${toSnakeCase(r.fkField)}', { length: 36 }),`;
710
718
  });
711
719
 
720
+ const statusLine = includeStatus ? " status: varchar('status', { length: 50 }).default('ACTIVE').notNull(),\n" : '';
721
+
712
722
  const schemaContent = `import { pgTable, varchar, text, integer, numeric, boolean, timestamp, json } from 'drizzle-orm/pg-core';
713
723
 
714
724
  export const ${toCamelCase(kebabName)}s = pgTable('${toSnakeCase(kebabName)}', {
715
725
  ${primaryKey}: varchar('${toSnakeCase(primaryKey)}', { length: 36 }).primaryKey().$defaultFn(() => crypto.randomUUID()),
716
726
  ${fieldLines.join('\n')}
717
- ${relLines.length > 0 ? relLines.join('\n') + '\n' : ''} status: varchar('status', { length: 50 }).default('ACTIVE').notNull(),
718
- createdAt: timestamp('created_at').defaultNow().notNull(),
727
+ ${relLines.length > 0 ? relLines.join('\n') + '\n' : ''}${statusLine} createdAt: timestamp('created_at').defaultNow().notNull(),
719
728
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
720
729
  deletedAt: timestamp('deleted_at'),
721
730
  });
@@ -734,7 +743,7 @@ export type New${singularPascal} = typeof ${toCamelCase(kebabName)}s.$inferInser
734
743
  /**
735
744
  * Auto-Generate Starter Seed File Template
736
745
  */
737
- async function generateSeedFileTemplate(targetDir, orm, singularPascal, kebabName, primaryKey, fields) {
746
+ async function generateSeedFileTemplate(targetDir, orm, singularPascal, kebabName, primaryKey, fields, includeStatus = true) {
738
747
  try {
739
748
  const singularCamel = toSingularCamel(kebabName);
740
749
  const dummyObjFields = fields.map((f) => {
@@ -743,6 +752,8 @@ async function generateSeedFileTemplate(targetDir, orm, singularPascal, kebabNam
743
752
  return ` ${f.name}: ${valStr},`;
744
753
  }).join('\n');
745
754
 
755
+ const statusProp = includeStatus ? '\n status: \'ACTIVE\',' : '';
756
+
746
757
  if (orm === 'prisma') {
747
758
  const seedsDir = path.join(targetDir, 'prisma', 'seeds');
748
759
  await fs.ensureDir(seedsDir);
@@ -754,8 +765,7 @@ export async function seed${singularPascal}(prisma: PrismaClient) {
754
765
  console.log('🌱 Seeding ${singularPascal}...');
755
766
  await (prisma as any).${singularCamel}.create({
756
767
  data: {
757
- ${dummyObjFields}
758
- status: 'ACTIVE',
768
+ ${dummyObjFields}${statusProp}
759
769
  },
760
770
  });
761
771
  }
@@ -1163,13 +1173,13 @@ async function generateModule(providedModuleName, targetDir = process.cwd(), spe
1163
1173
 
1164
1174
  // 1. Dynamic ORM Schema Synchronization
1165
1175
  if (orm === 'prisma') {
1166
- await syncPrismaSchema(targetDir, singularPascal, kebabName, primaryKey, options.fields, options.relations);
1176
+ await syncPrismaSchema(targetDir, singularPascal, kebabName, primaryKey, options.fields, options.relations, options.includeStatus);
1167
1177
  } else if (orm === 'typeorm') {
1168
- await syncTypeOrmSchema(moduleDir, singularPascal, kebabName, primaryKey, options.fields, options.relations);
1178
+ await syncTypeOrmSchema(moduleDir, singularPascal, kebabName, primaryKey, options.fields, options.relations, options.includeStatus);
1169
1179
  } else if (orm === 'mongoose') {
1170
- await syncMongooseSchema(moduleDir, singularPascal, kebabName, primaryKey, options.fields, options.relations);
1180
+ await syncMongooseSchema(moduleDir, singularPascal, kebabName, primaryKey, options.fields, options.relations, options.includeStatus);
1171
1181
  } else if (orm === 'drizzle') {
1172
- await syncDrizzleSchema(moduleDir, singularPascal, kebabName, primaryKey, options.fields, options.relations);
1182
+ await syncDrizzleSchema(moduleDir, singularPascal, kebabName, primaryKey, options.fields, options.relations, options.includeStatus);
1173
1183
  }
1174
1184
 
1175
1185
  // 2. Generate DTOs
@@ -1242,6 +1252,10 @@ export class ${updateDtoName} extends PartialType(${createDtoName}) {}
1242
1252
  return ` ${swaggerDecorator}\n ${f.name}${f.isOptional ? '?' : ''}: ${details.tsType};`;
1243
1253
  }).join('\n\n');
1244
1254
 
1255
+ const statusDtoField = options.includeStatus !== false
1256
+ ? ` @ApiProperty({ example: 'ACTIVE' })\n status: string;\n\n`
1257
+ : '';
1258
+
1245
1259
  await fs.writeFile(
1246
1260
  path.join(dtoDir, `${kebabName}.dto.ts`),
1247
1261
  `import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
@@ -1252,10 +1266,7 @@ export class ${responseDtoName} {
1252
1266
 
1253
1267
  ${responseFieldsText}
1254
1268
 
1255
- @ApiProperty({ example: 'ACTIVE' })
1256
- status: string;
1257
-
1258
- @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
1269
+ ${statusDtoField} @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
1259
1270
  createdAt: Date;
1260
1271
 
1261
1272
  @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
@@ -1365,7 +1376,7 @@ ${ops.create ? `
1365
1376
  const isAutoRegistered = await registerInAppModule(targetDir, pascalName, kebabName);
1366
1377
 
1367
1378
  // 7. Auto-Generate Starter Seed File Template
1368
- await generateSeedFileTemplate(targetDir, orm, singularPascal, kebabName, primaryKey, options.fields);
1379
+ await generateSeedFileTemplate(targetDir, orm, singularPascal, kebabName, primaryKey, options.fields, options.includeStatus);
1369
1380
 
1370
1381
  console.log(chalk.green(`\n✅ Module "${kebabName}" successfully generated in ${path.relative(process.cwd(), moduleDir)}`));
1371
1382
  console.log(chalk.gray(` Detected ORM: ${orm}`));
@@ -1392,10 +1403,134 @@ export class AppModule {}
1392
1403
  }
1393
1404
  }
1394
1405
 
1406
+ /**
1407
+ * Regenerate module DTOs and ORM schemas when fields are updated via fieldManager
1408
+ */
1409
+ async function regenerateModuleComponents(moduleName, fields, targetDir = process.cwd()) {
1410
+ const orm = await detectOrm(targetDir);
1411
+ const kebabName = toKebabCase(moduleName);
1412
+ const pascalName = toPascalCase(moduleName);
1413
+ const singularPascal = toSingularPascal(pascalName);
1414
+ const primaryKey = 'id';
1415
+
1416
+ const srcDir = path.join(targetDir, 'src');
1417
+ const moduleDir = (await fs.pathExists(srcDir))
1418
+ ? path.join(srcDir, 'modules', kebabName)
1419
+ : path.join(targetDir, 'modules', kebabName);
1420
+ const dtoDir = path.join(moduleDir, 'dto');
1421
+
1422
+ await fs.ensureDir(moduleDir);
1423
+ await fs.ensureDir(dtoDir);
1424
+
1425
+ // 1. Sync ORM Schema
1426
+ if (orm === 'prisma') {
1427
+ await syncPrismaSchema(targetDir, singularPascal, kebabName, primaryKey, fields, [], true);
1428
+ } else if (orm === 'typeorm') {
1429
+ await syncTypeOrmSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, [], true);
1430
+ } else if (orm === 'mongoose') {
1431
+ await syncMongooseSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, [], true);
1432
+ } else if (orm === 'drizzle') {
1433
+ await syncDrizzleSchema(moduleDir, singularPascal, kebabName, primaryKey, fields, [], true);
1434
+ }
1435
+
1436
+ // 2. Regenerate DTOs
1437
+ const createDtoName = `Create${singularPascal}Dto`;
1438
+ const updateDtoName = `Update${singularPascal}Dto`;
1439
+ const responseDtoName = `${pascalName}Dto`;
1440
+
1441
+ const createFieldsText = fields.map((f) => {
1442
+ const details = getFieldDetails(f.type);
1443
+ const ex = getFieldExampleValue(f, pascalName);
1444
+ const exValStr = typeof ex === 'string' ? `'${ex}'` : JSON.stringify(ex);
1445
+
1446
+ const swaggerDecorator = f.isOptional
1447
+ ? `@ApiPropertyOptional({ description: '${f.name} property', example: ${exValStr} })`
1448
+ : `@ApiProperty({ description: '${f.name} property', example: ${exValStr} })`;
1449
+
1450
+ const valDecorators = [...details.valDecorators];
1451
+ if (f.isOptional) {
1452
+ valDecorators.push('@IsOptional()');
1453
+ } else {
1454
+ valDecorators.push('@IsNotEmpty()');
1455
+ }
1456
+
1457
+ return ` ${swaggerDecorator}\n ${valDecorators.join('\n ')}\n ${f.name}${f.isOptional ? '?' : ''}: ${details.tsType};`;
1458
+ }).join('\n\n');
1459
+
1460
+ const hasDateFields = fields.some((f) => ['DateTime', 'timestamp', 'Date'].includes(f.type));
1461
+
1462
+ const allValDecorators = new Set(['IsOptional', 'IsNotEmpty']);
1463
+ fields.forEach((f) => {
1464
+ const details = getFieldDetails(f.type);
1465
+ details.valDecorators.forEach((dec) => {
1466
+ const name = dec.replace('@', '').replace(/\(.*\)/, '');
1467
+ if (name) allValDecorators.add(name);
1468
+ });
1469
+ });
1470
+
1471
+ await fs.writeFile(
1472
+ path.join(dtoDir, `create-${kebabName}.dto.ts`),
1473
+ `import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
1474
+ import { ${Array.from(allValDecorators).join(', ')} } from 'class-validator';
1475
+ ${hasDateFields ? `import { Type } from 'class-transformer';\n` : ''}
1476
+ export class ${createDtoName} {
1477
+ ${createFieldsText}
1478
+ }
1479
+ `
1480
+ );
1481
+
1482
+ await fs.writeFile(
1483
+ path.join(dtoDir, `update-${kebabName}.dto.ts`),
1484
+ `import { PartialType } from '@nestjs/swagger';
1485
+ import { ${createDtoName} } from './create-${kebabName}.dto';
1486
+
1487
+ export class ${updateDtoName} extends PartialType(${createDtoName}) {}
1488
+ `
1489
+ );
1490
+
1491
+ const responseFieldsText = fields.map((f) => {
1492
+ const details = getFieldDetails(f.type);
1493
+ const ex = getFieldExampleValue(f, pascalName);
1494
+ const exValStr = typeof ex === 'string' ? `'${ex}'` : JSON.stringify(ex);
1495
+
1496
+ const swaggerDecorator = f.isOptional
1497
+ ? `@ApiPropertyOptional({ example: ${exValStr} })`
1498
+ : `@ApiProperty({ example: ${exValStr} })`;
1499
+
1500
+ return ` ${swaggerDecorator}\n ${f.name}${f.isOptional ? '?' : ''}: ${details.tsType};`;
1501
+ }).join('\n\n');
1502
+
1503
+ await fs.writeFile(
1504
+ path.join(dtoDir, `${kebabName}.dto.ts`),
1505
+ `import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
1506
+
1507
+ export class ${responseDtoName} {
1508
+ @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174000', format: 'uuid' })
1509
+ id: string;
1510
+
1511
+ ${responseFieldsText}
1512
+
1513
+ @ApiProperty({ example: 'ACTIVE' })
1514
+ status: string;
1515
+
1516
+ @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
1517
+ createdAt: Date;
1518
+
1519
+ @ApiProperty({ example: '2025-01-01T00:00:00.000Z' })
1520
+ updatedAt: Date;
1521
+
1522
+ @ApiPropertyOptional({ nullable: true, example: null })
1523
+ deletedAt?: Date | null;
1524
+ }
1525
+ `
1526
+ );
1527
+ }
1528
+
1395
1529
  module.exports = {
1396
1530
  generateModule,
1397
1531
  promptForModuleOptions,
1398
1532
  detectOrm,
1399
1533
  registerInAppModule,
1400
1534
  ensureBaseArchitecture,
1535
+ regenerateModuleComponents,
1401
1536
  };
package/src/postSetup.js CHANGED
@@ -48,14 +48,14 @@ async function handlePostSetup(targetDir, appName, options) {
48
48
  // Step 1: Configure JWT secrets and database URL
49
49
  await configureEnvironment(targetDir, database);
50
50
 
51
- // Step 2: ORM-specific database setup (Schema/Migration Seed)
52
- await setupDatabase(targetDir, orm, packageManager);
53
-
54
- // Step 3: CRUD module generation
51
+ // Step 2: CRUD module generation (updates ORM schema before running migrations)
55
52
  if (generateFirstCrud !== false) {
56
53
  await promptCrudGeneration(targetDir, orm, firstModuleName);
57
54
  }
58
55
 
56
+ // Step 3: ORM-specific database setup (Generate Client → Migration → Seed)
57
+ await setupDatabase(targetDir, orm, packageManager);
58
+
59
59
  // Step 4: Display Post-Setup Summary Log with Next Steps
60
60
  printNextStepsSummary(orm);
61
61