nestcraftx 0.4.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 (54) 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 +59 -57
  5. package/README.fr.md +60 -69
  6. package/bin/nestcraft.js +8 -0
  7. package/commands/demo.js +10 -0
  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 +26 -9
  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/configs/setupCleanArchitecture.js +1 -1
  31. package/utils/configs/setupLightArchitecture.js +98 -26
  32. package/utils/envGenerator.js +3 -1
  33. package/utils/file-utils/saveProjectConfig.js +3 -0
  34. package/utils/fullModeInput.js +4 -0
  35. package/utils/generators/application/dtoGenerator.js +20 -3
  36. package/utils/generators/cleanModuleGenerator.js +40 -11
  37. package/utils/generators/infrastructure/middlewareGenerator.js +112 -315
  38. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +78 -1
  39. package/utils/generators/infrastructure/repositoryGenerator.js +114 -14
  40. package/utils/generators/lightModuleGenerator.js +14 -0
  41. package/utils/generators/presentation/controllerGenerator.js +70 -19
  42. package/utils/interactive/askRelationCommand.js +98 -0
  43. package/utils/interactive/entityBuilder.js +11 -0
  44. package/utils/lightModeInput.js +14 -0
  45. package/utils/setups/orms/typeOrmSetup.js +11 -4
  46. package/utils/setups/projectSetup.js +10 -2
  47. package/utils/setups/setupAuth.js +334 -18
  48. package/utils/setups/setupDatabase.js +4 -1
  49. package/utils/setups/setupHealth.js +74 -0
  50. package/utils/setups/setupMongoose.js +4 -1
  51. package/utils/setups/setupPrisma.js +110 -1
  52. package/utils/setups/setupSwagger.js +4 -1
  53. package/utils/setups/setupThrottler.js +92 -0
  54. package/utils/shell.js +21 -4
@@ -3,10 +3,13 @@ const path = require("path");
3
3
  const { logError } = require("../utils/loggers/logError");
4
4
  const { logInfo } = require("../utils/loggers/logInfo");
5
5
  const { logWarning } = require("../utils/loggers/logWarning");
6
+ const { logSuccess } = require("../utils/loggers/logSuccess");
6
7
  const generateCleanModule = require("../utils/generators/cleanModuleGenerator");
7
8
  const { askEntityInputs } = require("../utils/interactive/askEntityInputs");
9
+ const { askRelationCommand } = require("../utils/interactive/askRelationCommand");
8
10
  const lightModuleGenerator = require("../utils/generators/lightModuleGenerator");
9
- const { printSetupWarnings, clearSetupWarnings } = require("../utils/shell");
11
+ const { printSetupWarnings, clearSetupWarnings, runCommand } = require("../utils/shell");
12
+ const { generateSecret } = require("../utils/envGenerator");
10
13
 
11
14
  async function generate(subCommand, targetName, flags) {
12
15
  clearSetupWarnings();
@@ -60,11 +63,17 @@ async function generate(subCommand, targetName, flags) {
60
63
  await handleAuthGeneration(projectConfig);
61
64
  break;
62
65
 
66
+ case "relation":
67
+ case "r":
68
+ await handleRelationGeneration(projectConfig);
69
+ break;
70
+
63
71
  default:
64
72
  logError(`Sous-commande inconnue : ${subCommand}`);
65
73
  console.log("\nUtilisations possibles :");
66
74
  console.log(" nestcraftx g module <name>");
67
75
  console.log(" nestcraftx g auth");
76
+ console.log(" nestcraftx g relation");
68
77
  break;
69
78
  }
70
79
  }
@@ -84,11 +93,551 @@ async function handleModuleGeneration(name, config) {
84
93
  await lightModuleGenerator(entityData, config);
85
94
  }
86
95
 
