nestcraftx 0.2.6 → 0.4.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 (42) hide show
  1. package/AUDIT_ROADMAP.md +798 -0
  2. package/CHANGELOG.fr.md +22 -0
  3. package/CHANGELOG.md +22 -0
  4. package/PROGRESS.md +259 -0
  5. package/commands/demo.js +2 -42
  6. package/commands/generate.js +5 -4
  7. package/commands/help.js +29 -2
  8. package/commands/new.js +6 -49
  9. package/package.json +1 -1
  10. package/utils/app-module.updater.js +102 -0
  11. package/utils/cliParser.js +0 -45
  12. package/utils/configs/setupCleanArchitecture.js +2 -2
  13. package/utils/envGenerator.js +6 -0
  14. package/utils/file-system.js +90 -0
  15. package/utils/file-utils/packageJsonUtils.js +6 -0
  16. package/utils/file-utils/saveProjectConfig.js +6 -0
  17. package/utils/fullModeInput.js +8 -229
  18. package/utils/generators/application/dtoGenerator.js +252 -0
  19. package/utils/generators/application/dtoUpdater.js +5 -0
  20. package/utils/generators/database/setupDatabase.js +32 -14
  21. package/utils/generators/domain/entityGenerator.js +126 -0
  22. package/utils/generators/domain/entityUpdater.js +5 -0
  23. package/utils/generators/infrastructure/mapperGenerator.js +114 -0
  24. package/utils/generators/infrastructure/mapperUpdater.js +5 -0
  25. package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
  26. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +229 -0
  27. package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
  28. package/utils/generators/presentation/controllerGenerator.js +142 -0
  29. package/utils/helpers.js +115 -0
  30. package/utils/interactive/askEntityInputs.js +5 -68
  31. package/utils/interactive/entityBuilder.js +400 -0
  32. package/utils/lightModeInput.js +13 -246
  33. package/utils/setups/orms/typeOrmSetup.js +31 -4
  34. package/utils/setups/projectSetup.js +78 -8
  35. package/utils/setups/setupAuth.js +6 -7
  36. package/utils/setups/setupDatabase.js +2 -1
  37. package/utils/setups/setupMongoose.js +96 -30
  38. package/utils/setups/setupPrisma.js +118 -134
  39. package/utils/setups/setupSwagger.js +12 -8
  40. package/utils/shell.js +125 -10
  41. package/utils/userInput.js +37 -101
  42. package/utils/utils.js +29 -2164
