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
@@ -0,0 +1,142 @@
1
+ /**
2
+ * utils/generators/presentation/controllerGenerator.js
3
+ *
4
+ * Générateur du Controller (présentation) pour le mode Full/Clean Architecture.
5
+ * Extrait de utils.js — Phase 1B.
6
+ *
7
+ * ATTENTION : Ce générateur est UNIQUEMENT pour le mode Full.
8
+ * Le mode Light a son propre générateur dans setupLightArchitecture.js
9
+ * avec des différences comportementales :
10
+ * - Full : chemins "presentation/controllers/", méthodes getAll()/getById()
11
+ * - Light : chemins "controllers/", méthodes findAll()/findById(), Logger intégré
12
+ *
13
+ * Ne jamais fusionner les deux.
14
+ */
15
+
16
+ const { capitalize, decapitalize, pluralize } = require("../../helpers");
17
+
18
+ /**
19
+ * Génère le contenu TypeScript du Controller NestJS (Full Architecture).
20
+ *
21
+ * @param {string} entityName - Le nom de l'entité (ex: "Post")
22
+ * @param {string} entityPath - Le chemin de base (ex: "src/post")
23
+ * @param {boolean} useSwagger - Inclure les décorateurs Swagger
24
+ * @returns {Promise<string>} Le code TypeScript du controller
25
+ */
26
+ async function generateController(entityName, entityPath, useSwagger) {
27
+ const entityNameLower = decapitalize(entityName);
28
+ const entityNameCapitalized = capitalize(entityName);
29
+ const pluralName = pluralize(entityNameLower);
30
+
31
+ const swaggerImports = useSwagger
32
+ ? `import { ApiTags, ApiOperation, ApiResponse, ApiParam } from '@nestjs/swagger';`
33
+ : "";
34
+
35
+ const swaggerClassDecorator = useSwagger
36
+ ? `@ApiTags('${capitalize(pluralName)}')`
37
+ : "";
38
+
39
+ return `
40
+ import { Controller, Get, Post, Body, Param, Patch, Delete, HttpCode, HttpStatus, Query } from '@nestjs/common';
41
+ ${swaggerImports}
42
+ import { ${entityNameCapitalized}Service } from '${entityPath}/application/services/${entityNameLower}.service';
43
+ import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from 'src/${entityNameLower}/application/dtos/${entityNameLower}.dto';
44
+
45
+
46
+ /**
47
+ * Controller for ${entityNameCapitalized} management.
48
+ * Handles incoming HTTP requests and delegates logic to the service layer.
49
+ */
50
+ ${swaggerClassDecorator}
51
+ @Controller('${pluralName}')
52
+ export class ${entityNameCapitalized}Controller {
53
+ constructor(private readonly service: ${entityNameCapitalized}Service) {}
54
+
55
+ ${
56
+ entityNameLower !== "user"
57
+ ? `
58
+ @Post()
59
+ @HttpCode(HttpStatus.CREATED)
60
+ ${
61
+ useSwagger
62
+ ? `
63
+ @ApiOperation({ summary: 'Create a new ${entityNameLower}', description: 'Creates a new record for ${entityNameLower} in the database.' })
64
+ @ApiResponse({ status: 201, description: 'The ${entityNameLower} has been successfully created.' })
65
+ @ApiResponse({ status: 400, description: 'Invalid input data.' })`
66
+ : ""
67
+ }
68
+ async create(@Body() dto: Create${entityNameCapitalized}Dto) {
69
+ const result = await this.service.create(dto);
70
+ return {
71
+ message: '${entityNameCapitalized} created successfully',
72
+ data: result
73
+ };
74
+ }
75
+ `
76
+ : ""
77
+ }
78
+
79
+ @Get()
80
+ ${
81
+ useSwagger
82
+ ? `
83
+ @ApiOperation({ summary: 'Get all ${pluralName}', description: 'Retrieves a list of all ${pluralName} available.' })
84
+ @ApiResponse({ status: 200, description: 'Return all ${pluralName}.' })`
85
+ : ""
86
+ }
87
+ async getAll() {
88
+ return await this.service.getAll();
89
+ }
90
+
91
+ @Get(':id')
92
+ ${
93
+ useSwagger
94
+ ? `
95
+ @ApiOperation({ summary: 'Get ${entityNameLower} by ID' })
96
+ @ApiParam({ name: 'id', description: 'The unique identifier of the ${entityNameLower}' })
97
+ @ApiResponse({ status: 200, description: 'The ${entityNameLower} has been found.' })
98
+ @ApiResponse({ status: 404, description: '${entityNameCapitalized} not found.' })`
99
+ : ""
100
+ }
101
+ async getById(@Param('id') id: string) {
102
+ return await this.service.getById(id);
103
+ }
104
+
105
+ @Patch(':id')
106
+ ${
107
+ useSwagger
108
+ ? `
109
+ @ApiOperation({ summary: 'Update an existing ${entityNameLower}' })
110
+ @ApiParam({ name: 'id', description: 'The unique identifier of the ${entityNameLower} to update' })
111
+ @ApiResponse({ status: 200, description: 'The ${entityNameLower} has been successfully updated.' })
112
+ @ApiResponse({ status: 404, description: '${entityNameCapitalized} not found.' })`
113
+ : ""
114
+ }
115
+ async update(
116
+ @Param('id') id: string,
117
+ @Body() dto: Update${entityNameCapitalized}Dto,
118
+ ) {
119
+ await this.service.update(id, dto);
120
+ return { message: '${entityNameCapitalized} updated successfully' };
121
+ }
122
+
123
+ @Delete(':id')
124
+ @HttpCode(HttpStatus.NO_CONTENT)
125
+ ${
126
+ useSwagger
127
+ ? `
128
+ @ApiOperation({ summary: 'Delete a ${entityNameLower}' })
129
+ @ApiParam({ name: 'id', description: 'The unique identifier of the ${entityNameLower} to delete' })
130
+ @ApiResponse({ status: 204, description: 'The ${entityNameLower} has been successfully deleted.' })
131
+ @ApiResponse({ status: 404, description: '${entityNameCapitalized} not found.' })`
132
+ : ""
133
+ }
134
+ async delete(@Param('id') id: string) {
135
+ await this.service.delete(id);
136
+ return { message: '${entityNameCapitalized} deleted successfully' };
137
+ }
138
+ }
139
+ `;
140
+ }
141
+
142
+ module.exports = { generateController };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * utils/helpers.js
3
+ *
4
+ * Fonctions utilitaires de manipulation de chaînes et de types.
5
+ * Partagées entre les générateurs full-mode et light-mode.
6
+ *
7
+ * Ces fonctions sont purement stateless : aucune dépendance externe,
8
+ * aucun effet de bord, aucune logique métier.
9
+ */
10
+
11
+ /**
12
+ * Met en majuscule la première lettre d'une chaîne.
13
+ * @param {string} str
14
+ * @returns {string}
15
+ */
16
+ function capitalize(str) {
17
+ if (!str) return str;
18
+ return str.charAt(0).toUpperCase() + str.slice(1);
19
+ }
20
+
21
+ /**
22
+ * Met en minuscule la première lettre d'une chaîne.
23
+ * @param {string} str
24
+ * @returns {string}
25
+ */
26
+ function decapitalize(str) {
27
+ if (!str) return str;
28
+ return str.charAt(0).toLowerCase() + str.slice(1);
29
+ }
30
+
31
+ /**
32
+ * Retourne le pluriel simple d'un nom.
33
+ * - si finit par 'y' → remplace par 'ies' (category → categories)
34
+ * - si finit déjà par 's' → retourne tel quel
35
+ * - sinon → ajoute 's'
36
+ * @param {string} name
37
+ * @returns {string}
38
+ */
39
+ function pluralize(name) {
40
+ if (name.endsWith("y")) {
41
+ return name.slice(0, -1) + "ies";
42
+ } else if (name.endsWith("s")) {
43
+ return name;
44
+ }
45
+ return name + "s";
46
+ }
47
+
48
+ /**
49
+ * Mappe le type de champ interne (sélectionné par l'utilisateur) au type
50
+ * TypeScript correspondant pour la génération de code.
51
+ *
52
+ * @param {string} type - Le type de base (string, number, boolean, Date, json, etc.)
53
+ * @returns {string} Le type formaté TypeScript
54
+ */
55
+ function formatType(type) {
56
+ switch (type.toLowerCase()) {
57
+ case "number":
58
+ case "decimal":
59
+ return "number";
60
+
61
+ case "boolean":
62
+ return "boolean";
63
+
64
+ case "date":
65
+ return "Date";
66
+
67
+ case "json":
68
+ return "Record<string, any>";
69
+
70
+ case "string":
71
+ case "text":
72
+ case "uuid":
73
+ return "string";
74
+ }
75
+
76
+ // Gère les tableaux (ex: 'string[]', 'number[]')
77
+ if (type.endsWith("[]")) {
78
+ return `${formatType(type.slice(0, -2))}[]`;
79
+ }
80
+
81
+ // Laisse passer les types complexes (DTO, Enum, classe custom)
82
+ if (type.match(/^[A-Za-z][A-Za-z0-9_]*$/)) {
83
+ return type;
84
+ }
85
+
86
+ return "any";
87
+ }
88
+
89
+ /**
90
+ * Gère les cas spéciaux avant l'application de formatType.
91
+ * Ex: le champ 'role' de type string est traité comme le type 'Role' (enum).
92
+ *
93
+ * @param {object} field - L'objet champ { name, type }
94
+ * @returns {string} Le type final formaté
95
+ */
96
+ function getFormattedType(field) {
97
+ let typeStr;
98
+ if (field.name === "role" && field.type.toLowerCase().startsWith("string")) {
99
+ typeStr = "Role";
100
+ } else {
101
+ typeStr = formatType(field.type);
102
+ }
103
+ if (field.nullable) {
104
+ typeStr += " | null";
105
+ }
106
+ return typeStr;
107
+ }
108
+
109
+ module.exports = {
110
+ capitalize,
111
+ decapitalize,
112
+ pluralize,
113
+ formatType,
114
+ getFormattedType,
115
+ };
@@ -6,78 +6,15 @@ const inquirer = require("inquirer");
6
6
  const { info, success } = require("../colors");
