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,460 +1,460 @@
1
- const readline = require("readline-sync");
2
- const { info, warning, success } = require("./colors");
3
- const inquirer = require("inquirer");
4
- const { question } = require("readline-sync");
5
- const { capitalize } = require("./userInput");
6
- const { logWarning } = require("./loggers/logWarning");
7
- const { logInfo } = require("./loggers/logInfo");
8
- const { getPackageManager } = require("./utils");
9
- const actualInquirer = inquirer.default || inquirer;
10
-
11
- async function getLightModeInputs(projectName, flags) {
12
- console.log(
13
- `\n${info("[LIGHT MODE]")} Simplified configuration for ${projectName}\n`
14
- );
15
-
16
- const inputs = {
17
- projectName,
18
- mode: "light",
19
- selectedDB: "postgresql",
20
- packageManager: "npm",
21
- entitiesData: {
22
- entities: [],
23
- relations: [],
24
- },
25
- };
26
-
27
- // db config
28
- const orm = await getOrmChoice(flags);
29
- inputs.selectedDB = orm === "mongoose" ? "mongodb" : "postgresql";
30
-
31
- if (orm === "mongoose") {
32
- logInfo("MongoDB configuration");
33
- inputs.dbConfig = {
34
- orm,
35
- MONGO_URI: readline.question(
36
- `MongoDB URI [mongodb://localhost:27017]: `,
37
- {
38
- defaultInput: "mongodb://localhost:27017",
39
- }
40
- ),
41
- MONGO_DB: readline.question(`Database name [${projectName}-db]: `, {
42
- defaultInput: `${projectName}-db`,
43
- }),
44
- };
45
- } else {
46
- logInfo("PostgreSQL configuration");
47
- inputs.dbConfig = {
48
- orm,
49
- POSTGRES_USER: readline.question("PostgreSQL user [postgres]: ", {
50
- defaultInput: "postgres",
51
- }),
52
- POSTGRES_PASSWORD: readline.question("PostgreSQL password [postgres]: ", {
53
- defaultInput: "postgres",
54
- hideEchoBack: true,
55
- }),
56
- POSTGRES_DB: readline.question(`Database name [${projectName}-db]: `, {
57
- defaultInput: `${projectName}-db`,
58
- }),
59
- POSTGRES_HOST: readline.question("PostgreSQL host [localhost]: ", {
60
- defaultInput: "localhost",
61
- }),
62
- POSTGRES_PORT: readline.question("PostgreSQL port [5432]: ", {
63
- defaultInput: "5432",
64
- }),
65
- };
66
- }
67
-
68
- // swagger config
69
- const useSwagger = getSwaggerChoice(flags);
70
- if (useSwagger) {
71
- inputs.swaggerInputs = {
72
- title: readline.question(`API Title [${projectName} API]: `, {
73
- defaultInput: `${projectName} API`,
74
- }),
75
- description: readline.question(
76
- "Description [API generated by NestCraftX]: ",
77
- {
78
- defaultInput: "API generated by NestCraftX",
79
- }
80
- ),
81
- version: readline.question("Version [1.0.0]: ", {
82
- defaultInput: "1.0.0",
83
- }),
84
- endpoint: readline.question("Swagger Endpoint [api/docs]: ", {
85
- defaultInput: "api/docs",
86
- }),
87
- };
88
- }
89
-
90
- // Docker config
91
- const useDocker = getDockerChoice(flags);
92
-
93
- // JWT Auth config
94
- const useAuth = getAuthChoice(flags);
95
-
96
- const packageManager = await getPackageManager(flags);
97
- inputs.packageManager = packageManager;
98
-
99
- inputs.useAuth = useAuth;
100
- inputs.useSwagger = useSwagger;
101
- inputs.useDocker = useDocker;
102
-
103
- if (useAuth) {
104
- console.log(
105
- `${info("[INFO]")} Auth active: adding User and Session entities`
106
- );
107
-
108
- // 1. Entité User
109
- inputs.entitiesData.entities.push({
110
- name: "user",
111
- fields: [
112
- { name: "email", type: "string", unique: true },
113
- { name: "password", type: "string" },
114
- { name: "role", type: "Role" },
115
- { name: "isActive", type: "boolean", default: true },
116
- ],
117
- });
118
-
119
- // 2. Entité Session
120
- inputs.entitiesData.entities.push({
121
- name: "session",
122
- fields: [
123
- { name: "refreshToken", type: "string" },
124
- { name: "userId", type: "string" },
125
- { name: "expiresAt", type: "Date" },
126
- { name: "createdAt", type: "Date", default: "now" },
127
- ],
128
- });
129
- // 3. relation user & session
130
- inputs.entitiesData.relations.push({
131
- from: "user",
132
- to: "session",
133
- type: "1-n",
134
- });
135
- }
136
-
137
- const addEntities = readline.keyInYNStrict(
138
- `${info("[?]")} Do you want to add supplementary entities ?`
139
- );
140
- if (addEntities) {
141
- console.log(`\n${info("[INFO]")} Entity input (simplified mode)`);
142
- await addCustomEntities(inputs.entitiesData);
143
- }
144
-
145
- // Demander les relations entre entités
146
- if (inputs.entitiesData.entities.length > 1) {
147
- const wantsRelation = readline.keyInYNStrict(
148
- `${info("[?]")} Do you want to add relationships between entities ?`
149
- );
150
- if (wantsRelation) {
151
- console.log(`\n${info("[INFO]")} Configuring relationships`);
152
- await addRelations(inputs.entitiesData);
153
- }
154
- }
155
-
156
- return inputs;
157
- }
158
-
159
- async function getOrmChoice(flags) {
160
- const validOrms = ["prisma", "typeorm", "mongoose"];
161
-
162
- // 1. Vérification du flag
163
- if (flags.orm && validOrms.includes(flags.orm.toLowerCase())) {
164
- console.log(`${info("[INFO]")} ORM: ${flags.orm} (via flag)`);
165
- return flags.orm.toLowerCase();
166
- }
167
-
168
- // 2. Mode interactif avec Inquirer
169
- const answers = await actualInquirer.prompt([
170
- {
171
- type: "list",
172
- name: "orm",
173
- message: "Choose an ORM:",
174
- choices: [
175
- { name: "Prisma (PostgreSQL)", value: "prisma" },
176
- { name: "TypeORM (PostgreSQL)", value: "typeorm" },
177
- { name: "Mongoose (MongoDB - Coming Soon)", value: "mongoose" },
178
- ],
179
- default: "prisma",
180
- },
181
- ]);
182
-
183
- return answers.orm;
184
- }
185
-
186
- function getAuthChoice(flags) {
187
- if (flags.auth !== undefined) {
188
- logInfo(`Auth: ${flags.auth ? "Yes" : "No"} (via flag)`);
189
- return !!flags.auth;
190
- }
191
- return readline.keyInYNStrict(`${info("[?]")} Enable JWT authentication ?`);
192
- }
193
-
194
- function getSwaggerChoice(flags) {
195
- if (flags.swagger !== undefined) {
196
- console.log(
197
- `${info("[INFO]")} Swagger: ${flags.swagger ? "Yes" : "No"} (via flag)`
198
- );
199
- return !!flags.swagger;
200
- }
201
- return readline.keyInYNStrict(
202
- `${info("[?]")} Enable Swagger for API documentation ?`
203
- );
204
- }
205
-
206
- function getDockerChoice(flags) {
207
- if (flags.docker !== undefined) {
208
- console.log(
209
- `${info("[INFO]")} Docker: ${flags.docker ? "Yes" : "No"} (via flag)`
210
- );
211
- return flags.docker === true || flags.docker === "true";
212
- }
213
- return readline.keyInYNStrict(`${info("[?]")} Generate Docker files ?`);
214
- }
215
-
216
- async function addCustomEntities(entitiesData) {
217
- while (true) {
218
- let name;
219
- while (true) {
220
- name = readline.question("\nEntity name (empty to finish) : ");
221
- if (!name) return;
222
- if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
223
- logWarning("Invalid name. Use letters, numbers, and _ only.");
224
- }
225
-
226
- const fields = [];
227
- console.log(` Fields for entity "${name}" :`);
228
-
229
- while (true) {
230
- const fieldName = readline.question(" Field name (empty to finish) : ");
231
- if (!fieldName) break;
232
-
233
- if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fieldName)) {
234
- logWarning("Invalid field name.");
235
- continue;
236
- }
237
-
238
- const baseTypeChoices = [
239
- "string",
240
- "text",
241
- "number",
242
- "decimal",
243
- "boolean",
244
- "Date",
245
- "uuid",
246
- "json",
247
- "enum",
248
- "array",
249
- "object",
250
- ];
251
-
252
- const typeQuestion = {
253
- type: "list",
254
- name: "ftype",
255
- message: `Type of "${fieldName}"`,
256
- default: "string",
257
- choices: baseTypeChoices,
258
- };
259
-
260
- const typeAnswer = await actualInquirer.prompt([typeQuestion]);
261
- let fieldType = typeAnswer.ftype; // --- ADVANCED LOGIC FOR ARRAY, ENUM, AND OBJECT ---
262
- // Déplace le curseur d'une ligne vers le haut :
263
- process.stdout.write("\x1B[1A");
264
- // Efface la ligne (où se trouvait le "√ Type for...") :
265
- process.stdout.write("\x1B[K");
266
-
267
- if (fieldType === "array") {
268
- // Prompt spécifique pour le type interne du tableau
269
- const arrayInnerQuestion = {
270
- type: "list",
271
- name: "innerType",
272
- message: `Type of elements in "${fieldName}[]"`,
273
- default: "string", // Exclure array et object du sous-type pour simplifier
274
- choices: baseTypeChoices.filter(
275
- (c) => c !== "array" && c !== "object"
276
- ),
277
- };
278
-
279
- const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
280
- fieldType = `${innerAnswer.innerType}[]`;
281
- } else if (fieldType === "enum") {
282
- const enumName = capitalize(fieldName) + "Enum";
283
- logInfo(
284
- `Enum type selected. Remember to define ${enumName} in your code.`
285
- );
286
- fieldType = enumName;
287
- } else if (fieldType === "object") {
288
- // Prompt pour nommer l'objet (ou 'json' par défaut)
289
- const objectNameQuestion = {
290
- type: "input",
291
- name: "objectName",
292
- message: `Complex type name (DTO/Class or leave 'json') :`,
293
- default: "json",
294
- };
295
-
296
- const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
297
- fieldType = capitalize(objectAnswer.objectName.trim() || "json");
298
- }
299
-
300
- console.log(
301
- ` Field type for "${fieldName}" : ${fieldType} ${success("[✓]")}`
302
- );
303
-
304
- fields.push({ name: fieldName, type: fieldType });
305
- }
306
-
307
- if (fields.length > 0) {
308
- entitiesData.entities.push({ name, fields });
309
- console.log(
310
- `${info("[INFO]")} Entity "${name}" added with ${
311
- fields.length
312
- } field(s)`
313
- );
314
- }
315
-
316
- const addMore = readline.keyInYNStrict("Add another entity?");
317
- if (!addMore) break;
318
- }
319
- }
320
-
321
- /**
322
- * Manages adding relationships between entities via an interactive interface.
323
- * All prompts and logs are in English.
324
- * @param {Object} entitiesData - The object containing entities and relations.
325
- */
326
- async function addRelations(entitiesData) {
327
- if (entitiesData.entities.length < 2) {
328
- logInfo("At least two entities are required to create a relationship.");
329
- return;
330
- }
331
-
332
- let configuring = true;
333
-
334
- while (configuring) {
335
- const entityNames = entitiesData.entities.map((e) => e.name);
336
-
337
- // 1. Interactive selection of entities
338
- const answers = await actualInquirer.prompt([
339
- {
340
- type: "list",
341
- name: "fromName",
342
- message: "Select the source entity (From) :",
343
- choices: entityNames,
344
- },
345
- {
346
- type: "list",
347
- name: "toName",
348
- message: (prev) =>
349
- `To which entity do you want to link ${prev.fromName} ?`,
350
- choices: (prev) => entityNames.filter((name) => name !== prev.fromName),
351
- },
352
- ]);
353
-
354
- // --- CHECK FOR EXISTING RELATIONSHIPS ---
355
- const alreadyExists = entitiesData.relations.find(
356
- (rel) =>
357
- (rel.from === answers.fromName && rel.to === answers.toName) ||
358
- (rel.from === answers.toName && rel.to === answers.fromName)
359
- );
360
-
361
- if (alreadyExists) {
362
- logWarning(
363
- `A relationship already exists between ${selection.fromName} and ${selection.toName} (${alreadyExists.type}).`
364
- );
365
-
366
- const { tryAgain } = await actualInquirer.prompt([
367
- {
368
- type: "confirm",
369
- name: "tryAgain",
370
- message: "Would you like to choose different entities ?",
371
- default: true,
372
- },
373
- ]);
374
-
375
- if (!tryAgain) break;
376
- // Restart the loop
377
- continue;
378
- }
379
-
380
- // 2. Select Relationship Type
381
- const typeAnswer = await actualInquirer.prompt([
382
- {
383
- type: "list",
384
- name: "relType",
385
- message: "What is the relationship type?",
386
- choices: [
387
- {
388
- name: `1-1 (One-to-One) : ${selection.fromName} is linked to exactly one ${selection.toName}`,
389
- value: "1-1",
390
- },
391
- {
392
- name: `1-n (One-to-Many) : ${selection.fromName} owns multiple ${selection.toName}s`,
393
- value: "1-n",
394
- },
395
- {
396
- name: `n-1 (Many-to-One) : Multiple ${selection.fromName}s belong to one ${selection.toName}`,
397
- value: "n-1",
398
- },
399
- {
400
- name: `n-n (Many-to-Many) : Multiple ${selection.fromName}s are linked to multiple ${selection.toName}s`,
401
- value: "n-n",
402
- },
403
- ],
404
- },
405
- ]);
406
-
407
- const from = entitiesData.entities.find((e) => e.name === answers.fromName);
408
- const to = entitiesData.entities.find((e) => e.name === answers.toName);
409
- const relType = typeAnswer.relType;
410
-
411
- // 3. Register relationship
412
- entitiesData.relations.push({
413
- from: from.name,
414
- to: to.name,
415
- type: relType,
416
- });
417
-
418
- // 4. Automatic field injection
419
- const fromLow = from.name.toLowerCase();
420
- const toLow = to.name.toLowerCase();
421
- if (relType === "1-1") {
422
- from.fields.push(
423
- { name: `${toLow}Id`, type: "string" },
424
- { name: toLow, type: to.name }
425
- );
426
- } else if (relType === "1-n") {
427
- from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
428
- to.fields.push(
429
- { name: `${fromLow}Id`, type: "string" },
430
- { name: fromLow, type: from.name }
431
- );
432
- } else if (relType === "n-1") {
433
- from.fields.push(
434
- { name: `${toLow}Id`, type: "string" },
435
- { name: toLow, type: to.name }
436
- );
437
- to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
438
- } else if (relType === "n-n") {
439
- from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
440
- to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
441
- }
442
- console.log(
443
- `${success("[✓]")} Relationship added: ${from.name} ${relType} ${to.name}`
444
- );
445
-
446
- // 5. Ask to continue
447
- const { addMore } = await actualInquirer.prompt([
448
- {
449
- type: "confirm",
450
- name: "addMore",
451
- message: "Do you want to add another relationship ?",
452
- default: false,
453
- },
454
- ]);
455
-
456
- configuring = addMore;
457
- }
458
- }
459
-
460
- module.exports = { getLightModeInputs };
1
+ const readline = require("readline-sync");
2
+ const { info, warning, success } = require("./colors");
3
+ const inquirer = require("inquirer");
4
+ const { question } = require("readline-sync");
5
+ const { capitalize } = require("./userInput");
6
+ const { logWarning } = require("./loggers/logWarning");
7
+ const { logInfo } = require("./loggers/logInfo");
8
+ const { getPackageManager } = require("./utils");
9
+ const actualInquirer = inquirer.default || inquirer;
10
+
11
+ async function getLightModeInputs(projectName, flags) {
12
+ console.log(
13
+ `\n${info("[LIGHT MODE]")} Simplified configuration for ${projectName}\n`
14
+ );
15
+
16
+ const inputs = {
17
+ projectName,
18
+ mode: "light",
19
+ selectedDB: "postgresql",
20
+ packageManager: "npm",
21
+ entitiesData: {
22
+ entities: [],
23
+ relations: [],
24
+ },
25
+ };
26
+
27
+ // db config
28
+ const orm = await getOrmChoice(flags);
29
+ inputs.selectedDB = orm === "mongoose" ? "mongodb" : "postgresql";
30
+
31
+ if (orm === "mongoose") {
32
+ logInfo("MongoDB configuration");
33
+ inputs.dbConfig = {
34
+ orm,
35
+ MONGO_URI: readline.question(
36
+ `MongoDB URI [mongodb://localhost:27017]: `,
37
+ {
38
+ defaultInput: "mongodb://localhost:27017",
39
+ }
40
+ ),
41
+ MONGO_DB: readline.question(`Database name [${projectName}-db]: `, {
42
+ defaultInput: `${projectName}-db`,
43
+ }),
44
+ };
45
+ } else {
46
+ logInfo("PostgreSQL configuration");
47
+ inputs.dbConfig = {
48
+ orm,
49
+ POSTGRES_USER: readline.question("PostgreSQL user [postgres]: ", {
50
+ defaultInput: "postgres",
51
+ }),
52
+ POSTGRES_PASSWORD: readline.question("PostgreSQL password [postgres]: ", {
53
+ defaultInput: "postgres",
54
+ hideEchoBack: true,
55
+ }),
56
+ POSTGRES_DB: readline.question(`Database name [${projectName}-db]: `, {
57
+ defaultInput: `${projectName}-db`,
58
+ }),
59
+ POSTGRES_HOST: readline.question("PostgreSQL host [localhost]: ", {
60
+ defaultInput: "localhost",
61
+ }),
62
+ POSTGRES_PORT: readline.question("PostgreSQL port [5432]: ", {
63
+ defaultInput: "5432",
64
+ }),
65
+ };
66
+ }
67
+
68
+ // swagger config
69
+ const useSwagger = getSwaggerChoice(flags);
70
+ if (useSwagger) {
71
+ inputs.swaggerInputs = {
72
+ title: readline.question(`API Title [${projectName} API]: `, {
73
+ defaultInput: `${projectName} API`,
74
+ }),
75
+ description: readline.question(
76
+ "Description [API generated by NestCraftX]: ",
77
+ {
78
+ defaultInput: "API generated by NestCraftX",
79
+ }
80
+ ),
81
+ version: readline.question("Version [1.0.0]: ", {
82
+ defaultInput: "1.0.0",
83
+ }),
84
+ endpoint: readline.question("Swagger Endpoint [api/docs]: ", {
85
+ defaultInput: "api/docs",
86
+ }),
87
+ };
88
+ }
89
+
90
+ // Docker config
91
+ const useDocker = getDockerChoice(flags);
92
+
93
+ // JWT Auth config
94
+ const useAuth = getAuthChoice(flags);
95
+
96
+ const packageManager = await getPackageManager(flags);
97
+ inputs.packageManager = packageManager;
98
+
99
+ inputs.useAuth = useAuth;
100
+ inputs.useSwagger = useSwagger;
101
+ inputs.useDocker = useDocker;
102
+
103
+ if (useAuth) {
104
+ console.log(
105
+ `${info("[INFO]")} Auth active: adding User and Session entities`
106
+ );
107
+
108
+ // 1. Entité User
109
+ inputs.entitiesData.entities.push({
110
+ name: "user",
111
+ fields: [
112
+ { name: "email", type: "string", unique: true },
113
+ { name: "password", type: "string" },
114
+ { name: "role", type: "Role" },
115
+ { name: "isActive", type: "boolean", default: true },
116
+ ],
117
+ });
118
+
119
+ // 2. Entité Session
120
+ inputs.entitiesData.entities.push({
121
+ name: "session",
122
+ fields: [
123
+ { name: "refreshToken", type: "string" },
124
+ { name: "userId", type: "string" },
125
+ { name: "expiresAt", type: "Date" },
126
+ { name: "createdAt", type: "Date", default: "now" },
127
+ ],
128
+ });
129
+ // 3. relation user & session
130
+ inputs.entitiesData.relations.push({
131
+ from: "user",
132
+ to: "session",
133
+ type: "1-n",
134
+ });
135
+ }
136
+
137
+ const addEntities = readline.keyInYNStrict(
138
+ `${info("[?]")} Do you want to add supplementary entities ?`
139
+ );
140
+ if (addEntities) {
141
+ console.log(`\n${info("[INFO]")} Entity input (simplified mode)`);
142
+ await addCustomEntities(inputs.entitiesData);
143
+ }
144
+
145
+ // Demander les relations entre entités
146
+ if (inputs.entitiesData.entities.length > 1) {
147
+ const wantsRelation = readline.keyInYNStrict(
148
+ `${info("[?]")} Do you want to add relationships between entities ?`
149
+ );
150
+ if (wantsRelation) {
151
+ console.log(`\n${info("[INFO]")} Configuring relationships`);
152
+ await addRelations(inputs.entitiesData);
153
+ }
154
+ }
155
+
156
+ return inputs;
157
+ }
158
+
159
+ async function getOrmChoice(flags) {
160
+ const validOrms = ["prisma", "typeorm", "mongoose"];
161
+
162
+ // 1. Vérification du flag
163
+ if (flags.orm && validOrms.includes(flags.orm.toLowerCase())) {
164
+ console.log(`${info("[INFO]")} ORM: ${flags.orm} (via flag)`);
165
+ return flags.orm.toLowerCase();
166
+ }
167
+
168
+ // 2. Mode interactif avec Inquirer
169
+ const answers = await actualInquirer.prompt([
170
+ {
171
+ type: "list",
172
+ name: "orm",
173
+ message: "Choose an ORM:",
174
+ choices: [
175
+ { name: "Prisma (PostgreSQL)", value: "prisma" },
176
+ { name: "TypeORM (PostgreSQL)", value: "typeorm" },
177
+ { name: "Mongoose (MongoDB)", value: "mongoose" },
178
+ ],
179
+ default: "prisma",
180
+ },
181
+ ]);
182
+
183
+ return answers.orm;
184
+ }
185
+
186
+ function getAuthChoice(flags) {
187
+ if (flags.auth !== undefined) {
188
+ logInfo(`Auth: ${flags.auth ? "Yes" : "No"} (via flag)`);
189
+ return !!flags.auth;
190
+ }
191
+ return readline.keyInYNStrict(`${info("[?]")} Enable JWT authentication ?`);
192
+ }
193
+
194
+ function getSwaggerChoice(flags) {
195
+ if (flags.swagger !== undefined) {
196
+ console.log(
197
+ `${info("[INFO]")} Swagger: ${flags.swagger ? "Yes" : "No"} (via flag)`
198
+ );
199
+ return !!flags.swagger;
200
+ }
201
+ return readline.keyInYNStrict(
202
+ `${info("[?]")} Enable Swagger for API documentation ?`
203
+ );
204
+ }
205
+
206
+ function getDockerChoice(flags) {
207
+ if (flags.docker !== undefined) {
208
+ console.log(
209
+ `${info("[INFO]")} Docker: ${flags.docker ? "Yes" : "No"} (via flag)`
210
+ );
211
+ return flags.docker === true || flags.docker === "true";
212
+ }
213
+ return readline.keyInYNStrict(`${info("[?]")} Generate Docker files ?`);
214
+ }
215
+
216
+ async function addCustomEntities(entitiesData) {
217
+ while (true) {
218
+ let name;
219
+ while (true) {
220
+ name = readline.question("\nEntity name (empty to finish) : ");
221
+ if (!name) return;
222
+ if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
223
+ logWarning("Invalid name. Use letters, numbers, and _ only.");
224
+ }
225
+
226
+ const fields = [];
227
+ console.log(` Fields for entity "${name}" :`);
228
+
229
+ while (true) {
230
+ const fieldName = readline.question(" Field name (empty to finish) : ");
231
+ if (!fieldName) break;
232
+
233
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fieldName)) {
234
+ logWarning("Invalid field name.");
235
+ continue;
236
+ }
237
+
238
+ const baseTypeChoices = [
239
+ "string",
240
+ "text",
241
+ "number",
242
+ "decimal",
243
+ "boolean",
244
+ "Date",
245
+ "uuid",
246
+ "json",
247
+ "enum",
248
+ "array",
249
+ "object",
250
+ ];
251
+
252
+ const typeQuestion = {
253
+ type: "list",
254
+ name: "ftype",
255
+ message: `Type of "${fieldName}"`,
256
+ default: "string",
257
+ choices: baseTypeChoices,
258
+ };
259
+
260
+ const typeAnswer = await actualInquirer.prompt([typeQuestion]);
261
+ let fieldType = typeAnswer.ftype; // --- ADVANCED LOGIC FOR ARRAY, ENUM, AND OBJECT ---
262
+ // Déplace le curseur d'une ligne vers le haut :
263
+ process.stdout.write("\x1B[1A");
264
+ // Efface la ligne (où se trouvait le "√ Type for...") :
265
+ process.stdout.write("\x1B[K");
266
+
267
+ if (fieldType === "array") {
268
+ // Prompt spécifique pour le type interne du tableau
269
+ const arrayInnerQuestion = {
270
+ type: "list",
271
+ name: "innerType",
272
+ message: `Type of elements in "${fieldName}[]"`,
273
+ default: "string", // Exclure array et object du sous-type pour simplifier
274
+ choices: baseTypeChoices.filter(
275
+ (c) => c !== "array" && c !== "object"
276
+ ),
277
+ };
278
+
279
+ const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
280
+ fieldType = `${innerAnswer.innerType}[]`;
281
+ } else if (fieldType === "enum") {
282
+ const enumName = capitalize(fieldName) + "Enum";
283
+ logInfo(
284
+ `Enum type selected. Remember to define ${enumName} in your code.`
285
+ );
286
+ fieldType = enumName;
287
+ } else if (fieldType === "object") {
288
+ // Prompt pour nommer l'objet (ou 'json' par défaut)
289
+ const objectNameQuestion = {
290
+ type: "input",
291
+ name: "objectName",
292
+ message: `Complex type name (DTO/Class or leave 'json') :`,
293
+ default: "json",
294
+ };
295
+
296
+ const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
297
+ fieldType = capitalize(objectAnswer.objectName.trim() || "json");
298
+ }
299
+
300
+ console.log(
301
+ ` Field type for "${fieldName}" : ${fieldType} ${success("[✓]")}`
302
+ );
303
+
304
+ fields.push({ name: fieldName, type: fieldType });
305
+ }
306
+
307
+ if (fields.length > 0) {
308
+ entitiesData.entities.push({ name, fields });
309
+ console.log(
310
+ `${info("[INFO]")} Entity "${name}" added with ${
311
+ fields.length
312
+ } field(s)`
313
+ );
314
+ }
315
+
316
+ const addMore = readline.keyInYNStrict("Add another entity?");
317
+ if (!addMore) break;
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Manages adding relationships between entities via an interactive interface.
323
+ * All prompts and logs are in English.
324
+ * @param {Object} entitiesData - The object containing entities and relations.
325
+ */
326
+ async function addRelations(entitiesData) {
327
+ if (entitiesData.entities.length < 2) {
328
+ logInfo("At least two entities are required to create a relationship.");
329
+ return;
330
+ }
331
+
332
+ let configuring = true;
333
+
334
+ while (configuring) {
335
+ const entityNames = entitiesData.entities.map((e) => e.name);
336
+
337
+ // 1. Interactive selection of entities
338
+ const answers = await actualInquirer.prompt([
339
+ {
340
+ type: "list",
341
+ name: "fromName",
342
+ message: "Select the source entity (From) :",
343
+ choices: entityNames,
344
+ },
345
+ {
346
+ type: "list",
347
+ name: "toName",
348
+ message: (prev) =>
349
+ `To which entity do you want to link ${prev.fromName} ?`,
350
+ choices: (prev) => entityNames.filter((name) => name !== prev.fromName),
351
+ },
352
+ ]);
353
+
354
+ // --- CHECK FOR EXISTING RELATIONSHIPS ---
355
+ const alreadyExists = entitiesData.relations.find(
356
+ (rel) =>
357
+ (rel.from === answers.fromName && rel.to === answers.toName) ||
358
+ (rel.from === answers.toName && rel.to === answers.fromName)
359
+ );
360
+
361
+ if (alreadyExists) {
362
+ logWarning(
363
+ `A relationship already exists between ${answers.fromName} and ${answers.toName} (${alreadyExists.type}).`
364
+ );
365
+
366
+ const { tryAgain } = await actualInquirer.prompt([
367
+ {
368
+ type: "confirm",
369
+ name: "tryAgain",
370
+ message: "Would you like to choose different entities ?",
371
+ default: true,
372
+ },
373
+ ]);
374
+
375
+ if (!tryAgain) break;
376
+ // Restart the loop
377
+ continue;
378
+ }
379
+
380
+ // 2. Select Relationship Type
381
+ const typeAnswer = await actualInquirer.prompt([
382
+ {
383
+ type: "list",
384
+ name: "relType",
385
+ message: "What is the relationship type?",
386
+ choices: [
387
+ {
388
+ name: `1-1 (One-to-One) : ${answers.fromName} is linked to exactly one ${answers.toName}`,
389
+ value: "1-1",
390
+ },
391
+ {
392
+ name: `1-n (One-to-Many) : ${answers.fromName} owns multiple ${answers.toName}s`,
393
+ value: "1-n",
394
+ },
395
+ {
396
+ name: `n-1 (Many-to-One) : Multiple ${answers.fromName}s belong to one ${answers.toName}`,
397
+ value: "n-1",
398
+ },
399
+ {
400
+ name: `n-n (Many-to-Many) : Multiple ${answers.fromName}s are linked to multiple ${answers.toName}s`,
401
+ value: "n-n",
402
+ },
403
+ ],
404
+ },
405
+ ]);
406
+
407
+ const from = entitiesData.entities.find((e) => e.name === answers.fromName);
408
+ const to = entitiesData.entities.find((e) => e.name === answers.toName);
409
+ const relType = typeAnswer.relType;
410
+
411
+ // 3. Register relationship
412
+ entitiesData.relations.push({
413
+ from: from.name,
414
+ to: to.name,
415
+ type: relType,
416
+ });
417
+
418
+ // 4. Automatic field injection
419
+ const fromLow = from.name.toLowerCase();
420
+ const toLow = to.name.toLowerCase();
421
+ if (relType === "1-1") {
422
+ from.fields.push(
423
+ { name: `${toLow}Id`, type: "string" },
424
+ { name: toLow, type: to.name }
425
+ );
426
+ } else if (relType === "1-n") {
427
+ from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
428
+ to.fields.push(
429
+ { name: `${fromLow}Id`, type: "string" },
430
+ { name: fromLow, type: from.name }
431
+ );
432
+ } else if (relType === "n-1") {
433
+ from.fields.push(
434
+ { name: `${toLow}Id`, type: "string" },
435
+ { name: toLow, type: to.name }
436
+ );
437
+ to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
438
+ } else if (relType === "n-n") {
439
+ from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
440
+ to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
441
+ }
442
+ console.log(
443
+ `${success("[✓]")} Relationship added: ${from.name} ${relType} ${to.name}`
444
+ );
445
+
446
+ // 5. Ask to continue
447
+ const { addMore } = await actualInquirer.prompt([
448
+ {
449
+ type: "confirm",
450
+ name: "addMore",
451
+ message: "Do you want to add another relationship ?",
452
+ default: false,
453
+ },
454
+ ]);
455
+
456
+ configuring = addMore;
457
+ }
458
+ }
459
+
460
+ module.exports = { getLightModeInputs };