nestcraftx 0.2.4 → 0.2.6

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 (63) hide show
  1. package/.gitattributes +6 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
  4. package/.github/ISSUE_TEMPLATE/pull_request_template.md +24 -0
  5. package/CHANGELOG.fr.md +97 -97
  6. package/CHANGELOG.md +98 -98
  7. package/CLI_USAGE.fr.md +331 -331
  8. package/CLI_USAGE.md +364 -364
  9. package/DEMO.fr.md +292 -292
  10. package/DEMO.md +294 -294
  11. package/LICENSE +21 -21
  12. package/MIGRATION_GUIDE.fr.md +127 -127
  13. package/MIGRATION_GUIDE.md +124 -124
  14. package/QUICK_START.fr.md +152 -152
  15. package/QUICK_START.md +169 -169
  16. package/README.fr.md +653 -659
  17. package/SECURITY.md +10 -0
  18. package/bin/nestcraft.js +84 -64
  19. package/commands/demo.js +333 -330
  20. package/commands/generate.js +93 -0
  21. package/commands/generateConf.js +91 -0
  22. package/commands/help.js +78 -78
  23. package/commands/info.js +48 -48
  24. package/commands/new.js +338 -335
  25. package/commands/start.js +19 -19
  26. package/commands/test.js +7 -7
  27. package/package.json +41 -41
  28. package/readme.md +638 -643
  29. package/utils/cliParser.js +133 -76
  30. package/utils/colors.js +62 -62
  31. package/utils/configs/configureDocker.js +120 -120
  32. package/utils/configs/setupCleanArchitecture.js +563 -557
  33. package/utils/configs/setupLightArchitecture.js +701 -660
  34. package/utils/envGenerator.js +122 -122
  35. package/utils/file-utils/packageJsonUtils.js +49 -55
  36. package/utils/file-utils/saveProjectConfig.js +36 -0
  37. package/utils/fullModeInput.js +607 -607
  38. package/utils/generators/application/dtoUpdater.js +54 -0
  39. package/utils/generators/cleanModuleGenerator.js +475 -0
  40. package/utils/generators/database/setupDatabase.js +31 -0
  41. package/utils/generators/domain/entityUpdater.js +78 -0
  42. package/utils/generators/infrastructure/mapperUpdater.js +65 -0
  43. package/utils/generators/lightModuleGenerator.js +131 -0
  44. package/utils/generators/relation/relation.engine.js +64 -0
  45. package/utils/interactive/askEntityInputs.js +165 -0
  46. package/utils/lightModeInput.js +460 -460
  47. package/utils/loggers/logError.js +7 -7
  48. package/utils/loggers/logInfo.js +7 -7
  49. package/utils/loggers/logSuccess.js +7 -7
  50. package/utils/loggers/logWarning.js +7 -7
  51. package/utils/setups/orms/typeOrmSetup.js +630 -630
  52. package/utils/setups/projectSetup.js +46 -46
  53. package/utils/setups/setupAuth.js +973 -926
  54. package/utils/setups/setupDatabase.js +75 -75
  55. package/utils/setups/setupLogger.js +69 -59
  56. package/utils/setups/setupMongoose.js +377 -432
  57. package/utils/setups/setupPrisma.js +802 -630
  58. package/utils/setups/setupSwagger.js +97 -88
  59. package/utils/shell.js +32 -32
  60. package/utils/spinner.js +57 -57
  61. package/utils/systemCheck.js +124 -124
  62. package/utils/userInput.js +421 -421
  63. package/utils/utils.js +2197 -1762
