nestcraftx 0.2.5 → 0.3.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/CLI_USAGE.fr.md +331 -331
- package/CLI_USAGE.md +364 -364
- package/LICENSE +21 -21
- package/PROGRESS.md +258 -0
- package/bin/nestcraft.js +84 -64
- package/commands/demo.js +337 -330
- package/commands/generate.js +94 -0
- package/commands/generateConf.js +91 -0
- package/commands/help.js +29 -2
- package/commands/info.js +48 -48
- package/commands/new.js +340 -335
- package/commands/start.js +19 -19
- package/commands/test.js +7 -7
- package/package.json +1 -1
- package/utils/app-module.updater.js +96 -0
- package/utils/cliParser.js +88 -76
- package/utils/colors.js +62 -62
- package/utils/configs/configureDocker.js +120 -120
- package/utils/configs/setupCleanArchitecture.js +17 -15
- package/utils/configs/setupLightArchitecture.js +15 -9
- package/utils/file-system.js +75 -0
- package/utils/file-utils/saveProjectConfig.js +36 -0
- package/utils/fullModeInput.js +607 -607
- package/utils/generators/application/dtoGenerator.js +251 -0
- package/utils/generators/application/dtoUpdater.js +54 -0
- package/utils/generators/cleanModuleGenerator.js +475 -0
- package/utils/generators/database/setupDatabase.js +49 -0
- package/utils/generators/domain/entityGenerator.js +126 -0
- package/utils/generators/domain/entityUpdater.js +78 -0
- package/utils/generators/infrastructure/mapperGenerator.js +114 -0
- package/utils/generators/infrastructure/mapperUpdater.js +65 -0
- package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +196 -0
- package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
- package/utils/generators/lightModuleGenerator.js +131 -0
- package/utils/generators/presentation/controllerGenerator.js +142 -0
- package/utils/generators/relation/relation.engine.js +64 -0
- package/utils/helpers.js +109 -0
- package/utils/interactive/askEntityInputs.js +165 -0
- package/utils/loggers/logError.js +7 -7
- package/utils/loggers/logInfo.js +7 -7
- package/utils/loggers/logSuccess.js +7 -7
- package/utils/loggers/logWarning.js +7 -7
- package/utils/setups/orms/typeOrmSetup.js +630 -630
- package/utils/setups/setupAuth.js +33 -20
- package/utils/setups/setupDatabase.js +76 -75
- package/utils/setups/setupMongoose.js +22 -5
- package/utils/setups/setupPrisma.js +748 -630
- package/utils/shell.js +112 -32
- package/utils/spinner.js +57 -57
- package/utils/systemCheck.js +124 -124
- package/utils/userInput.js +357 -421
- package/utils/utils.js +29 -2164
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/utils/generators/domain/entityUpdater.js
|
|
2
|
+
const { updateFile, capitalize } = require("../../userInput");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
async function updateExistingEntityRelation(
|
|
6
|
+
targetName,
|
|
7
|
+
newEntityName,
|
|
8
|
+
relationType,
|
|
9
|
+
mode,
|
|
10
|
+
) {
|
|
11
|
+
const targetLow = targetName.toLowerCase();
|
|
12
|
+
const newCap = capitalize(newEntityName);
|
|
13
|
+
const newLow = newEntityName.toLowerCase();
|
|
14
|
+
|
|
15
|
+
const entityPath =
|
|
16
|
+
mode === "full"
|
|
17
|
+
? `src/${targetLow}/domain/entities/${targetLow}.entity.ts`
|
|
18
|
+
: `src/${targetLow}/entities/${targetLow}.entity.ts`;
|
|
19
|
+
|
|
20
|
+
if (!fs.existsSync(entityPath)) return;
|
|
21
|
+
|
|
22
|
+
// Déterminer le champ à ajouter selon la relation
|
|
23
|
+
let fieldToAdd = "";
|
|
24
|
+
let typeToAdd = "";
|
|
25
|
+
|
|
26
|
+
switch (relationType) {
|
|
27
|
+
case "n-1": // L'inverse d'un n-1 est un 1-n (donc une liste)
|
|
28
|
+
/* fieldToAdd = `${newLow}s`;
|
|
29
|
+
typeToAdd = `${newCap}Entity[]`; */
|
|
30
|
+
break;
|
|
31
|
+
case "1-n": // L'inverse d'un 1-n est un n-1 (donc un ID ou l'objet)
|
|
32
|
+
fieldToAdd = `${newLow}Id`;
|
|
33
|
+
typeToAdd = `string`;
|
|
34
|
+
break;
|
|
35
|
+
case "1-1":
|
|
36
|
+
fieldToAdd = `${newLow}Id`;
|
|
37
|
+
typeToAdd = `string`;
|
|
38
|
+
break;
|
|
39
|
+
case "n-n":
|
|
40
|
+
/* fieldToAdd = `${newLow}s`;
|
|
41
|
+
typeToAdd = `${newCap}Entity[]`; */
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 1. Ajouter l'import de la nouvelle entité au début du fichier (si c'est un type complexe)
|
|
46
|
+
if (typeToAdd.includes("Entity")) {
|
|
47
|
+
const importLine = `import { ${newCap}Entity } from 'src/${newLow}/domain/entities/${newLow}.entity';\n`;
|
|
48
|
+
await updateFile({
|
|
49
|
+
path: entityPath,
|
|
50
|
+
pattern: /^/, // Début du fichier
|
|
51
|
+
replacement: importLine,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Injecter dans le constructeur (avant la parenthèse fermante)
|
|
56
|
+
await updateFile({
|
|
57
|
+
path: entityPath,
|
|
58
|
+
pattern: /(constructor\([\s\S]*?)\n\s+\)/m,
|
|
59
|
+
replacement: `$1\n private readonly ${fieldToAdd}?: ${typeToAdd}, \n )`,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 3. Injecter le Getter (avant la méthode toJSON)
|
|
63
|
+
const getterMethod = `\n get${capitalize(fieldToAdd)}(): ${typeToAdd} | undefined {\n return this.${fieldToAdd};\n }\n`;
|
|
64
|
+
await updateFile({
|
|
65
|
+
path: entityPath,
|
|
66
|
+
pattern: /toJSON\(\)/,
|
|
67
|
+
replacement: `${getterMethod}\n toJSON()`,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 4. Injecter dans toJSON
|
|
71
|
+
await updateFile({
|
|
72
|
+
path: entityPath,
|
|
73
|
+
pattern: /(toJSON\(\) \{[\s\S]*?return \{[\s\S]*?)\n\s+\};/m,
|
|
74
|
+
replacement: `$1\n ${fieldToAdd}: this.${fieldToAdd},}`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { updateExistingEntityRelation };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* utils/generators/infrastructure/mapperGenerator.js
|
|
3
|
+
*
|
|
4
|
+
* Générateur du Mapper infrastructure (Full/Clean Architecture uniquement).
|
|
5
|
+
* Extrait de utils.js — Phase 1B.
|
|
6
|
+
*
|
|
7
|
+
* Le mapper n'est JAMAIS utilisé en mode light :
|
|
8
|
+
* - light : le repository utilise une méthode `private toEntity()` inline
|
|
9
|
+
* - full : le mapper découple persistence <-> domaine <-> DTO
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { capitalize, decapitalize } = require("../../helpers");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Génère le contenu TypeScript du Mapper pour une entité.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} entity - L'objet entité { name, fields[] }
|
|
18
|
+
* @returns {Promise<string>} Le code TypeScript du mapper
|
|
19
|
+
*/
|
|
20
|
+
async function generateMapper(entity) {
|
|
21
|
+
const entityName = capitalize(entity.name);
|
|
22
|
+
|
|
23
|
+
// 1. Types scalaires / primitifs autorisés dans le mapper
|
|
24
|
+
const SCALAR_TYPES = [
|
|
25
|
+
"string",
|
|
26
|
+
"text",
|
|
27
|
+
"uuid",
|
|
28
|
+
"json",
|
|
29
|
+
"number",
|
|
30
|
+
"int",
|
|
31
|
+
"float",
|
|
32
|
+
"decimal",
|
|
33
|
+
"boolean",
|
|
34
|
+
"date",
|
|
35
|
+
"role",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
// 2. Filtrage cohérent : scalaires + IDs techniques
|
|
39
|
+
const filterDomainFields = (f) => {
|
|
40
|
+
const typeName = f.type.toLowerCase().replace("[]", "");
|
|
41
|
+
return (
|
|
42
|
+
SCALAR_TYPES.includes(typeName) || f.name.toLowerCase().endsWith("id")
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// 3. Champs filtrés pour le constructeur de l'Entity
|
|
47
|
+
const filteredFields = entity.fields.filter(filterDomainFields);
|
|
48
|
+
|
|
49
|
+
const domainArgs = ["data.id"]
|
|
50
|
+
.concat(["data.createdAt", "data.updatedAt"])
|
|
51
|
+
.concat(filteredFields.map((f) => `data.${f.name}`))
|
|
52
|
+
.join(",\n ");
|
|
53
|
+
|
|
54
|
+
// 4. Champs pour la persistence
|
|
55
|
+
const toPersistenceFields = filteredFields
|
|
56
|
+
.map((f) => `${f.name}: dto.${f.name},`)
|
|
57
|
+
.join("\n ");
|
|
58
|
+
|
|
59
|
+
const toUpdateFields = filteredFields
|
|
60
|
+
.map(
|
|
61
|
+
(f) => `if (dto.${f.name} !== undefined) data.${f.name} = dto.${f.name};`,
|
|
62
|
+
)
|
|
63
|
+
.join("\n ");
|
|
64
|
+
|
|
65
|
+
return `/**
|
|
66
|
+
* ${entityName}Mapper transforms data between
|
|
67
|
+
* different layers (Persistence <-> Domain <-> DTO).
|
|
68
|
+
*
|
|
69
|
+
* Ensures that the internal database structure
|
|
70
|
+
* never leaks into the API responses.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
import { Injectable } from '@nestjs/common';
|
|
74
|
+
import { ${entityName}Entity } from 'src/${decapitalize(
|
|
75
|
+
entity.name,
|
|
76
|
+
)}/domain/entities/${decapitalize(entity.name)}.entity';
|
|
77
|
+
import { Create${entityName}Dto, Update${entityName}Dto } from 'src/${decapitalize(
|
|
78
|
+
entity.name,
|
|
79
|
+
)}/application/dtos/${decapitalize(entity.name)}.dto';
|
|
80
|
+
|
|
81
|
+
@Injectable()
|
|
82
|
+
export class ${entityName}Mapper {
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Transforme les données (Prisma/TypeORM) en Entité de Domaine
|
|
86
|
+
*/
|
|
87
|
+
toDomain(data: any): ${entityName}Entity {
|
|
88
|
+
return new ${entityName}Entity(
|
|
89
|
+
${domainArgs}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Transforme le DTO en objet pour la création en base de données
|
|
95
|
+
*/
|
|
96
|
+
toPersistence(dto: Create${entityName}Dto): any {
|
|
97
|
+
return {
|
|
98
|
+
${toPersistenceFields}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Prépare l'objet de mise à jour partielle
|
|
104
|
+
*/
|
|
105
|
+
toUpdatePersistence(dto: Update${entityName}Dto): any {
|
|
106
|
+
const data: any = {};
|
|
107
|
+
${toUpdateFields}
|
|
108
|
+
return data;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { generateMapper };
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { updateFile } = require("../../userInput");
|
|
2
|
+
|
|
3
|
+
// src/utils/generators/infrastructure/mapperUpdater.js
|
|
4
|
+
async function patchMapperWithRelation(source, targetName, mode = "full") {
|
|
5
|
+
const targetLow = targetName.toLowerCase();
|
|
6
|
+
|
|
7
|
+
if (mode === "full") {
|
|
8
|
+
return patchCleanMapper(source, targetLow);
|
|
9
|
+
} else {
|
|
10
|
+
return patchLightRepositoryMapper(source, targetLow);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function patchCleanMapper(source, targetName) {
|
|
15
|
+
const mapperPath = `src/${source.toLowerCase()}/infrastructure/mappers/${source.toLowerCase()}.mapper.ts`;
|
|
16
|
+
const targetLow = targetName.toLowerCase();
|
|
17
|
+
|
|
18
|
+
// 1. ToDomain : Ajouter l'argument au constructeur de l'entité
|
|
19
|
+
await updateFile({
|
|
20
|
+
path: mapperPath,
|
|
21
|
+
pattern:
|
|
22
|
+
/(toDomain\(data: any\): .* \{[\s\S]*?return new .*?\()([\s\S]*?)(\);)/m,
|
|
23
|
+
replacement: `$1$2 data.${targetLow}Id$3`,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// 2. ToPersistence : Ajouter le champ pour Prisma
|
|
27
|
+
await updateFile({
|
|
28
|
+
path: mapperPath,
|
|
29
|
+
pattern: /(toPersistence\(dto: .* \{[\s\S]*?return \{)/m,
|
|
30
|
+
replacement: `$1\n ${targetLow}Id: dto.${targetLow}Id,`,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 3. PATCH toUpdatePersistence → add FK update condition
|
|
34
|
+
await updateFile({
|
|
35
|
+
path: mapperPath,
|
|
36
|
+
pattern: /(toUpdatePersistence\(dto: .*?\{\s*const data: any = \{\};)/m,
|
|
37
|
+
replacement: `$1\n\n if (dto.${targetLow}Id !== undefined) data.${targetLow}Id = dto.${targetLow}Id;`,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* LIGHT MODE → patch repository.ts (toEntity + create/update)
|
|
43
|
+
*/
|
|
44
|
+
async function patchLightRepositoryMapper(entityName, targetLow) {
|
|
45
|
+
const repoPath = `src/${entityName.toLowerCase()}/repositories/${entityName.toLowerCase()}.repository.ts`;
|
|
46
|
+
// const targetRepoPath = `src/${targetLow.toLowerCase()}/repositories/${targetLow.toLowerCase()}.repository.ts`;
|
|
47
|
+
|
|
48
|
+
// 1. Patch toEntity constructor
|
|
49
|
+
/* await updateFile({
|
|
50
|
+
path: repoPath,
|
|
51
|
+
pattern:
|
|
52
|
+
/(private toEntity\(raw: any\): .*?\{\s*return new .*?\([\s\S]*?)(\);\s*\})/m,
|
|
53
|
+
replacement: `$1,\n raw.${targetLow}Id$2`,
|
|
54
|
+
}); */
|
|
55
|
+
|
|
56
|
+
// 2. Patch toEntity constructor in target
|
|
57
|
+
await updateFile({
|
|
58
|
+
path: repoPath,
|
|
59
|
+
pattern:
|
|
60
|
+
/(private toEntity\(raw: any\): .*?\{\s*return new .*?\([\s\S]*?)(\);\s*\})/m,
|
|
61
|
+
replacement: `$1\n raw.${targetLow}Id$2`,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { patchMapperWithRelation };
|