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,421 +1,421 @@
1
- import * as readline from "readline-sync";
2
- import * as fs from "fs";
3
- import { logInfo } from "./loggers/logInfo.js";
4
-
5
- export async function getUserInputs2() {
6
- console.log("\n🔹🔹🔹 Configuration du projet 🔹🔹🔹\n");
7
-
8
- const dataBases = [
9
- {
10
- name: "postgresql",
11
- label: "PostgreSQL",
12
- ormOptions: ["prisma", "typeorm"],
13
- required: [
14
- {
15
- title: "Nom d’utilisateur PostgreSQL",
16
- envVar: "POSTGRES_USER",
17
- defaultValue: "postgres",
18
- hideEchoBack: false,
19
- },
20
- {
21
- title: "Mot de passe PostgreSQL",
22
- envVar: "POSTGRES_PASSWORD",
23
- defaultValue: null,
24
- hideEchoBack: true,
25
- },
26
- {
27
- title: "Nom de la base de données",
28
- envVar: "POSTGRES_DB",
29
- defaultValue: "mydb",
30
- hideEchoBack: false,
31
- },
32
- {
33
- title: "Hôte PostgreSQL",
34
- envVar: "POSTGRES_HOST",
35
- defaultValue: "localhost",
36
- hideEchoBack: false,
37
- },
38
- {
39
- title: "Port PostgreSQL",
40
- envVar: "POSTGRES_PORT",
41
- defaultValue: "5432",
42
- hideEchoBack: false,
43
- },
44
- ],
45
- },
46
- {
47
- name: "mongodb",
48
- label: "MongoDB",
49
- ormOptions: ["mongoose"],
50
- required: [
51
- {
52
- title: "URL de connexion MongoDB",
53
- envVar: "MONGO_URI",
54
- defaultValue: "mongodb://localhost:27017",
55
- hideEchoBack: false,
56
- },
57
- {
58
- title: "Nom de la base de données",
59
- envVar: "MONGO_DB",
60
- defaultValue: "mydb",
61
- hideEchoBack: false,
62
- },
63
- ],
64
- },
65
- ];
66
-
67
- // Validation du nom du projet
68
- let projectName;
69
- while (true) {
70
- projectName = readline.question("Nom du projet: ");
71
- if (/^[A-Za-z][A-Za-z0-9_-]*$/.test(projectName)) break;
72
- console.log(
73
- "❌ Nom de projet invalide. Lettres, chiffres, _ ou - uniquement, commencez par une lettre."
74
- );
75
- }
76
-
77
- // Sélection de la base de données avec validation
78
- let usedDB = readline.question(
79
- `Quelle base de donnée voulez-vous utiliser ? (${dataBases
80
- .map((db) => db.name)
81
- .join(", ")}) : `,
82
- { defaultInput: "postgresql" }
83
- );
84
- let selectedDB = dataBases.find(
85
- (db) => db.name.toLowerCase() === usedDB.toLowerCase()
86
- );
87
- while (!selectedDB) {
88
- console.log("❌ Base de données non reconnue.");
89
- usedDB = readline.question(
90
- `Quelle base de donnée voulez-vous utiliser ? (${dataBases
91
- .map((db) => db.name)
92
- .join(", ")}) : `
93
- );
94
- selectedDB = dataBases.find(
95
- (db) => db.name.toLowerCase() === usedDB.toLowerCase()
96
- );
97
- }
98
-
99
- const dbConfig = {};
100
- selectedDB.required.forEach((field) => {
101
- let answer;
102
- while (true) {
103
- answer = readline.question(
104
- `${field.title} (par défaut: ${field.defaultValue}) : `,
105
- { hideEchoBack: field.hideEchoBack }
106
- );
107
- if (answer || field.defaultValue !== null) break;
108
- console.log("❌ Ce champ est requis.");
109
- }
110
- dbConfig[field.envVar] = answer || field.defaultValue;
111
- });
112
-
113
- // Choix de l'ORM avec validation
114
- if (selectedDB.ormOptions && selectedDB.ormOptions.length > 0) {
115
- let ormChoice;
116
- while (true) {
117
- ormChoice = readline.question(
118
- `Choisissez un ORM pour ${
119
- selectedDB.label
120
- } (${selectedDB.ormOptions.join(", ")}): `
121
- );
122
- if (!ormChoice) ormChoice = selectedDB.ormOptions[0];
123
- if (selectedDB.ormOptions.includes(ormChoice.toLowerCase())) break;
124
- console.log(
125
- "❌ ORM non reconnu. Choix possibles :",
126
- selectedDB.ormOptions.join(", ")
127
- );
128
- }
129
- dbConfig.orm = ormChoice.toLowerCase();
130
- }
131
-
132
- const useYarn = readline.keyInYNStrict("Utiliser Yarn ?");
133
- const useDocker = readline.keyInYNStrict(
134
- "Voulez-vous générer un fichier Docker?"
135
- );
136
- const useAuth = readline.keyInYNStrict(
137
- "Voulez-vous ajouter une authentification JWT?"
138
- );
139
- const useSwagger = readline.keyInYNStrict("Voulez-vous installer Swagger?");
140
- const packageManager = useYarn ? "yarn" : "npm";
141
-
142
- // Saisie des entités et champs avec validation
143
- const entitiesData = {
144
- entities: [],
145
- relations: [],
146
- };
147
-
148
- if (useAuth) {
149
- console.log("🔐 Auth activé : ajout automatique de l'entité 'User'");
150
- entitiesData.entities.push({
151
- name: "user",
152
- fields: [
153
- { name: "email", type: "string" },
154
- { name: "password", type: "string" },
155
- { name: "isActive", type: "boolean" },
156
- ],
157
- });
158
- }
159
-
160
- let addEntity = readline.keyInYNStrict("Voulez-vous ajouter une entité ?");
161
- while (addEntity) {
162
- // Validation du nom d'entité
163
- let name;
164
- while (true) {
165
- name = readline.question("Nom de l'entité (lettres, chiffres, _): ");
166
- if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
167
- console.log(
168
- "❌ Nom invalide. Utilisez uniquement lettres, chiffres, _ et commencez par une lettre."
169
- );
170
- }
171
-
172
- // Saisie des champs avec validation
173
- const fields = [];
174
- while (true) {
175
- let fname = readline.question(" ➤ Nom du champ (vide pour terminer) : ");
176
- if (!fname) break;
177
- if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
178
- console.log(
179
- "❌ Nom de champ invalide. Lettres, chiffres, _ uniquement, commencez par une lettre."
180
- );
181
- continue;
182
- }
183
- let ftype;
184
- while (true) {
185
- ftype = readline.question(
186
- ` Type du champ "${fname}" (string, number, boolean, Date, enum, etc.) : `
187
- );
188
- if (ftype) break;
189
- console.log("❌ Type de champ requis.");
190
- }
191
- fields.push({ name: fname, type: ftype });
192
- }
193
-
194
- entitiesData.entities.push({ name, fields });
195
-
196
- addEntity = readline.keyInYNStrict(
197
- "Voulez-vous ajouter une autre entité ?"
198
- );
199
- }
200
-
201
- // Gestion des relations avec validation
202
- const wantsRelation = readline.keyInYNStrict(
203
- "Souhaites-tu ajouter des relations entre les entités ?"
204
- );
205
- if (wantsRelation && entitiesData.entities.length > 1) {
206
- while (true) {
207
- console.log("\n🧩 Entités disponibles :");
208
- entitiesData.entities.forEach((ent, index) =>
209
- console.log(` [${index}] ${ent.name}`)
210
- );
211
-
212
- let fromIndex, toIndex;
213
- while (true) {
214
- fromIndex = parseInt(
215
- readline.question("Depuis quelle entité ? (index) : "),
216
- 10
217
- );
218
- if (!isNaN(fromIndex) && entitiesData.entities[fromIndex]) break;
219
- console.log("❌ Indice invalide, réessaye !");
220
- }
221
- while (true) {
222
- toIndex = parseInt(
223
- readline.question("Vers quelle entité ? (index) : "),
224
- 10
225
- );
226
- if (!isNaN(toIndex) && entitiesData.entities[toIndex]) break;
227
- console.log("❌ Indice invalide, réessaye !");
228
- }
229
-
230
- let relType;
231
- while (true) {
232
- relType = readline.question("Type de relation ? (1-1 / 1-n / n-n) : ");
233
- if (["1-1", "1-n", "n-n"].includes(relType)) break;
234
- console.log(
235
- "❌ Type de relation invalide. Choix possibles : 1-1, 1-n, n-n"
236
- );
237
- }
238
-
239
- const from = entitiesData.entities[fromIndex];
240
- const to = entitiesData.entities[toIndex];
241
-
242
- entitiesData.relations.push({
243
- from: from.name,
244
- to: to.name,
245
- type: relType,
246
- });
247
-
248
- if (relType === "1-1") {
249
- from.fields.push({
250
- name: `${to.name.toLowerCase()}Id`,
251
- type: "string",
252
- });
253
- to.fields.push({
254
- name: `${from.name.toLowerCase()}Id`,
255
- type: "string",
256
- });
257
- } else if (relType === "1-n") {
258
- to.fields.push({
259
- name: `${from.name.toLowerCase()}Id`,
260
- type: "string",
261
- }); // ex: video.userId
262
- // pas de champ inverse dans from (user)
263
- } else if (relType === "n-n") {
264
- // pour n-n tu peux gérer ça plus tard avec une table pivot
265
- from.fields.push({
266
- name: `${to.name.toLowerCase()}Ids`,
267
- type: "string[]",
268
- });
269
- to.fields.push({
270
- name: `${from.name.toLowerCase()}Ids`,
271
- type: "string[]",
272
- });
273
- }
274
-
275
- const again = readline.keyInYNStrict("Ajouter une autre relation ?");
276
- if (!again) break;
277
- }
278
- } else if (wantsRelation) {
279
- console.log("❌ Il faut au moins deux entités pour créer une relation.");
280
- }
281
-
282
- // Swagger (facultatif)
283
- let swaggerInputs;
284
- if (useSwagger) {
285
- swaggerInputs = getUserInputsSwagger();
286
- }
287
-
288
- return {
289
- projectName: projectName,
290
- useYarn: useYarn,
291
- useDocker: useDocker,
292
- useAuth: useAuth,
293
- useSwagger: useSwagger,
294
- swaggerInputs: swaggerInputs,
295
- packageManager: packageManager,
296
- entitiesData: entitiesData,
297
- selectedDB: selectedDB.name,
298
- dbConfig: dbConfig,
299
- };
300
- }
301
-
302
- export function getUserInputsSwagger() {
303
- console.log("\n🔹 Configuration de Swagger 🔹");
304
-
305
- const title = readline.question("Titre de l'API ? (ex: Mon API) ", {
306
- defaultInput: "Mon API",
307
- });
308
-
309
- const description = readline.question(
310
- "Description de l'API ? (ex: API de gestion) ",
311
- {
312
- defaultInput: "API de gestion",
313
- }
314
- );
315
-
316
- const version = readline.question("Version de l'API ? (ex: 1.0.0) ", {
317
- defaultInput: "1.0.0",
318
- });
319
-
320
- const endpoint = readline.question("Endpoint Swagger (ex: api/docs) ", {
321
- defaultInput: "api/docs",
322
- });
323
-
324
- return { title, description, version, endpoint };
325
- }
326
-
327
- export async function createDirectory(directoryPath) {
328
- try {
329
- if (!fs.existsSync(directoryPath)) {
330
- fs.mkdirSync(directoryPath, { recursive: true });
331
- // console.log(`Dossier créé : ${directoryPath}`);
332
- }
333
- } catch (error) {
334
- console.error(
335
- `Erreur lors de la création du dossier ${directoryPath}:`,
336
- error
337
- );
338
- }
339
- }
340
-
341
- export async function createFile(fileData) {
342
- try {
343
- if (!fs.existsSync(fileData.path)) {
344
- fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
345
- } else {
346
- console.log(`Existing file : ${fileData.path}`);
347
- fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
348
- }
349
- } catch (error) {
350
- console.error(`Erreur creating file ${fileData.path}:`, error);
351
- }
352
- }
353
-
354
- export async function updateFile({ path, pattern, replacement }) {
355
- try {
356
- let mainTs = fs.readFileSync(path, "utf8");
357
- const updatedContent = mainTs.replace(pattern, replacement);
358
- fs.writeFileSync(path, updatedContent, "utf-8");
359
- // console.log(` Updated file: ${path}`);
360
- } catch (error) {
361
- console.error(` Error updating file ${path}:`, error);
362
- }
363
- }
364
-
365
- export async function safeUpdateAppModule(entity) {
366
- const filePath = "src/app.module.ts";
367
- const moduleName = `${capitalize(entity)}Module`;
368
- const importLine = `import { ${moduleName} } from 'src/${entity}/${entity}.module';`;
369
-
370
- let content = fs.readFileSync(filePath, "utf-8");
371
-
372
- // Étape 1 : Ajout de l'import si nécessaire
373
- if (!content.includes(importLine)) {
374
- const importMarker = `import { ConfigModule } from '@nestjs/config';`;
375
-
376
- if (content.includes(importMarker)) {
377
- await updateFile({
378
- path: filePath,
379
- pattern: importMarker,
380
- replacement: `${importMarker}\n${importLine}`,
381
- });
382
- content = fs.readFileSync(filePath, "utf-8");
383
- } else {
384
- logInfo(
385
- " Impossible de trouver le point d'insertion de l'import (ConfigModule manquant)"
386
- );
387
- }
388
- }
389
-
390
- // Étape 2 : Vérifier le bloc des imports du @Module
391
- const importsBlockRegex = /imports:\s*\[((.|\n)*?)\]/m;
392
- const match = content.match(importsBlockRegex);
393
-
394
- if (!match) {
395
- logInfo(" Impossible de trouver le bloc 'imports' dans AppModule.");
396
- return;
397
- }
398
-
399
- const currentImportsBlock = match[1];
400
- const isAlreadyImportedInModule = currentImportsBlock.includes(moduleName);
401
-
402
- if (!isAlreadyImportedInModule) {
403
- const updatedBlock = currentImportsBlock.trim().endsWith(",")
404
- ? `${currentImportsBlock.trim()} ${moduleName},`
405
- : `${currentImportsBlock.trim()}, ${moduleName},`;
406
-
407
- const newContent = content.replace(
408
- importsBlockRegex,
409
- `imports: [${updatedBlock}]`
410
- );
411
- fs.writeFileSync(filePath, newContent, "utf-8");
412
- }
413
- }
414
-
415
- export function capitalize(str) {
416
- return str.charAt(0).toUpperCase() + str.slice(1);
417
- }
418
-
419
- export function decapitalize(str) {
420
- return str.charAt(0).toLowerCase() + str.slice(1);
421
- }
1
+ import * as readline from "readline-sync";
2
+ import * as fs from "fs";
3
+ import { logInfo } from "./loggers/logInfo.js";
4
+
5
+ export async function getUserInputs2() {
6
+ console.log("\n🔹🔹🔹 Configuration du projet 🔹🔹🔹\n");
7
+
8
+ const dataBases = [
9
+ {
10
+ name: "postgresql",
11
+ label: "PostgreSQL",
12
+ ormOptions: ["prisma", "typeorm"],
13
+ required: [
14
+ {
15
+ title: "Nom d’utilisateur PostgreSQL",
16
+ envVar: "POSTGRES_USER",
17
+ defaultValue: "postgres",
18
+ hideEchoBack: false,
19
+ },
20
+ {
21
+ title: "Mot de passe PostgreSQL",
22
+ envVar: "POSTGRES_PASSWORD",
23
+ defaultValue: null,
24
+ hideEchoBack: true,
25
+ },
26
+ {
27
+ title: "Nom de la base de données",
28
+ envVar: "POSTGRES_DB",
29
+ defaultValue: "mydb",
30
+ hideEchoBack: false,
31
+ },
32
+ {
33
+ title: "Hôte PostgreSQL",
34
+ envVar: "POSTGRES_HOST",
35
+ defaultValue: "localhost",
36
+ hideEchoBack: false,
37
+ },
38
+ {
39
+ title: "Port PostgreSQL",
40
+ envVar: "POSTGRES_PORT",
41
+ defaultValue: "5432",
42
+ hideEchoBack: false,
43
+ },
44
+ ],
45
+ },
46
+ {
47
+ name: "mongodb",
48
+ label: "MongoDB",
49
+ ormOptions: ["mongoose"],
50
+ required: [
51
+ {
52
+ title: "URL de connexion MongoDB",
53
+ envVar: "MONGO_URI",
54
+ defaultValue: "mongodb://localhost:27017",
55
+ hideEchoBack: false,
56
+ },
57
+ {
58
+ title: "Nom de la base de données",
59
+ envVar: "MONGO_DB",
60
+ defaultValue: "mydb",
61
+ hideEchoBack: false,
62
+ },
63
+ ],
64
+ },
65
+ ];
66
+
67
+ // Validation du nom du projet
68
+ let projectName;
69
+ while (true) {
70
+ projectName = readline.question("Nom du projet: ");
71
+ if (/^[A-Za-z][A-Za-z0-9_-]*$/.test(projectName)) break;
72
+ console.log(
73
+ "❌ Nom de projet invalide. Lettres, chiffres, _ ou - uniquement, commencez par une lettre.",
74
+ );
75
+ }
76
+
77
+ // Sélection de la base de données avec validation
78
+ let usedDB = readline.question(
79
+ `Quelle base de donnée voulez-vous utiliser ? (${dataBases
80
+ .map((db) => db.name)
81
+ .join(", ")}) : `,
82
+ { defaultInput: "postgresql" },
83
+ );
84
+ let selectedDB = dataBases.find(
85
+ (db) => db.name.toLowerCase() === usedDB.toLowerCase(),
86
+ );
87
+ while (!selectedDB) {
88
+ console.log("❌ Base de données non reconnue.");
89
+ usedDB = readline.question(
90
+ `Quelle base de donnée voulez-vous utiliser ? (${dataBases
91
+ .map((db) => db.name)
92
+ .join(", ")}) : `,
93
+ );
94
+ selectedDB = dataBases.find(
95
+ (db) => db.name.toLowerCase() === usedDB.toLowerCase(),
96
+ );
97
+ }
98
+
99
+ const dbConfig = {};
100
+ selectedDB.required.forEach((field) => {
101
+ let answer;
102
+ while (true) {
103
+ answer = readline.question(
104
+ `${field.title} (par défaut: ${field.defaultValue}) : `,
105
+ { hideEchoBack: field.hideEchoBack },
106
+ );
107
+ if (answer || field.defaultValue !== null) break;
108
+ console.log("❌ Ce champ est requis.");
109
+ }
110
+ dbConfig[field.envVar] = answer || field.defaultValue;
111
+ });
112
+
113
+ // Choix de l'ORM avec validation
114
+ if (selectedDB.ormOptions && selectedDB.ormOptions.length > 0) {
115
+ let ormChoice;
116
+ while (true) {
117
+ ormChoice = readline.question(
118
+ `Choisissez un ORM pour ${
119
+ selectedDB.label
120
+ } (${selectedDB.ormOptions.join(", ")}): `,
121
+ );
122
+ if (!ormChoice) ormChoice = selectedDB.ormOptions[0];
123
+ if (selectedDB.ormOptions.includes(ormChoice.toLowerCase())) break;
124
+ console.log(
125
+ "❌ ORM non reconnu. Choix possibles :",
126
+ selectedDB.ormOptions.join(", "),
127
+ );
128
+ }
129
+ dbConfig.orm = ormChoice.toLowerCase();
130
+ }
131
+
132
+ const useYarn = readline.keyInYNStrict("Utiliser Yarn ?");
133
+ const useDocker = readline.keyInYNStrict(
134
+ "Voulez-vous générer un fichier Docker?",
135
+ );
136
+ const useAuth = readline.keyInYNStrict(
137
+ "Voulez-vous ajouter une authentification JWT?",
138
+ );
139
+ const useSwagger = readline.keyInYNStrict("Voulez-vous installer Swagger?");
140
+ const packageManager = useYarn ? "yarn" : "npm";
141
+
142
+ // Saisie des entités et champs avec validation
143
+ const entitiesData = {
144
+ entities: [],
145
+ relations: [],
146
+ };
147
+
148
+ if (useAuth) {
149
+ console.log("🔐 Auth activé : ajout automatique de l'entité 'User'");
150
+ entitiesData.entities.push({
151
+ name: "user",
152
+ fields: [
153
+ { name: "email", type: "string" },
154
+ { name: "password", type: "string" },
155
+ { name: "isActive", type: "boolean" },
156
+ ],
157
+ });
158
+ }
159
+
160
+ let addEntity = readline.keyInYNStrict("Voulez-vous ajouter une entité ?");
161
+ while (addEntity) {
162
+ // Validation du nom d'entité
163
+ let name;
164
+ while (true) {
165
+ name = readline.question("Nom de l'entité (lettres, chiffres, _): ");
166
+ if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
167
+ console.log(
168
+ "❌ Nom invalide. Utilisez uniquement lettres, chiffres, _ et commencez par une lettre.",
169
+ );
170
+ }
171
+
172
+ // Saisie des champs avec validation
173
+ const fields = [];
174
+ while (true) {
175
+ let fname = readline.question(" ➤ Nom du champ (vide pour terminer) : ");
176
+ if (!fname) break;
177
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
178
+ console.log(
179
+ "❌ Nom de champ invalide. Lettres, chiffres, _ uniquement, commencez par une lettre.",
180
+ );
181
+ continue;
182
+ }
183
+ let ftype;
184
+ while (true) {
185
+ ftype = readline.question(
186
+ ` Type du champ "${fname}" (string, number, boolean, Date, enum, etc.) : `,
187
+ );
188
+ if (ftype) break;
189
+ console.log("❌ Type de champ requis.");
190
+ }
191
+ fields.push({ name: fname, type: ftype });
192
+ }
193
+
194
+ entitiesData.entities.push({ name, fields });
195
+
196
+ addEntity = readline.keyInYNStrict(
197
+ "Voulez-vous ajouter une autre entité ?",
198
+ );
199
+ }
200
+
201
+ // Gestion des relations avec validation
202
+ const wantsRelation = readline.keyInYNStrict(
203
+ "Souhaites-tu ajouter des relations entre les entités ?",
204
+ );
205
+ if (wantsRelation && entitiesData.entities.length > 1) {
206
+ while (true) {
207
+ console.log("\n🧩 Entités disponibles :");
208
+ entitiesData.entities.forEach((ent, index) =>
209
+ console.log(` [${index}] ${ent.name}`),
210
+ );
211
+
212
+ let fromIndex, toIndex;
213
+ while (true) {
214
+ fromIndex = parseInt(
215
+ readline.question("Depuis quelle entité ? (index) : "),
216
+ 10,
217
+ );
218
+ if (!isNaN(fromIndex) && entitiesData.entities[fromIndex]) break;
219
+ console.log("❌ Indice invalide, réessaye !");
220
+ }
221
+ while (true) {
222
+ toIndex = parseInt(
223
+ readline.question("Vers quelle entité ? (index) : "),
224
+ 10,
225
+ );
226
+ if (!isNaN(toIndex) && entitiesData.entities[toIndex]) break;
227
+ console.log("❌ Indice invalide, réessaye !");
228
+ }
229
+
230
+ let relType;
231
+ while (true) {
232
+ relType = readline.question("Type de relation ? (1-1 / 1-n / n-n) : ");
233
+ if (["1-1", "1-n", "n-n"].includes(relType)) break;
234
+ console.log(
235
+ "❌ Type de relation invalide. Choix possibles : 1-1, 1-n, n-n",
236
+ );
237
+ }
238
+
239
+ const from = entitiesData.entities[fromIndex];
240
+ const to = entitiesData.entities[toIndex];
241
+
242
+ entitiesData.relations.push({
243
+ from: from.name,
244
+ to: to.name,
245
+ type: relType,
246
+ });
247
+
248
+ if (relType === "1-1") {
249
+ from.fields.push({
250
+ name: `${to.name.toLowerCase()}Id`,
251
+ type: "string",
252
+ });
253
+ to.fields.push({
254
+ name: `${from.name.toLowerCase()}Id`,
255
+ type: "string",
256
+ });
257
+ } else if (relType === "1-n") {
258
+ to.fields.push({
259
+ name: `${from.name.toLowerCase()}Id`,
260
+ type: "string",
261
+ }); // ex: video.userId
262
+ // pas de champ inverse dans from (user)
263
+ } else if (relType === "n-n") {
264
+ // pour n-n tu peux gérer ça plus tard avec une table pivot
265
+ from.fields.push({
266
+ name: `${to.name.toLowerCase()}Ids`,
267
+ type: "string[]",
268
+ });
269
+ to.fields.push({
270
+ name: `${from.name.toLowerCase()}Ids`,
271
+ type: "string[]",
272
+ });
273
+ }
274
+
275
+ const again = readline.keyInYNStrict("Ajouter une autre relation ?");
276
+ if (!again) break;
277
+ }
278
+ } else if (wantsRelation) {
279
+ console.log("❌ Il faut au moins deux entités pour créer une relation.");
280
+ }
281
+
282
+ // Swagger (facultatif)
283
+ let swaggerInputs;
284
+ if (useSwagger) {
285
+ swaggerInputs = getUserInputsSwagger();
286
+ }
287
+
288
+ return {
289
+ projectName: projectName,
290
+ useYarn: useYarn,
291
+ useDocker: useDocker,
292
+ useAuth: useAuth,
293
+ useSwagger: useSwagger,
294
+ swaggerInputs: swaggerInputs,
295
+ packageManager: packageManager,
296
+ entitiesData: entitiesData,
297
+ selectedDB: selectedDB.name,
298
+ dbConfig: dbConfig,
299
+ };
300
+ }
301
+
302
+ export function getUserInputsSwagger() {
303
+ console.log("\n🔹 Configuration de Swagger 🔹");
304
+
305
+ const title = readline.question("Titre de l'API ? (ex: Mon API) ", {
306
+ defaultInput: "Mon API",
307
+ });
308
+
309
+ const description = readline.question(
310
+ "Description de l'API ? (ex: API de gestion) ",
311
+ {
312
+ defaultInput: "API de gestion",
313
+ },
314
+ );
315
+
316
+ const version = readline.question("Version de l'API ? (ex: 1.0.0) ", {
317
+ defaultInput: "1.0.0",
318
+ });
319
+
320
+ const endpoint = readline.question("Endpoint Swagger (ex: api/docs) ", {
321
+ defaultInput: "api/docs",
322
+ });
323
+
324
+ return { title, description, version, endpoint };
325
+ }
326
+
327
+ export async function createDirectory(directoryPath) {
328
+ try {
329
+ if (!fs.existsSync(directoryPath)) {
330
+ fs.mkdirSync(directoryPath, { recursive: true });
331
+ // console.log(`Dossier créé : ${directoryPath}`);
332
+ }
333
+ } catch (error) {
334
+ console.error(
335
+ `Erreur lors de la création du dossier ${directoryPath}:`,
336
+ error,
337
+ );
338
+ }
339
+ }
340
+
341
+ export async function createFile(fileData) {
342
+ try {
343
+ if (!fs.existsSync(fileData.path)) {
344
+ fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
345
+ } else {
346
+ console.log(`Existing file : ${fileData.path}`);
347
+ fs.writeFileSync(`${fileData.path}`, `${fileData.contente}`);
348
+ }
349
+ } catch (error) {
350
+ console.error(`Erreur creating file ${fileData.path}:`, error);
351
+ }
352
+ }
353
+
354
+ export async function updateFile({ path, pattern, replacement }) {
355
+ try {
356
+ let mainTs = fs.readFileSync(path, "utf8");
357
+ const updatedContent = mainTs.replace(pattern, replacement);
358
+ fs.writeFileSync(path, updatedContent, "utf-8");
359
+ // console.log(` Updated file: ${path}`);
360
+ } catch (error) {
361
+ console.error(` Error updating file ${path}:`, error);
362
+ }
363
+ }
364
+
365
+ export async function safeUpdateAppModule(entity) {
366
+ const filePath = "src/app.module.ts";
367
+ const moduleName = `${capitalize(entity)}Module`;
368
+ const importLine = `import { ${moduleName} } from 'src/${entity}/${entity}.module';`;
369
+
370
+ let content = fs.readFileSync(filePath, "utf-8");
371
+
372
+ // Étape 1 : Ajout de l'import si nécessaire
373
+ if (!content.includes(importLine)) {
374
+ const importMarker = `import { ConfigModule } from '@nestjs/config';`;
375
+
376
+ if (content.includes(importMarker)) {
377
+ await updateFile({
378
+ path: filePath,
379
+ pattern: importMarker,
380
+ replacement: `${importMarker}\n${importLine}`,
381
+ });
382
+ content = fs.readFileSync(filePath, "utf-8");
383
+ } else {
384
+ logInfo(
385
+ " Impossible de trouver le point d'insertion de l'import (ConfigModule manquant)",
386
+ );
387
+ }
388
+ }
389
+
390
+ // Étape 2 : Vérifier le bloc des imports du @Module
391
+ const importsBlockRegex = /imports:\s*\[((.|\n)*?)\]/m;
392
+ const match = content.match(importsBlockRegex);
393
+
394
+ if (!match) {
395
+ logInfo(" Impossible de trouver le bloc 'imports' dans AppModule.");
396
+ return;
397
+ }
398
+
399
+ const currentImportsBlock = match[1];
400
+ const isAlreadyImportedInModule = currentImportsBlock.includes(moduleName);
401
+
402
+ if (!isAlreadyImportedInModule) {
403
+ const updatedBlock = currentImportsBlock.trim().endsWith(",")
404
+ ? `${currentImportsBlock.trim()} ${moduleName},`
405
+ : `${currentImportsBlock.trim()}, ${moduleName},`;
406
+
407
+ const newContent = content.replace(
408
+ importsBlockRegex,
409
+ `imports: [${updatedBlock}]`,
410
+ );
411
+ fs.writeFileSync(filePath, newContent, "utf-8");
412
+ }
413
+ }
414
+
415
+ export function capitalize(str) {
416
+ return str.charAt(0).toUpperCase() + str.slice(1);
417
+ }
418
+
419
+ export function decapitalize(str) {
420
+ return str.charAt(0).toLowerCase() + str.slice(1);
421
+ }