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.
Files changed (56) hide show
  1. package/AUDIT_ROADMAP.md +798 -0
  2. package/CHANGELOG.fr.md +22 -0
  3. package/CHANGELOG.md +22 -0
  4. package/CLI_USAGE.fr.md +331 -331
  5. package/CLI_USAGE.md +364 -364
  6. package/LICENSE +21 -21
  7. package/PROGRESS.md +258 -0
  8. package/bin/nestcraft.js +84 -64
  9. package/commands/demo.js +337 -330
  10. package/commands/generate.js +94 -0
  11. package/commands/generateConf.js +91 -0
  12. package/commands/help.js +29 -2
  13. package/commands/info.js +48 -48
  14. package/commands/new.js +340 -335
  15. package/commands/start.js +19 -19
  16. package/commands/test.js +7 -7
  17. package/package.json +1 -1
  18. package/utils/app-module.updater.js +96 -0
  19. package/utils/cliParser.js +88 -76
  20. package/utils/colors.js +62 -62
  21. package/utils/configs/configureDocker.js +120 -120
  22. package/utils/configs/setupCleanArchitecture.js +17 -15
  23. package/utils/configs/setupLightArchitecture.js +15 -9
  24. package/utils/file-system.js +75 -0
  25. package/utils/file-utils/saveProjectConfig.js +36 -0
  26. package/utils/fullModeInput.js +607 -607
  27. package/utils/generators/application/dtoGenerator.js +251 -0
  28. package/utils/generators/application/dtoUpdater.js +54 -0
  29. package/utils/generators/cleanModuleGenerator.js +475 -0
  30. package/utils/generators/database/setupDatabase.js +49 -0
  31. package/utils/generators/domain/entityGenerator.js +126 -0
  32. package/utils/generators/domain/entityUpdater.js +78 -0
  33. package/utils/generators/infrastructure/mapperGenerator.js +114 -0
  34. package/utils/generators/infrastructure/mapperUpdater.js +65 -0
  35. package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
  36. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +196 -0
  37. package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
  38. package/utils/generators/lightModuleGenerator.js +131 -0
  39. package/utils/generators/presentation/controllerGenerator.js +142 -0
  40. package/utils/generators/relation/relation.engine.js +64 -0
  41. package/utils/helpers.js +109 -0
  42. package/utils/interactive/askEntityInputs.js +165 -0
  43. package/utils/loggers/logError.js +7 -7
  44. package/utils/loggers/logInfo.js +7 -7
  45. package/utils/loggers/logSuccess.js +7 -7
  46. package/utils/loggers/logWarning.js +7 -7
  47. package/utils/setups/orms/typeOrmSetup.js +630 -630
  48. package/utils/setups/setupAuth.js +33 -20
  49. package/utils/setups/setupDatabase.js +76 -75
  50. package/utils/setups/setupMongoose.js +22 -5
  51. package/utils/setups/setupPrisma.js +748 -630
  52. package/utils/shell.js +112 -32
  53. package/utils/spinner.js +57 -57
  54. package/utils/systemCheck.js +124 -124
  55. package/utils/userInput.js +357 -421
  56. package/utils/utils.js +29 -2164