96
+ // 2. Mettre à jour .nestcraftxrc avec la nouvelle entité
97
+ if (!config.entities) config.entities = [];
98
+ if (!config.relations) config.relations = [];
99
+
100
+ // Éviter les doublons
101
+ config.entities = config.entities.filter(e => e.name.toLowerCase() !== name.toLowerCase());
102
+ config.entities.push({
103
+ name: entityData.name,
104
+ fields: entityData.fields
105
+ });
106
+
107
+ if (entityData.relation) {
108
+ config.relations = config.relations.filter(
109
+ r => !(r.from.toLowerCase() === entityData.name.toLowerCase() && r.to.toLowerCase() === entityData.relation.target.toLowerCase())
110
+ );
111
+ config.relations.push({
112
+ from: entityData.name,
113
+ to: entityData.relation.target,
114
+ type: entityData.relation.type
115
+ });
116
+ }
117
+
118
+ const configPath = path.join(process.cwd(), ".nestcraftx", ".nestcraftxrc");
119
+ const isDryRun = process.argv.includes("--dry-run");
120
+ if (!isDryRun) {
121
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
122
+ }
123
+
124
+ printModuleGenerationReport(name, config, entityData);
125
+
87
126
  printSetupWarnings();
88
127
  }
89
128
 
129
+ /**
130
+ * Crée une relation entre deux modules EXISTANTS.
131
+ * Patche : entité cible, DTO source, Mapper source, schema DB (Prisma/Mongoose/TypeORM).
132
+ * Enregistre la relation dans .nestcraftxrc.
133
+ */
134
+ async function handleRelationGeneration(config) {
135
+ const isDryRun = process.argv.includes("--dry-run");
136
+
137
+ // 1. Prompt interactif — source, cible, type
138
+ const relationData = await askRelationCommand(config);
139
+ if (!relationData) return; // Annulé ou déjà existant
140
+
141
+ const { source, target, type } = relationData;
142
+ const { capitalize } = require("../utils/userInput");
143
+
144
+ logInfo(`🔗 Création de la relation ${capitalize(source)} →(${type})→ ${capitalize(target)}...`);
145
+
146
+ // 2. Patcher l'entité CIBLE (ajoute FK dans la couche domaine)
147
+ const { updateExistingEntityRelation } = require("../utils/generators/domain/entityUpdater");
148
+ if (!isDryRun) {
149
+ await updateExistingEntityRelation(target, source, type, config.mode);
150
+ logInfo(" ✔ Entité cible patchée (domain layer)");
151
+ } else {
152
+ console.log(`[DRY-RUN] Simulated: patch entity ${target} with relation to ${source}`);
153
+ }
154
+
155
+ // 3. Patcher le DTO SOURCE + Mapper SOURCE via relation.engine
156
+ const { applyRelationPatches } = require("../utils/generators/relation/relation.engine");
157
+ if (!isDryRun) {
158
+ await applyRelationPatches(source, target, type, config.swagger, config.mode);
159
+ logInfo(" ✔ DTO + Mapper source patchés (application & infrastructure layers)");
160
+ } else {
161
+ console.log(`[DRY-RUN] Simulated: patch DTO and mapper for ${source} with relation to ${target}`);
162
+ }
163
+
164
+ // 4. Mise à jour du schema de base de données selon l'ORM
165
+ if (config.orm === "prisma") {
166
+ const { addPrismaRelation } = require("../utils/setups/setupPrisma");
167
+ if (!isDryRun) {
168
+ await addPrismaRelation(source, target, type);
169
+ logInfo(" ✔ schema.prisma mis à jour + migration lancée");
170
+ } else {
171
+ console.log(`[DRY-RUN] Simulated: addPrismaRelation(${source}, ${target}, ${type})`);
172
+ }
173
+ } else if (config.orm === "mongoose") {
174
+ const { addMongooseRelation } = require("../utils/generators/infrastructure/mongooseSchemaGenerator");
175
+ if (!isDryRun) {
176
+ await addMongooseRelation(source, target, type, config.mode);
177
+ logInfo(" ✔ Schémas Mongoose mis à jour");
178
+ } else {
179
+ console.log(`[DRY-RUN] Simulated: addMongooseRelation(${source}, ${target}, ${type})`);
180
+ }
181
+ } else if (config.orm === "typeorm") {
182
+ // TypeORM : patch FK @Column dans les entités src/entities/
183
+ // Note : les annotations @ManyToOne/@OneToMany doivent être ajoutées manuellement
184
+ // pour un typage ORM complet — le champ FK est injecté automatiquement.
185
+ logWarning(
186
+ "⚠️ TypeORM détecté : le champ FK est injecté dans l'entité source.\n" +
187
+ " Pour des annotations ORM complètes (@ManyToOne/@OneToMany), éditez manuellement les entités.",
188
+ );
189
+ }
190
+
191
+ // 5. Mise à jour de .nestcraftxrc
192
+ if (!config.relations) config.relations = [];
193
+ config.relations = config.relations.filter(
194
+ (r) => !(
195
+ r.from.toLowerCase() === source &&
196
+ r.to.toLowerCase() === target &&
197
+ r.type === type
198
+ ),
199
+ );
200
+ config.relations.push({ from: source, to: target, type });
201
+
202
+ const configPath = path.join(process.cwd(), ".nestcraftx", ".nestcraftxrc");
203
+ if (!isDryRun) {
204
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
205
+ logInfo(" ✔ .nestcraftxrc mis à jour");
206
+ }
207
+
208
+ // 6. Résumé final
209
+ logSuccess(
210
+ `\n✅ Relation ${capitalize(source)} →(${type})→ ${capitalize(target)} créée avec succès !\n` +
211
+ `\n📋 Résumé :\n` +
212
+ ` ✔ Entité ${capitalize(target)} : champ FK ajouté\n` +
213
+ ` ✔ DTO ${capitalize(source)} : ${target}Id ajouté dans CreateDto\n` +
214
+ ` ✔ Mapper ${capitalize(source)} : toDomain/toPersistence mis à jour\n` +
215
+ (config.orm === "prisma" ? ` ✔ schema.prisma : champs @relation des deux côtés ajoutés\n` : "") +
216
+ (config.orm === "mongoose" ? ` ✔ Schémas Mongoose : champs de référence ajoutés\n` : "") +
217
+ ` ✔ .nestcraftxrc : relation enregistrée`,
218
+ );
219
+ }
220
+
221
+ /**
222
+ * Affiche un rapport de génération complet à la fin du scaffolding d'un module.
223
+ */
224
+ function printModuleGenerationReport(name, config, entityData) {
225
+ const { capitalize, decapitalize } = require("../utils/userInput");
226
+ const entityCap = capitalize(name);
227
+ const entityLow = decapitalize(name);
228
+ const isFull = config.mode === "full";
229
+
230
+ console.log("\n╔" + "═".repeat(60) + "╗");
231
+ console.log("║" + " ".repeat(15) + "🏗️ NESTCRAFTX GENERATION REPORT 🏗️" + " ".repeat(11) + "║");
232
+ console.log("╠" + "═".repeat(60) + "╣");
233
+
234
+ console.log(`║ Module generated : ${entityCap}`);
235
+ console.log(`║ Mode : ${config.mode.toUpperCase()}`);
236
+ console.log(`║ ORM / Database : ${config.orm} / ${config.database}`);
237
+ console.log("╠" + "═".repeat(60) + "╣");
238
+
239
+ console.log("║ 📁 Generated Files :");
240
+ if (isFull) {
241
+ console.log(`║ • Domain layer :`);
242
+ console.log(`║ - src/${entityLow}/domain/entities/${entityLow}.entity.ts`);
243
+ console.log(`║ - src/${entityLow}/domain/interfaces/${entityLow}.repository.interface.ts`);
244
+ console.log(`║ • Application layer :`);
245
+ console.log(`║ - src/${entityLow}/application/dtos/${entityLow}.dto.ts`);
246
+ console.log(`║ - src/${entityLow}/application/services/${entityLow}.service.ts`);
247
+ console.log(`║ - src/${entityLow}/application/use-cases/create-${entityLow}.use-case.ts`);
248
+ console.log(`║ - src/${entityLow}/application/use-cases/getById-${entityLow}.use-case.ts`);
249
+ console.log(`║ - src/${entityLow}/application/use-cases/getAll-${entityLow}.use-case.ts`);
250
+ console.log(`║ - src/${entityLow}/application/use-cases/update-${entityLow}.use-case.ts`);
251
+ console.log(`║ - src/${entityLow}/application/use-cases/delete-${entityLow}.use-case.ts`);
252
+ console.log(`║ • Infrastructure layer :`);
253
+ console.log(`║ - src/${entityLow}/infrastructure/mappers/${entityLow}.mapper.ts`);
254
+ console.log(`║ - src/${entityLow}/infrastructure/repositories/${entityLow}.repository.ts`);
255
+ console.log(`║ • Presentation layer :`);
256
+ console.log(`║ - src/${entityLow}/presentation/controllers/${entityLow}.controller.ts`);
257
+ console.log(`║ • Nest Module :`);
258
+ console.log(`║ - src/${entityLow}/${entityLow}.module.ts`);
259
+ } else {
260
+ console.log(`║ • Entities : src/${entityLow}/entities/${entityLow}.entity.ts`);
261
+ console.log(`║ • DTOs : src/${entityLow}/dtos/${entityLow}.dto.ts`);
262
+ console.log(`║ • Services : src/${entityLow}/services/${entityLow}.service.ts`);
263
+ console.log(`║ • Repos : src/${entityLow}/repositories/${entityLow}.repository.ts`);
264
+ console.log(`║ • Control. : src/${entityLow}/controllers/${entityLow}.controller.ts`);
265
+ console.log(`║ • Module : src/${entityLow}/${entityLow}.module.ts`);
266
+ }
267
+
268
+ console.log("╠" + "═".repeat(60) + "╣");
269
+ console.log("║ ⚙️ Integration :");
270
+ console.log("║ ✔ Registered in src/app.module.ts");
271
+ console.log(`║ ✔ Database schema updated (${config.orm})`);
272
+
273
+ if (entityData.relation) {
274
+ const { target, type } = entityData.relation;
275
+ console.log("╠" + "═".repeat(60) + "╣");
276
+ console.log(`║ 🔗 Relation established :`);
277
+ console.log(`║ • ${entityCap} →(${type})→ ${capitalize(target)}`);
278
+ }
279
+
280
+ console.log("╠" + "═".repeat(60) + "╣");
281
+ console.log("║ 🚀 Next Steps :");
282
+ console.log(`║ 1. Inspect the entity validation rules in the DTO.`);
283
+ if (config.orm === "prisma") {
284
+ console.log(`║ 2. Run Prisma sync command to update client :`);
285
+ console.log(`║ - npx prisma format && npx prisma generate`);
286
+ } else {
287
+ console.log(`║ 2. Review your DB configuration in .env`);
288
+ }
289
+ console.log("║ 3. Start your dev server :");
290
+ console.log(`║ - ${config.packageManager || "npm"} run start:dev`);
291
+
292
+ console.log("╚" + "═".repeat(60) + "╝\n");
293
+ }
294
+
90
295
  async function handleAuthGeneration(config) {
91
- logWarning("nestcraftx g auth n'est pas encore disponible en v0.3 — prévu en v0.7");
296
+ const isDryRun = process.argv.includes("--dry-run");
297
+ logInfo("🚀 Initialisation de la configuration de l'Authentification...");
298
+
299
+ const isFull = config.mode === "full";
300
+ const orm = config.orm;
301
+
302
+ // Vérifier la présence de l'entité User — si absente, auto-scaffold
303
+ let userExists = false;
304
+
305
+ if (orm === "prisma") {
306
+ const schemaPath = path.join(process.cwd(), "prisma", "schema.prisma");
307
+ if (!fs.existsSync(schemaPath)) {
308
+ logError("Le fichier 'prisma/schema.prisma' est introuvable.");
309
+ return;
310
+ }
311
+ const schemaContent = fs.readFileSync(schemaPath, "utf8");
312
+ userExists = schemaContent.includes("model User");
313
+ } else if (orm === "typeorm") {
314
+ const userEntityPath = path.join(process.cwd(), "src", "entities", "User.entity.ts");
315
+ userExists = fs.existsSync(userEntityPath);
316
+ } else if (orm === "mongoose") {
317
+ const userSchemaPath = isFull
318
+ ? path.join(process.cwd(), "src", "user", "infrastructure", "persistence", "mongoose", "user.schema.ts")
319
+ : path.join(process.cwd(), "src", "user", "persistence", "user.schema.ts");
320
+ userExists = fs.existsSync(userSchemaPath);
321
+ }
322
+
323
+ if (!userExists) {
324
+ logInfo("⚙️ Entité 'User' absente — création automatique d'un module User minimal...");
325
+
326
+ // Construire un entityData minimal pour User (sans champs customs — les champs
327
+ // auth (email, password, role, isActive) seront ajoutés juste après par le bloc
328
+ // de mise à jour du schéma, exactement comme pour Session/Otp/PasswordReset)
329
+ const minimalUserEntity = {
330
+ name: "User",
331
+ fields: [
332
+ {
333
+ name: "email",
334
+ type: "string",
335
+ unique: true,
336
+ nullable: false,
337
+ default: null,
338
+ description: "User email address for login"
339
+ },
340
+ {
341
+ name: "password",
342
+ type: "string",
343
+ unique: false,
344
+ nullable: false,
345
+ default: null,
346
+ description: "User password"
347
+ },
348
+ {
349
+ name: "role",
350
+ type: "string",
351
+ unique: false,
352
+ nullable: false,
353
+ default: "USER",
354
+ description: "User role in the application"
355
+ },
356
+ {
357
+ name: "isActive",
358
+ type: "boolean",
359
+ unique: false,
360
+ nullable: false,
361
+ default: "true",
362
+ description: "Flag indicating whether user is active"
363
+ }
364
+ ],
365
+ relation: null,
366
+ };
367
+
368
+ // Générer le module User via les générateurs existants
369
+ if (config.mode === "full") {
370
+ await generateCleanModule("User", config, minimalUserEntity);
371
+ } else {
372
+ await lightModuleGenerator(minimalUserEntity, config);
373
+ }
374
+
375
+ // Enregistrer l'entité User dans .nestcraftxrc
376
+ if (!config.entities) config.entities = [];
377
+ if (!config.entities.find(e => e.name.toLowerCase() === "user")) {
378
+ config.entities.push({ name: "User", fields: minimalUserEntity.fields });
379
+ }
380
+
381
+ const configPath = path.join(process.cwd(), ".nestcraftx", ".nestcraftxrc");
382
+ if (!isDryRun) {
383
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
384
+ }
385
+
386
+ logSuccess("✅ Module User minimal créé avec succès !");
387
+ }
388
+
389
+ // 1. Mise à jour de .env et .env.example
390
+ const jwtSecret = generateSecret(64);
391
+ const jwtRefreshSecret = generateSecret(64);
392
+ const envPath = path.join(process.cwd(), ".env");
393
+ const examplePath = path.join(process.cwd(), ".env.example");
394
+
395
+ const authEnvBlock = `
396
+ # ------------------------------------------------------------------------------
397
+ # AUTHENTICATION (JWT)
398
+ # ------------------------------------------------------------------------------
399
+ JWT_SECRET=${jwtSecret}
400
+ JWT_REFRESH_SECRET=${jwtRefreshSecret}
401
+ JWT_EXPIRES_IN=15m
402
+ JWT_REFRESH_EXPIRES_IN=7d
403
+ `;
404
+
405
+ const authExampleBlock = `
406
+ # ------------------------------------------------------------------------------
407
+ # AUTHENTICATION (JWT)
408
+ # ------------------------------------------------------------------------------
409
+ JWT_SECRET=votre_cle_secrete_ici
410
+ JWT_REFRESH_SECRET=votre_cle_secrete_ici
411
+ JWT_EXPIRES_IN=15m
412
+ JWT_REFRESH_EXPIRES_IN=7d
413
+ `;
414
+
415
+ if (fs.existsSync(envPath)) {
416
+ const envContent = fs.readFileSync(envPath, "utf8");
417
+ if (!envContent.includes("JWT_SECRET")) {
418
+ if (isDryRun) {
419
+ console.log(`[DRY-RUN] Simulated: append JWT secrets to ${envPath}`);
420
+ } else {
421
+ fs.appendFileSync(envPath, authEnvBlock, "utf8");
422
+ }
423
+ }
424
+ }
425
+
426
+ if (fs.existsSync(examplePath)) {
427
+ const exampleContent = fs.readFileSync(examplePath, "utf8");
428
+ if (!exampleContent.includes("JWT_SECRET")) {
429
+ if (isDryRun) {
430
+ console.log(`[DRY-RUN] Simulated: append JWT secrets template to ${examplePath}`);
431
+ } else {
432
+ fs.appendFileSync(examplePath, authExampleBlock, "utf8");
433
+ }
434
+ }
435
+ }
436
+
437
+ // 2. Mise à jour du schéma User et des tables d'Auth selon l'ORM
438
+ if (orm === "prisma") {
439
+ const schemaPath = path.join(process.cwd(), "prisma", "schema.prisma");
440
+ if (fs.existsSync(schemaPath)) {
441
+ let schemaContent = fs.readFileSync(schemaPath, "utf8");
442
+ let updated = false;
443
+
444
+ // Ajouter l'enum Role si absente
445
+ if (!schemaContent.includes("enum Role")) {
446
+ schemaContent += `
447
+ enum Role {
448
+ USER
449
+ ADMIN
450
+ SUPER_ADMIN
451
+ }
452
+ `;
453
+ updated = true;
454
+ }
455
+
456
+ // Ajouter PasswordReset, Otp, Session si absents
457
+ if (!schemaContent.includes("model PasswordReset")) {
458
+ schemaContent += `
459
+ model PasswordReset {
460
+ id String @id @default(uuid())
461
+ email String
462
+ token String @unique
463
+ expiresAt DateTime
464
+ createdAt DateTime @default(now())
465
+ }
466
+ `;
467
+ updated = true;
468
+ }
469
+
470
+ if (!schemaContent.includes("model Otp")) {
471
+ schemaContent += `
472
+ model Otp {
473
+ id String @id @default(uuid())
474
+ email String
475
+ otp String
476
+ expiresAt DateTime
477
+ createdAt DateTime @default(now())
478
+ }
479
+ `;
480
+ updated = true;
481
+ }
482
+
483
+ if (!schemaContent.includes("model Session")) {
484
+ schemaContent += `
485
+ model Session {
486
+ id String @id @default(uuid())
487
+ refreshToken String @unique
488
+ userId String
489
+ expiresAt DateTime
490
+ createdAt DateTime @default(now())
491
+ user User @relation(fields: [userId], references: [id])
492
+ }
493
+ `;
494
+ updated = true;
495
+ }
496
+
497
+ // Mettre à jour User
498
+ const userModelMatch = schemaContent.match(/model User \{([\s\S]*?)\}/);
499
+ if (userModelMatch) {
500
+ let userFields = userModelMatch[1];
501
+ let userUpdated = false;
502
+
503
+ if (!userFields.includes("email")) {
504
+ userFields = `\n email String @unique` + userFields;
505
+ userUpdated = true;
506
+ }
507
+ if (!userFields.includes("password")) {
508
+ userFields = `\n password String` + userFields;
509
+ userUpdated = true;
510
+ }
511
+ if (!userFields.includes("role")) {
512
+ userFields = `\n role Role @default(USER)` + userFields;
513
+ userUpdated = true;
514
+ }
515
+ if (!userFields.includes("isActive")) {
516
+ userFields = `\n isActive Boolean @default(true)` + userFields;
517
+ userUpdated = true;
518
+ }
519
+ if (!userFields.includes("sessions")) {
520
+ userFields = `\n sessions Session[]` + userFields;
521
+ userUpdated = true;
522
+ }
523
+
524
+ if (userUpdated) {
525
+ schemaContent = schemaContent.replace(/model User \{([\s\S]*?)\}/, `model User {${userFields}}`);
526
+ updated = true;
527
+ }
528
+ }
529
+
530
+ if (updated) {
531
+ if (isDryRun) {
532
+ console.log(`[DRY-RUN] Simulated: update prisma/schema.prisma with Auth models`);
533
+ } else {
534
+ fs.writeFileSync(schemaPath, schemaContent, "utf8");
535
+ await runCommand("npx prisma format", "Prisma format failed", { critical: false });
536
+ await runCommand("npx prisma generate", "Prisma generate failed", { critical: false });
537
+ await runCommand("npx prisma migrate dev --name add_auth", "Prisma migration failed", { critical: false });
538
+ }
539
+ }
540
+ }
541
+ } else if (orm === "typeorm") {
542
+ // S'assurer que src/entities/User.entity.ts a les champs requis
543
+ const userEntityPath = path.join(process.cwd(), "src", "entities", "User.entity.ts");
544
+ if (fs.existsSync(userEntityPath)) {
545
+ let content = fs.readFileSync(userEntityPath, "utf8");
546
+ let updated = false;
547
+
548
+ if (!content.includes("email")) {
549
+ content = content.replace("export class User {", `export class User {\n @Column({ type: 'varchar', unique: true })\n email: string;\n`);
550
+ updated = true;
551
+ }
552
+ if (!content.includes("password")) {
553
+ content = content.replace("export class User {", `export class User {\n @Column({ type: 'varchar' })\n password: string;\n`);
554
+ updated = true;
555
+ }
556
+ if (!content.includes("role")) {
557
+ content = content.replace("export class User {", `export class User {\n @Column({ type: 'varchar', default: 'USER' })\n role: string;\n`);
558
+ updated = true;
559
+ }
560
+ if (!content.includes("isActive")) {
561
+ content = content.replace("export class User {", `export class User {\n @Column({ type: 'boolean', default: true })\n isActive: boolean;\n`);
562
+ updated = true;
563
+ }
564
+
565
+ if (updated) {
566
+ if (isDryRun) {
567
+ console.log(`[DRY-RUN] Simulated: update ${userEntityPath} with Auth columns`);
568
+ } else {
569
+ fs.writeFileSync(userEntityPath, content, "utf8");
570
+ }
571
+ }
572
+ }
573
+ } else if (orm === "mongoose") {
574
+ // S'assurer que le schéma User Mongoose a les champs
575
+ const userSchemaPath = isFull
576
+ ? path.join(process.cwd(), "src", "user", "infrastructure", "persistence", "mongoose", "user.schema.ts")
577
+ : path.join(process.cwd(), "src", "user", "persistence", "user.schema.ts");
578
+ if (fs.existsSync(userSchemaPath)) {
579
+ let content = fs.readFileSync(userSchemaPath, "utf8");
580
+ let updated = false;
581
+
582
+ if (!content.includes("email")) {
583
+ content = content.replace("export class User {", `export class User {\n @Prop({ required: true, unique: true })\n email: string;\n`);
584
+ updated = true;
585
+ }
586
+ if (!content.includes("password")) {
587
+ content = content.replace("export class User {", `export class User {\n @Prop({ required: true })\n password: string;\n`);
588
+ updated = true;
589
+ }
590
+ if (!content.includes("role")) {
591
+ content = content.replace("export class User {", `export class User {\n @Prop({ required: true, default: 'USER' })\n role: string;\n`);
592
+ updated = true;
593
+ }
594
+ if (!content.includes("isActive")) {
595
+ content = content.replace("export class User {", `export class User {\n @Prop({ required: true, default: true })\n isActive: boolean;\n`);
596
+ updated = true;
597
+ }
598
+
599
+ if (updated) {
600
+ if (isDryRun) {
601
+ console.log(`[DRY-RUN] Simulated: update ${userSchemaPath} with Auth properties`);
602
+ } else {
603
+ fs.writeFileSync(userSchemaPath, content, "utf8");
604
+ }
605
+ }
606
+ }
607
+ }
608
+
609
+ // 3. Appel à setupAuth
610
+ const { setupAuth } = require("../utils/setups/setupAuth");
611
+
612
+ // Reconstituer l'objet inputs attendu par setupAuth
613
+ const authInputs = {
614
+ projectName: config.name,
615
+ mode: config.mode,
616
+ selectedDB: config.database,
617
+ dbConfig: {
618
+ orm: config.orm
619
+ },
620
+ useSwagger: config.swagger,
621
+ useAuth: true,
622
+ packageManager: config.packageManager
623
+ };
624
+
625
+ await setupAuth(authInputs);
626
+
627
+ // 4. Enregistrer dans AppModule
628
+ const { safeUpdateAppModule } = require("../utils/app-module.updater");
629
+ await safeUpdateAppModule("auth");
630
+
631
+ // 5. Mettre à jour .nestcraftxrc
632
+ config.auth = true;
633
+ const configPath = path.join(process.cwd(), ".nestcraftx", ".nestcraftxrc");
634
+ if (isDryRun) {
635
+ console.log(`[DRY-RUN] Simulated: update .nestcraftxrc with auth: true`);
636
+ } else {
637
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
638
+ }
639
+
640
+ logSuccess("✅ Authentification configurée et déployée avec succès dans le projet !");
92
641
  }