package/utils/utils.js CHANGED
@@ -1,2168 +1,30 @@
1
- import { logInfo } from "./loggers/logInfo.js";
2
- import { logSuccess } from "./loggers/logSuccess.js";
3
- import { createDirectory, createFile, updateFile } from "./userInput.js";
4
- import inquirer from "inquirer";
5
- const actualInquirer = inquirer.default || inquirer;
6
-
7
- export async function generateEntityFileContent(entity, mode = "full") {
8
- // console.log("Entity name:", entity.name); // Log de l'entité
9
-
10
- if (!entity || !entity.name) {
11
- throw new Error("Nom de l'entité manquant !");
12
- }
13
- const entityName = capitalize(entity.name);
14
- const className = `${entityName}Entity`;
15
-
16
- const defaultFields = [
17
- {
18
- name: "id",
19
- type: "string",
20
- comment:
21
- "L'identifiant unique de l'entité.\n * Utilisé pour retrouver de manière unique un enregistrement dans la base de données.\n *\n * Exemple : '123e4567-e89b-12d3-a456-426614174000'",
22
- },
23
- {
24
- name: "createdAt",
25
- type: "Date",
26
- comment:
27
- "La date de création de l'entité.\n * Définie lors de la création et ne change pas.\n *\n * Exemple : new Date('2022-01-01T10:00:00Z')",
28
- },
29
- {
30
- name: "updatedAt",
31
- type: "Date",
32
- comment:
33
- "La date de dernière mise à jour de l'entité.\n * Mise à jour à chaque modification.\n *\n * Exemple : new Date('2022-02-01T15:00:00Z')",
34
- },
35
- ];
36
- // 1. Types de base autorisés dans une Entité de Domaine
37
- const DOMAIN_SCALAR_TYPES = [
38
- "string",
39
- "number",
40
- "boolean",
41
- "date",
42
- "json",
43
- "text",
44
- "uuid",
45
- "decimal",
46
- "float",
47
- "int",
48
- "role",
49
- ];
50
-
51
- // 2. Filtrage : On ne garde que les types scalaires ou les IDs techniques
52
- const filteredFields = entity.fields.filter((f) => {
53
- const typeLower = f.type.toLowerCase().replace("[]", "");
54
- return DOMAIN_SCALAR_TYPES.includes(typeLower) || f.name.endsWith("Id");
55
- });
56
-
57
- const isUserEntityWithRole =
58
- entity.name.toLowerCase() === "user" &&
59
- entity.fields.some((f) => f.name === "role");
60
-
61
- const allFields = [...defaultFields, ...filteredFields];
62
-
63
- const constructorParams = allFields
64
- .map(
65
- (f) => `
66
- private readonly ${f.name}: ${getFormattedType(f)},`,
67
- )
68
- .join("");
69
-
70
- /* const constructorAssignments = allFields
71
- .map((f) => ` this.${f.name} = ${f.name};`)
72
- .join("\n"); */
73
-
74
- const getters = allFields
75
- .map(
76
- (f) => `
77
- get${capitalize(f.name)}(): ${getFormattedType(f)}
78
- {
79
- return this.${f.name};
80
- }`,
81
- )
82
- .join("\n");
83
-
84
- const jsonFields = allFields
85
- .map((f) => ` ${f.name}: this.${f.name},`)
86
- .join("\n");
87
-
88
- let importStatements = "";
89
- if (isUserEntityWithRole) {
90
- importStatements +=
91
- mode == "full"
92
- ? `import { Role } from 'src/user/domain/enums/role.enum';\n\n`
93
- : `import { Role } from 'src/common/enums/role.enum'; \n\n`;
94
- }
95
-
96
- return `${importStatements}/**
97
- * ${className} représente l'entité principale de ${entityName} dans le domaine.
98
- * Elle contient les propriétés de base nécessaires à la gestion des données liées à ${entityName}.
99
- */
100
- export class ${className} {
101
- constructor(${constructorParams}
102
- ) {}
103
- ${getters}
104
-
105
- /**
106
- * transforme entity data to json
107
- */
108
- toJSON() {
109
- return {
110
- ${jsonFields}
111
- };
112
- }
113
- }
114
- `;
115
- }
116
-
117
- export async function generateMapper(entity) {
118
- const entityName = capitalize(entity.name);
119
-
120
- // 1. Liste des types autorisés (Scalaires / Primitifs)
121
- const SCALAR_TYPES = [
122
- "string",
123
- "text",
124
- "uuid",
125
- "json",
126
- "number",
127
- "int",
128
- "float",
129
- "decimal",
130
- "boolean",
131
- "date",
132
- "role",
133
- ];
134
-
135
- // 2. Fonction de filtrage cohérente
136
- const filterDomainFields = (f) => {
137
- const typeName = f.type.toLowerCase().replace("[]", "");
138
- return (
139
- SCALAR_TYPES.includes(typeName) || f.name.toLowerCase().endsWith("id")
140
- );
141
- };
142
-
143
- // 3. Filtrage des champs pour le constructeur de l'Entity
144
- // On ne passe au constructeur QUE ce qui est filtré
145
- const filteredFields = entity.fields.filter(filterDomainFields);
146
-
147
- const domainArgs = ["data.id"]
148
- .concat(["data.createdAt", "data.updatedAt"])
149
- .concat(filteredFields.map((f) => `data.${f.name}`)) // UTILISE LES CHAMPS FILTRÉS ICI
150
- .join(",\n ");
151
-
152
- // 4. Filtrage pour la persistence (Base de données)
153
- const toPersistenceFields = filteredFields
154
- .map((f) => `${f.name}: dto.${f.name},`)
155
- .join("\n ");
156
-
157
- const toUpdateFields = filteredFields
158
- .map(
159
- (f) => `if (dto.${f.name} !== undefined) data.${f.name} = dto.${f.name};`,
160
- )
161
- .join("\n ");
162
-
163
- // ... (Logique isUserWithRole inchangée)
164
-
165
- return `/**
166
- * PostMapper transforms data between
167
- * different layers (Persistence <-> Domain <-> DTO).
168
- *
169
- * Ensures that the internal database structure
170
- * never leaks into the API responses.
171
- */
172
-
173
- import { Injectable } from '@nestjs/common';
174
- import { ${entityName}Entity } from 'src/${decapitalize(
175
- entity.name,
176
- )}/domain/entities/${decapitalize(entity.name)}.entity';
177
- import { Create${entityName}Dto, Update${entityName}Dto } from 'src/${decapitalize(
178
- entity.name,
179
- )}/application/dtos/${decapitalize(entity.name)}.dto';
180
-
181
- @Injectable()
182
- export class ${entityName}Mapper {
183
-
184
- /**
185
- * Transforme les données (Prisma/TypeORM) en Entité de Domaine
186
- */
187
- toDomain(data: any): ${entityName}Entity {
188
- return new ${entityName}Entity(
189
- ${domainArgs}
190
- );
191
- }
192
-
193
- /**
194
- * Transforme le DTO en objet pour la création en base de données
195
- */
196
- toPersistence(dto: Create${entityName}Dto): any {
197
- return {
198
- ${toPersistenceFields}
199
- };
200
- }
201
-
202
- /**
203
- * Prépare l'objet de mise à jour partielle
204
- */
205
- toUpdatePersistence(dto: Update${entityName}Dto): any {
206
- const data: any = {};
207
- ${toUpdateFields}
208
- return data;
209
- }
210
- }
211
- `;
212
- }
213
-
214
- export async function generateDtox(
215
- entity,
216
- useSwagger,
217
- isAuthDto = false,
218
- mode = "full",
219
- ) {
220
- const entityName = capitalize(entity.name);
221
- const entityNameLower = entity.name.toLowerCase();
222
-
223
- /* ===============================
224
- ENUM IMPORT
225
- =============================== */
226
- let enumImport = "";
227
- if (entityName === "User") {
228
- enumImport =
229
- mode === "light"
230
- ? "\nimport { Role } from 'src/common/enums/role.enum';"
231
- : "\nimport { Role } from 'src/user/domain/enums/role.enum';";
232
- }
233
-
234
- /* ===============================
235
- SWAGGER HELPERS (PRO)
236
- =============================== */
237
- const getFieldDescription = (f) => {
238
- const name = f.name.toLowerCase();
239
- if (name.includes("email")) return "The official email address of the user";
240
- if (name.includes("password"))
241
- return "Must contain at least 8 characters, one letter and one number";
242
- if (name.includes("token")) return "Authentication token";
243
- if (name.includes("id") && name !== "id")
244
- return `Unique identifier of the related ${name.replace("id", "")}`;
245
- return `The ${f.name} of the ${entityNameLower}`;
246
- };
247
-
248
- const getExampleForField = (f) => {
249
- const fieldName = f.name.toLowerCase();
250
- const isArray = f.type.endsWith("[]");
251
- const cleanType = f.type.toLowerCase().replace("[]", "");
252
-
253
- const getBaseExample = () => {
254
- if (fieldName.includes("email")) return "user@example.com";
255
- if (fieldName.includes("password")) return "SecurePass@2024";
256
- if (fieldName.includes("token")) return "eyJhbGciOi...";
257
- if (fieldName.includes("id") && fieldName !== "id")
258
- return "550e8400-e29b-41d4-a716-446655440000";
259
-
260
- if (fieldName.includes("title") || fieldName.includes("name"))
261
- return `${capitalize(entityName)} Example`;
262
- if (fieldName.includes("content") || fieldName.includes("description"))
263
- return "This is a detailed example content.";
264
- if (
265
- fieldName.includes("url") ||
266
- fieldName.includes("image") ||
267
- fieldName.includes("avatar")
268
- )
269
- return "https://images.unsplash.com/photo-123456789";
270
-
271
- if (fieldName.includes("price") || fieldName.includes("amount"))
272
- return 99.99;
273
- if (fieldName.includes("quantity") || fieldName.includes("count"))
274
- return 10;
275
- if (fieldName.includes("status") || fieldName.includes("type"))
276
- return "active";
277
- if (fieldName.includes("date") || fieldName.includes("at"))
278
- return "2024-12-01T10:30:00Z";
279
-
280
- switch (cleanType) {
281
- case "string":
282
- case "text":
283
- case "uuid":
284
- return `${f.name.toLowerCase()}_val`;
285
- case "number":
286
- case "int":
287
- case "decimal":
288
- return 42;
289
- case "float":
290
- return 23.5;
291
- case "boolean":
292
- return true;
293
- case "json":
294
- return { metadata: "value", version: 1 }; // objet réel
295
- case "date":
296
- return new Date().toISOString();
297
- default:
298
- return `${f.name.toLowerCase()}_val`;
299
- }
300
- };
301
-
302
- const base = getBaseExample();
303
- return isArray ? [base, base] : base;
304
- };
305
-
306
- /* ===============================
307
- FIELD GENERATOR (PRO)
308
- =============================== */
309
- const generateFieldLine = (f, optional = false) => {
310
- if (entityName === "User" && f.name.toLowerCase() === "role") return null;
311
-
312
- const name = f.name;
313
- const type = f.type.toLowerCase();
314
- const isArray = type.endsWith("[]");
315
- const cleanType = type.replace("[]", "");
316
-
317
- const SCALAR_TYPES = [
318
- "string",
319
- "text",
320
- "uuid",
321
- "json",
322
- "number",
323
- "decimal",
324
- "int",
325
- "float",
326
- "boolean",
327
- "date",
328
- "role",
329
- "enum",
330
- ];
331
- if (!SCALAR_TYPES.includes(cleanType)) return null;
332
-
333
- let validators = [];
334
- if (optional) validators.push("@IsOptional()");
335
-
336
- if (name.toLowerCase().includes("email")) {
337
- validators.push("@IsEmail()");
338
- } else if (name.toLowerCase().includes("password")) {
339
- validators.push(
340
- "@MinLength(8, { message: 'Password is too short (min 8 characters)' })",
341
- );
342
- }
343
-
344
- switch (cleanType) {
345
- case "string":
346
- case "text":
347
- validators.push(isArray ? "@IsString({ each: true })" : "@IsString()");
348
- if (!isArray) validators.push("@MinLength(2)");
349
- break;
350
- case "number":
351
- case "float":
352
- validators.push(
353
- isArray ? "@IsNumber({}, { each: true })" : "@IsNumber()",
354
- );
355
- break;
356
- case "int":
357
- validators.push(isArray ? "@IsInt({ each: true })" : "@IsInt()");
358
- break;
359
- case "boolean":
360
- validators.push(
361
- isArray ? "@IsBoolean({ each: true })" : "@IsBoolean()",
362
- );
363
- break;
364
- case "uuid":
365
- validators.push("@IsUUID()");
366
- break;
367
- case "date":
368
- validators.push("@IsDateString()");
369
- break;
370
- }
371
-
372
- if (isArray) validators.push("@IsArray()");
373
-
374
- let swaggerDecorator = "";
375
- if (useSwagger) {
376
- const decorator = optional ? "@ApiPropertyOptional" : "@ApiProperty";
377
-
378
- const options = JSON.stringify(
379
- {
380
- example: getExampleForField(f),
381
- description: getFieldDescription(f),
382
- },
383
- null,
384
- 2,
385
- ).replace(/"([^"]+)":/g, "$1:");
386
-
387
- swaggerDecorator = `${decorator}(${options})\n `;
388
- }
389
-
390
- return `${swaggerDecorator}${validators.join("\n ")}\n ${name}${
391
- optional ? "?" : ""
392
- }: ${formatType(f.type)};`;
393
- };
394
-
395
- /* ======================================================
396
- AUTH DTO → UN SEUL DTO
397
- ====================================================== */
398
- if (isAuthDto) {
399
- const authFields = entity.fields
400
- .map((f) => generateFieldLine(f, false))
401
- .filter(Boolean)
402
- .join("\n\n ");
403
-
404
- return `import {
405
- IsString, IsInt, IsBoolean, IsEmail, IsArray,
406
- IsUUID, IsDateString, MinLength, IsOptional
407
- } from 'class-validator';
408
-
409
- /**
410
- * Auth DTO
411
- */
412
- export class ${entityName}Dto {
413
- ${authFields}
414
- }
415
- `;
416
- }
417
-
418
- /* ======================================================
419
- CRUD DTOs (Create / Update)
420
- ====================================================== */
421
- const createFields = entity.fields
422
- .map((f) => generateFieldLine(f, false))
423
- .filter(Boolean)
424
- .join("\n\n ");
425
-
426
- const updateFields = entity.fields
427
- .map((f) => generateFieldLine(f, true))
428
- .filter(Boolean)
429
- .join("\n\n ");
430
-
431
- return `import {
432
- IsOptional, IsString, IsInt, IsBoolean, IsEmail,
433
- IsArray, IsUUID, IsDateString, MinLength, IsEnum
434
- } from 'class-validator';
435
- ${
436
- useSwagger
437
- ? "import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger';"
438
- : ""
439
- }
440
- ${enumImport}
441
- ${useSwagger ? "" : "import { PartialType } from '@nestjs/mapped-types';"}
442
-
443
- /**
444
- * DTO for creating a ${entityName}
445
- */
446
- export class Create${entityName}Dto {
447
- ${createFields}
448
-
449
- ${
450
- entityName === "User"
451
- ? `
452
- ${useSwagger ? "@ApiHideProperty()\n " : ""}
453
- @IsOptional()
454
- @IsEnum(Role)
455
- role: Role = Role.USER;`
456
- : ""
457
- }
458
- }
459
-
460
- /**
461
- * DTO for updating a ${entityName}
462
- */
463
- ${
464
- useSwagger
465
- ? `export class Update${entityName}Dto extends PartialType(Create${entityName}Dto) {}`
466
- : `export class Update${entityName}Dto {
467
- ${updateFields}
468
-
469
- ${
470
- entityName === "User"
471
- ? `@IsEnum(Role)
472
- @IsOptional()
473
- role?: Role;`
474
- : ""
475
- }
476
- }`
477
- }
478
- `;
479
- }
480
-
481
- export async function generateDto(
482
- entity,
483
- useSwagger,
484
- isAuthDto = false,
485
- mode = "full",
486
- ) {
487
- const entityName = capitalize(entity.name);
488
- const entityNameLower = entity.name.toLowerCase();
489
-
490
- /* ===============================
491
- ENUM IMPORT
492
- =============================== */
493
- let enumImport = "";
494
- if (entityName === "User") {
495
- enumImport =
496
- mode === "light"
497
- ? "\nimport { Role } from 'src/common/enums/role.enum';"
498
- : "\nimport { Role } from 'src/user/domain/enums/role.enum';";
499
- }
500
-
501
- /* ===============================
502
- SWAGGER HELPERS
503
- =============================== */
504
- const getFieldDescription = (f) => {
505
- const name = f.name.toLowerCase();
506
- if (name.includes("email")) return "The official email address of the user";
507
- if (name.includes("password"))
508
- return "Must contain at least 8 characters, one letter and one number";
509
- if (name.includes("token")) return "Authentication token";
510
- if (name.includes("id") && name !== "id")
511
- return `Unique identifier of the related ${name.replace("id", "")}`;
512
- return `The ${f.name} of the ${entityNameLower}`;
513
- };
514
-
515
- const getExampleForField = (f) => {
516
- const fieldName = f.name.toLowerCase();
517
- const isArray = f.type.endsWith("[]");
518
- const cleanType = f.type.toLowerCase().replace("[]", "");
519
-
520
- const getBaseExample = () => {
521
- if (fieldName.includes("email")) return "user@example.com";
522
- if (fieldName.includes("password")) return "SecurePass@2024";
523
- if (fieldName.includes("token")) return "eyJhbGciOi...";
524
- if (fieldName.includes("id") && fieldName !== "id")
525
- return "550e8400-e29b-41d4-a716-446655440000";
526
- if (fieldName.includes("title") || fieldName.includes("name"))
527
- return `${capitalize(entityName)} Example`;
528
- if (cleanType === "boolean") return true;
529
- if (cleanType === "number" || cleanType === "int") return 42;
530
- return `${f.name.toLowerCase()}_val`;
531
- };
532
-
533
- const base = getBaseExample();
534
- return isArray ? [base, base] : base;
535
- };
536
-
537
- /* ===============================
538
- FIELD GENERATOR
539
- =============================== */
540
- const generateFieldLine = (f, optional = false, forceNoSwagger = false) => {
541
- if (entityName === "User" && f.name.toLowerCase() === "role") return null;
542
-
543
- const name = f.name;
544
- const cleanType = f.type.toLowerCase().replace("[]", "");
545
- const isArray = f.type.endsWith("[]");
546
-
547
- const SCALAR_TYPES = [
548
- "string",
549
- "text",
550
- "uuid",
551
- "json",
552
- "number",
553
- "decimal",
554
- "int",
555
- "float",
556
- "boolean",
557
- "date",
558
- ];
559
- if (!SCALAR_TYPES.includes(cleanType)) return null;
560
-
561
- let validators = [];
562
- if (optional) validators.push("@IsOptional()");
563
- if (name.toLowerCase().includes("email")) validators.push("@IsEmail()");
564
- else if (name.toLowerCase().includes("password"))
565
- validators.push(
566
- "@MinLength(8, { message: 'Password is too short (min 8 characters)' })",
567
- );
568
-
569
- switch (cleanType) {
570
- case "string":
571
- case "text":
572
- validators.push(isArray ? "@IsString({ each: true })" : "@IsString()");
573
- if (!isArray && !name.toLowerCase().includes("password"))
574
- validators.push("@MinLength(2)");
575
- break;
576
- case "number":
577
- case "int":
578
- case "float":
579
- validators.push(
580
- isArray ? "@IsNumber({}, { each: true })" : "@IsNumber()",
581
- );
582
- break;
583
- case "boolean":
584
- validators.push(
585
- isArray ? "@IsBoolean({ each: true })" : "@IsBoolean()",
586
- );
587
- break;
588
- case "uuid":
589
- validators.push("@IsUUID()");
590
- break;
591
- case "date":
592
- validators.push("@IsDateString()");
593
- break;
594
- }
595
- if (isArray) validators.push("@IsArray()");
596
-
597
- let swaggerDecorator = "";
598
- if (useSwagger && !forceNoSwagger) {
599
- const decorator = optional ? "@ApiPropertyOptional" : "@ApiProperty";
600
- const options = JSON.stringify(
601
- {
602
- example: getExampleForField(f),
603
- description: getFieldDescription(f),
604
- },
605
- null,
606
- 2,
607
- ).replace(/"([^"]+)":/g, "$1:");
608
- swaggerDecorator = `${decorator}(${options})\n `;
609
- }
610
-
611
- return `${swaggerDecorator}${validators.join("\n ")}\n ${name}${
612
- optional ? "?" : "!"
613
- }: ${formatType(f.type)};`;
614
- };
615
-
616
- /* ======================================================
617
- AUTH DTO (Strict & Clean)
618
- ====================================================== */
619
- if (isAuthDto) {
620
- const authFields = entity.fields
621
- .map((f) => generateFieldLine(f, false))
622
- .filter(Boolean)
623
- .join("\n\n ");
624
-
625
- return `import {
626
- IsString, IsInt, IsBoolean, IsEmail, IsArray,
627
- IsUUID, IsDateString, MinLength, IsOptional
628
- } from 'class-validator';
629
- ${useSwagger ? "import { ApiProperty } from '@nestjs/swagger';" : ""}
630
-
631
- /**
632
- * Auth DTO - Strict contract for authentication
633
- */
634
- export class ${entityName}Dto {
635
- ${authFields}
636
- }
637
- `;
638
- }
639
-
640
- /* ======================================================
641
- CRUD DTOs (Create / Update)
642
- ====================================================== */
643
- const createFields = entity.fields
644
- .map((f) => generateFieldLine(f, false))
645
- .filter(Boolean)
646
- .join("\n\n ");
647
-
648
- const updateFields = entity.fields
649
- .map((f) => generateFieldLine(f, true))
650
- .filter(Boolean)
651
- .join("\n\n ");
652
-
653
- // On prépare les imports Swagger dynamiquement
654
- let swaggerImports = ["ApiProperty", "ApiPropertyOptional", "PartialType"];
655
- if (entityName === "User") swaggerImports.push("ApiHideProperty");
656
-
657
- return `import {
658
- IsOptional, IsString, IsInt, IsBoolean, IsEmail,
659
- IsArray, IsUUID, IsDateString, MinLength, IsEnum
660
- } from 'class-validator';
661
- ${
662
- useSwagger
663
- ? `import { ${swaggerImports.join(", ")} } from '@nestjs/swagger';`
664
- : ""
665
- }
666
- ${enumImport}
667
-
668
1
  /**
669
- * DTO for creating a ${entityName}
670
- */
671
- export class Create${entityName}Dto {
672
- ${createFields}
673
-
674
- ${
675
- entityName === "User"
676
- ? `
677
- ${useSwagger ? "@ApiHideProperty()" : ""}
678
- @IsOptional()
679
- @IsEnum(Role)
680
- role: Role = Role.USER;`
681
- : ""
682
- }
683
- }
684
-
685
- /**
686
- * DTO for updating a ${entityName}
687
- */
688
- ${
689
- useSwagger
690
- ? `export class Update${entityName}Dto extends PartialType(Create${entityName}Dto) {}`
691
- : `export class Update${entityName}Dto {
692
- ${updateFields}
693
-
694
- ${
695
- entityName === "User"
696
- ? `@IsEnum(Role)
697
- @IsOptional()
698
- role?: Role;`
699
- : ""
700
- }
701
- }`
702
- }
703
- `;
704
- }
705
-
706
- export async function generateController(entityName, entityPath, useSwagger) {
707
- const entityNameLower = decapitalize(entityName);
708
- const entityNameCapitalized = capitalize(entityName);
709
- const pluralName = pluralize(entityNameLower);
710
-
711
- const swaggerImports = useSwagger
712
- ? `import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';`
713
- : "";
714
-
715
- const swaggerClassDecorator = useSwagger
716
- ? `@ApiTags('${capitalize(pluralName)}')` // Tags en Majuscule et au pluriel
717
- : "";
718
-
719
- return `
720
- import { Controller, Get, Post, Body, Param, Patch, Delete, HttpCode, HttpStatus, Query } from '@nestjs/common';
721
- ${swaggerImports}
722
- import { ${entityNameCapitalized}Service } from '${entityPath}/application/services/${entityNameLower}.service';
723
- import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from 'src/${entityNameLower}/application/dtos/${entityNameLower}.dto';
724
-
725
-
726
- /**
727
- * Controller for ${entityNameCapitalized} management.
728
- * Handles incoming HTTP requests and delegates logic to the service layer.
729
- */
730
- ${swaggerClassDecorator}
731
- @Controller('${pluralName}')
732
- export class ${entityNameCapitalized}Controller {
733
- constructor(private readonly service: ${entityNameCapitalized}Service) {}
734
-
735
- ${
736
- entityNameLower !== "user"
737
- ? `
738
- @Post()
739
- @HttpCode(HttpStatus.CREATED)
740
- ${
741
- useSwagger
742
- ? `
743
- @ApiOperation({ summary: 'Create a new ${entityNameLower}', description: 'Creates a new record for ${entityNameLower} in the database.' })
744
- @ApiResponse({ status: 201, description: 'The ${entityNameLower} has been successfully created.' })
745
- @ApiResponse({ status: 400, description: 'Invalid input data.' })`
746
- : ""
747
- }
748
- async create(@Body() dto: Create${entityNameCapitalized}Dto) {
749
- const result = await this.service.create(dto);
750
- return {
751
- message: '${entityNameCapitalized} created successfully',
752
- data: result
753
- };
754
- }
755
- `
756
- : ""
757
- }
758
-
759
- @Get()
760
- ${
761
- useSwagger
762
- ? `
763
- @ApiOperation({ summary: 'Get all ${pluralName}', description: 'Retrieves a list of all ${pluralName} available.' })
764
- @ApiResponse({ status: 200, description: 'Return all ${pluralName}.' })`
765
- : ""
766
- }
767
- async getAll() {
768
- return await this.service.getAll();
769
- }
770
-
771
- @Get(':id')
772
- ${
773
- useSwagger
774
- ? `
775
- @ApiOperation({ summary: 'Get ${entityNameLower} by ID' })
776
- @ApiParam({ name: 'id', description: 'The unique identifier of the ${entityNameLower}' })
777
- @ApiResponse({ status: 200, description: 'The ${entityNameLower} has been found.' })
778
- @ApiResponse({ status: 404, description: '${entityNameCapitalized} not found.' })`
779
- : ""
780
- }
781
- async getById(@Param('id') id: string) {
782
- return await this.service.getById(id);
783
- }
784
-
785
- @Patch(':id')
786
- ${
787
- useSwagger
788
- ? `
789
- @ApiOperation({ summary: 'Update an existing ${entityNameLower}' })
790
- @ApiParam({ name: 'id', description: 'The unique identifier of the ${entityNameLower} to update' })
791
- @ApiResponse({ status: 200, description: 'The ${entityNameLower} has been successfully updated.' })
792
- @ApiResponse({ status: 404, description: '${entityNameCapitalized} not found.' })`
793
- : ""
794
- }
795
- async update(
796
- @Param('id') id: string,
797
- @Body() dto: Update${entityNameCapitalized}Dto,
798
- ) {
799
- await this.service.update(id, dto);
800
- return { message: '${entityNameCapitalized} updated successfully' };
801
- }
802
-
803
- @Delete(':id')
804
- @HttpCode(HttpStatus.NO_CONTENT)
805
- ${
806
- useSwagger
807
- ? `
808
- @ApiOperation({ summary: 'Delete a ${entityNameLower}' })
809
- @ApiParam({ name: 'id', description: 'The unique identifier of the ${entityNameLower} to delete' })
810
- @ApiResponse({ status: 204, description: 'The ${entityNameLower} has been successfully deleted.' })
811
- @ApiResponse({ status: 404, description: '${entityNameCapitalized} not found.' })`
812
- : ""
813
- }
814
- async delete(@Param('id') id: string) {
815
- await this.service.delete(id);
816
- return { message: '${entityNameCapitalized} deleted successfully' };
817
- }
818
- }
819
- `;
820
- }
821
-
822
- export async function generateMiddlewares(orm = "global") {
823
- logInfo(
824
- "\u2728 G\u00e9n\u00e9ration des middlewares, interceptors, guards et filters personnalis\u00e9s...",
825
- );
826
-
827
- const basePath = "src/common";
828
- await createDirectory(`${basePath}/middlewares`);
829
- await createDirectory(`${basePath}/interceptors`);
830
- await createDirectory(`${basePath}/filters`);
831
- await createDirectory(`${basePath}/decorators`);
832
-
833
- // Logger Middleware
834
- await createFile({
835
- path: `${basePath}/middlewares/logger.middleware.ts`,
836
- contente: `import { Request, Response, NextFunction } from 'express';
837
-
838
- export function LoggerMiddleware(
839
- req: Request,
840
- res: Response,
841
- next: NextFunction,
842
- ) {
843
- if (process.env.NODE_ENV === 'production') {
844
- return next();
845
- }
846
-
847
- res.on('finish', () => {
848
- console.log(
849
- \`[Request] \${req.method} \${req.originalUrl} - \${res.statusCode}\`,
850
- );
851
- });
852
-
853
- next();
854
- }
855
- `,
856
- });
857
-
858
- // Error Handling Filter (personnalisé selon l'ORM)
859
- await createFile({
860
- path: `${basePath}/filters/all-exceptions.filter.ts`,
861
- contente: getExceptionFilterContent(orm),
862
- });
863
-
864
- // Response Interceptor
865
- await createFile({
866
- path: `${basePath}/interceptors/response.interceptor.ts`,
867
- contente: `import {
868
- CallHandler,
869
- ExecutionContext,
870
- Injectable,
871
- NestInterceptor,
872
- } from '@nestjs/common';
873
- import { Observable } from 'rxjs';
874
- import { map } from 'rxjs/operators';
875
-
876
- @Injectable()
877
- export class ResponseInterceptor<T> implements NestInterceptor<T, any> {
878
- intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
879
- const httpContext = context.switchToHttp();
880
- const response = httpContext.getResponse();
881
- const request = httpContext.getRequest();
882
-
883
- return next.handle().pipe(
884
- map((data) => {
885
- const message =
886
- response.locals.message || this.getDefaultMessage(request.method);
887
-
888
- return {
889
- statusCode: response.statusCode,
890
- message,
891
- path: request.url,
892
- method: request.method,
893
- timestamp: new Date().toISOString(),
894
- data: data ?? null,
895
- };
896
- }),
897
- );
898
- }
899
-
900
- private getDefaultMessage(method: string): string {
901
- switch (method.toUpperCase()) {
902
- case 'POST':
903
- return 'Resource created successfully';
904
- case 'PUT':
905
- case 'PATCH':
906
- return 'Resource updated successfully';
907
- case 'DELETE':
908
- return 'Resource deleted successfully';
909
- case 'GET':
910
- return 'Request processed successfully';
911
- default:
912
- return 'Request processed successfully';
913
- }
914
- }
915
- }
916
- `,
917
- });
918
-
919
- // public Decorator
920
- await createFile({
921
- path: `${basePath}/decorators/public.decorator.ts`,
922
- contente: `import { SetMetadata } from '@nestjs/common';
923
-
924
- export const IS_PUBLIC_KEY = 'isPublic';
925
- export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
926
- `,
927
- });
928
-
929
- await createFile({
930
- path: `${basePath}/decorators/current-user.decorator.ts`,
931
- contente: `import { createParamDecorator, ExecutionContext } from '@nestjs/common';\nexport const CurrentUser = createParamDecorator((data, ctx: ExecutionContext) => ctx.switchToHttp().getRequest().user);`,
932
- });
933
-
934
- // Auth role Decorator
935
- await createFile({
936
- path: `${basePath}/decorators/role.decorator.ts`,
937
- contente: `import { SetMetadata } from '@nestjs/common';
938
-
939
- export const ROLES_KEY = 'roles';
940
- export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
941
- `,
942
- });
943
-
944
- // Modification de main.ts pour intégrer les middlewares
945
- // Chemin vers main.ts
946
- const mainTsPath = "src/main.ts";
947
-
948
- // 1. Mise à jour des imports
949
- const importPattern = "import { AppModule } from './app.module';";
950
- const importReplacer = `import { AppModule } from 'src/app.module'
951
- import { AllExceptionsFilter } from 'src/common/filters/all-exceptions.filter'
952
- import { ResponseInterceptor } from 'src/common/interceptors/response.interceptor'
953
- import { LoggerMiddleware } from 'src/common/middlewares/logger.middleware'
954
- import { ValidationPipe } from '@nestjs/common';`;
955
-
956
- // 2. Injection dans le contenu de bootstrap()
957
- const contentPattern = `const app = await NestFactory.create(AppModule);`;
958
-
959
- const contentReplacer = `
960
- const app = await NestFactory.create(AppModule);
961
-
962
- // 🔒 Global filter pour gérer toutes les exceptions
963
- app.useGlobalFilters(new AllExceptionsFilter())
964
-
965
- // 🔁 Global interceptor pour structurer les réponses
966
- // app.useGlobalInterceptors(new ResponseInterceptor()); //deja appliquer dans le app.module.ts par convention (ne choisir que l'un des deux)
967
-
968
- // 📋 Middleware pour logger toutes les requêtes entrantes
969
- app.use(LoggerMiddleware);`;
970
-
971
- // Appels
972
- await updateFile({
973
- path: mainTsPath,
974
- pattern: importPattern,
975
- replacement: importReplacer,
976
- });
977
-
978
- await updateFile({
979
- path: mainTsPath,
980
- pattern: contentPattern,
981
- replacement: contentReplacer,
982
- });
983
-
984
- // modification de AppModule
985
- const appModulePath = "src/app.module.ts";
986
-
987
- const addNestModuleInterface = `providers: [`;
988
- const replaceWithNestModule = `providers: [
989
- {
990
- provide: APP_INTERCEPTOR,
991
- useClass: ResponseInterceptor, // 🔁 Global interceptor pour structurer les réponses
992
- },`;
993
-
994
- await updateFile({
995
- path: appModulePath,
996
- pattern: addNestModuleInterface,
997
- replacement: replaceWithNestModule,
998
- });
999
-
1000
- logSuccess(
1001
- "\u2705 Middlewares, filters, interceptors et guards g\u00e9n\u00e9r\u00e9s avec succ\u00e8s !",
1002
- );
1003
- }
1004
-
1005
- export async function generateRepository(entityName, orm) {
1006
- const entityNameCapitalized = capitalize(entityName);
1007
- const entityNameLower = entityName.toLowerCase();
1008
- const entityPath = `src/${entityNameLower}`;
1009
-
1010
- // Générateur de méthode spécifique (ex: findByEmail pour Auth)
1011
- const isUser = entityNameLower === "user";
1012
- const getExtraMethods = (ormType) => {
1013
- if (!isUser) return "";
1014
-
1015
- switch (ormType) {
1016
- case "typeorm":
1017
- return `
1018
- async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
1019
- const record = await this.repository.findOne({ where: { email } as any });
1020
- return record ? this.mapper.toDomain(record) : null;
1021
- }`;
1022
- case "prisma":
1023
- return `
1024
- async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
1025
- const record = await this.prisma.${entityNameLower}.findFirst({ where: { email } });
1026
- return record ? this.mapper.toDomain(record) : null;
1027
- }`;
1028
- case "mongoose":
1029
- return `
1030
- async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
1031
- const record = await this.model.findOne({ email }).exec();
1032
- return record ? this.mapper.toDomain(record) : null;
1033
- }`;
1034
- case "sequelize":
1035
- return `
1036
- async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
1037
- const record = await this.model.findOne({ where: { email } });
1038
- return record ? this.mapper.toDomain(record) : null;
1039
- }`;
1040
- default:
1041
- return "";
1042
- }
1043
- };
1044
-
1045
- const extraMethods = getExtraMethods(orm);
1046
-
1047
- // Gère le switch en fonction de l'ORM choisi
1048
- switch (orm) {
1049
- case "typeorm":
1050
- // Implémentation du repository pour TypeORM
1051
- await createFile({
1052
- path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
1053
- contente: `/**
1054
- * PostRepository handles data persistence
1055
- * for the Post entity.
2
+ * utils/utils.js
1056
3
  *
1057
- * This layer abstracts the database engine (Prisma/TypeORM)
1058
- * and provides a clean interface for data operations.
4
+ * Point d'entrée des générateurs et utilitaires de code.
5
+ * Phase 1B Délègue les implémentations aux modules dédiés
6
+ * sous utils/generators/ tout en conservant la même interface publique.
1059
7
  */
1060
8
 
1061
- import { Injectable, NotFoundException } from '@nestjs/common';
1062
- import { Repository } from 'typeorm';
1063
- import { InjectRepository } from '@nestjs/typeorm';
1064
- import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
1065
- import { ${entityNameCapitalized} } from 'src/entities/${entityNameCapitalized}.entity';
1066
- import type { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
1067
- import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
1068
- import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
1069
-
1070
- @Injectable()
1071
- export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
1072
- constructor(
1073
- @InjectRepository(${entityNameCapitalized})
1074
- private readonly repository: Repository<${entityNameCapitalized}>,
1075
- private readonly mapper: ${entityNameCapitalized}Mapper,
1076
- ) {}
1077
-
1078
- // create
1079
- async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1080
- const toPersist = this.mapper.toPersistence(data);
1081
- const created = await this.repository.save(toPersist);
1082
- return this.mapper.toDomain(created);
1083
- }
1084
-
1085
- // find by id
1086
- async findById(id: string): Promise<${entityNameCapitalized}Entity | null> {
1087
- const record = await this.repository.findOne({
1088
- where: { id },
1089
- });
9
+ const { generateEntityFileContent } = require("./generators/domain/entityGenerator");
10
+ const { generateMapper } = require("./generators/infrastructure/mapperGenerator");
11
+ const { generateDto } = require("./generators/application/dtoGenerator");
12
+ const { generateController } = require("./generators/presentation/controllerGenerator");
13
+ const { generateMiddlewares } = require("./generators/infrastructure/middlewareGenerator");
14
+ const { generateRepository } = require("./generators/infrastructure/repositoryGenerator");
15
+ const { generateMongooseSchemaFileContent } = require("./generators/infrastructure/mongooseSchemaGenerator");
1090
16
 
1091
- if (!record) return null;
1092
-
1093
- return this.mapper.toDomain(record);
1094
- }
1095
-
1096
- ${extraMethods}
1097
-
1098
- // update
1099
- async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1100
- const toUpdate = this.mapper.toUpdatePersistence(data);
1101
- const updated = await this.repository.save({ ...toUpdate, id });
1102
- return this.mapper.toDomain(updated);
1103
- }
1104
-
1105
- // find all
1106
- async findAll(): Promise<${entityNameCapitalized}Entity[]> {
1107
- const records = await this.repository.find();
1108
- return records.map(record => this.mapper.toDomain(record));
1109
- }
1110
-
1111
- // delete
1112
- async delete(id: string): Promise<void> {
1113
- await this.repository.delete(id);
1114
- }
1115
- }
1116
- `,
1117
- });
1118
- break;
1119
-
1120
- case "prisma":
1121
- // Implémentation pour Prisma
1122
- await createFile({
1123
- path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
1124
- contente: `import { Injectable, NotFoundException } from '@nestjs/common';
1125
- import { PrismaService } from 'src/prisma/prisma.service';
1126
- import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
1127
- import { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
1128
- import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
1129
- import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
1130
-
1131
- @Injectable()
1132
- export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
1133
- constructor(
1134
- private readonly prisma: PrismaService,
1135
- private readonly mapper: ${entityNameCapitalized}Mapper,
1136
- ) {}
1137
-
1138
- // create
1139
- async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1140
- const toPersist = this.mapper.toPersistence(data);
1141
- const created = await this.prisma.${entityNameLower}.create({ data: toPersist });
1142
- return this.mapper.toDomain(created);
1143
- }
1144
-
1145
- // find by id
1146
- async findById(id: string): Promise<${entityNameCapitalized}Entity | null> {
1147
- const record = await this.prisma.${entityNameLower}.findUnique({
1148
- where: { id },
1149
- });
1150
-
1151
- if (!record) return null;
1152
-
1153
- return this.mapper.toDomain(record);
1154
- }
1155
-
1156
- ${extraMethods}
1157
-
1158
- // update
1159
- async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1160
- const toUpdate = this.mapper.toUpdatePersistence(data);
1161
- const updated = await this.prisma.${entityNameLower}.update({
1162
- where: { id },
1163
- data: toUpdate,
1164
- });
1165
-
1166
- return this.mapper.toDomain(updated);
1167
- }
1168
-
1169
- // find all
1170
- async findAll(): Promise<${entityNameCapitalized}Entity[]> {
1171
- const records = await this.prisma.${entityNameLower}.findMany();
1172
- return records.map(record => this.mapper.toDomain(record));
1173
- }
1174
-
1175
- // delete
1176
- async delete(id: string): Promise<void> {
1177
- await this.prisma.${entityNameLower}.delete({
1178
- where: { id },
1179
- });
1180
- }
1181
- }
1182
- `,
1183
- });
1184
- break;
1185
-
1186
- case "mongoose":
1187
- // Implémentation pour MongoDB avec Mongoose
1188
- await createFile({
1189
- path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
1190
- contente: `import { Injectable, NotFoundException } from '@nestjs/common';
1191
- import { InjectModel } from '@nestjs/mongoose';
1192
- import { Model } from 'mongoose';
1193
- import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
1194
- import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
1195
- import { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
1196
- import { ${entityNameCapitalized} } from '${entityPath}/infrastructure/persistence/mongoose/${entityNameLower}.schema';
1197
- import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
1198
-
1199
- @Injectable()
1200
- export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
1201
- constructor(
1202
- @InjectModel(${entityNameCapitalized}.name)
1203
- private readonly model: Model<${entityNameCapitalized}>,
1204
- private readonly mapper: ${entityNameCapitalized}Mapper,
1205
- ) {}
1206
-
1207
- // create
1208
- async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1209
- const toPersist = this.mapper.toPersistence(data);
1210
- const created = await this.model.create(toPersist);
1211
- return this.mapper.toDomain(created);
1212
- }
1213
-
1214
- // find by id
1215
- async findById(id: string): Promise<${entityNameCapitalized}Entity | null> {
1216
- const record = await this.model.findById(id).exec();
1217
-
1218
- if (!record) return null;
1219
-
1220
- return this.mapper.toDomain(record);
1221
- }
1222
-
1223
- ${extraMethods}
1224
-
1225
- // update
1226
- async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1227
- const toUpdate = this.mapper.toUpdatePersistence(data);
1228
- const updated = await this.model.findByIdAndUpdate(id, toUpdate, { new: true }).exec();
1229
- return this.mapper.toDomain(updated);
1230
- }
1231
-
1232
- // find all
1233
- async findAll(): Promise<${entityNameCapitalized}Entity[]> {
1234
- const records = await this.model.find().exec();
1235
- return records.map(record => this.mapper.toDomain(record));
1236
- }
1237
-
1238
- // delete
1239
- async delete(id: string): Promise<void> {
1240
- await this.model.findByIdAndDelete(id).exec();
1241
- }
1242
- }
1243
- `,
1244
- });
1245
- break;
1246
-
1247
- case "sequelize":
1248
- // Implémentation pour Sequelize
1249
- await createFile({
1250
- path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
1251
- contente: `import { Injectable, NotFoundException } from '@nestjs/common';
1252
- import { InjectModel } from '@nestjs/sequelize';
1253
- import { Model } from 'sequelize-typescript';
1254
- import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
1255
- import { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
1256
- import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
1257
- import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
1258
-
1259
- @Injectable()
1260
- export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
1261
- constructor(
1262
- @InjectModel(${entityNameCapitalized}Entity)
1263
- private readonly model: Model<${entityNameCapitalized}Entity>,
1264
- private readonly mapper: ${entityNameCapitalized}Mapper,
1265
- ) {}
1266
-
1267
- // create
1268
- async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1269
- const toPersist = this.mapper.toPersistence(data);
1270
- const created = await this.model.create(toPersist);
1271
- return this.mapper.toDomain(created);
1272
- }
1273
-
1274
- // find by id
1275
- async findById(id: string): Promise<${entityNameCapitalized}Entity> {
1276
- const record = await this.model.findByPk(id);
1277
-
1278
- if (!record) {
1279
- throw new NotFoundException(\`${entityNameCapitalized}Entity with id \${id} not found\`);
1280
- }
1281
-
1282
- return this.mapper.toDomain(record);
1283
- }
1284
-
1285
- ${extraMethods}
1286
-
1287
- // update
1288
- async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
1289
- const toUpdate = this.mapper.toUpdatePersistence(data);
1290
- const updated = await this.model.update(toUpdate, { where: { id } });
1291
- return this.mapper.toDomain(updated);
1292
- }
1293
-
1294
- // find all
1295
- async findAll(): Promise<${entityNameCapitalized}Entity[]> {
1296
- const records = await this.model.find();
1297
- return records.map(record => this.mapper.toDomain(record));
1298
- }
1299
-
1300
- // delete
1301
- async delete(id: string): Promise<void> {
1302
- await this.model.destroy({ where: { id } });
1303
- }
1304
- }
1305
- `,
1306
- });
1307
- break;
1308
-
1309
- default:
1310
- console.error("Unsupported ORM: " + orm);
1311
- break;
1312
- }
1313
- }
1314
-
1315
- export async function generateMongooseSchemaFileContentx(
1316
- entity,
1317
- entitiesData,
1318
- mode = "full",
1319
- ) {
1320
- const entityName = capitalize(entity.name);
1321
- const entityNameLower = entity.name.toLowerCase();
1322
- const isFull = mode === "full";
1323
-
1324
- let extraImports = "";
1325
-
1326
- // 1. GESTION DES IMPORTS DES RELATIONS (Inclus Session dans Auth)
1327
- const relatedEntities = entitiesData.relations
1328
- .filter((rel) => rel.from === entityNameLower || rel.to === entityNameLower)
1329
- .map((rel) => (rel.from === entityNameLower ? rel.to : rel.from));
1330
-
1331
- // On retire les doublons et l'entité elle-même
1332
- const uniqueRelated = [...new Set(relatedEntities)].filter(
1333
- (e) => e !== entityNameLower,
1334
- );
1335
-
1336
- uniqueRelated.forEach((target) => {
1337
- const targetCap = capitalize(target);
1338
- // EXCEPTION : Si c'est session, le dossier est auth
1339
- const moduleName = target === "session" ? "auth" : target;
1340
-
1341
- const importPath =
1342
- mode === "full"
1343
- ? `../../../../${moduleName}/infrastructure/persistence/mongoose/${target}.schema`
1344
- : `../../${moduleName}/entities/${target}.schema`;
1345
-
1346
- extraImports += `import { ${targetCap} } from '${importPath}';\n`;
1347
- });
1348
-
1349
- // GESTION DU ROLE (USER)
1350
- if (entityNameLower === "user") {
1351
- const rolePath = isFull
1352
- ? "../../../domain/enums/role.enum"
1353
- : "../../common/enums/role.enum";
1354
- extraImports += `import { Role } from '${rolePath}';\n`;
1355
- }
1356
-
1357
- // --- RESTE DE TON CODE (directFields & dynamicRelations) ---
1358
-
1359
- /* const directFields = entity.fields.map((f) => {
1360
-
1361
- const fieldName = f.name;
1362
- const fieldNameLow = fieldName.toLowerCase();
1363
-
1364
- if (entityNameLower === "user" && fieldName === "role") {
1365
- return ` @Prop({ type: String, enum: Role, default: Role.USER })\n role: Role;`;
1366
- }
1367
-
1368
- if (fieldNameLow.endsWith("id")) {
1369
- const targetEntity = fieldNameLow.replace("id", "");
1370
- const hasRelation = entitiesData.relations.some(
1371
- (r) => r.from === targetEntity || r.to === targetEntity
1372
- );
1373
-
1374
- if (hasRelation) {
1375
- const refModel = capitalize(targetEntity);
1376
- return ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${refModel}.name, required: true })\n ${fieldName}: mongoose.Types.ObjectId;`;
1377
- }
1378
- }
1379
-
1380
- return ` @Prop({ required: true })\n ${fieldName}: ${
1381
- f.type.toLowerCase() === "date" ? "Date" : f.type.toLowerCase()
1382
- };`;
1383
- }); */
1384
-
1385
- const directFields = entity.fields
1386
- .filter((f) => {
1387
- // ❌ ignore les champs relationnels objets (post, user, etc.)
1388
- if (isRelationObjectField(f, entitiesData)) return false;
1389
-
1390
- // ❌ ignore aussi les champs non scalaires
1391
- const scalarTypes = [
1392
- "string",
1393
- "text",
1394
- "number",
1395
- "int",
1396
- "float",
1397
- "boolean",
1398
- "date",
1399
- "uuid",
1400
- "json",
1401
- ];
1402
-
1403
- return scalarTypes.includes(f.type.toLowerCase());
1404
- })
1405
- .map((f) => {
1406
- const fieldName = f.name;
1407
- const fieldNameLow = fieldName.toLowerCase();
1408
-
1409
- if (entityNameLower === "user" && fieldName === "role") {
1410
- return ` @Prop({ type: String, enum: Role, default: Role.USER })\n role: Role;`;
1411
- }
1412
-
1413
- if (fieldNameLow.endsWith("id")) {
1414
- const targetEntity = fieldNameLow.replace("id", "");
1415
- const hasRelation = entitiesData.relations.some(
1416
- (r) => r.from === targetEntity || r.to === targetEntity,
1417
- );
1418
-
1419
- if (hasRelation) {
1420
- const refModel = capitalize(targetEntity);
1421
- return ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${refModel}.name, required: true })\n ${fieldName}: mongoose.Types.ObjectId;`;
1422
- }
1423
- }
1424
-
1425
- return ` @Prop({ required: true })\n ${fieldName}: ${
1426
- f.type.toLowerCase() === "date" ? "Date" : f.type.toLowerCase()
1427
- };`;
1428
- });
1429
-
1430
- const dynamicRelations = entitiesData.relations
1431
- .map((rel) => {
1432
- const isFrom = rel.from === entityNameLower;
1433
- const isTo = rel.to === entityNameLower;
1434
- if (!isFrom && !isTo) return null;
1435
-
1436
- const otherEntity = isFrom ? rel.to : rel.from;
1437
- const otherCap = capitalize(otherEntity);
1438
-
1439
- switch (rel.type) {
1440
- // ==========================================
1441
- // CASE 1-n
1442
- // ==========================================
1443
- case "1-n":
1444
- if (isTo) {
1445
- return ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, required: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`;
1446
- }
1447
- return null;
1448
-
1449
- // ==========================================
1450
- // CASE n-1
1451
- // ==========================================
1452
- case "n-1":
1453
- if (isFrom) {
1454
- return ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, required: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`;
1455
- }
1456
- return null;
1457
-
1458
- // ==========================================
1459
- // CASE 1-1
1460
- // ==========================================
1461
- case "1-1":
1462
- if (isFrom) {
1463
- return ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, unique: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`;
1464
- }
1465
- return null;
1466
-
1467
- // ==========================================
1468
- // CASE n-n
1469
- // ==========================================
1470
- case "n-n":
1471
- return ` @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name }] })\n ${otherEntity}Ids: mongoose.Types.ObjectId[];`;
1472
-
1473
- default:
1474
- return null;
1475
- }
1476
- })
1477
- .filter(Boolean);
1478
-
1479
- const allFields = [...new Set([...directFields, ...dynamicRelations])].join(
1480
- "\n\n",
1481
- );
1482
-
1483
- return `
1484
- import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
1485
- import * as mongoose from 'mongoose';
1486
- import { Document } from 'mongoose';
1487
- ${extraImports}
1488
-
1489
- export type ${entityName}Document = ${entityName} & Document;
1490
-
1491
- @Schema({ timestamps: true })
1492
- export class ${entityName} {
1493
- ${allFields}
1494
- }
1495
-
1496
- export const ${entityName}Schema = SchemaFactory.createForClass(${entityName});
1497
- `.trim();
1498
- }
1499
-
1500
- export async function generateMongooseSchemaFileContent(
1501
- entity,
1502
- entitiesData,
1503
- mode = "full",
1504
- ) {
1505
- const entityName = capitalize(entity.name);
1506
- const entityNameLower = entity.name.toLowerCase();
1507
- const isFull = mode === "full";
1508
-
1509
- // --- 1. IMPORTS DYNAMIQUES ---
1510
- const relatedEntities = entitiesData.relations
1511
- .filter((rel) => rel.from === entityNameLower || rel.to === entityNameLower)
1512
- .map((rel) => (rel.from === entityNameLower ? rel.to : rel.from));
1513
-
1514
- const uniqueRelated = [...new Set(relatedEntities)].filter(
1515
- (e) => e !== entityNameLower,
1516
- );
1517
-
1518
- let extraImports = "";
1519
- /* uniqueRelated.forEach((target) => {
1520
- const targetCap = capitalize(target);
1521
- const moduleName = target === "session" ? "auth" : target;
1522
- const importPath = isFull
1523
- ? `../../../../${moduleName}/infrastructure/persistence/mongoose/${target}.schema`
1524
- : `../../${moduleName}/entities/${target}.schema`;
1525
-
1526
- extraImports += `import { ${targetCap} } from '${importPath}';\n`;
1527
- }); */
1528
- uniqueRelated.forEach((target) => {
1529
- const targetCap = capitalize(target);
1530
- const moduleName = target === "session" ? "auth" : target;
1531
-
1532
- let importPath = "";
1533
-
1534
- if (isFull) {
1535
- // Mode Clean Architecture (Full)
1536
- importPath = `../../../../${moduleName}/infrastructure/persistence/mongoose/${target}.schema`;
1537
- } else {
1538
- // Mode Light
1539
- // Si pour Auth tu as fait une exception et mis le schéma dans un sous-dossier :
1540
- if (target === "session") {
1541
- importPath = `../../auth/persistence/${target}.schema`;
1542
- } else {
1543
- // Pour les autres entités en mode Light (ex: src/post/entities/post.schema.ts)
1544
- importPath = `../../${moduleName}/entities/${target}.schema`;
1545
- }
1546
- }
1547
-
1548
- extraImports += `import { ${targetCap} } from '${importPath}';\n`;
1549
- });
1550
-
1551
- if (entityNameLower === "user") {
1552
- const rolePath = isFull
1553
- ? "../../../domain/enums/role.enum"
1554
- : "../../common/enums/role.enum";
1555
- extraImports += `import { Role } from '${rolePath}';\n`;
1556
- }
1557
-
1558
- // --- 2. LOGIQUE DES RELATIONS DYNAMIQUES (Prioritaire) ---
1559
- const dynamicRelations = entitiesData.relations
1560
- .map((rel) => {
1561
- const isFrom = rel.from === entityNameLower;
1562
- const isTo = rel.to === entityNameLower;
1563
- if (!isFrom && !isTo) return null;
1564
-
1565
- const otherEntity = isFrom ? rel.to : rel.from;
1566
- const otherCap = capitalize(otherEntity);
1567
-
1568
- switch (rel.type) {
1569
- case "1-n":
1570
- return isTo
1571
- ? ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, required: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`
1572
- : null;
1573
- case "n-1":
1574
- return isFrom
1575
- ? ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, required: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`
1576
- : null;
1577
- case "1-1":
1578
- return isFrom
1579
- ? ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, unique: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`
1580
- : null;
1581
- case "n-n":
1582
- return ` @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name }] })\n ${otherEntity}Ids: mongoose.Types.ObjectId[];`;
1583
- default:
1584
- return null;
1585
- }
1586
- })
1587
- .filter(Boolean);
1588
-
1589
- // --- 3. FILTRAGE DES CHAMPS DIRECTS (Scalaires uniquement) ---
1590
- const scalarTypes = [
1591
- "string",
1592
- "text",
1593
- "number",
1594
- "int",
1595
- "float",
1596
- "boolean",
1597
- "date",
1598
- "uuid",
1599
- "json",
1600
- ];
1601
-
1602
- const directFields = entity.fields
1603
- .filter((f) => {
1604
- const nameLow = f.name.toLowerCase();
1605
- // On dégage tout ce qui ressemble à une relation pour éviter les doublons avec dynamicRelations
1606
- const isRelId = uniqueRelated.some(
1607
- (rel) => nameLow === rel + "id" || nameLow === rel,
1608
- );
1609
- return scalarTypes.includes(f.type.toLowerCase()) && !isRelId;
1610
- })
1611
- .map((f) => {
1612
- const fieldName = f.name;
1613
- const rawType = f.type.toLowerCase();
1614
-
1615
- // 1. Gestion spécifique du Role
1616
- if (entityNameLower === "user" && fieldName === "role") {
1617
- return ` @Prop({ type: String, enum: Role, default: Role.USER })\n role: Role;`;
1618
- }
1619
-
1620
- // 2. Mapping des types CLI -> TypeScript/Mongoose
1621
- let tsType = "string"; // Valeur par défaut
1622
- let propOptions = "required: true";
1623
-
1624
- switch (rawType) {
1625
- case "text":
1626
- case "uuid":
1627
- case "string":
1628
- tsType = "string";
1629
- break;
1630
- case "int":
1631
- case "number":
1632
- case "float":
1633
- case "decimal":
1634
- tsType = "number";
1635
- break;
1636
- case "boolean":
1637
- tsType = "boolean";
1638
- break;
1639
- case "date":
1640
- tsType = "Date";
1641
- break;
1642
- case "json":
1643
- tsType = "Record<string, any>"; // Ou 'any'
1644
- propOptions = "type: Object, required: true";
1645
- break;
1646
- default:
1647
- tsType = "any";
1648
- }
1649
-
1650
- return ` @Prop({ ${propOptions} })\n ${fieldName}: ${tsType};`;
1651
- });
1652
-
1653
- const allFields = [...new Set([...directFields, ...dynamicRelations])].join(
1654
- "\n\n",
1655
- );
1656
-
1657
- return `import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
1658
- import * as mongoose from 'mongoose';
1659
- import { Document } from 'mongoose';
1660
- ${extraImports}
1661
-
1662
- export type ${entityName}Document = ${entityName} & Document;
1663
-
1664
- @Schema({ timestamps: true })
1665
- export class ${entityName} {
1666
- ${allFields}
1667
- }
1668
-
1669
- export const ${entityName}Schema = SchemaFactory.createForClass(${entityName});`.trim();
1670
- }
1671
-
1672
- function isRelationObjectField(field, entitiesData) {
1673
- const typeLower = field.type.toLowerCase();
1674
-
1675
- return entitiesData.entities?.some((e) => e.name.toLowerCase() === typeLower);
1676
- }
1677
-
1678
- function capitalize(str) {
1679
- return str.charAt(0).toUpperCase() + str.slice(1);
1680
- }
1681
-
1682
- function decapitalize(str) {
1683
- return str.charAt(0).toLowerCase() + str.slice(1);
1684
- }
17
+ const { pluralize } = require("./helpers");
18
+ const inquirer = require("inquirer");
19
+ const actualInquirer = inquirer.default || inquirer;
1685
20
 
1686
21
  /**
1687
- * Mappe le type de champ interne (sélectionné par l'utilisateur) au type
1688
- * TypeScript ou DTO/Classe/Enum correspondant pour la génération de code.
22
+ * Récupère le gestionnaire de paquets choisi (CLI ou interactif).
1689
23
  *
1690
- * @param {string} type - Le type de base sélectionné (string, number, array, DTO, Enum, etc.)
1691
- * @returns {string} Le type formaté pour la génération de code (ex: 'string', 'number', 'Article[]', 'UserRoleEnum')
24
+ * @param {object} flags - Les flags de la CLI
25
+ * @returns {Promise<string>} npm, yarn ou pnpm
1692
26
  */
1693
- function formatType(type) {
1694
- // 1. Gestion des types simples
1695
- switch (type.toLowerCase()) {
1696
- case "number":
1697
- case "decimal":
1698
- return "number";
1699
-
1700
- case "boolean":
1701
- return "boolean";
1702
-
1703
- case "date":
1704
- return "Date";
1705
-
1706
- case "json":
1707
- return "Record<string, any>";
1708
-
1709
- case "string":
1710
- case "text":
1711
- case "uuid":
1712
- return "string";
1713
- }
1714
-
1715
- //2. Vérifie si c'est un Array
1716
- // Gère les Arrays de scalaires (ex: 'string[]')
1717
- if (type.endsWith("[]")) {
1718
- // Applique le formatage au type interne et ajoute '[]'
1719
- return `${formatType(type.slice(0, -2))}[]`;
1720
- }
1721
-
1722
- if (type.match(/^[A-Za-z][A-Za-z0-9_]*$/)) {
1723
- return type;
1724
- }
1725
-
1726
- return "any";
1727
- }
1728
-
1729
- /**
1730
- * Gère les cas spéciaux (comme le rôle utilisateur) avant l'application du formatage.
1731
- * * @param {object} field - L'objet champ avec { name, type }
1732
- * @returns {string} Le type final formaté.
1733
- */
1734
- function getFormattedType(field) {
1735
- // Cas spécial pour la propriété 'role'
1736
- if (field.name === "role" && field.type.toLowerCase().startsWith("string")) {
1737
- return "Role";
1738
- }
1739
-
1740
- return formatType(field.type);
1741
- }
1742
-
1743
- // Génère le contenu du filtre d'exception selon l'ORM
1744
- function getExceptionFilterContent(orm) {
1745
- if (orm === "prisma") {
1746
- return `
1747
- import {
1748
- ExceptionFilter,
1749
- Catch,
1750
- ArgumentsHost,
1751
- HttpException,
1752
- HttpStatus,
1753
- Logger,
1754
- } from '@nestjs/common';
1755
- import { Request, Response } from 'express';
1756
-
1757
- @Catch()
1758
- export class AllExceptionsFilter implements ExceptionFilter {
1759
- private readonly logger = new Logger(AllExceptionsFilter.name);
1760
-
1761
- catch(exception: unknown, host: ArgumentsHost) {
1762
- const ctx = host.switchToHttp();
1763
- const response = ctx.getResponse<Response>();
1764
- const request = ctx.getRequest<Request>();
1765
-
1766
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
1767
- let message: string | string[] = 'Internal server error';
1768
- let errorDetails: any = null;
1769
-
1770
- if (exception instanceof HttpException) {
1771
- status = exception.getStatus();
1772
- const res = exception.getResponse();
1773
- if (typeof res === 'string') {
1774
- message = res;
1775
- } else if (typeof res === 'object' && res !== null) {
1776
- const resObj = res as any;
1777
- message = resObj.message || resObj.error || 'HttpException';
1778
- errorDetails = resObj;
1779
- }
1780
- } else if (
1781
- typeof exception === 'object' &&
1782
- exception &&
1783
- exception.constructor &&
1784
- (
1785
- exception.constructor.name === 'PrismaClientKnownRequestError' ||
1786
- exception.constructor.name === 'PrismaClientValidationError'
1787
- )
1788
- ) {
1789
- status = HttpStatus.BAD_REQUEST;
1790
- message = (exception as any).message || 'Prisma error';
1791
- errorDetails = exception;
1792
- } else if (exception instanceof Error) {
1793
- message = exception.message;
1794
- errorDetails = {
1795
- name: exception.name,
1796
- stack: exception.stack,
1797
- };
1798
- } else {
1799
- message = 'Une erreur inattendue est survenue';
1800
- errorDetails = exception;
1801
- }
1802
-
1803
- if (process.env.NODE_ENV !== 'production') {
1804
- this.logger.error(
1805
- \`Exception on \${request.method} \${request.url}\`,
1806
- JSON.stringify({ message, status, errorDetails, exception }),
1807
- );
1808
- }
1809
-
1810
- response.status(status).json({
1811
- statusCode: status,
1812
- timestamp: new Date().toISOString(),
1813
- path: request.url,
1814
- method: request.method,
1815
- message,
1816
- error: errorDetails,
1817
- });
1818
- }
1819
- }
1820
- `.trim();
1821
- }
1822
-
1823
- if (orm === "mongoose") {
1824
- return `
1825
- import {
1826
- ExceptionFilter,
1827
- Catch,
1828
- ArgumentsHost,
1829
- HttpException,
1830
- HttpStatus,
1831
- Logger,
1832
- } from '@nestjs/common';
1833
- import { Request, Response } from 'express';
1834
-
1835
- @Catch()
1836
- export class AllExceptionsFilter implements ExceptionFilter {
1837
- private readonly logger = new Logger(AllExceptionsFilter.name);
1838
-
1839
- catch(exception: unknown, host: ArgumentsHost) {
1840
- const ctx = host.switchToHttp();
1841
- const response = ctx.getResponse<Response>();
1842
- const request = ctx.getRequest<Request>();
1843
-
1844
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
1845
- let message: string | string[] = 'Internal server error';
1846
- let errorDetails: any = null;
1847
-
1848
- if (exception instanceof HttpException) {
1849
- status = exception.getStatus();
1850
- const res = exception.getResponse();
1851
- if (typeof res === 'string') {
1852
- message = res;
1853
- } else if (typeof res === 'object' && res !== null) {
1854
- const resObj = res as any;
1855
- message = resObj.message || resObj.error || 'HttpException';
1856
- errorDetails = resObj;
1857
- }
1858
- } else if (
1859
- typeof exception === 'object' &&
1860
- exception &&
1861
- 'name' in exception &&
1862
- (
1863
- (exception as any).name === 'MongoError' ||
1864
- (exception as any).name === 'MongooseError'
1865
- )
1866
- ) {
1867
- status = HttpStatus.BAD_REQUEST;
1868
- message = (exception as any).message || 'MongoDB error';
1869
- errorDetails = exception;
1870
- } else if (exception instanceof Error) {
1871
- message = exception.message;
1872
- errorDetails = {
1873
- name: exception.name,
1874
- stack: exception.stack,
1875
- };
1876
- } else {
1877
- message = 'Une erreur inattendue est survenue';
1878
- errorDetails = exception;
1879
- }
1880
-
1881
- if (process.env.NODE_ENV !== 'production') {
1882
- this.logger.error(
1883
- \`Exception on \${request.method} \${request.url}\`,
1884
- JSON.stringify({ message, status, errorDetails, exception }),
1885
- );
1886
- }
1887
-
1888
- response.status(status).json({
1889
- statusCode: status,
1890
- timestamp: new Date().toISOString(),
1891
- path: request.url,
1892
- method: request.method,
1893
- message,
1894
- error: errorDetails,
1895
- });
1896
- }
1897
- }
1898
- `.trim();
1899
- }
1900
-
1901
- if (orm === "typeorm") {
1902
- return `
1903
- import {
1904
- ExceptionFilter,
1905
- Catch,
1906
- ArgumentsHost,
1907
- HttpException,
1908
- HttpStatus,
1909
- Logger,
1910
- } from '@nestjs/common';
1911
- import { Request, Response } from 'express';
1912
-
1913
- @Catch()
1914
- export class AllExceptionsFilter implements ExceptionFilter {
1915
- private readonly logger = new Logger(AllExceptionsFilter.name);
1916
-
1917
- catch(exception: unknown, host: ArgumentsHost) {
1918
- const ctx = host.switchToHttp();
1919
- const response = ctx.getResponse<Response>();
1920
- const request = ctx.getRequest<Request>();
1921
-
1922
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
1923
- let message: string | string[] = 'Internal server error';
1924
- let errorDetails: any = null;
1925
-
1926
- if (exception instanceof HttpException) {
1927
- status = exception.getStatus();
1928
- const res = exception.getResponse();
1929
- if (typeof res === 'string') {
1930
- message = res;
1931
- } else if (typeof res === 'object' && res !== null) {
1932
- const resObj = res as any;
1933
- message = resObj.message || resObj.error || 'HttpException';
1934
- errorDetails = resObj;
1935
- }
1936
- } else if (
1937
- typeof exception === 'object' &&
1938
- exception &&
1939
- 'name' in exception &&
1940
- (
1941
- (exception as any).name === 'QueryFailedError' ||
1942
- (exception as any).name === 'EntityNotFoundError' ||
1943
- (exception as any).name === 'CannotCreateEntityIdMapError'
1944
- )
1945
- ) {
1946
- status = HttpStatus.BAD_REQUEST;
1947
- message = (exception as any).message || 'TypeORM error';
1948
- errorDetails = exception;
1949
- } else if (exception instanceof Error) {
1950
- message = exception.message;
1951
- errorDetails = {
1952
- name: exception.name,
1953
- stack: exception.stack,
1954
- };
1955
- } else {
1956
- message = 'Une erreur inattendue est survenue';
1957
- errorDetails = exception;
1958
- }
1959
-
1960
- if (process.env.NODE_ENV !== 'production') {
1961
- this.logger.error(
1962
- \`Exception on \${request.method} \${request.url}\`,
1963
- JSON.stringify({ message, status, errorDetails, exception }),
1964
- );
1965
- }
1966
-
1967
- response.status(status).json({
1968
- statusCode: status,
1969
- timestamp: new Date().toISOString(),
1970
- path: request.url,
1971
- method: request.method,
1972
- message,
1973
- error: errorDetails,
1974
- });
1975
- }
1976
- }
1977
- `.trim();
1978
- }
1979
-
1980
- if (orm === "sequelize") {
1981
- return `
1982
- import {
1983
- ExceptionFilter,
1984
- Catch,
1985
- ArgumentsHost,
1986
- HttpException,
1987
- HttpStatus,
1988
- Logger,
1989
- } from '@nestjs/common';
1990
- import { Request, Response } from 'express';
1991
-
1992
- @Catch()
1993
- export class AllExceptionsFilter implements ExceptionFilter {
1994
- private readonly logger = new Logger(AllExceptionsFilter.name);
1995
-
1996
- catch(exception: unknown, host: ArgumentsHost) {
1997
- const ctx = host.switchToHttp();
1998
- const response = ctx.getResponse<Response>();
1999
- const request = ctx.getRequest<Request>();
2000
-
2001
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
2002
- let message: string | string[] = 'Internal server error';
2003
- let errorDetails: any = null;
2004
-
2005
- if (exception instanceof HttpException) {
2006
- status = exception.getStatus();
2007
- const res = exception.getResponse();
2008
- if (typeof res === 'string') {
2009
- message = res;
2010
- } else if (typeof res === 'object' && res !== null) {
2011
- const resObj = res as any;
2012
- message = resObj.message || resObj.error || 'HttpException';
2013
- errorDetails = resObj;
2014
- }
2015
- } else if (
2016
- typeof exception === 'object' &&
2017
- exception &&
2018
- exception.constructor &&
2019
- (
2020
- exception.constructor.name === 'SequelizeDatabaseError' ||
2021
- exception.constructor.name === 'SequelizeValidationError'
2022
- )
2023
- ) {
2024
- status = HttpStatus.BAD_REQUEST;
2025
- message = (exception as any).message || 'Sequelize error';
2026
- errorDetails = exception;
2027
- } else if (exception instanceof Error) {
2028
- message = exception.message;
2029
- errorDetails = {
2030
- name: exception.name,
2031
- stack: exception.stack,
2032
- };
2033
- } else {
2034
- message = 'Une erreur inattendue est survenue';
2035
- errorDetails = exception;
2036
- }
2037
-
2038
- if (process.env.NODE_ENV !== 'production') {
2039
- this.logger.error(
2040
- \`Exception on \${request.method} \${request.url}\`,
2041
- JSON.stringify({ message, status, errorDetails, exception }),
2042
- );
2043
- }
2044
-
2045
- response.status(status).json({
2046
- statusCode: status,
2047
- timestamp: new Date().toISOString(),
2048
- path: request.url,
2049
- method: request.method,
2050
- message,
2051
- error: errorDetails,
2052
- });
2053
- }
2054
- }
2055
- `.trim();
2056
- }
2057
-
2058
- // Version universelle (multi-ORM)
2059
- return `
2060
- import {
2061
- ExceptionFilter,
2062
- Catch,
2063
- ArgumentsHost,
2064
- HttpException,
2065
- HttpStatus,
2066
- Logger,
2067
- } from '@nestjs/common';
2068
- import { Request, Response } from 'express';
2069
-
2070
- @Catch()
2071
- export class AllExceptionsFilter implements ExceptionFilter {
2072
- private readonly logger = new Logger(AllExceptionsFilter.name);
2073
-
2074
- catch(exception: unknown, host: ArgumentsHost) {
2075
- const ctx = host.switchToHttp();
2076
- const response = ctx.getResponse<Response>();
2077
- const request = ctx.getRequest<Request>();
2078
-
2079
- let status = HttpStatus.INTERNAL_SERVER_ERROR;
2080
- let message: string | string[] = 'Internal server error';
2081
- let errorDetails: any = null;
2082
-
2083
- if (exception instanceof HttpException) {
2084
- status = exception.getStatus();
2085
- const res = exception.getResponse();
2086
- if (typeof res === 'string') {
2087
- message = res;
2088
- } else if (typeof res === 'object' && res !== null) {
2089
- const resObj = res as any;
2090
- message = resObj.message || resObj.error || 'HttpException';
2091
- errorDetails = resObj;
2092
- }
2093
- }
2094
- // Prisma
2095
- else if (
2096
- typeof exception === 'object' &&
2097
- exception &&
2098
- exception.constructor &&
2099
- (
2100
- exception.constructor.name === 'PrismaClientKnownRequestError' ||
2101
- exception.constructor.name === 'PrismaClientValidationError'
2102
- )
2103
- ) {
2104
- status = HttpStatus.BAD_REQUEST;
2105
- message = (exception as any).message || 'Prisma error';
2106
- errorDetails = exception;
2107
- }
2108
- // Mongoose/Mongo
2109
- else if (
2110
- typeof exception === 'object' &&
2111
- exception &&
2112
- 'name' in exception &&
2113
- (
2114
- (exception as any).name === 'MongoError' ||
2115
- (exception as any).name === 'MongooseError'
2116
- )
2117
- ) {
2118
- status = HttpStatus.BAD_REQUEST;
2119
- message = (exception as any).message || 'MongoDB error';
2120
- errorDetails = exception;
2121
- }
2122
- // Sequelize
2123
- else if (
2124
- typeof exception === 'object' &&
2125
- exception &&
2126
- exception.constructor &&
2127
- (
2128
- exception.constructor.name === 'SequelizeDatabaseError' ||
2129
- exception.constructor.name === 'SequelizeValidationError'
2130
- )
2131
- ) {
2132
- status = HttpStatus.BAD_REQUEST;
2133
- message = (exception as any).message || 'Sequelize error';
2134
- errorDetails = exception;
2135
- }
2136
- else if (exception instanceof Error) {
2137
- message = exception.message;
2138
- errorDetails = {
2139
- name: exception.name,
2140
- stack: exception.stack,
2141
- };
2142
- } else {
2143
- message = 'Une erreur inattendue est survenue';
2144
- errorDetails = exception;
2145
- }
2146
-
2147
- this.logger.error(
2148
- \`Exception on \${request.method} \${request.url}\`,
2149
- JSON.stringify({ message, status, errorDetails, exception }),
2150
- );
2151
-
2152
- response.status(status).json({
2153
- statusCode: status,
2154
- timestamp: new Date().toISOString(),
2155
- path: request.url,
2156
- method: request.method,
2157
- message,
2158
- error: errorDetails,
2159
- });
2160
- }
2161
- }
2162
- `.trim();
2163
- }
2164
-
2165
- export async function getPackageManager(flags) {
27
+ async function getPackageManager(flags) {
2166
28
  const managers = ["npm", "yarn", "pnpm"];
2167
29
 
2168
30
  // 1. Vérification du flag
@@ -2187,11 +49,14 @@ export async function getPackageManager(flags) {
2187
49
  return answers.packageManager;
2188
50
  }
2189
51
 
2190
- export function pluralize(name) {
2191
- if (name.endsWith("y")) {
2192
- return name.slice(0, -1) + "ies"; // Category -> Categories
2193
- } else if (name.endsWith("s")) {
2194
- return name; // Déjà un pluriel
2195
- }
2196
- return name + "s"; // User -> Users
2197
- }
52
+ module.exports = {
53
+ generateEntityFileContent,
54
+ generateMapper,
55
+ generateDto,
56
+ generateController,
57
+ generateMiddlewares,
58
+ generateRepository,
59
+ generateMongooseSchemaFileContent,
60
+ getPackageManager,
61
+ pluralize,
62
+ };