@@ -0,0 +1,196 @@
1
+ /**
2
+ * utils/generators/infrastructure/mongooseSchemaGenerator.js
3
+ *
4
+ * Générateur du schéma Mongoose (Document Schema).
5
+ * Extrait de utils.js — Phase 1B.
6
+ *
7
+ * Supporte les modes "full" et "light" :
8
+ * - full : chemins d'import depuis infrastructure/persistence/mongoose/
9
+ * - light : chemins d'import depuis entities/
10
+ * - Role : full → domain/enums/role.enum | light → common/enums/role.enum
11
+ */
12
+
13
+ const { capitalize } = require("../../helpers");
14
+
15
+ /**
16
+ * Vérifie si un champ est un objet relationnel (non scalaire).
17
+ * Utilisé pour filtrer les champs qui ne doivent pas être inclus dans le schéma.
18
+ *
19
+ * @param {object} field - Le champ { name, type }
20
+ * @param {object} entitiesData - Les données complètes { entities[], relations[] }
21
+ * @returns {boolean}
22
+ */
23
+ function isRelationObjectField(field, entitiesData) {
24
+ const typeLower = field.type.toLowerCase();
25
+ return entitiesData.entities?.some((e) => e.name.toLowerCase() === typeLower);
26
+ }
27
+
28
+ /**
29
+ * Génère le contenu TypeScript du schéma Mongoose pour une entité.
30
+ *
31
+ * @param {object} entity - L'objet entité { name, fields[] }
32
+ * @param {object} entitiesData - { entities[], relations[] }
33
+ * @param {"full"|"light"} mode - Mode d'architecture
34
+ * @returns {Promise<string>} Le code TypeScript du schéma Mongoose
35
+ */
36
+ async function generateMongooseSchemaFileContent(
37
+ entity,
38
+ entitiesData,
39
+ mode = "full",
40
+ ) {
41
+ const entityName = capitalize(entity.name);
42
+ const entityNameLower = entity.name.toLowerCase();
43
+ const isFull = mode === "full";
44
+
45
+ // --- 1. IMPORTS DYNAMIQUES ---
46
+ const relatedEntities = entitiesData.relations
47
+ .filter((rel) => rel.from === entityNameLower || rel.to === entityNameLower)
48
+ .map((rel) => (rel.from === entityNameLower ? rel.to : rel.from));
49
+
50
+ const uniqueRelated = [...new Set(relatedEntities)].filter(
51
+ (e) => e !== entityNameLower,
52
+ );
53
+
54
+ let extraImports = "";
55
+
56
+ uniqueRelated.forEach((target) => {
57
+ const targetCap = capitalize(target);
58
+ const moduleName = target === "session" ? "auth" : target;
59
+
60
+ let importPath = "";
61
+
62
+ if (isFull) {
63
+ // Mode Clean Architecture (Full)
64
+ importPath = `../../../../${moduleName}/infrastructure/persistence/mongoose/${target}.schema`;
65
+ } else {
66
+ // Mode Light
67
+ if (target === "session") {
68
+ importPath = `../../auth/persistence/${target}.schema`;
69
+ } else {
70
+ importPath = `../../${moduleName}/entities/${target}.schema`;
71
+ }
72
+ }
73
+
74
+ extraImports += `import { ${targetCap} } from '${importPath}';\n`;
75
+ });
76
+
77
+ if (entityNameLower === "user") {
78
+ const rolePath = isFull
79
+ ? "../../../domain/enums/role.enum"
80
+ : "../../common/enums/role.enum";
81
+ extraImports += `import { Role } from '${rolePath}';\n`;
82
+ }
83
+
84
+ // --- 2. LOGIQUE DES RELATIONS DYNAMIQUES (Prioritaire) ---
85
+ const dynamicRelations = entitiesData.relations
86
+ .map((rel) => {
87
+ const isFrom = rel.from === entityNameLower;
88
+ const isTo = rel.to === entityNameLower;
89
+ if (!isFrom && !isTo) return null;
90
+
91
+ const otherEntity = isFrom ? rel.to : rel.from;
92
+ const otherCap = capitalize(otherEntity);
93
+
94
+ switch (rel.type) {
95
+ case "1-n":
96
+ return isTo
97
+ ? ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, required: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`
98
+ : null;
99
+ case "n-1":
100
+ return isFrom
101
+ ? ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, required: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`
102
+ : null;
103
+ case "1-1":
104
+ return isFrom
105
+ ? ` @Prop({ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name, unique: true })\n ${otherEntity}Id: mongoose.Types.ObjectId;`
106
+ : null;
107
+ case "n-n":
108
+ return ` @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: ${otherCap}.name }] })\n ${otherEntity}Ids: mongoose.Types.ObjectId[];`;
109
+ default:
110
+ return null;
111
+ }
112
+ })
113
+ .filter(Boolean);
114
+
115
+ // --- 3. FILTRAGE DES CHAMPS DIRECTS (Scalaires uniquement) ---
116
+ const scalarTypes = [
117
+ "string",
118
+ "text",
119
+ "number",
120
+ "int",
121
+ "float",
122
+ "boolean",
123
+ "date",
124
+ "uuid",
125
+ "json",
126
+ ];
127
+
128
+ const directFields = entity.fields
129
+ .filter((f) => {
130
+ const nameLow = f.name.toLowerCase();
131
+ const isRelId = uniqueRelated.some(
132
+ (rel) => nameLow === rel + "id" || nameLow === rel,
133
+ );
134
+ return scalarTypes.includes(f.type.toLowerCase()) && !isRelId;
135
+ })
136
+ .map((f) => {
137
+ const fieldName = f.name;
138
+ const rawType = f.type.toLowerCase();
139
+
140
+ // 1. Gestion spécifique du Role
141
+ if (entityNameLower === "user" && fieldName === "role") {
142
+ return ` @Prop({ type: String, enum: Role, default: Role.USER })\n role: Role;`;
143
+ }
144
+
145
+ // 2. Mapping des types CLI -> TypeScript/Mongoose
146
+ let tsType = "string";
147
+ let propOptions = "required: true";
148
+
149
+ switch (rawType) {
150
+ case "text":
151
+ case "uuid":
152
+ case "string":
153
+ tsType = "string";
154
+ break;
155
+ case "int":
156
+ case "number":
157
+ case "float":
158
+ case "decimal":
159
+ tsType = "number";
160
+ break;
161
+ case "boolean":
162
+ tsType = "boolean";
163
+ break;
164
+ case "date":
165
+ tsType = "Date";
166
+ break;
167
+ case "json":
168
+ tsType = "Record<string, any>";
169
+ propOptions = "type: Object, required: true";
170
+ break;
171
+ default:
172
+ tsType = "any";
173
+ }
174
+
175
+ return ` @Prop({ ${propOptions} })\n ${fieldName}: ${tsType};`;
176
+ });
177
+
178
+ const allFields = [...new Set([...directFields, ...dynamicRelations])].join(
179
+ "\n\n",
180
+ );
181
+
182
+ return `import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
183
+ import * as mongoose from 'mongoose';
184
+ import { Document } from 'mongoose';
185
+ ${extraImports}
186
+ export type ${entityName}Document = ${entityName} & Document;
187
+
188
+ @Schema({ timestamps: true })
189
+ export class ${entityName} {
190
+ ${allFields}
191
+ }
192
+
193
+ export const ${entityName}Schema = SchemaFactory.createForClass(${entityName});`.trim();
194
+ }
195
+
196
+ module.exports = { generateMongooseSchemaFileContent };
@@ -0,0 +1,334 @@
1
+ /**
2
+ * utils/generators/infrastructure/repositoryGenerator.js
3
+ *
4
+ * Générateur du Repository (Full/Clean Architecture uniquement).
5
+ * Extrait de utils.js — Phase 1B.
6
+ *
7
+ * ATTENTION : Ce générateur est UNIQUEMENT pour le mode Full (Clean Architecture).
8
+ * Il implémente l'interface I${Entity}Repository et utilise le Mapper.
9
+ *
10
+ * Le mode Light a son propre générateur `generateLightRepository` dans
11
+ * setupLightArchitecture.js avec une implémentation entièrement différente :
12
+ * - Pas de Mapper, méthode toEntity() locale
13
+ * - Imports depuis '../dtos/' et '../entities/' (chemins relatifs simples)
14
+ * - Pas d'interface IEntityRepository
15
+ *
16
+ * Ne jamais fusionner les deux.
17
+ */
18
+
19
+ const { capitalize } = require("../../helpers");
20
+ const { createFile } = require("../../file-system");
21
+
22
+ /**
23
+ * Génère et écrit le fichier repository pour une entité (Full Architecture).
24
+ *
25
+ * @param {string} entityName - Le nom de l'entité (ex: "post", "user")
26
+ * @param {"prisma"|"typeorm"|"mongoose"|"sequelize"} orm - L'ORM cible
27
+ * @returns {Promise<void>}
28
+ */
29
+ async function generateRepository(entityName, orm) {
30
+ const entityNameCapitalized = capitalize(entityName);
31
+ const entityNameLower = entityName.toLowerCase();
32
+ const entityPath = `src/${entityNameLower}`;
33
+
34
+ // Méthode supplémentaire findByEmail pour l'entité User
35
+ const isUser = entityNameLower === "user";
36
+ const getExtraMethods = (ormType) => {
37
+ if (!isUser) return "";
38
+
39
+ switch (ormType) {
40
+ case "typeorm":
41
+ return `
42
+ async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
43
+ const record = await this.repository.findOne({ where: { email } as any });
44
+ return record ? this.mapper.toDomain(record) : null;
45
+ }`;
46
+ case "prisma":
47
+ return `
48
+ async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
49
+ const record = await this.prisma.${entityNameLower}.findFirst({ where: { email } });
50
+ return record ? this.mapper.toDomain(record) : null;
51
+ }`;
52
+ case "mongoose":
53
+ return `
54
+ async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
55
+ const record = await this.model.findOne({ email }).exec();
56
+ return record ? this.mapper.toDomain(record) : null;
57
+ }`;
58
+ case "sequelize":
59
+ return `
60
+ async findByEmail(email: string): Promise<${entityNameCapitalized}Entity | null> {
61
+ const record = await this.model.findOne({ where: { email } });
62
+ return record ? this.mapper.toDomain(record) : null;
63
+ }`;
64
+ default:
65
+ return "";
66
+ }
67
+ };
68
+
69
+ const extraMethods = getExtraMethods(orm);
70
+
71
+ switch (orm) {
72
+ case "typeorm":
73
+ await createFile({
74
+ path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
75
+ contente: `/**
76
+ * PostRepository handles data persistence
77
+ * for the Post entity.
78
+ *
79
+ * This layer abstracts the database engine (Prisma/TypeORM)
80
+ * and provides a clean interface for data operations.
81
+ */
82
+
83
+ import { Injectable, NotFoundException } from '@nestjs/common';
84
+ import { Repository } from 'typeorm';
85
+ import { InjectRepository } from '@nestjs/typeorm';
86
+ import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
87
+ import { ${entityNameCapitalized} } from 'src/entities/${entityNameCapitalized}.entity';
88
+ import type { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
89
+ import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
90
+ import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
91
+
92
+ @Injectable()
93
+ export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
94
+ constructor(
95
+ @InjectRepository(${entityNameCapitalized})
96
+ private readonly repository: Repository<${entityNameCapitalized}>,
97
+ private readonly mapper: ${entityNameCapitalized}Mapper,
98
+ ) {}
99
+
100
+ // create
101
+ async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
102
+ const toPersist = this.mapper.toPersistence(data);
103
+ const created = await this.repository.save(toPersist);
104
+ return this.mapper.toDomain(created);
105
+ }
106
+
107
+ // find by id
108
+ async findById(id: string): Promise<${entityNameCapitalized}Entity | null> {
109
+ const record = await this.repository.findOne({
110
+ where: { id },
111
+ });
112
+
113
+ if (!record) return null;
114
+
115
+ return this.mapper.toDomain(record);
116
+ }
117
+
118
+ ${extraMethods}
119
+
120
+ // update
121
+ async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
122
+ const toUpdate = this.mapper.toUpdatePersistence(data);
123
+ const updated = await this.repository.save({ ...toUpdate, id });
124
+ return this.mapper.toDomain(updated);
125
+ }
126
+
127
+ // find all
128
+ async findAll(): Promise<${entityNameCapitalized}Entity[]> {
129
+ const records = await this.repository.find();
130
+ return records.map(record => this.mapper.toDomain(record));
131
+ }
132
+
133
+ // delete
134
+ async delete(id: string): Promise<void> {
135
+ await this.repository.delete(id);
136
+ }
137
+ }
138
+ `,
139
+ });
140
+ break;
141
+
142
+ case "prisma":
143
+ await createFile({
144
+ path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
145
+ contente: `import { Injectable, NotFoundException } from '@nestjs/common';
146
+ import { PrismaService } from 'src/prisma/prisma.service';
147
+ import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
148
+ import { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
149
+ import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
150
+ import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
151
+
152
+ @Injectable()
153
+ export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
154
+ constructor(
155
+ private readonly prisma: PrismaService,
156
+ private readonly mapper: ${entityNameCapitalized}Mapper,
157
+ ) {}
158
+
159
+ // create
160
+ async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
161
+ const toPersist = this.mapper.toPersistence(data);
162
+ const created = await this.prisma.${entityNameLower}.create({ data: toPersist });
163
+ return this.mapper.toDomain(created);
164
+ }
165
+
166
+ // find by id
167
+ async findById(id: string): Promise<${entityNameCapitalized}Entity | null> {
168
+ const record = await this.prisma.${entityNameLower}.findUnique({
169
+ where: { id },
170
+ });
171
+
172
+ if (!record) return null;
173
+
174
+ return this.mapper.toDomain(record);
175
+ }
176
+
177
+ ${extraMethods}
178
+
179
+ // update
180
+ async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
181
+ const toUpdate = this.mapper.toUpdatePersistence(data);
182
+ const updated = await this.prisma.${entityNameLower}.update({
183
+ where: { id },
184
+ data: toUpdate,
185
+ });
186
+
187
+ return this.mapper.toDomain(updated);
188
+ }
189
+
190
+ // find all
191
+ async findAll(): Promise<${entityNameCapitalized}Entity[]> {
192
+ const records = await this.prisma.${entityNameLower}.findMany();
193
+ return records.map(record => this.mapper.toDomain(record));
194
+ }
195
+
196
+ // delete
197
+ async delete(id: string): Promise<void> {
198
+ await this.prisma.${entityNameLower}.delete({
199
+ where: { id },
200
+ });
201
+ }
202
+ }
203
+ `,
204
+ });
205
+ break;
206
+
207
+ case "mongoose":
208
+ await createFile({
209
+ path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
210
+ contente: `import { Injectable, NotFoundException } from '@nestjs/common';
211
+ import { InjectModel } from '@nestjs/mongoose';
212
+ import { Model } from 'mongoose';
213
+ import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
214
+ import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
215
+ import { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
216
+ import { ${entityNameCapitalized} } from '${entityPath}/infrastructure/persistence/mongoose/${entityNameLower}.schema';
217
+ import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
218
+
219
+ @Injectable()
220
+ export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
221
+ constructor(
222
+ @InjectModel(${entityNameCapitalized}.name)
223
+ private readonly model: Model<${entityNameCapitalized}>,
224
+ private readonly mapper: ${entityNameCapitalized}Mapper,
225
+ ) {}
226
+
227
+ // create
228
+ async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
229
+ const toPersist = this.mapper.toPersistence(data);
230
+ const created = await this.model.create(toPersist);
231
+ return this.mapper.toDomain(created);
232
+ }
233
+
234
+ // find by id
235
+ async findById(id: string): Promise<${entityNameCapitalized}Entity | null> {
236
+ const record = await this.model.findById(id).exec();
237
+
238
+ if (!record) return null;
239
+
240
+ return this.mapper.toDomain(record);
241
+ }
242
+
243
+ ${extraMethods}
244
+
245
+ // update
246
+ async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
247
+ const toUpdate = this.mapper.toUpdatePersistence(data);
248
+ const updated = await this.model.findByIdAndUpdate(id, toUpdate, { new: true }).exec();
249
+ return this.mapper.toDomain(updated);
250
+ }
251
+
252
+ // find all
253
+ async findAll(): Promise<${entityNameCapitalized}Entity[]> {
254
+ const records = await this.model.find().exec();
255
+ return records.map(record => this.mapper.toDomain(record));
256
+ }
257
+
258
+ // delete
259
+ async delete(id: string): Promise<void> {
260
+ await this.model.findByIdAndDelete(id).exec();
261
+ }
262
+ }
263
+ `,
264
+ });
265
+ break;
266
+
267
+ case "sequelize":
268
+ await createFile({
269
+ path: `${entityPath}/infrastructure/repositories/${entityNameLower}.repository.ts`,
270
+ contente: `import { Injectable, NotFoundException } from '@nestjs/common';
271
+ import { InjectModel } from '@nestjs/sequelize';
272
+ import { Model } from 'sequelize-typescript';
273
+ import { Create${entityNameCapitalized}Dto, Update${entityNameCapitalized}Dto } from '${entityPath}/application/dtos/${entityNameLower}.dto';
274
+ import { I${entityNameCapitalized}Repository } from '${entityPath}/domain/interfaces/${entityNameLower}.repository.interface';
275
+ import { ${entityNameCapitalized}Entity } from '${entityPath}/domain/entities/${entityNameLower}.entity';
276
+ import { ${entityNameCapitalized}Mapper } from '${entityPath}/infrastructure/mappers/${entityNameLower}.mapper';
277
+
278
+ @Injectable()
279
+ export class ${entityNameCapitalized}Repository implements I${entityNameCapitalized}Repository {
280
+ constructor(
281
+ @InjectModel(${entityNameCapitalized}Entity)
282
+ private readonly model: Model<${entityNameCapitalized}Entity>,
283
+ private readonly mapper: ${entityNameCapitalized}Mapper,
284
+ ) {}
285
+
286
+ // create
287
+ async create(data: Create${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
288
+ const toPersist = this.mapper.toPersistence(data);
289
+ const created = await this.model.create(toPersist);
290
+ return this.mapper.toDomain(created);
291
+ }
292
+
293
+ // find by id
294
+ async findById(id: string): Promise<${entityNameCapitalized}Entity> {
295
+ const record = await this.model.findByPk(id);
296
+
297
+ if (!record) {
298
+ throw new NotFoundException(\`${entityNameCapitalized}Entity with id \${id} not found\`);
299
+ }
300
+
301
+ return this.mapper.toDomain(record);
302
+ }
303
+
304
+ ${extraMethods}
305
+
306
+ // update
307
+ async update(id: string, data: Update${entityNameCapitalized}Dto): Promise<${entityNameCapitalized}Entity> {
308
+ const toUpdate = this.mapper.toUpdatePersistence(data);
309
+ const updated = await this.model.update(toUpdate, { where: { id } });
310
+ return this.mapper.toDomain(updated);
311
+ }
312
+
313
+ // find all
314
+ async findAll(): Promise<${entityNameCapitalized}Entity[]> {
315
+ const records = await this.model.find();
316
+ return records.map(record => this.mapper.toDomain(record));
317
+ }
318
+
319
+ // delete
320
+ async delete(id: string): Promise<void> {
321
+ await this.model.destroy({ where: { id } });
322
+ }
323
+ }
324
+ `,
325
+ });
326
+ break;
327
+
328
+ default:
329
+ console.error("Unsupported ORM: " + orm);
330
+ break;
331
+ }
332
+ }
333
+
334
+ module.exports = { generateRepository };
@@ -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;