nestcraftx 0.3.0 → 1.0.0

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.
Files changed (62) hide show
  1. package/.github/workflows/ci.yml +35 -0
  2. package/CHANGELOG.fr.md +74 -0
  3. package/CHANGELOG.md +74 -0
  4. package/PROGRESS.md +70 -67
  5. package/README.fr.md +60 -69
  6. package/bin/nestcraft.js +8 -0
  7. package/commands/demo.js +12 -46
  8. package/commands/generate.js +551 -2
  9. package/commands/generateConf.js +4 -4
  10. package/commands/help.js +11 -0
  11. package/commands/info.js +2 -3
  12. package/commands/list.js +93 -0
  13. package/commands/new.js +28 -56
  14. package/jest.config.js +9 -0
  15. package/package.json +7 -2
  16. package/readme.md +59 -68
  17. package/tests/e2e/generator.spec.js +71 -0
  18. package/tests/unit/appModuleUpdater.spec.js +111 -0
  19. package/tests/unit/cliParser.spec.js +74 -0
  20. package/tests/unit/controllerGenerator.spec.js +145 -0
  21. package/tests/unit/dtoGenerator.spec.js +87 -0
  22. package/tests/unit/entityGenerator.spec.js +65 -0
  23. package/tests/unit/generateCommand.spec.js +100 -0
  24. package/tests/unit/health.spec.js +69 -0
  25. package/tests/unit/listCommand.spec.js +91 -0
  26. package/tests/unit/middlewareGenerator.spec.js +64 -0
  27. package/tests/unit/newCommand.spec.js +88 -0
  28. package/tests/unit/relationCommand.spec.js +202 -0
  29. package/tests/unit/throttler.spec.js +117 -0
  30. package/utils/app-module.updater.js +6 -0
  31. package/utils/configs/setupCleanArchitecture.js +1 -1
  32. package/utils/configs/setupLightArchitecture.js +98 -26
  33. package/utils/envGenerator.js +9 -1
  34. package/utils/file-system.js +15 -0
  35. package/utils/file-utils/packageJsonUtils.js +6 -0
  36. package/utils/file-utils/saveProjectConfig.js +9 -0
  37. package/utils/fullModeInput.js +12 -229
  38. package/utils/generators/application/dtoGenerator.js +26 -8
  39. package/utils/generators/application/dtoUpdater.js +5 -0
  40. package/utils/generators/cleanModuleGenerator.js +40 -11
  41. package/utils/generators/domain/entityUpdater.js +5 -0
  42. package/utils/generators/infrastructure/mapperUpdater.js +5 -0
  43. package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
  44. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +114 -4
  45. package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
  46. package/utils/generators/lightModuleGenerator.js +14 -0
  47. package/utils/generators/presentation/controllerGenerator.js +70 -19
  48. package/utils/helpers.js +8 -2
  49. package/utils/interactive/askEntityInputs.js +5 -68
  50. package/utils/interactive/askRelationCommand.js +98 -0
  51. package/utils/interactive/entityBuilder.js +411 -0
  52. package/utils/lightModeInput.js +22 -241
  53. package/utils/setups/orms/typeOrmSetup.js +42 -8
  54. package/utils/setups/projectSetup.js +88 -10
  55. package/utils/setups/setupAuth.js +335 -20
  56. package/utils/setups/setupDatabase.js +4 -1
  57. package/utils/setups/setupHealth.js +74 -0
  58. package/utils/setups/setupMongoose.js +84 -32
  59. package/utils/setups/setupPrisma.js +151 -4
  60. package/utils/setups/setupSwagger.js +15 -8
  61. package/utils/setups/setupThrottler.js +92 -0
  62. package/utils/shell.js +61 -9