93
642
 
94
643
  module.exports = generate;
@@ -27,14 +27,14 @@ async function generateConf() {
27
27
  fs.existsSync(path.join(root, "src", "common", "domain"));
28
28
  const mode = isFull ? "full" : "light";
29
29
 
30
- // 2. Détecter l'ORM
30
+ // 2. Détecter l'ORM (vérifier les fichiers physiques d'abord, puis les dépendances)
31
31
  let orm = "prisma"; // par défaut
32
- if (deps["@nestjs/mongoose"] || deps["mongoose"]) {
32
+ if (fs.existsSync(path.join(root, "prisma"))) {
33
+ orm = "prisma";
34
+ } else if (deps["@nestjs/mongoose"] || deps["mongoose"]) {
33
35
  orm = "mongoose";
34
36
  } else if (deps["@nestjs/typeorm"] || deps["typeorm"]) {
35
37
  orm = "typeorm";
36
- } else if (fs.existsSync(path.join(root, "prisma"))) {
37
- orm = "prisma";
38
38
  }
39
39
 
40
40
  // 3. Détecter les options
package/commands/help.js CHANGED
@@ -59,6 +59,17 @@ async function helpCommand() {
59
59
  --auth Adds JWT Auth
60
60
  --swagger Adds Swagger UI
61
61
 
62
+ nestcraftx generate, g <subcommand> [target]
63
+ Generates modules or auth post-scaffolding
64
+ Subcommands:
65
+ module <name> Generates a new module (MVP or Clean Architecture)
66
+ auth Enables JWT Auth on an existing project
67
+ relation, r Creates a relation between two existing modules
68
+ (interactive: select source, target, and relation type)
69
+
70
+ nestcraftx list, l
71
+ Lists all scaffolded entities and their relationships
72
+
62
73
  nestcraftx test
63
74
  Checks system environment (Node, npm, Nest CLI, Docker, etc.)
64
75
 
package/commands/info.js CHANGED
@@ -27,11 +27,10 @@ async function infoCommand() {
27
27
  console.log(" " + packageJson.repository.url);
28
28
 
29
29
  console.log("\n📅 Upcoming:");
30
- console.log(" • Custom Middlewares");
30
+ console.log(" • MySQL & SQLite support");
31
+ console.log(" • Custom templates from GitHub");
31
32
  console.log(" • Microservices support");
32
- console.log(" • CI/CD Templates");
33
33
  console.log(" • GraphQL integration");
34
- console.log(" • Automated Tests");
35
34
 
36
35
  console.log("\n💡 Available Commands:");
37
36
  console.log(" nestcraftx new <name> [options] Create a project");