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
@@ -1,630 +1,630 @@
1
- // setupTypeORM.js
2
- // const { execSync } = require("child_process");
3
- // const path = require("path");
4
- // const { runCommand } = require("../shell");
5
-
6
- const {
7
- updateFile,
8
- capitalize,
9
- createFile,
10
- createDirectory,
11
- decapitalize,
12
- } = require("../../userInput");
13
- const { logInfo } = require("../../loggers/logInfo");
14
- const { logSuccess } = require("../../loggers/logSuccess");
15
- const path = require("path");
16
- const { runCommand } = require("../../shell");
17
- const { updatePackageJson } = require("../../file-utils/packageJsonUtils");
18
- const { info } = require("../../colors");
19
-
20
- async function setupTypeORM(inputs) {
21
- logInfo("📦 Installing TypeORM and PostgreSQL dependencies...");
22
-
23
- const mode = inputs.mode;
24
- await runCommand(
25
- "npm install @nestjs/typeorm typeorm pg reflect-metadata",
26
- "TypeORM and PostgreSQL dependencies installed successfully"
27
- ); // Updating app.module.ts with TypeORM
28
-
29
- const appModulePath = "src/app.module.ts";
30
- const typeOrmImport = `import { TypeOrmModule } from '@nestjs/typeorm';`;
31
- const typeOrmConfig = `
32
- TypeOrmModule.forRoot({
33
- type: 'postgres',
34
- host: process.env.POSTGRES_HOST,
35
- port: process.env.POSTGRES_PORT
36
- ? parseInt(process.env.POSTGRES_PORT, 10)
37
- : 5432,
38
- username: process.env.POSTGRES_USER,
39
- password: process.env.POSTGRES_PASSWORD,
40
- database: process.env.POSTGRES_DB,
41
- autoLoadEntities: true, // Reinstated for automatic loading of entities registered in .forFeature
42
- synchronize: true, // Only for dev use!
43
- // dropSchema: true, //// ⚠️ wipes the entire schema on every restart! Only for dev use!
44
- }),`;
45
-
46
- logInfo("⚙️ Updating app.module.ts with TypeORM..."); // 1. Updating TypeOrmModule.forRoot()
47
- await updateFile({
48
- path: appModulePath,
49
- pattern: `ConfigModule.forRoot({
50
- isGlobal: true, // Make ConfigModule globally accessible
51
- envFilePath: '.env', // Load environment variables
52
- }),`,
53
- replacement: ` ConfigModule.forRoot({
54
- isGlobal: true, // Make ConfigModule globally accessible
55
- envFilePath: '.env', // Load environment variables
56
- }),
57
- ${typeOrmConfig}`,
58
- }); // 2. Adding TypeOrmModule import
59
-
60
- await updateFile({
61
- path: appModulePath,
62
- pattern: "import { Module } from '@nestjs/common';",
63
- replacement: `import { Module } from '@nestjs/common';
64
- ${typeOrmImport}`,
65
- }); // Entity Generation
66
-
67
- logInfo("📁 Generating entities for TypeORM...");
68
-
69
- await createDirectory("src/entities");
70
-
71
- for (const entity of inputs.entitiesData.entities) {
72
- const entityName = capitalize(entity.name);
73
- const entityNameLower = decapitalize(entity.name);
74
- let fieldsContent = "";
75
- let relationsContent = "";
76
- let imports = [
77
- "Entity",
78
- "Column",
79
- "PrimaryGeneratedColumn",
80
- "CreateDateColumn",
81
- "UpdateDateColumn",
82
- ];
83
- let extraImports = ""; // --- Basic Data Field Generation Logic --- // Filtering to avoid duplication of relationship fields
84
-
85
- const relationFields = inputs.entitiesData.relations
86
- .reduce((acc, rel) => {
87
- // Foreign key and relation name (Many side)
88
- if (
89
- rel.from.toLowerCase() === entityNameLower &&
90
- ["n-1", "1-1"].includes(rel.type)
91
- ) {
92
- acc.push(`${rel.to}Id`, rel.to);
93
- }
94
- if (
95
- rel.to.toLowerCase() === entityNameLower &&
96
- ["1-n", "1-1"].includes(rel.type)
97
- ) {
98
- acc.push(`${rel.from}Id`, rel.from);
99
- } // List name (One side)
100
- if (rel.from.toLowerCase() === entityNameLower && rel.type === "1-n") {
101
- acc.push(`${rel.to}s`);
102
- }
103
- if (rel.to.toLowerCase() === entityNameLower && rel.type === "n-1") {
104
- acc.push(`${rel.from}s`);
105
- } // For 1-1
106
- if (rel.type === "1-1") {
107
- // We manage both sides, ensure only one has the foreign key
108
- // We just exclude model names that will become relations
109
- acc.push(rel.from, rel.to);
110
- }
111
- return acc;
112
- }, [])
113
- .map((f) => f.toLowerCase());
114
-
115
- const isUserEntity = entity.name.toLowerCase() === "user";
116
- const hasRoleField = entity.fields.some((f) => f.name === "role");
117
-
118
- // Utilisation d'un Set pour éviter les doublons d'imports
119
- const enumImports = new Set();
120
-
121
- if (isUserEntity && hasRoleField) {
122
- const rolePath =
123
- mode === "full"
124
- ? "src/user/domain/enums/role.enum"
125
- : "src/common/enums/role.enum";
126
- enumImports.add(`import { Role } from '${rolePath}';`);
127
- }
128
-
129
- // Liste des colonnes déjà présentes dans le template de classe
130
- const RESERVED_FIELDS = ["id", "createdat", "updatedat"];
131
-
132
- for (const field of entity.fields) {
133
- const fieldNameLower = field.name.toLowerCase();
134
-
135
- // 1. Skip si le champ est géré par une relation
136
- if (relationFields.includes(fieldNameLower)) continue;
137
-
138
- // 2. Skip si le champ est déjà présent par défaut (id, createdAt, updatedAt)
139
- if (RESERVED_FIELDS.includes(fieldNameLower)) {
140
- console.log(
141
- `${info("[INFO]")} Skipping default field: ${
142
- field.name
143
- } for entity ${entityName}`
144
- );
145
- continue;
146
- }
147
-
148
- const mapping = mapTypeToTypeORM(field.type);
149
- const columnOptions = [`type: '${mapping.type}'`];
150
-
151
- // 1. Gestion des Options spécifiques (Enum, JSON, Array)
152
- if (mapping.type === "enum") {
153
- columnOptions.push(`enum: ${mapping.tsType}`);
154
- }
155
-
156
- // ✅ Default role = USER pour User.role
157
- if (
158
- isUserEntity &&
159
- field.name.toLowerCase() === "role" &&
160
- mapping.tsType === "Role"
161
- ) {
162
- columnOptions.push("default: Role.USER");
163
- }
164
-
165
- if (field.optional) {
166
- columnOptions.push("nullable: true");
167
- }
168
-
169
- if (field.name.toLowerCase() === "email") {
170
- columnOptions.push("unique: true");
171
- }
172
-
173
- // 2. Gestion des Imports d'Enums Custom
174
- const isEnum = mapping.type === "enum";
175
- const isNotRole = mapping.tsType.toLowerCase() !== "role";
176
-
177
- if (isEnum && isNotRole) {
178
- // On stocke l'import dans le Set
179
- enumImports.add(
180
- `import { ${
181
- mapping.tsType
182
- } } from '../shared/enums/${mapping.tsType.toLowerCase()}.enum';`
183
- );
184
- }
185
-
186
- // 3. Construction du champ
187
- fieldsContent += `
188
- @Column({ ${columnOptions.join(", ")} })
189
- ${field.name}${field.optional ? "?" : ""}: ${mapping.tsType};
190
- `;
191
- }
192
-
193
- // On ajoute tous les imports uniques au début du fichier
194
- extraImports += Array.from(enumImports).join("\n") + "\n";
195
-
196
- // --- TypeORM Relation Generation Logic ---
197
- for (const relation of inputs.entitiesData.relations) {
198
- const relFrom = relation.from;
199
- const relTo = relation.to;
200
- const relType = relation.type;
201
-
202
- if (relType === "1-n") {
203
- // If the current entity is on the 'One' side (relFrom), it holds the @OneToMany
204
- if (relFrom.toLowerCase() === entityNameLower) {
205
- const targetEntity = capitalize(relTo);
206
- relationsContent += `
207
- @OneToMany(() => ${targetEntity}, (${decapitalize(
208
- relTo
209
- )}) => ${decapitalize(relTo)}.${relFrom.toLowerCase()})
210
- ${relTo.toLowerCase()}s: ${targetEntity}[];
211
- `;
212
- imports.push("OneToMany");
213
- extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
214
- } // If the current entity is on the 'Many' side (relTo), it holds the @ManyToOne
215
- else if (relTo.toLowerCase() === entityNameLower) {
216
- const targetEntity = capitalize(relFrom);
217
- const fkName = `${relFrom.toLowerCase()}Id`;
218
- relationsContent += `
219
- @Column({ type: 'uuid' }) // Foreign Key
220
- ${fkName}: string;
221
-
222
- @ManyToOne(() => ${targetEntity}, (${decapitalize(
223
- relFrom
224
- )}) => ${decapitalize(relFrom)}.${relTo.toLowerCase()}s)
225
- ${relFrom.toLowerCase()}: ${targetEntity};
226
- `;
227
- imports.push("ManyToOne");
228
- extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
229
- }
230
- } else if (relType === "n-1") {
231
- // n-1 is the inverse: relFrom is the 'Many', relTo is the 'One'
232
-
233
- // If the current entity is on the 'Many' side (relFrom), it holds the @ManyToOne
234
- if (relFrom.toLowerCase() === entityNameLower) {
235
- const targetEntity = capitalize(relTo);
236
- const fkName = `${relTo.toLowerCase()}Id`;
237
- relationsContent += `
238
- @Column({ type: 'uuid' }) // Foreign Key
239
- ${fkName}: string;
240
-
241
- @ManyToOne(() => ${targetEntity}, (${decapitalize(
242
- relTo
243
- )}) => ${decapitalize(relTo)}.${relFrom.toLowerCase()}s)
244
- ${relTo.toLowerCase()}: ${targetEntity};
245
- `;
246
- imports.push("ManyToOne");
247
- extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
248
- } // If the current entity is on the 'One' side (relTo), it holds the @OneToMany
249
- else if (relTo.toLowerCase() === entityNameLower) {
250
- const targetEntity = capitalize(relFrom);
251
- relationsContent += `
252
- @OneToMany(() => ${targetEntity}, (${decapitalize(
253
- relFrom
254
- )}) => ${decapitalize(relFrom)}.${relTo.toLowerCase()})
255
- ${relFrom.toLowerCase()}s: ${targetEntity}[];
256
- `;
257
- imports.push("OneToMany");
258
- extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
259
- }
260
- }
261
- if (relType === "1-1") {
262
- // If the current entity is on the 'from' side, it holds the @OneToOne and the foreign key
263
- if (relFrom.toLowerCase() === entityNameLower) {
264
- const targetEntity = capitalize(relTo);
265
- const fkName = `${relTo.toLowerCase()}Id`;
266
-
267
- relationsContent += `
268
- @Column({ type: 'uuid', unique: true }) // Unique foreign key for 1-1
269
- ${fkName}: string;
270
-
271
- @OneToOne(() => ${targetEntity}, (${decapitalize(relTo)}) => ${decapitalize(
272
- relTo
273
- )}.${relFrom.toLowerCase()})
274
- @JoinColumn({ name: '${fkName}' }) // Requires JoinColumn for the foreign key
275
- ${relTo.toLowerCase()}: ${targetEntity};
276
- `;
277
- imports.push("OneToOne", "JoinColumn");
278
- extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
279
- } // If the current entity is on the 'to' side, it holds the inverse @OneToOne relationship (mapping)
280
- else if (relTo.toLowerCase() === entityNameLower) {
281
- const targetEntity = capitalize(relFrom);
282
- relationsContent += `
283
- @OneToOne(() => ${targetEntity}, (${decapitalize(
284
- relFrom
285
- )}) => ${decapitalize(relFrom)}.${relTo.toLowerCase()})
286
- ${relFrom.toLowerCase()}: ${targetEntity};
287
- `;
288
- imports.push("OneToOne");
289
- extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
290
- }
291
- } else if (relType === "n-n") {
292
- const targetEntity = capitalize(
293
- relFrom.toLowerCase() === entityNameLower ? relTo : relFrom
294
- );
295
- const currentSide =
296
- relFrom.toLowerCase() === entityNameLower ? relTo : relFrom;
297
- const otherSide =
298
- relFrom.toLowerCase() === entityNameLower ? relFrom : relTo; // TypeORM requires JoinTable on one side (we choose the 'from' side for simplicity)
299
-
300
- if (relFrom.toLowerCase() === entityNameLower) {
301
- relationsContent += `
302
- @ManyToMany(() => ${targetEntity}, (${decapitalize(
303
- currentSide
304
- )}) => ${decapitalize(currentSide)}.${otherSide.toLowerCase()}s)
305
- @JoinTable() // Adding JoinTable to create the junction table
306
- ${currentSide.toLowerCase()}s: ${targetEntity}[];
307
- `;
308
- imports.push("ManyToMany", "JoinTable");
309
- } else {
310
- relationsContent += `
311
- @ManyToMany(() => ${targetEntity}, (${decapitalize(
312
- currentSide
313
- )}) => ${decapitalize(currentSide)}.${otherSide.toLowerCase()}s)
314
- ${currentSide.toLowerCase()}s: ${targetEntity}[];
315
- `;
316
- imports.push("ManyToMany");
317
- }
318
- extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
319
- }
320
- }
321
-
322
- // --- Final File Generation ---
323
- const uniqueImports = Array.from(new Set(imports)).join(", ");
324
-
325
- const content = `${extraImports}
326
- import { ${uniqueImports} } from 'typeorm';
327
-
328
- @Entity('${entityNameLower}')
329
- export class ${entityName} {
330
- @PrimaryGeneratedColumn('uuid')
331
- id: string;
332
-
333
- @CreateDateColumn()
334
- createdAt: Date;
335
-
336
- @UpdateDateColumn()
337
- updatedAt: Date;
338
-
339
- ${fieldsContent}
340
- ${relationsContent}
341
- }`;
342
-
343
- await createFile({
344
- path: `src/entities/${entityName}.entity.ts`,
345
- contente: content,
346
- });
347
- }
348
-
349
- if (inputs.isDemo) {
350
- await setupTypeOrmSeeding(inputs);
351
- }
352
-
353
- logSuccess("✅ TypeORM configuration complete. Ready to code!");
354
- }
355
-
356
- /**
357
- * Maps entity types to TypeORM column types (PostgreSQL).
358
- * @param type The input type (string, number, boolean, Date, enum, array, etc.)
359
- * @returns { object: type, tsType: string } The mapping information
360
- */
361
- function mapTypeToTypeORM(type) {
362
- // Special case for arrays (e.g., 'string[]')
363
- if (type.endsWith("[]")) {
364
- const innerType = type.slice(0, -2);
365
- const innerMapping = mapTypeToTypeORM(innerType); // Recursive
366
- return {
367
- type: innerMapping.type,
368
- tsType: `${innerMapping.tsType}[]`,
369
- options: "array: true", // TypeORM option for arrays
370
- };
371
- }
372
-
373
- const typeLower = type.toLowerCase();
374
-
375
- switch (typeLower) {
376
- case "string":
377
- return { type: "varchar", tsType: "string" };
378
- case "text":
379
- return { type: "text", tsType: "string" };
380
- case "number":
381
- case "float":
382
- return { type: "float", tsType: "number" };
383
- case "int":
384
- return { type: "integer", tsType: "number" };
385
- case "decimal":
386
- return { type: "decimal", tsType: "number" };
387
- case "uuid":
388
- return { type: "uuid", tsType: "string" };
389
- case "date":
390
- return { type: "timestamp", tsType: "Date" };
391
- case "boolean":
392
- return { type: "boolean", tsType: "boolean" };
393
- case "json":
394
- case "object":
395
- return { type: "jsonb", tsType: "any" }; // JSONB type is flexible
396
-
397
- default: // Case of an Enum type or a named object (e.g., 'Address')
398
- // By default, we assume it's an enum to be stored in the database (varchar)
399
- if (type.charAt(0) === type.charAt(0).toUpperCase()) {
400
- // For Enums, TypeORM needs the 'enum' and 'enumName' option
401
- return {
402
- type: "enum",
403
- tsType: type, // The name of the Enum/object in TypeScript
404
- options: `enum: ${type}, enumName: '${type.toLowerCase()}_enum'`, // Example of enum options
405
- };
406
- }
407
- return { type: "varchar", tsType: "string" };
408
- }
409
- }
410
-
411
- async function setupTypeOrmSeeding(inputs) {
412
- logInfo("⚙️ Configuring seeding for TypeORM...");
413
-
414
- // --- Dependencies ---
415
- const typeOrmDevDeps = [
416
- "ts-node",
417
- "@types/node",
418
- "@types/bcrypt",
419
- "typeorm-extension",
420
- ];
421
-
422
- await runCommand(
423
- `${inputs.packageManager} add -D ${typeOrmDevDeps.join(" ")}`,
424
- "❌ Failed to install TypeORM seeding dependencies"
425
- );
426
-
427
- await runCommand(
428
- `${inputs.packageManager} install bcrypt`,
429
- "❌ Failed to install bcrypt"
430
- );
431
-
432
- // --- Scripts in package.json ---
433
- const typeOrmScripts = {
434
- "typeorm:migrate:run":
435
- "typeorm-ts-node-commonjs migration:run -d ./src/database/typeorm.config.ts",
436
- "typeorm:seed":
437
- "typeorm-extension seed:run -d src/database/typeorm.config.ts",
438
- seed: "npm run typeorm:seed",
439
- };
440
-
441
- await updatePackageJson(inputs, typeOrmScripts);
442
-
443
- // --- Creating structure and Seeder ---
444
- await createDirectory("src/database/seeders");
445
-
446
- const userSeederContent = generateTypeOrmSeederContent();
447
- await createFile({
448
- path: `src/database/seeders/DemoSeeder.ts`,
449
- contente: userSeederContent,
450
- });
451
-
452
- await createFile({
453
- path: `src/database/typeorm.config.ts`,
454
- contente: `import * as dotenv from 'dotenv';
455
- dotenv.config();
456
- import { DataSource } from 'typeorm';
457
- import { DataSourceOptions } from 'typeorm';
458
- import { SeederOptions } from 'typeorm-extension';
459
- import { DemoSeeder } from './seeders/DemoSeeder';
460
- import { User } from 'src/entities/User.entity';
461
- import { Post } from 'src/entities/Post.entity';
462
- import { Comment } from 'src/entities/Comment.entity';
463
- import { Session } from 'src/entities/Session.entity';
464
-
465
- const config: DataSourceOptions & SeederOptions = {
466
- type: 'postgres',
467
- host: process.env.POSTGRES_HOST,
468
- port: Number(process.env.POSTGRES_PORT ?? 5432),
469
- username: process.env.POSTGRES_USER,
470
- password: process.env.POSTGRES_PASSWORD,
471
- database: process.env.POSTGRES_DB,
472
-
473
- entities: [User, Post, Comment, Session],
474
-
475
- synchronize: process.env.NODE_ENV !== 'production',
476
- logging: process.env.NODE_ENV === 'development',
477
-
478
- seeds: [DemoSeeder],
479
- };
480
-
481
- export const AppDataSource = new DataSource(config);
482
- `,
483
- });
484
-
485
- logSuccess("✅ TypeORM seeding configured.");
486
- }
487
-
488
- function generateTypeOrmSeederContent() {
489
- return `
490
- import { DataSource } from 'typeorm';
491
- import { User } from 'src/entities/User.entity';
492
- import { Post } from 'src/entities/Post.entity';
493
- import { Comment } from 'src/entities/Comment.entity';
494
- import * as bcrypt from 'bcrypt';
495
-
496
- export class DemoSeeder {
497
- constructor() {}
498
-
499
- async run(dataSource: DataSource) {
500
- console.log('🌱 Starting TypeORM seeding...');
501
-
502
- const userRepository = dataSource.getRepository(User);
503
- const postRepository = dataSource.getRepository(Post);
504
- const commentRepository = dataSource.getRepository(Comment);
505
-
506
- // --- 1. ADMIN ---
507
- const salt = await bcrypt.genSalt(10);
508
- const hashedPassword = await bcrypt.hash('password123', salt);
509
-
510
- const admin = userRepository.create({
511
- email: 'admin@nestcraft.com',
512
- password: hashedPassword,
513
- username: 'NestCraftAdmin',
514
- isActive: true,
515
- });
516
-
517
- const exists = await userRepository.findOneBy({
518
- email: admin.email,
519
- });
520
-
521
- if (!exists) {
522
- await userRepository.save(admin);
523
- console.log('👑 Admin user created');
524
- } else {
525
- admin.id = exists.id;
526
- console.log('👑 Admin user realy exists');
527
- }
528
-
529
- // --- 2. DEMO USERS ---
530
- const demoUsersData = [
531
- { email: 'emma.jones@demo.com', password: hashedPassword, username: 'EmmaJones', isActive: true },
532
- { email: 'lucas.martin@demo.com', password: hashedPassword, username: 'LucasMartin', isActive: true },
533
- { email: 'sophia.bernard@demo.com', password: hashedPassword, username: 'SophiaBernard', isActive: true },
534
- { email: 'alexandre.dubois@demo.com', password: hashedPassword, username: 'AlexandreDubois', isActive: true },
535
- { email: 'chloe.moreau@demo.com', password: hashedPassword, username: 'ChloeMoreau', isActive: true },
536
- ];
537
-
538
- const users: User[] = [];
539
-
540
- for (const userData of demoUsersData) {
541
- let user = await userRepository.findOneBy({ email: userData.email });
542
-
543
- if (!user) {
544
- user = userRepository.create(userData);
545
- user = await userRepository.save(user);
546
- }
547
-
548
- users.push(user);
549
- }
550
-
551
- console.log(\`👥 \${users.length} demo users created.\`);
552
-
553
- const allUsers = [admin, ...users];
554
-
555
- // --- 3. DEMO POSTS ---
556
- const postsData = [
557
- {
558
- title: 'The Basics of NestJS for Modern Developers',
559
- content: 'Discover how to build a robust and maintainable API with NestJS...',
560
- published: true,
561
- userId: allUsers[1].id,
562
- },
563
- {
564
- title: 'How to Secure Your API with JWT',
565
- content: 'JWT authentication is a standard for securing APIs...',
566
- published: true,
567
- userId: allUsers[2].id,
568
- },
569
- {
570
- title: 'Optimizing Node.js API Performance',
571
- content: 'Discover best practices for improving performance...',
572
- published: true,
573
- userId: allUsers[3].id,
574
- },
575
- {
576
- title: 'Introduction to Prisma ORM',
577
- content: 'Prisma is a modern ORM that simplifies interactions with the database...',
578
- published: true,
579
- userId: allUsers[4].id,
580
- },
581
- {
582
- title: 'Understanding Clean Architecture',
583
- content: 'Clean Architecture helps separate business logic from the rest of the code...',
584
- published: false,
585
- userId: allUsers[0].id,
586
- },
587
- ];
588
-
589
- const posts = await postRepository.save(postsData);
590
- console.log(\`📝 \${posts.length} articles created.\`);
591
-
592
- // --- 4. DEMO COMMENTS ---
593
- const commentsData = [
594
- {
595
- content: 'Excellent article! I was able to apply these tips directly to my NestJS project.',
596
- post: posts[0],
597
- userId: allUsers[2].id,
598
- },
599
- {
600
- content: 'Very clear and well explained, thanks for sharing about Prisma 👏',
601
- post: posts[3],
602
- userId: allUsers[0].id,
603
- },
604
- {
605
- content: "I didn't know about JWT before this article, it's a real revelation.",
606
- post: posts[1],
607
- userId: allUsers[4].id,
608
- },
609
- {
610
- content: 'Clean Architecture always seemed blurry to me, this article finally enlightened me.',
611
- post: posts[4],
612
- userId: allUsers[1].id,
613
- },
614
- {
615
- content: 'Thanks for the content! I would like to see a complete tutorial with NestJS + Prisma.',
616
- post: posts[2],
617
- userId: allUsers[3].id,
618
- },
619
- ];
620
-
621
- const comments = await commentRepository.save(commentsData);
622
- console.log(\`💬 \${comments.length} comments created.\`);
623
-
624
- console.log('✅ TypeORM seeding finished successfully! 🚀');
625
- }
626
- }
627
- `;
628
- }
629
-
630
- module.exports = { setupTypeORM };
1
+ // setupTypeORM.js
2
+ // const { execSync } = require("child_process");
3
+ // const path = require("path");
4
+ // const { runCommand } = require("../shell");
5
+
6
+ const {
7
+ updateFile,
8
+ capitalize,
9
+ createFile,
10
+ createDirectory,
11
+ decapitalize,
12
+ } = require("../../userInput");
13
+ const { logInfo } = require("../../loggers/logInfo");
14
+ const { logSuccess } = require("../../loggers/logSuccess");
15
+ const path = require("path");
16
+ const { runCommand } = require("../../shell");
17
+ const { updatePackageJson } = require("../../file-utils/packageJsonUtils");
18
+ const { info } = require("../../colors");
19
+
20
+ async function setupTypeORM(inputs) {
21
+ logInfo("📦 Installing TypeORM and PostgreSQL dependencies...");
22
+
23
+ const mode = inputs.mode;
24
+ await runCommand(
25
+ "npm install @nestjs/typeorm typeorm pg reflect-metadata",
26
+ "TypeORM and PostgreSQL dependencies installed successfully"
27
+ ); // Updating app.module.ts with TypeORM
28
+
29
+ const appModulePath = "src/app.module.ts";
30
+ const typeOrmImport = `import { TypeOrmModule } from '@nestjs/typeorm';`;
31
+ const typeOrmConfig = `
32
+ TypeOrmModule.forRoot({
33
+ type: 'postgres',
34
+ host: process.env.POSTGRES_HOST,
35
+ port: process.env.POSTGRES_PORT
36
+ ? parseInt(process.env.POSTGRES_PORT, 10)
37
+ : 5432,
38
+ username: process.env.POSTGRES_USER,
39
+ password: process.env.POSTGRES_PASSWORD,
40
+ database: process.env.POSTGRES_DB,
41
+ autoLoadEntities: true, // Reinstated for automatic loading of entities registered in .forFeature
42
+ synchronize: true, // Only for dev use!
43
+ // dropSchema: true, //// ⚠️ wipes the entire schema on every restart! Only for dev use!
44
+ }),`;
45
+
46
+ logInfo("⚙️ Updating app.module.ts with TypeORM..."); // 1. Updating TypeOrmModule.forRoot()
47
+ await updateFile({
48
+ path: appModulePath,
49
+ pattern: `ConfigModule.forRoot({
50
+ isGlobal: true, // Make ConfigModule globally accessible
51
+ envFilePath: '.env', // Load environment variables
52
+ }),`,
53
+ replacement: ` ConfigModule.forRoot({
54
+ isGlobal: true, // Make ConfigModule globally accessible
55
+ envFilePath: '.env', // Load environment variables
56
+ }),
57
+ ${typeOrmConfig}`,
58
+ }); // 2. Adding TypeOrmModule import
59
+
60
+ await updateFile({
61
+ path: appModulePath,
62
+ pattern: "import { Module } from '@nestjs/common';",
63
+ replacement: `import { Module } from '@nestjs/common';
64
+ ${typeOrmImport}`,
65
+ }); // Entity Generation
66
+
67
+ logInfo("📁 Generating entities for TypeORM...");
68
+
69
+ await createDirectory("src/entities");
70
+
71
+ for (const entity of inputs.entitiesData.entities) {
72
+ const entityName = capitalize(entity.name);
73
+ const entityNameLower = decapitalize(entity.name);
74
+ let fieldsContent = "";
75
+ let relationsContent = "";
76
+ let imports = [
77
+ "Entity",
78
+ "Column",
79
+ "PrimaryGeneratedColumn",
80
+ "CreateDateColumn",
81
+ "UpdateDateColumn",
82
+ ];
83
+ let extraImports = ""; // --- Basic Data Field Generation Logic --- // Filtering to avoid duplication of relationship fields
84
+
85
+ const relationFields = inputs.entitiesData.relations
86
+ .reduce((acc, rel) => {
87
+ // Foreign key and relation name (Many side)
88
+ if (
89
+ rel.from.toLowerCase() === entityNameLower &&
90
+ ["n-1", "1-1"].includes(rel.type)
91
+ ) {
92
+ acc.push(`${rel.to}Id`, rel.to);
93
+ }
94
+ if (
95
+ rel.to.toLowerCase() === entityNameLower &&
96
+ ["1-n", "1-1"].includes(rel.type)
97
+ ) {
98
+ acc.push(`${rel.from}Id`, rel.from);
99
+ } // List name (One side)
100
+ if (rel.from.toLowerCase() === entityNameLower && rel.type === "1-n") {
101
+ acc.push(`${rel.to}s`);
102
+ }
103
+ if (rel.to.toLowerCase() === entityNameLower && rel.type === "n-1") {
104
+ acc.push(`${rel.from}s`);
105
+ } // For 1-1
106
+ if (rel.type === "1-1") {
107
+ // We manage both sides, ensure only one has the foreign key
108
+ // We just exclude model names that will become relations
109
+ acc.push(rel.from, rel.to);
110
+ }
111
+ return acc;
112
+ }, [])
113
+ .map((f) => f.toLowerCase());
114
+
115
+ const isUserEntity = entity.name.toLowerCase() === "user";
116
+ const hasRoleField = entity.fields.some((f) => f.name === "role");
117
+
118
+ // Utilisation d'un Set pour éviter les doublons d'imports
119
+ const enumImports = new Set();
120
+
121
+ if (isUserEntity && hasRoleField) {
122
+ const rolePath =
123
+ mode === "full"
124
+ ? "src/user/domain/enums/role.enum"
125
+ : "src/common/enums/role.enum";
126
+ enumImports.add(`import { Role } from '${rolePath}';`);
127
+ }
128
+
129
+ // Liste des colonnes déjà présentes dans le template de classe
130
+ const RESERVED_FIELDS = ["id", "createdat", "updatedat"];
131
+
132
+ for (const field of entity.fields) {
133
+ const fieldNameLower = field.name.toLowerCase();
134
+
135
+ // 1. Skip si le champ est géré par une relation
136
+ if (relationFields.includes(fieldNameLower)) continue;
137
+
138
+ // 2. Skip si le champ est déjà présent par défaut (id, createdAt, updatedAt)
139
+ if (RESERVED_FIELDS.includes(fieldNameLower)) {
140
+ console.log(
141
+ `${info("[INFO]")} Skipping default field: ${
142
+ field.name
143
+ } for entity ${entityName}`
144
+ );
145
+ continue;
146
+ }
147
+
148
+ const mapping = mapTypeToTypeORM(field.type);
149
+ const columnOptions = [`type: '${mapping.type}'`];
150
+
151
+ // 1. Gestion des Options spécifiques (Enum, JSON, Array)
152
+ if (mapping.type === "enum") {
153
+ columnOptions.push(`enum: ${mapping.tsType}`);
154
+ }
155
+
156
+ // ✅ Default role = USER pour User.role
157
+ if (
158
+ isUserEntity &&
159
+ field.name.toLowerCase() === "role" &&
160
+ mapping.tsType === "Role"
161
+ ) {
162
+ columnOptions.push("default: Role.USER");
163
+ }
164
+
165
+ if (field.optional) {
166
+ columnOptions.push("nullable: true");
167
+ }
168
+
169
+ if (field.name.toLowerCase() === "email") {
170
+ columnOptions.push("unique: true");
171
+ }
172
+
173
+ // 2. Gestion des Imports d'Enums Custom
174
+ const isEnum = mapping.type === "enum";
175
+ const isNotRole = mapping.tsType.toLowerCase() !== "role";
176
+
177
+ if (isEnum && isNotRole) {
178
+ // On stocke l'import dans le Set
179
+ enumImports.add(
180
+ `import { ${
181
+ mapping.tsType
182
+ } } from '../shared/enums/${mapping.tsType.toLowerCase()}.enum';`
183
+ );
184
+ }
185
+
186
+ // 3. Construction du champ
187
+ fieldsContent += `
188
+ @Column({ ${columnOptions.join(", ")} })
189
+ ${field.name}${field.optional ? "?" : ""}: ${mapping.tsType};
190
+ `;
191
+ }
192
+
193
+ // On ajoute tous les imports uniques au début du fichier
194
+ extraImports += Array.from(enumImports).join("\n") + "\n";
195
+
196
+ // --- TypeORM Relation Generation Logic ---
197
+ for (const relation of inputs.entitiesData.relations) {
198
+ const relFrom = relation.from;
199
+ const relTo = relation.to;
200
+ const relType = relation.type;
201
+
202
+ if (relType === "1-n") {
203
+ // If the current entity is on the 'One' side (relFrom), it holds the @OneToMany
204
+ if (relFrom.toLowerCase() === entityNameLower) {
205
+ const targetEntity = capitalize(relTo);
206
+ relationsContent += `
207
+ @OneToMany(() => ${targetEntity}, (${decapitalize(
208
+ relTo
209
+ )}) => ${decapitalize(relTo)}.${relFrom.toLowerCase()})
210
+ ${relTo.toLowerCase()}s: ${targetEntity}[];
211
+ `;
212
+ imports.push("OneToMany");
213
+ extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
214
+ } // If the current entity is on the 'Many' side (relTo), it holds the @ManyToOne
215
+ else if (relTo.toLowerCase() === entityNameLower) {
216
+ const targetEntity = capitalize(relFrom);
217
+ const fkName = `${relFrom.toLowerCase()}Id`;
218
+ relationsContent += `
219
+ @Column({ type: 'uuid' }) // Foreign Key
220
+ ${fkName}: string;
221
+
222
+ @ManyToOne(() => ${targetEntity}, (${decapitalize(
223
+ relFrom
224
+ )}) => ${decapitalize(relFrom)}.${relTo.toLowerCase()}s)
225
+ ${relFrom.toLowerCase()}: ${targetEntity};
226
+ `;
227
+ imports.push("ManyToOne");
228
+ extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
229
+ }
230
+ } else if (relType === "n-1") {
231
+ // n-1 is the inverse: relFrom is the 'Many', relTo is the 'One'
232
+
233
+ // If the current entity is on the 'Many' side (relFrom), it holds the @ManyToOne
234
+ if (relFrom.toLowerCase() === entityNameLower) {
235
+ const targetEntity = capitalize(relTo);
236
+ const fkName = `${relTo.toLowerCase()}Id`;
237
+ relationsContent += `
238
+ @Column({ type: 'uuid' }) // Foreign Key
239
+ ${fkName}: string;
240
+
241
+ @ManyToOne(() => ${targetEntity}, (${decapitalize(
242
+ relTo
243
+ )}) => ${decapitalize(relTo)}.${relFrom.toLowerCase()}s)
244
+ ${relTo.toLowerCase()}: ${targetEntity};
245
+ `;
246
+ imports.push("ManyToOne");
247
+ extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
248
+ } // If the current entity is on the 'One' side (relTo), it holds the @OneToMany
249
+ else if (relTo.toLowerCase() === entityNameLower) {
250
+ const targetEntity = capitalize(relFrom);
251
+ relationsContent += `
252
+ @OneToMany(() => ${targetEntity}, (${decapitalize(
253
+ relFrom
254
+ )}) => ${decapitalize(relFrom)}.${relTo.toLowerCase()})
255
+ ${relFrom.toLowerCase()}s: ${targetEntity}[];
256
+ `;
257
+ imports.push("OneToMany");
258
+ extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
259
+ }
260
+ }
261
+ if (relType === "1-1") {
262
+ // If the current entity is on the 'from' side, it holds the @OneToOne and the foreign key
263
+ if (relFrom.toLowerCase() === entityNameLower) {
264
+ const targetEntity = capitalize(relTo);
265
+ const fkName = `${relTo.toLowerCase()}Id`;
266
+
267
+ relationsContent += `
268
+ @Column({ type: 'uuid', unique: true }) // Unique foreign key for 1-1
269
+ ${fkName}: string;
270
+
271
+ @OneToOne(() => ${targetEntity}, (${decapitalize(relTo)}) => ${decapitalize(
272
+ relTo
273
+ )}.${relFrom.toLowerCase()})
274
+ @JoinColumn({ name: '${fkName}' }) // Requires JoinColumn for the foreign key
275
+ ${relTo.toLowerCase()}: ${targetEntity};
276
+ `;
277
+ imports.push("OneToOne", "JoinColumn");
278
+ extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
279
+ } // If the current entity is on the 'to' side, it holds the inverse @OneToOne relationship (mapping)
280
+ else if (relTo.toLowerCase() === entityNameLower) {
281
+ const targetEntity = capitalize(relFrom);
282
+ relationsContent += `
283
+ @OneToOne(() => ${targetEntity}, (${decapitalize(
284
+ relFrom
285
+ )}) => ${decapitalize(relFrom)}.${relTo.toLowerCase()})
286
+ ${relFrom.toLowerCase()}: ${targetEntity};
287
+ `;
288
+ imports.push("OneToOne");
289
+ extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
290
+ }
291
+ } else if (relType === "n-n") {
292
+ const targetEntity = capitalize(
293
+ relFrom.toLowerCase() === entityNameLower ? relTo : relFrom
294
+ );
295
+ const currentSide =
296
+ relFrom.toLowerCase() === entityNameLower ? relTo : relFrom;
297
+ const otherSide =
298
+ relFrom.toLowerCase() === entityNameLower ? relFrom : relTo; // TypeORM requires JoinTable on one side (we choose the 'from' side for simplicity)
299
+
300
+ if (relFrom.toLowerCase() === entityNameLower) {
301
+ relationsContent += `
302
+ @ManyToMany(() => ${targetEntity}, (${decapitalize(
303
+ currentSide
304
+ )}) => ${decapitalize(currentSide)}.${otherSide.toLowerCase()}s)
305
+ @JoinTable() // Adding JoinTable to create the junction table
306
+ ${currentSide.toLowerCase()}s: ${targetEntity}[];
307
+ `;
308
+ imports.push("ManyToMany", "JoinTable");
309
+ } else {
310
+ relationsContent += `
311
+ @ManyToMany(() => ${targetEntity}, (${decapitalize(
312
+ currentSide
313
+ )}) => ${decapitalize(currentSide)}.${otherSide.toLowerCase()}s)
314
+ ${currentSide.toLowerCase()}s: ${targetEntity}[];
315
+ `;
316
+ imports.push("ManyToMany");
317
+ }
318
+ extraImports += `\nimport { ${targetEntity} } from './${targetEntity}.entity';`;
319
+ }
320
+ }
321
+
322
+ // --- Final File Generation ---
323
+ const uniqueImports = Array.from(new Set(imports)).join(", ");
324
+
325
+ const content = `${extraImports}
326
+ import { ${uniqueImports} } from 'typeorm';
327
+
328
+ @Entity('${entityNameLower}')
329
+ export class ${entityName} {
330
+ @PrimaryGeneratedColumn('uuid')
331
+ id: string;
332
+
333
+ @CreateDateColumn()
334
+ createdAt: Date;
335
+
336
+ @UpdateDateColumn()
337
+ updatedAt: Date;
338
+
339
+ ${fieldsContent}
340
+ ${relationsContent}
341
+ }`;
342
+
343
+ await createFile({
344
+ path: `src/entities/${entityName}.entity.ts`,
345
+ contente: content,
346
+ });
347
+ }
348
+
349
+ if (inputs.isDemo) {
350
+ await setupTypeOrmSeeding(inputs);
351
+ }
352
+
353
+ logSuccess("✅ TypeORM configuration complete. Ready to code!");
354
+ }
355
+
356
+ /**
357
+ * Maps entity types to TypeORM column types (PostgreSQL).
358
+ * @param type The input type (string, number, boolean, Date, enum, array, etc.)
359
+ * @returns { object: type, tsType: string } The mapping information
360
+ */
361
+ function mapTypeToTypeORM(type) {
362
+ // Special case for arrays (e.g., 'string[]')
363
+ if (type.endsWith("[]")) {
364
+ const innerType = type.slice(0, -2);
365
+ const innerMapping = mapTypeToTypeORM(innerType); // Recursive
366
+ return {
367
+ type: innerMapping.type,
368
+ tsType: `${innerMapping.tsType}[]`,
369
+ options: "array: true", // TypeORM option for arrays
370
+ };
371
+ }
372
+
373
+ const typeLower = type.toLowerCase();
374
+
375
+ switch (typeLower) {
376
+ case "string":
377
+ return { type: "varchar", tsType: "string" };
378
+ case "text":
379
+ return { type: "text", tsType: "string" };
380
+ case "number":
381
+ case "float":
382
+ return { type: "float", tsType: "number" };
383
+ case "int":
384
+ return { type: "integer", tsType: "number" };
385
+ case "decimal":
386
+ return { type: "decimal", tsType: "number" };
387
+ case "uuid":
388
+ return { type: "uuid", tsType: "string" };
389
+ case "date":
390
+ return { type: "timestamp", tsType: "Date" };
391
+ case "boolean":
392
+ return { type: "boolean", tsType: "boolean" };
393
+ case "json":
394
+ case "object":
395
+ return { type: "jsonb", tsType: "any" }; // JSONB type is flexible
396
+
397
+ default: // Case of an Enum type or a named object (e.g., 'Address')
398
+ // By default, we assume it's an enum to be stored in the database (varchar)
399
+ if (type.charAt(0) === type.charAt(0).toUpperCase()) {
400
+ // For Enums, TypeORM needs the 'enum' and 'enumName' option
401
+ return {
402
+ type: "enum",
403
+ tsType: type, // The name of the Enum/object in TypeScript
404
+ options: `enum: ${type}, enumName: '${type.toLowerCase()}_enum'`, // Example of enum options
405
+ };
406
+ }
407
+ return { type: "varchar", tsType: "string" };
408
+ }
409
+ }
410
+
411
+ async function setupTypeOrmSeeding(inputs) {
412
+ logInfo("⚙️ Configuring seeding for TypeORM...");
413
+
414
+ // --- Dependencies ---
415
+ const typeOrmDevDeps = [
416
+ "ts-node",
417
+ "@types/node",
418
+ "@types/bcrypt",
419
+ "typeorm-extension",
420
+ ];
421
+
422
+ await runCommand(
423
+ `${inputs.packageManager} add -D ${typeOrmDevDeps.join(" ")}`,
424
+ "❌ Failed to install TypeORM seeding dependencies"
425
+ );
426
+
427
+ await runCommand(
428
+ `${inputs.packageManager} install bcrypt`,
429
+ "❌ Failed to install bcrypt"
430
+ );
431
+
432
+ // --- Scripts in package.json ---
433
+ const typeOrmScripts = {
434
+ "typeorm:migrate:run":
435
+ "typeorm-ts-node-commonjs migration:run -d ./src/database/typeorm.config.ts",
436
+ "typeorm:seed":
437
+ "typeorm-extension seed:run -d src/database/typeorm.config.ts",
438
+ seed: "npm run typeorm:seed",
439
+ };
440
+
441
+ await updatePackageJson(inputs, typeOrmScripts);
442
+
443
+ // --- Creating structure and Seeder ---
444
+ await createDirectory("src/database/seeders");
445
+
446
+ const userSeederContent = generateTypeOrmSeederContent();
447
+ await createFile({
448
+ path: `src/database/seeders/DemoSeeder.ts`,
449
+ contente: userSeederContent,
450
+ });
451
+
452
+ await createFile({
453
+ path: `src/database/typeorm.config.ts`,
454
+ contente: `import * as dotenv from 'dotenv';
455
+ dotenv.config();
456
+ import { DataSource } from 'typeorm';
457
+ import { DataSourceOptions } from 'typeorm';
458
+ import { SeederOptions } from 'typeorm-extension';
459
+ import { DemoSeeder } from './seeders/DemoSeeder';
460
+ import { User } from 'src/entities/User.entity';
461
+ import { Post } from 'src/entities/Post.entity';
462
+ import { Comment } from 'src/entities/Comment.entity';
463
+ import { Session } from 'src/entities/Session.entity';
464
+
465
+ const config: DataSourceOptions & SeederOptions = {
466
+ type: 'postgres',
467
+ host: process.env.POSTGRES_HOST,
468
+ port: Number(process.env.POSTGRES_PORT ?? 5432),
469
+ username: process.env.POSTGRES_USER,
470
+ password: process.env.POSTGRES_PASSWORD,
471
+ database: process.env.POSTGRES_DB,
472
+
473
+ entities: [User, Post, Comment, Session],
474
+
475
+ synchronize: process.env.NODE_ENV !== 'production',
476
+ logging: process.env.NODE_ENV === 'development',
477
+
478
+ seeds: [DemoSeeder],
479
+ };
480
+
481
+ export const AppDataSource = new DataSource(config);
482
+ `,
483
+ });
484
+
485
+ logSuccess("✅ TypeORM seeding configured.");
486
+ }
487
+
488
+ function generateTypeOrmSeederContent() {
489
+ return `
490
+ import { DataSource } from 'typeorm';
491
+ import { User } from 'src/entities/User.entity';
492
+ import { Post } from 'src/entities/Post.entity';
493
+ import { Comment } from 'src/entities/Comment.entity';
494
+ import * as bcrypt from 'bcrypt';
495
+
496
+ export class DemoSeeder {
497
+ constructor() {}
498
+
499
+ async run(dataSource: DataSource) {
500
+ console.log('🌱 Starting TypeORM seeding...');
501
+
502
+ const userRepository = dataSource.getRepository(User);
503
+ const postRepository = dataSource.getRepository(Post);
504
+ const commentRepository = dataSource.getRepository(Comment);
505
+
506
+ // --- 1. ADMIN ---
507
+ const salt = await bcrypt.genSalt(10);
508
+ const hashedPassword = await bcrypt.hash('password123', salt);
509
+
510
+ const admin = userRepository.create({
511
+ email: 'admin@nestcraft.com',
512
+ password: hashedPassword,
513
+ username: 'NestCraftAdmin',
514
+ isActive: true,
515
+ });
516
+
517
+ const exists = await userRepository.findOneBy({
518
+ email: admin.email,
519
+ });
520
+
521
+ if (!exists) {
522
+ await userRepository.save(admin);
523
+ console.log('👑 Admin user created');
524
+ } else {
525
+ admin.id = exists.id;
526
+ console.log('👑 Admin user realy exists');
527
+ }
528
+
529
+ // --- 2. DEMO USERS ---
530
+ const demoUsersData = [
531
+ { email: 'emma.jones@demo.com', password: hashedPassword, username: 'EmmaJones', isActive: true },
532
+ { email: 'lucas.martin@demo.com', password: hashedPassword, username: 'LucasMartin', isActive: true },
533
+ { email: 'sophia.bernard@demo.com', password: hashedPassword, username: 'SophiaBernard', isActive: true },
534
+ { email: 'alexandre.dubois@demo.com', password: hashedPassword, username: 'AlexandreDubois', isActive: true },
535
+ { email: 'chloe.moreau@demo.com', password: hashedPassword, username: 'ChloeMoreau', isActive: true },
536
+ ];
537
+
538
+ const users: User[] = [];
539
+
540
+ for (const userData of demoUsersData) {
541
+ let user = await userRepository.findOneBy({ email: userData.email });
542
+
543
+ if (!user) {
544
+ user = userRepository.create(userData);
545
+ user = await userRepository.save(user);
546
+ }
547
+
548
+ users.push(user);
549
+ }
550
+
551
+ console.log(\`👥 \${users.length} demo users created.\`);
552
+
553
+ const allUsers = [admin, ...users];
554
+
555
+ // --- 3. DEMO POSTS ---
556
+ const postsData = [
557
+ {
558
+ title: 'The Basics of NestJS for Modern Developers',
559
+ content: 'Discover how to build a robust and maintainable API with NestJS...',
560
+ published: true,
561
+ userId: allUsers[1].id,
562
+ },
563
+ {
564
+ title: 'How to Secure Your API with JWT',
565
+ content: 'JWT authentication is a standard for securing APIs...',
566
+ published: true,
567
+ userId: allUsers[2].id,
568
+ },
569
+ {
570
+ title: 'Optimizing Node.js API Performance',
571
+ content: 'Discover best practices for improving performance...',
572
+ published: true,
573
+ userId: allUsers[3].id,
574
+ },
575
+ {
576
+ title: 'Introduction to Prisma ORM',
577
+ content: 'Prisma is a modern ORM that simplifies interactions with the database...',
578
+ published: true,
579
+ userId: allUsers[4].id,
580
+ },
581
+ {
582
+ title: 'Understanding Clean Architecture',
583
+ content: 'Clean Architecture helps separate business logic from the rest of the code...',
584
+ published: false,
585
+ userId: allUsers[0].id,
586
+ },
587
+ ];
588
+
589
+ const posts = await postRepository.save(postsData);
590
+ console.log(\`📝 \${posts.length} articles created.\`);
591
+
592
+ // --- 4. DEMO COMMENTS ---
593
+ const commentsData = [
594
+ {
595
+ content: 'Excellent article! I was able to apply these tips directly to my NestJS project.',
596
+ post: posts[0],
597
+ userId: allUsers[2].id,
598
+ },
599
+ {
600
+ content: 'Very clear and well explained, thanks for sharing about Prisma 👏',
601
+ post: posts[3],
602
+ userId: allUsers[0].id,
603
+ },
604
+ {
605
+ content: "I didn't know about JWT before this article, it's a real revelation.",
606
+ post: posts[1],
607
+ userId: allUsers[4].id,
608
+ },
609
+ {
610
+ content: 'Clean Architecture always seemed blurry to me, this article finally enlightened me.',
611
+ post: posts[4],
612
+ userId: allUsers[1].id,
613
+ },
614
+ {
615
+ content: 'Thanks for the content! I would like to see a complete tutorial with NestJS + Prisma.',
616
+ post: posts[2],
617
+ userId: allUsers[3].id,
618
+ },
619
+ ];
620
+
621
+ const comments = await commentRepository.save(commentsData);
622
+ console.log(\`💬 \${comments.length} comments created.\`);
623
+
624
+ console.log('✅ TypeORM seeding finished successfully! 🚀');
625
+ }
626
+ }
627
+ `;
628
+ }
629
+
630
+ module.exports = { setupTypeORM };