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.
- package/AUDIT_ROADMAP.md +798 -0
- package/CHANGELOG.fr.md +22 -0
- package/CHANGELOG.md +22 -0
- package/PROGRESS.md +259 -0
- package/commands/demo.js +2 -42
- package/commands/generate.js +5 -4
- package/commands/help.js +29 -2
- package/commands/new.js +6 -49
- package/package.json +1 -1
- package/utils/app-module.updater.js +102 -0
- package/utils/cliParser.js +0 -45
- package/utils/configs/setupCleanArchitecture.js +2 -2
- package/utils/envGenerator.js +6 -0
- package/utils/file-system.js +90 -0
- package/utils/file-utils/packageJsonUtils.js +6 -0
- package/utils/file-utils/saveProjectConfig.js +6 -0
- package/utils/fullModeInput.js +8 -229
- package/utils/generators/application/dtoGenerator.js +252 -0
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/database/setupDatabase.js +32 -14
- package/utils/generators/domain/entityGenerator.js +126 -0
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperGenerator.js +114 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +229 -0
- package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
- package/utils/generators/presentation/controllerGenerator.js +142 -0
- package/utils/helpers.js +115 -0
- package/utils/interactive/askEntityInputs.js +5 -68
- package/utils/interactive/entityBuilder.js +400 -0
- package/utils/lightModeInput.js +13 -246
- package/utils/setups/orms/typeOrmSetup.js +31 -4
- package/utils/setups/projectSetup.js +78 -8
- package/utils/setups/setupAuth.js +6 -7
- package/utils/setups/setupDatabase.js +2 -1
- package/utils/setups/setupMongoose.js +96 -30
- package/utils/setups/setupPrisma.js +118 -134
- package/utils/setups/setupSwagger.js +12 -8
- package/utils/shell.js +125 -10
- package/utils/userInput.js +37 -101
- 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 };
|
package/utils/helpers.js
ADDED
|
@@ -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
|
|
13
|
-
|
|
14
|
-
|
|
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;
|