7
7
  const { logWarning } = require("../loggers/logWarning");
8
8
  const { capitalize } = require("../userInput");
9
+ const { buildEntityInteractive } = require("./entityBuilder");
9
10
  const actualInquirer = inquirer.default || inquirer;
10
11
 
11
12
  async function askEntityInputs(targetName) {
12
- const entity = { name: targetName, fields: [], relation: null };
13
-
14
- console.log(
15
- `\n${info("[ENTITY DESIGN]")} Define fields for "${targetName}" :`,
16
- );
17
-
18
- while (true) {
19
- let fname = readline.question(" Field name (leave empty to finish) : ");
20
- if (!fname) break;
21
-
22
- if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
23
- logWarning("Invalid field name.");
24
- continue;
25
- }
26
-
27
- const baseTypeChoices = [
28
- "string",
29
- "text",
30
- "number",
31
- "boolean",
32
- "Date",
33
- "uuid",
34
- "json",
35
- "enum",
36
- "array",
37
- "object",
38
- ];
39
-
40
- const typeAnswer = await actualInquirer.prompt([
41
- {
42
- type: "list",
43
- name: "ftype",
44
- message: `Type for "${fname}"`,
45
- choices: baseTypeChoices,
46
- },
47
- ]);
48
-
49
- let ftype = typeAnswer.ftype;
50
-
51
- // Logique de raffinement des types (identique à ton script original)
52
- if (ftype === "array") {
53
- const inner = await actualInquirer.prompt([
54
- {
55
- type: "list",
56
- name: "innerType",
57
- message: `Type of elements for "${fname}[]"`,
58
- choices: baseTypeChoices.filter(
59
- (c) => c !== "array" && c !== "object",
60
- ),
61
- },
62
- ]);
63
- ftype = `${inner.innerType}[]`;
64
- } else if (ftype === "enum") {
65
- ftype = capitalize(fname) + "Enum";
66
- } else if (ftype === "object") {
67
- const obj = await actualInquirer.prompt([
68
- {
69
- type: "input",
70
- name: "val",
71
- message: "Complex type name :",
72
- default: "json",
73
- },
74
- ]);
75
- ftype = capitalize(obj.val);
76
- }
77
-
78
- entity.fields.push({ name: fname, type: ftype });
79
- console.log(` Type for "${fname}" : ${ftype} ${success("[✓]")}`);
13
+ const result = await buildEntityInteractive(targetName);
14
+ if (!result) {
15
+ throw new Error("Module generation cancelled by user.");
80
16
  }
17
+ const entity = { name: result.name, fields: result.fields, relation: null };
81
18
 
82
19
  const relationData = await askRelationInputs(entity.name);
83
20
  entity.relation = relationData;