@@ -17,11 +17,11 @@ async function setupAuth(inputs) {
17
17
  const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
18
18
 
19
19
  await runCommand(
20
- `${installCmd} @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt uuid`,
20
+ `${installCmd} @nestjs/jwt@^10.2.0 @nestjs/passport@^10.0.3 passport@^0.7.0 passport-jwt@^4.0.1 bcrypt@^5.1.1 uuid@^9.0.1`,
21
21
  "Erreur install deps auth",
22
22
  );
23
23
  await runCommand(
24
- `${installCmd} -D @types/passport-jwt @types/bcrypt @types/uuid`,
24
+ `${installCmd} -D @types/passport-jwt@^4.0.1 @types/bcrypt@^5.0.2 @types/uuid@^9.0.8`,
25
25
  "Erreur install dev-deps auth",
26
26
  );
27
27
 
@@ -120,6 +120,109 @@ export class Session {
120
120
  }
121
121
 
122
122
  export const SessionSchema = SchemaFactory.createForClass(Session);
123
+ `.trim(),
124
+ });
125
+
126
+ await createFile({
127
+ path: `${sessionSchemaPath}/password-reset.schema.ts`,
128
+ contente: `
129
+ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
130
+ import { Document } from 'mongoose';
131
+
132
+ export type PasswordResetDocument = PasswordReset & Document;
133
+
134
+ @Schema({ timestamps: true })
135
+ export class PasswordReset {
136
+ @Prop({ required: true })
137
+ email: string;
138
+
139
+ @Prop({ required: true, unique: true })
140
+ token: string;
141
+
142
+ @Prop({ required: true })
143
+ expiresAt: Date;
144
+ }
145
+
146
+ export const PasswordResetSchema = SchemaFactory.createForClass(PasswordReset);
147
+ `.trim(),
148
+ });
149
+
150
+ await createFile({
151
+ path: `${sessionSchemaPath}/otp.schema.ts`,
152
+ contente: `
153
+ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
154
+ import { Document } from 'mongoose';
155
+
156
+ export type OtpDocument = Otp & Document;
157
+
158
+ @Schema({ timestamps: true })
159
+ export class Otp {
160
+ @Prop({ required: true })
161
+ email: string;
162
+
163
+ @Prop({ required: true })
164
+ otp: string;
165
+
166
+ @Prop({ required: true })
167
+ expiresAt: Date;
168
+ }
169
+
170
+ export const OtpSchema = SchemaFactory.createForClass(Otp);
171
+ `.trim(),
172
+ });
173
+ }
174
+
175
+ // --- GÉNÉRATION DES ENTITÉS (Spécifique TypeORM) ---
176
+ if (dbConfig.orm === "typeorm") {
177
+ await createDirectory("src/entities");
178
+
179
+ await createFile({
180
+ path: `src/entities/PasswordReset.entity.ts`,
181
+ contente: `
182
+ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
183
+
184
+ @Entity('password_reset')
185
+ export class PasswordReset {
186
+ @PrimaryGeneratedColumn('uuid')
187
+ id: string;
188
+
189
+ @Column({ type: 'varchar' })
190
+ email: string;
191
+
192
+ @Column({ type: 'varchar', unique: true })
193
+ token: string;
194
+
195
+ @Column({ type: 'timestamp' })
196
+ expiresAt: Date;
197
+
198
+ @CreateDateColumn()
199
+ createdAt: Date;
200
+ }
201
+ `.trim(),
202
+ });
203
+
204
+ await createFile({
205
+ path: `src/entities/Otp.entity.ts`,
206
+ contente: `
207
+ import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm';
208
+
209
+ @Entity('otp')
210
+ export class Otp {
211
+ @PrimaryGeneratedColumn('uuid')
212
+ id: string;
213
+
214
+ @Column({ type: 'varchar' })
215
+ email: string;
216
+
217
+ @Column({ type: 'varchar' })
218
+ otp: string;
219
+
220
+ @Column({ type: 'timestamp' })
221
+ expiresAt: Date;
222
+
223
+ @CreateDateColumn()
224
+ createdAt: Date;
225
+ }
123
226
  `.trim(),
124
227
  });
125
228
  }
@@ -252,6 +355,8 @@ import { SendOtpDto } from '${paths.appDtos}/sendOtp.dto';
252
355
  import { VerifyOtpDto } from '${paths.appDtos}/verifyOtp.dto';
253
356
  import { ForgotPasswordDto } from '${paths.appDtos}/forgotPassword.dto';
254
357
  import { ResetPasswordDto } from '${paths.appDtos}/resetPassword.dto';
358
+ import { OtpRepository } from '${paths.persistence}/otp.repository';
359
+ import { PasswordResetRepository } from '${paths.persistence}/password-reset.repository';
255
360
  ${enumImport}
256
361
  ${
257
362
  mode === "light"
@@ -263,10 +368,11 @@ ${enumImport}
263
368
 
264
369
  @Injectable()
265
370
  export class AuthService {
266
- private otps = new Map<string, string>();
267
371
  constructor(
268
372
  private readonly jwtService: JwtService,
269
373
  private readonly sessionService: SessionService,
374
+ private readonly otpRepository: OtpRepository,
375
+ private readonly passwordResetRepository: PasswordResetRepository,
270
376
  ${
271
377
  mode === "light"
272
378
  ? `private readonly userRepository: UserRepository,`
@@ -345,21 +451,26 @@ export class AuthService {
345
451
  }
346
452
 
347
453
  // 📲 Send OTP
348
- sendOtp(dto: SendOtpDto) {
454
+ async sendOtp(dto: SendOtpDto) {
349
455
  const otp = Math.floor(100000 + Math.random() * 900000).toString();
350
- this.otps.set(dto.email, otp);
456
+ const expiresAt = new Date();
457
+ expiresAt.setMinutes(expiresAt.getMinutes() + 5); // Valid for 5 mins
458
+
459
+ await this.otpRepository.deleteByEmail(dto.email);
460
+ await this.otpRepository.save({ email: dto.email, otp, expiresAt });
461
+
351
462
  console.log(\`[OTP] for \${dto.email} is \${otp}\`);
352
463
  return { message: 'OTP sent' };
353
464
  }
354
465
 
355
466
  // Verify OTP
356
- verifyOtp(dto: VerifyOtpDto) {
357
- const valid = this.otps.get(dto.email);
358
- if (valid === dto.otp) {
359
- this.otps.delete(dto.email);
360
- return { message: 'OTP verified' };
467
+ async verifyOtp(dto: VerifyOtpDto) {
468
+ const record = await this.otpRepository.findLatestByEmail(dto.email);
469
+ if (record && record.otp === dto.otp && record.expiresAt > new Date()) {
470
+ await this.otpRepository.deleteByEmail(dto.email);
471
+ return { message: 'OTP verified' };
361
472
  }
362
- throw new UnauthorizedException('Invalid OTP');
473
+ throw new UnauthorizedException('Invalid or expired OTP');
363
474
  }
364
475
 
365
476
  // 📬 Forgot Password
@@ -368,18 +479,31 @@ export class AuthService {
368
479
  if (!existingUser) throw new NotFoundException('User not found');
369
480
 
370
481
  const token = uuidv4();
482
+ const expiresAt = new Date();
483
+ expiresAt.setHours(expiresAt.getHours() + 1); // Valid for 1 hour
484
+
485
+ await this.passwordResetRepository.deleteByEmail(dto.email);
486
+ await this.passwordResetRepository.save({ email: dto.email, token, expiresAt });
487
+
371
488
  console.log(\`[ResetToken] for \${dto.email} is \${token}\`);
372
489
  return { message: 'Reset token sent' };
373
490
  }
374
491
 
375
492
  // 🔄 Reset Password
376
493
  async resetPassword(dto: ResetPasswordDto) {
494
+ const record = await this.passwordResetRepository.findByToken(dto.token);
495
+ if (!record || record.email !== dto.email || record.expiresAt < new Date()) {
496
+ throw new UnauthorizedException('Invalid or expired reset token');
497
+ }
498
+
377
499
  const existingUser = await this.userRepository.findByEmail(dto.email);
378
- if (!existingUser) throw new UnauthorizedException('Invalid reset token');
500
+ if (!existingUser) throw new UnauthorizedException('User not found');
379
501
 
380
502
  const password = await this.hashPassword(dto.newPassword);
381
503
  await this.userRepository.update(existingUser.getId(), { password });
382
504
 
505
+ await this.passwordResetRepository.deleteByEmail(dto.email);
506
+
383
507
  return { message: 'Password reset successful' };
384
508
  }
385
509
 
@@ -566,6 +690,188 @@ export class SessionRepository ${implementsClause} {
566
690
  contente: repoContent.trim(),
567
691
  });
568
692
 
693
+ // --- PASSWORD RESET REPOSITORY ---
694
+ let resetRepoContent = "";
695
+ if (dbConfig.orm === "typeorm") {
696
+ resetRepoContent = `
697
+ import { Injectable } from '@nestjs/common';
698
+ import { InjectRepository } from '@nestjs/typeorm';
699
+ import { Repository } from 'typeorm';
700
+ import { PasswordReset as PasswordResetEntity } from 'src/entities/PasswordReset.entity';
701
+
702
+ @Injectable()
703
+ export class PasswordResetRepository {
704
+ constructor(
705
+ @InjectRepository(PasswordResetEntity)
706
+ private readonly repo: Repository<PasswordResetEntity>,
707
+ ) {}
708
+
709
+ async save(data: { email: string; token: string; expiresAt: Date }): Promise<PasswordResetEntity> {
710
+ return this.repo.save(data);
711
+ }
712
+
713
+ async findByToken(token: string): Promise<PasswordResetEntity | null> {
714
+ return this.repo.findOne({ where: { token } });
715
+ }
716
+
717
+ async deleteByEmail(email: string): Promise<void> {
718
+ await this.repo.delete({ email });
719
+ }
720
+ }
721
+ `.trim();
722
+ } else if (dbConfig.orm === "prisma") {
723
+ resetRepoContent = `
724
+ import { Injectable } from '@nestjs/common';
725
+ import { PrismaService } from 'src/prisma/prisma.service';
726
+
727
+ @Injectable()
728
+ export class PasswordResetRepository {
729
+ constructor(private readonly prisma: PrismaService) {}
730
+
731
+ async save(data: { email: string; token: string; expiresAt: Date }) {
732
+ return this.prisma.passwordReset.create({ data });
733
+ }
734
+
735
+ async findByToken(token: string) {
736
+ return this.prisma.passwordReset.findUnique({ where: { token } });
737
+ }
738
+
739
+ async deleteByEmail(email: string) {
740
+ await this.prisma.passwordReset.deleteMany({ where: { email } });
741
+ }
742
+ }
743
+ `.trim();
744
+ } else if (dbConfig.orm === "mongoose") {
745
+ const mongooseSchemaPath = isFull
746
+ ? "src/auth/infrastructure/persistence/mongoose"
747
+ : "src/auth/persistence";
748
+ resetRepoContent = `
749
+ import { Injectable } from '@nestjs/common';
750
+ import { InjectModel } from '@nestjs/mongoose';
751
+ import { Model } from 'mongoose';
752
+ import { PasswordReset, PasswordResetDocument } from '${mongooseSchemaPath}/password-reset.schema';
753
+
754
+ @Injectable()
755
+ export class PasswordResetRepository {
756
+ constructor(
757
+ @InjectModel(PasswordReset.name)
758
+ private readonly model: Model<PasswordResetDocument>,
759
+ ) {}
760
+
761
+ async save(data: { email: string; token: string; expiresAt: Date }) {
762
+ return new this.model(data).save();
763
+ }
764
+
765
+ async findByToken(token: string) {
766
+ return this.model.findOne({ token }).exec();
767
+ }
768
+
769
+ async deleteByEmail(email: string) {
770
+ await this.model.deleteMany({ email }).exec();
771
+ }
772
+ }
773
+ `.trim();
774
+ }
775
+
776
+ await createFile({
777
+ path: `${paths.persistence}/password-reset.repository.ts`,
778
+ contente: resetRepoContent,
779
+ });
780
+
781
+ // --- OTP REPOSITORY ---
782
+ let otpRepoContent = "";
783
+ if (dbConfig.orm === "typeorm") {
784
+ otpRepoContent = `
785
+ import { Injectable } from '@nestjs/common';
786
+ import { InjectRepository } from '@nestjs/typeorm';
787
+ import { Repository } from 'typeorm';
788
+ import { Otp as OtpEntity } from 'src/entities/Otp.entity';
789
+
790
+ @Injectable()
791
+ export class OtpRepository {
792
+ constructor(
793
+ @InjectRepository(OtpEntity)
794
+ private readonly repo: Repository<OtpEntity>,
795
+ ) {}
796
+
797
+ async save(data: { email: string; otp: string; expiresAt: Date }): Promise<OtpEntity> {
798
+ return this.repo.save(data);
799
+ }
800
+
801
+ async findLatestByEmail(email: string): Promise<OtpEntity | null> {
802
+ return this.repo.findOne({
803
+ where: { email },
804
+ order: { createdAt: 'DESC' }
805
+ });
806
+ }
807
+
808
+ async deleteByEmail(email: string): Promise<void> {
809
+ await this.repo.delete({ email });
810
+ }
811
+ }
812
+ `.trim();
813
+ } else if (dbConfig.orm === "prisma") {
814
+ otpRepoContent = `
815
+ import { Injectable } from '@nestjs/common';
816
+ import { PrismaService } from 'src/prisma/prisma.service';
817
+
818
+ @Injectable()
819
+ export class OtpRepository {
820
+ constructor(private readonly prisma: PrismaService) {}
821
+
822
+ async save(data: { email: string; otp: string; expiresAt: Date }) {
823
+ return this.prisma.otp.create({ data });
824
+ }
825
+
826
+ async findLatestByEmail(email: string) {
827
+ return this.prisma.otp.findFirst({
828
+ where: { email },
829
+ orderBy: { createdAt: 'desc' }
830
+ });
831
+ }
832
+
833
+ async deleteByEmail(email: string) {
834
+ await this.prisma.otp.deleteMany({ where: { email } });
835
+ }
836
+ }
837
+ `.trim();
838
+ } else if (dbConfig.orm === "mongoose") {
839
+ const mongooseSchemaPath = isFull
840
+ ? "src/auth/infrastructure/persistence/mongoose"
841
+ : "src/auth/persistence";
842
+ otpRepoContent = `
843
+ import { Injectable } from '@nestjs/common';
844
+ import { InjectModel } from '@nestjs/mongoose';
845
+ import { Model } from 'mongoose';
846
+ import { Otp, OtpDocument } from '${mongooseSchemaPath}/otp.schema';
847
+
848
+ @Injectable()
849
+ export class OtpRepository {
850
+ constructor(
851
+ @InjectModel(Otp.name)
852
+ private readonly model: Model<OtpDocument>,
853
+ ) {}
854
+
855
+ async save(data: { email: string; otp: string; expiresAt: Date }) {
856
+ return new this.model(data).save();
857
+ }
858
+
859
+ async findLatestByEmail(email: string) {
860
+ return this.model.findOne({ email }).sort({ createdAt: -1 }).exec();
861
+ }
862
+
863
+ async deleteByEmail(email: string) {
864
+ await this.model.deleteMany({ email }).exec();
865
+ }
866
+ }
867
+ `.trim();
868
+ }
869
+
870
+ await createFile({
871
+ path: `${paths.persistence}/otp.repository.ts`,
872
+ contente: otpRepoContent,
873
+ });
874
+
569
875
  // --- 6. INFRASTRUCTURE WEB (CONTROLLER, GUARD, STRATEGY) ---
570
876
 
571
877
  await createFile({
@@ -788,25 +1094,30 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
788
1094
  let dbProviders = "";
789
1095
  if (dbConfig.orm === "typeorm") {
790
1096
  dbImports =
791
- "import { TypeOrmModule } from '@nestjs/typeorm';\nimport { Session as SessionEntity } from 'src/entities/Session.entity';";
792
- dbProviders = "TypeOrmModule.forFeature([SessionEntity]),";
1097
+ "import { TypeOrmModule } from '@nestjs/typeorm';\nimport { Session as SessionEntity } from 'src/entities/Session.entity';\nimport { PasswordReset as PasswordResetEntity } from 'src/entities/PasswordReset.entity';\nimport { Otp as OtpEntity } from 'src/entities/Otp.entity';";
1098
+ dbProviders = "TypeOrmModule.forFeature([SessionEntity, PasswordResetEntity, OtpEntity]),";
793
1099
  } else if (dbConfig.orm === "mongoose") {
794
1100
  const schemaRelativePath = isFull
795
1101
  ? "./infrastructure/persistence/mongoose/session.schema"
796
1102
  : "./persistence/session.schema";
797
1103
 
798
1104
  dbImports = `import { MongooseModule } from '@nestjs/mongoose';
799
- import { Session, SessionSchema } from '${schemaRelativePath}';`;
800
-
801
- dbProviders = `MongooseModule.forFeature([{ name: Session.name, schema: SessionSchema }]),`;
1105
+ import { Session, SessionSchema } from '${schemaRelativePath}';
1106
+ import { PasswordReset, PasswordResetSchema } from '${schemaRelativePath.replace("session.schema", "password-reset.schema")}';
1107
+ import { Otp, OtpSchema } from '${schemaRelativePath.replace("session.schema", "otp.schema")}';`;
1108
+
1109
+ dbProviders = `MongooseModule.forFeature([
1110
+ { name: Session.name, schema: SessionSchema },
1111
+ { name: PasswordReset.name, schema: PasswordResetSchema },
1112
+ { name: Otp.name, schema: OtpSchema }
1113
+ ]),`;
802
1114
  } else if (dbConfig.orm === "prisma") {
803
1115
  dbImports = "import { PrismaModule } from 'src/prisma/prisma.module';";
804
1116
  dbProviders = "PrismaModule,";
805
1117
  }