@@ -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 };
@@ -0,0 +1,131 @@
1
+ const {
2
+ createDirectory,
3
+ createFile,
4
+ safeUpdateAppModule,
5
+ capitalize,
6
+ decapitalize,
7
+ } = require("../userInput");
8
+ const { generateEntityFileContent, generateDto } = require("../utils");
9
+ const {
10
+ generateLightRepository,
11
+ generateLightService,
12
+ generateLightController,
13
+ generateLightModule,
14
+ } = require("../configs/setupLightArchitecture");
15
+ const { logSuccess } = require("../loggers/logSuccess");
16
+ const { logError } = require("../loggers/logError");
17
+ const setupDatabase = require("./database/setupDatabase");
18
+ const { logInfo } = require("../loggers/logInfo");
19
+ const { updateExistingEntityRelation } = require("./domain/entityUpdater");
20
+ const { applyRelationPatches } = require("./relation/relation.engine");
21
+
22
+ async function lightModuleGenerator(entity, config) {
23
+ try {
24
+ const entityNameCap = capitalize(entity.name);
25
+ const entityNameLow = decapitalize(entity.name);
26
+ const entityPath = `src/${entityNameLow}`;
27
+ const mode = "light";
28
+
29
+ logInfo(`🚀 Generating light module for ${entityNameCap}`);
30
+
31
+ // 1️⃣ Créer les dossiers
32
+ await createDirectory(`${entityPath}/entities`);
33
+ await createDirectory(`${entityPath}/dtos`);
34
+ await createDirectory(`${entityPath}/services`);
35
+ await createDirectory(`${entityPath}/repositories`);
36
+ await createDirectory(`${entityPath}/controllers`);
37
+
38
+ // 2️⃣ Générer l'entité
39
+ const entityContent = await generateEntityFileContent(entity, "light");
40
+ await createFile({
41
+ path: `${entityPath}/entities/${entityNameLow}.entity.ts`,
42
+ contente: entityContent,
43
+ });
44
+
45
+ // 3️⃣ Générer les DTOs
46
+ const dtoContent = await generateDto(
47
+ entity,
48
+ config.swagger,
49
+ false,
50
+ "light",
51
+ );
52
+ await createFile({
53
+ path: `${entityPath}/dtos/${entityNameLow}.dto.ts`,
54
+ contente: dtoContent,
55
+ });
56
+
57
+ // 4️⃣ Générer le repository
58
+ const repositoryContent = generateLightRepository(
59
+ entityNameCap,
60
+ entityNameLow,
61
+ config.orm,
62
+ entity,
63
+ );
64
+ await createFile({
65
+ path: `${entityPath}/repositories/${entityNameLow}.repository.ts`,
66
+ contente: repositoryContent,
67
+ });
68
+
69
+ // 5️⃣ Générer le service
70
+ const serviceContent = generateLightService(entityNameCap, entityNameLow);
71
+ await createFile({
72
+ path: `${entityPath}/services/${entityNameLow}.service.ts`,
73
+ contente: serviceContent,
74
+ });
75
+
76
+ // 6️⃣ Générer le controller
77
+ const controllerContent = generateLightController(
78
+ entityNameCap,
79
+ entityNameLow,
80
+ config.swagger,
81
+ );
82
+ await createFile({
83
+ path: `${entityPath}/controllers/${entityNameLow}.controller.ts`,
84
+ contente: controllerContent,
85
+ });
86
+
87
+ // 7️⃣ Générer le module
88
+ const moduleContent = generateLightModule(
89
+ entityNameCap,
90
+ entityNameLow,
91
+ entityPath,
92
+ config.orm,
93
+ config.auth,
94
+ );
95
+ await createFile({
96
+ path: `${entityPath}/${entityNameLow}.module.ts`,
97
+ contente: moduleContent,
98
+ });
99
+
100
+ // ÉTAPE RELATIONS : Patching des fichiers si une relation existe
101
+ if (entity.relation) {
102
+ const { target, type } = entity.relation;
103
+ logInfo(
104
+ `🔗 Linking ${entityNameCap} with ${capitalize(target)} (${type})...`,
105
+ );
106
+
107
+ await updateExistingEntityRelation(target, entity.name, type);
108
+
109
+ await applyRelationPatches(
110
+ entity.name,
111
+ target,
112
+ type,
113
+ config.swagger,
114
+ mode,
115
+ );
116
+ }
117
+
118
+ // 8️ Auto-enregistrement dans AppModule
119
+ await safeUpdateAppModule(entityNameLow);
120
+
121
+ // 9
122
+ await setupDatabase(config, entity);
123
+
124
+ logSuccess(`✨ Light module ${entityNameCap} generated successfully!`);
125
+ } catch (error) {
126
+ logError(`Error generating light module: ${error}`);
127
+ throw error;
128
+ }
129
+ }
130
+
131
+ module.exports = lightModuleGenerator;
@@ -0,0 +1,64 @@
1
+ const { patchDtoWithRelation } = require("../application/dtoUpdater");
2
+ const { patchMapperWithRelation } = require("../infrastructure/mapperUpdater");
3
+
4
+ function resolveForeignKeyOwner(newEntityName, target, relationType) {
5
+ switch (relationType) {
6
+ case "1-n":
7
+ // FK on the N side → target
8
+ return null /* {
9
+ ownerEntity: target,
10
+ relatedEntity: newEntityName,
11
+ } */;
12
+
13
+ case "n-1":
14
+ // FK on the N side → source
15
+ return {
16
+ ownerEntity: target,
17
+ relatedEntity: newEntityName,
18
+ };
19
+
20
+ case "1-1":
21
+ // Convention: FK on source side
22
+ return {
23
+ ownerEntity: target,
24
+ relatedEntity: newEntityName,
25
+ };
26
+
27
+ case "n-n":
28
+ // Pivot table → no FK field
29
+ return null;
30
+
31
+ default:
32
+ throw new Error(`❌ Unknown relation type: ${relationType}`);
33
+ }
34
+ }
35
+
36
+ async function applyRelationPatches(
37
+ newEntityName,
38
+ target,
39
+ relationType,
40
+ useSwagger,
41
+ mode = "full",
42
+ ) {
43
+ const owner = resolveForeignKeyOwner(newEntityName, target, relationType);
44
+
45
+ if (!owner) {
46
+ // console.log("ℹ️ n-n relation detected → pivot table, skipping DTO patch");
47
+ return;
48
+ }
49
+
50
+ // Patch DTO
51
+ await patchDtoWithRelation(
52
+ owner.ownerEntity,
53
+ owner.relatedEntity,
54
+ useSwagger,
55
+ mode,
56
+ );
57
+
58
+ await patchMapperWithRelation(owner.ownerEntity, owner.relatedEntity, mode);
59
+ }
60
+
61
+ module.exports = {
62
+ resolveForeignKeyOwner,
63
+ applyRelationPatches,
64
+ };
@@ -0,0 +1,165 @@
1
+ // src/utils/interactive/askEntityInputs.js
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const readline = require("readline-sync");
5
+ const inquirer = require("inquirer");
6
+ const { info, success } = require("../colors");
7
+ const { logWarning } = require("../loggers/logWarning");
8
+ const { capitalize } = require("../userInput");
9
+ const actualInquirer = inquirer.default || inquirer;
10
+
11
+ 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("[✓]")}`);
80
+ }
81
+
82
+ const relationData = await askRelationInputs(entity.name);
83
+ entity.relation = relationData;
84
+
85
+ return entity;
86
+ }
87
+
88
+ async function askRelationInputs(newEntityName) {
89
+ const srcPath = path.join(process.cwd(), "src");
90
+
91
+ // Sécurité : Vérifie si le dossier src existe
92
+ if (!fs.existsSync(srcPath)) return null;
93
+
94
+ const blacklist = [
95
+ "common",
96
+ "prisma",
97
+ "auth",
98
+ "mail",
99
+ "infrastructure",
100
+ "shared",
101
+ ];
102
+
103
+ const modules = fs.readdirSync(srcPath).filter((folder) => {
104
+ const fullPath = path.join(srcPath, folder);
105
+
106
+ // 1. D'abord vérifier si c'est un dossier (important pour éviter ENOTDIR)
107
+ const isDirectory = fs.lstatSync(fullPath).isDirectory();
108
+ if (!isDirectory) return false;
109
+
110
+ // 2. Ensuite les autres filtres
111
+ const isNotBlacklisted = !blacklist.includes(folder.toLowerCase());
112
+ const isNotCurrent = folder.toLowerCase() !== newEntityName.toLowerCase();
113
+
114
+ // 3. Enfin vérifier s'il y a un fichier .module.ts à l'intérieur
115
+ const hasModuleFile = fs
116
+ .readdirSync(fullPath)
117
+ .some((file) => file.endsWith(".module.ts"));
118
+
119
+ return isNotBlacklisted && isNotCurrent && hasModuleFile;
120
+ });
121
+
122
+ if (modules.length === 0) {
123
+ console.log(info("\n[INFO] No existing modules found for relations."));
124
+ return null;
125
+ }
126
+
127
+ const { wantRelation } = await actualInquirer.prompt([
128
+ {
129
+ type: "confirm",
130
+ name: "wantRelation",
131
+ message: "Add relation with an existing module?",
132
+ default: false,
133
+ },
134
+ ]);
135
+
136
+ if (!wantRelation) return null;
137
+
138
+ // On demande les détails de la relation
139
+ const relation = await actualInquirer.prompt([
140
+ {
141
+ type: "list",
142
+ name: "target",
143
+ message: "Select the target module:",
144
+ choices: modules,
145
+ },
146
+ {
147
+ type: "list",
148
+ name: "type",
149
+ message: "Relation type (NewEntity -> Target):",
150
+ choices: [
151
+ { name: "1-n (One-to-Many: This has many of those)", value: "1-n" },
152
+ {
153
+ name: "n-1 (Many-to-One: This belongs to one of those)",
154
+ value: "n-1",
155
+ },
156
+ { name: "1-1 (One-to-One)", value: "1-1" },
157
+ { name: "n-n (Many-to-Many)", value: "n-n" },
158
+ ],
159
+ },
160
+ ]);
161
+
162
+ return relation;
163
+ }
164
+
165
+ module.exports = { askEntityInputs };