806
1118
 
807
1119
  if (inputs.mode == "full") {
808
- dbImports =
809
- +"import { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';";
1120
+ dbImports += `\nimport { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';`;
810
1121
  }
811
1122
 
812
1123
  await createFile({
@@ -823,6 +1134,8 @@ import { SessionService } from '${paths.services}/session.service';
823
1134
  import { AuthController } from '${paths.controllers}/auth.controller';
824
1135
  import { JwtStrategy } from '${paths.strategies}/jwt.strategy';
825
1136
  import { SessionRepository } from '${paths.persistence}/session.repository';
1137
+ import { PasswordResetRepository } from '${paths.persistence}/password-reset.repository';
1138
+ import { OtpRepository } from '${paths.persistence}/otp.repository';
826
1139
 
827
1140
  @Module({
828
1141
  imports: [
@@ -843,12 +1156,13 @@ import { SessionRepository } from '${paths.persistence}/session.repository';
843
1156
  AuthService,
844
1157
  SessionService,
845
1158
  JwtStrategy,
1159
+ PasswordResetRepository,
1160
+ OtpRepository,
846
1161
  ${
847
1162
  isFull
848
1163
  ? "{ provide: ISessionRepositoryName, useClass: SessionRepository }"
849
1164
  : "SessionRepository"
850
1165
  }
851
-
852
1166
  ],
853
1167
  exports: [AuthService, SessionService],
854
1168
  })
@@ -901,6 +1215,7 @@ export class AuthModule {}`.trim(),
901
1215
  name: "resetPassword",
902
1216
  fields: [
903
1217
  { name: "email", type: "string", swaggerExample: "user@example.com" },
1218
+ { name: "token", type: "string", swaggerExample: "uuid-reset-token" },
904
1219
  {
905
1220
  name: "newPassword",
906
1221
  type: "string",
@@ -7,8 +7,11 @@ const { setupPrisma } = require("./setupPrisma");
7
7
  async function setupDatabase(inputs) {
8
8
  logInfo("🚀 Configuring the database...");
9
9
 
10
+ const pkgManager = inputs.packageManager || "npm";
11
+ const installCmd = pkgManager === "yarn" || pkgManager === "pnpm" ? `${pkgManager} add` : "npm install";
12
+
10
13
  await runCommand(
11
- "npm install dotenv",
14
+ `${installCmd} dotenv`,
12
15
  "Error installing dotenv automatically, please run it manually now",
13
16
  { critical: false },
14
17
  );
@@ -0,0 +1,74 @@
1
+ // utils/setups/setupHealth.js
2
+ //
3
+ // Script d'initialisation du Health Check.
4
+ // Crée HealthController et HealthModule dans src/health
5
+ // et met à jour app.module.ts de manière sécurisée et idempotente.
6
+
7
+ const { createDirectory, createFile } = require("../userInput");
8
+ const { safeUpdateAppModule } = require("../app-module.updater");
9
+ const { logInfo } = require("../loggers/logInfo");
10
+ const { logSuccess } = require("../loggers/logSuccess");
11
+
12
+ /**
13
+ * Configure un module de Health Check natif sur la route /health.
14
+ */
15
+ async function setupHealth() {
16
+ logInfo("Génération du module Health Check (/health)...");
17
+
18
+ const isDryRun = process.argv.includes("--dry-run");
19
+ if (isDryRun) {
20
+ console.log("[DRY-RUN] Simulated: create src/health directory, files and register in app.module.ts");
21
+ logSuccess(" Module Health Check configuré avec succès !");
22
+ return;
23
+ }
24
+
25
+ // 1. Créer le dossier src/health
26
+ await createDirectory("src/health");
27
+
28
+ // 2. Créer health.controller.ts
29
+ const controllerContent = `import { Controller, Get } from '@nestjs/common';
30
+ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
31
+
32
+ @ApiTags('Health')
33
+ @Controller('health')
34
+ export class HealthController {
35
+ @Get()
36
+ @ApiOperation({ summary: 'Check application health status' })
37
+ @ApiResponse({ status: 200, description: 'Application is healthy and running.' })
38
+ check() {
39
+ return {
40
+ status: 'ok',
41
+ timestamp: new Date().toISOString(),
42
+ uptime: process.uptime(),
43
+ };
44
+ }
45
+ }
46
+ `;
47
+
48
+ await createFile({
49
+ path: "src/health/health.controller.ts",
50
+ contente: controllerContent,
51
+ });
52
+
53
+ // 3. Créer health.module.ts
54
+ const moduleContent = `import { Module } from '@nestjs/common';
55
+ import { HealthController } from './health.controller';
56
+
57
+ @Module({
58
+ controllers: [HealthController],
59
+ })
60
+ export class HealthModule {}
61
+ `;
62
+
63
+ await createFile({
64
+ path: "src/health/health.module.ts",
65
+ contente: moduleContent,
66
+ });
67
+
68
+ // 4. Mettre à jour app.module.ts
69
+ await safeUpdateAppModule("health");
70
+
71
+ logSuccess(" Module Health Check configuré avec succès !");
72
+ }
73
+
74
+ module.exports = { setupHealth };