nestcraftx 0.2.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/AUDIT_ROADMAP.md +798 -0
  2. package/CHANGELOG.fr.md +22 -0
  3. package/CHANGELOG.md +22 -0
  4. package/PROGRESS.md +259 -0
  5. package/commands/demo.js +2 -42
  6. package/commands/generate.js +5 -4
  7. package/commands/help.js +29 -2
  8. package/commands/new.js +6 -49
  9. package/package.json +1 -1
  10. package/utils/app-module.updater.js +102 -0
  11. package/utils/cliParser.js +0 -45
  12. package/utils/configs/setupCleanArchitecture.js +2 -2
  13. package/utils/envGenerator.js +6 -0
  14. package/utils/file-system.js +90 -0
  15. package/utils/file-utils/packageJsonUtils.js +6 -0
  16. package/utils/file-utils/saveProjectConfig.js +6 -0
  17. package/utils/fullModeInput.js +8 -229
  18. package/utils/generators/application/dtoGenerator.js +252 -0
  19. package/utils/generators/application/dtoUpdater.js +5 -0
  20. package/utils/generators/database/setupDatabase.js +32 -14
  21. package/utils/generators/domain/entityGenerator.js +126 -0
  22. package/utils/generators/domain/entityUpdater.js +5 -0
  23. package/utils/generators/infrastructure/mapperGenerator.js +114 -0
  24. package/utils/generators/infrastructure/mapperUpdater.js +5 -0
  25. package/utils/generators/infrastructure/middlewareGenerator.js +645 -0
  26. package/utils/generators/infrastructure/mongooseSchemaGenerator.js +229 -0
  27. package/utils/generators/infrastructure/repositoryGenerator.js +334 -0
  28. package/utils/generators/presentation/controllerGenerator.js +142 -0
  29. package/utils/helpers.js +115 -0
  30. package/utils/interactive/askEntityInputs.js +5 -68
  31. package/utils/interactive/entityBuilder.js +400 -0
  32. package/utils/lightModeInput.js +13 -246
  33. package/utils/setups/orms/typeOrmSetup.js +31 -4
  34. package/utils/setups/projectSetup.js +78 -8
  35. package/utils/setups/setupAuth.js +6 -7
  36. package/utils/setups/setupDatabase.js +2 -1
  37. package/utils/setups/setupMongoose.js +96 -30
  38. package/utils/setups/setupPrisma.js +118 -134
  39. package/utils/setups/setupSwagger.js +12 -8
  40. package/utils/shell.js +125 -10
  41. package/utils/userInput.js +37 -101
  42. package/utils/utils.js +29 -2164
@@ -0,0 +1,400 @@
1
+ const readline = require("readline-sync");
2
+ const inquirer = require("inquirer");
3
+ const { info, success, warning } = require("../colors");
4
+ const { logWarning } = require("../loggers/logWarning");
5
+ const { capitalize } = require("../helpers");
6
+ const actualInquirer = inquirer.default || inquirer;
7
+
8
+ /**
9
+ * Interactive entity builder with full revision capabilities.
10
+ * @param {string} [initialName] - Optional starting name for the entity.
11
+ * @returns {Promise<{name: string, fields: Array<{name: string, type: string, unique: boolean, nullable: boolean, default: string|null>}|null>}
12
+ */
13
+ async function buildEntityInteractive(initialName) {
14
+ let entityName = initialName;
15
+ while (!entityName || !/^[A-Za-z][A-Za-z0-9_]*$/.test(entityName)) {
16
+ entityName = readline.question(`\n${info("[?]")} Entity name : `);
17
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(entityName)) {
18
+ logWarning("Invalid name. Letters, numbers, _ (start with a letter).");
19
+ }
20
+ }
21
+
22
+ const fields = [];
23
+
24
+ console.log(`\n Fields for "${entityName}" :`);
25
+
26
+ // Initial field prompting loop
27
+ while (true) {
28
+ let fname = readline.question(" Field name (leave empty to finish) : ");
29
+ if (!fname) break;
30
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
31
+ logWarning("Invalid field name.");
32
+ continue;
33
+ }
34
+
35
+ const field = await promptFieldDetails(fname);
36
+ fields.push(field);
37
+ const defaultText = field.default !== null && field.default !== "" ? `, default=${field.default}` : " (no default)";
38
+ console.log(` Field "${fname}" added: type=${field.type}, unique=${field.unique}, nullable=${field.nullable}${defaultText} ${success("[✓]")}`);
39
+ }
40
+
41
+ // Revision Loop
42
+ while (true) {
43
+ console.log(`\n--------------------------------------------`);
44
+ console.log(`📋 Current Entity: "${entityName}"`);
45
+ if (fields.length === 0) {
46
+ console.log(" (No fields added yet)");
47
+ } else {
48
+ fields.forEach((f, idx) => {
49
+ const constraints = [];
50
+ if (f.unique) constraints.push("unique");
51
+ if (f.nullable) constraints.push("nullable");
52
+ if (f.default !== null && f.default !== "") constraints.push(`default: ${f.default}`);
53
+ const constraintStr = constraints.length > 0 ? ` (${constraints.join(", ")})` : "";
54
+ console.log(` ${idx + 1}. ${f.name} : ${f.type}${constraintStr}`);
55
+ });
56
+ }
57
+ console.log(`--------------------------------------------`);
58
+
59
+ const answers = await actualInquirer.prompt([
60
+ {
61
+ type: "list",
62
+ name: "action",
63
+ message: "Select an option:",
64
+ choices: [
65
+ { name: "✅ Confirm and continue", value: "confirm" },
66
+ { name: "➕ Add another field", value: "add" },
67
+ { name: "✏️ Edit an existing field", value: "edit" },
68
+ { name: "🗑️ Delete a field", value: "delete" },
69
+ { name: "✏️ Rename the entity", value: "rename" },
70
+ { name: "❌ Cancel this entity", value: "cancel" },
71
+ ],
72
+ },
73
+ ]);
74
+
75
+ switch (answers.action) {
76
+ case "confirm":
77
+ return { name: entityName, fields };
78
+
79
+ case "add": {
80
+ let fname = readline.question("\n Field name (leave empty to cancel) : ");
81
+ if (!fname) break;
82
+ if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
83
+ logWarning("Invalid field name.");
84
+ break;
85
+ }
86
+ const field = await promptFieldDetails(fname);
87
+ fields.push(field);
88
+ const defaultText = field.default !== null && field.default !== "" ? `, default=${field.default}` : " (no default)";
89
+ console.log(` Field "${fname}" added: type=${field.type}, unique=${field.unique}, nullable=${field.nullable}${defaultText} ${success("[✓]")}`);
90
+ break;
91
+ }
92
+
93
+ case "delete": {
94
+ if (fields.length === 0) {
95
+ logWarning("No fields to delete.");
96
+ break;
97
+ }
98
+ const deleteAns = await actualInquirer.prompt([
99
+ {
100
+ type: "list",
101
+ name: "targetIndex",
102
+ message: "Select the field to delete:",
103
+ choices: fields.map((f, idx) => ({
104
+ name: `${f.name} (${f.type})`,
105
+ value: idx,
106
+ })),
107
+ },
108
+ ]);
109
+ const deletedField = fields.splice(deleteAns.targetIndex, 1)[0];
110
+ console.log(`${success("[✓]")} Field "${deletedField.name}" deleted.`);
111
+ break;
112
+ }
113
+
114
+ case "rename": {
115
+ let newName = readline.question(`\n${info("[?]")} New Entity name [${entityName}]: `);
116
+ if (!newName) break;
117
+ if (/^[A-Za-z][A-Za-z0-9_]*$/.test(newName)) {
118
+ entityName = newName;
119
+ console.log(`${success("[✓]")} Entity renamed to "${entityName}"`);
120
+ } else {
121
+ logWarning("Invalid name.");
122
+ }
123
+ break;
124
+ }
125
+
126
+ case "edit": {
127
+ if (fields.length === 0) {
128
+ logWarning("No fields to edit.");
129
+ break;
130
+ }
131
+ const selectAns = await actualInquirer.prompt([
132
+ {
133
+ type: "list",
134
+ name: "targetIndex",
135
+ message: "Select the field to edit:",
136
+ choices: fields.map((f, idx) => ({
137
+ name: `${f.name} (${f.type})`,
138
+ value: idx,
139
+ })),
140
+ },
141
+ ]);
142
+ const fIdx = selectAns.targetIndex;
143
+ const currentField = fields[fIdx];
144
+
145
+ console.log(`\n✏️ Editing field "${currentField.name}":`);
146
+ let newName = readline.question(` New field name [${currentField.name}]: `) || currentField.name;
147
+ while (!/^[A-Za-z][A-Za-z0-9_]*$/.test(newName)) {
148
+ logWarning("Invalid field name.");
149
+ newName = readline.question(` New field name [${currentField.name}]: `) || currentField.name;
150
+ }
151
+
152
+ const editedField = await promptFieldDetails(newName, currentField);
153
+ fields[fIdx] = editedField;
154
+ console.log(`${success("[✓]")} Field successfully updated.`);
155
+ break;
156
+ }
157
+
158
+ case "cancel": {
159
+ const confirmCancel = readline.keyInYNStrict(`${warning("[warning]")} Are you sure you want to cancel and discard this entity?`);
160
+ if (confirmCancel) {
161
+ return null;
162
+ }
163
+ break;
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Prompts the user for all details and constraints of a field.
171
+ * @private
172
+ */
173
+ async function promptFieldDetails(fieldName, existingField = null) {
174
+ const baseTypeChoices = [
175
+ "string",
176
+ "text",
177
+ "number",
178
+ "boolean",
179
+ "Date",
180
+ "uuid",
181
+ "json",
182
+ "enum",
183
+ "array",
184
+ "object",
185
+ ];
186
+
187
+ let defaultType = "string";
188
+ if (existingField) {
189
+ if (existingField.type.endsWith("[]")) defaultType = "array";
190
+ else if (existingField.type.endsWith("Enum")) defaultType = "enum";
191
+ else if (baseTypeChoices.includes(existingField.type)) defaultType = existingField.type;
192
+ else defaultType = "object";
193
+ }
194
+
195
+ const typeQuestion = {
196
+ type: "list",
197
+ name: "ftype",
198
+ message: `Type for "${fieldName}"`,
199
+ default: defaultType,
200
+ choices: baseTypeChoices,
201
+ };
202
+
203
+ const typeAnswer = await actualInquirer.prompt([typeQuestion]);
204
+ let ftype = typeAnswer.ftype;
205
+
206
+ if (ftype === "array") {
207
+ let defaultInner = "string";
208
+ if (existingField && existingField.type.endsWith("[]")) {
209
+ defaultInner = existingField.type.slice(0, -2);
210
+ }
211
+ const arrayInnerQuestion = {
212
+ type: "list",
213
+ name: "innerType",
214
+ message: `Type of elements for "${fieldName}[]"`,
215
+ default: defaultInner,
216
+ choices: baseTypeChoices.filter((c) => c !== "array" && c !== "object"),
217
+ };
218
+ const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
219
+ ftype = `${innerAnswer.innerType}[]`;
220
+ } else if (ftype === "enum") {
221
+ const enumName = capitalize(fieldName) + "Enum";
222
+ console.log(` ${info("[INFO]")} Enum type selected: ${enumName}`);
223
+ ftype = enumName;
224
+ } else if (ftype === "object") {
225
+ let defaultObj = "json";
226
+ if (existingField && !baseTypeChoices.includes(existingField.type) && !existingField.type.endsWith("[]") && !existingField.type.endsWith("Enum")) {
227
+ defaultObj = existingField.type;
228
+ }
229
+ const objectNameQuestion = {
230
+ type: "input",
231
+ name: "objectName",
232
+ message: `Complex type name (DTO/Class or leave 'json') :`,
233
+ default: defaultObj,
234
+ };
235
+ const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
236
+ ftype = capitalize(objectAnswer.objectName.trim() || "json");
237
+ }
238
+
239
+ // Ask constraints: unique, nullable, default
240
+ const isUnique = readline.keyInYNStrict(` Is "${fieldName}" unique?`, {
241
+ defaultInput: existingField && existingField.unique ? "y" : "n",
242
+ });
243
+
244
+ const isNullable = readline.keyInYNStrict(` Is "${fieldName}" nullable/optional?`, {
245
+ defaultInput: existingField && existingField.nullable ? "y" : "n",
246
+ });
247
+
248
+ let defaultValuePrompt = " Default value (press Enter for none): ";
249
+ if (existingField && existingField.default !== null) {
250
+ defaultValuePrompt = ` Default value [${existingField.default}]: `;
251
+ }
252
+ let defaultValue = readline.question(defaultValuePrompt);
253
+ if (defaultValue === "") {
254
+ defaultValue = existingField ? existingField.default : null;
255
+ } else if (defaultValue.toLowerCase() === "none" || defaultValue.toLowerCase() === "null") {
256
+ defaultValue = null;
257
+ }
258
+
259
+ return {
260
+ name: fieldName,
261
+ type: ftype,
262
+ unique: isUnique,
263
+ nullable: isNullable,
264
+ default: defaultValue,
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Manages adding relationships between entities via an interactive interface.
270
+ * @param {Object} entitiesData - The object containing entities and relations.
271
+ */
272
+ async function buildRelationsInteractive(entitiesData) {
273
+ if (entitiesData.entities.length < 2) {
274
+ logWarning("At least two entities are required to configure a relationship.");
275
+ return;
276
+ }
277
+
278
+ let configuring = true;
279
+ while (configuring) {
280
+ const entityNames = entitiesData.entities.map((e) => e.name);
281
+
282
+ // 1. Select entities
283
+ const selection = await actualInquirer.prompt([
284
+ {
285
+ type: "list",
286
+ name: "fromName",
287
+ message: "Select the source entity (From) :",
288
+ choices: entityNames,
289
+ },
290
+ {
291
+ type: "list",
292
+ name: "toName",
293
+ message: (prev) => `To which entity should "${prev.fromName}" be linked?`,
294
+ choices: (prev) => entityNames.filter((name) => name !== prev.fromName),
295
+ },
296
+ ]);
297
+
298
+ // Check if link already exists
299
+ const alreadyExists = entitiesData.relations.find(
300
+ (rel) =>
301
+ (rel.from === selection.fromName && rel.to === selection.toName) ||
302
+ (rel.from === selection.toName && rel.to === selection.fromName)
303
+ );
304
+
305
+ if (alreadyExists) {
306
+ logWarning(`A relationship already exists between ${selection.fromName} and ${selection.toName} (${alreadyExists.type}).`);
307
+ const { tryAgain } = await actualInquirer.prompt([
308
+ {
309
+ type: "confirm",
310
+ name: "tryAgain",
311
+ message: "Do you want to choose different entities?",
312
+ default: true,
313
+ },
314
+ ]);
315
+ if (!tryAgain) break;
316
+ continue;
317
+ }
318
+
319
+ // 2. Select Relationship type
320
+ const typeAnswer = await actualInquirer.prompt([
321
+ {
322
+ type: "list",
323
+ name: "relType",
324
+ message: "Relationship type:",
325
+ choices: [
326
+ {
327
+ name: `1-1 (One-to-One) : ${selection.fromName} has one ${selection.toName}`,
328
+ value: "1-1",
329
+ },
330
+ {
331
+ name: `1-n (One-to-Many) : ${selection.fromName} has many ${selection.toName}s`,
332
+ value: "1-n",
333
+ },
334
+ {
335
+ name: `n-1 (Many-to-One) : Many ${selection.fromName}s belong to one ${selection.toName}`,
336
+ value: "n-1",
337
+ },
338
+ {
339
+ name: `n-n (Many-to-Many) : Many ${selection.fromName}s linked to many ${selection.toName}s`,
340
+ value: "n-n",
341
+ },
342
+ ],
343
+ },
344
+ ]);
345
+
346
+ const from = entitiesData.entities.find((e) => e.name === selection.fromName);
347
+ const to = entitiesData.entities.find((e) => e.name === selection.toName);
348
+ const relType = typeAnswer.relType;
349
+
350
+ // Register Relationship
351
+ entitiesData.relations.push({
352
+ from: from.name,
353
+ to: to.name,
354
+ type: relType,
355
+ });
356
+
357
+ const fromLow = from.name.toLowerCase();
358
+ const toLow = to.name.toLowerCase();
359
+
360
+ // Add fields logic
361
+ if (relType === "1-1") {
362
+ from.fields.push(
363
+ { name: `${toLow}Id`, type: "string" },
364
+ { name: toLow, type: to.name }
365
+ );
366
+ } else if (relType === "1-n") {
367
+ from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
368
+ to.fields.push(
369
+ { name: `${fromLow}Id`, type: "string" },
370
+ { name: fromLow, type: from.name }
371
+ );
372
+ } else if (relType === "n-1") {
373
+ from.fields.push(
374
+ { name: `${toLow}Id`, type: "string" },
375
+ { name: toLow, type: to.name }
376
+ );
377
+ to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
378
+ } else if (relType === "n-n") {
379
+ from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
380
+ to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
381
+ }
382
+
383
+ console.log(`\n${success("[✓]")} Relationship added: ${from.name} ${relType} ${to.name}`);
384
+
385
+ const { addMore } = await actualInquirer.prompt([
386
+ {
387
+ type: "confirm",
388
+ name: "addMore",
389
+ message: "Add another relationship?",
390
+ default: false,
391
+ },
392
+ ]);
393
+ configuring = addMore;
394
+ }
395
+ }
396
+
397
+ module.exports = {
398
+ buildEntityInteractive,
399
+ buildRelationsInteractive,
400
+ };
@@ -6,6 +6,7 @@ const { capitalize } = require("./userInput");
6
6
  const { logWarning } = require("./loggers/logWarning");
7
7
  const { logInfo } = require("./loggers/logInfo");
8
8
  const { getPackageManager } = require("./utils");
9
+ const { buildEntityInteractive, buildRelationsInteractive } = require("./interactive/entityBuilder");
9
10
  const actualInquirer = inquirer.default || inquirer;
10
11
 
11
12
  async function getLightModeInputs(projectName, flags) {
@@ -139,7 +140,17 @@ async function getLightModeInputs(projectName, flags) {
139
140
  );
140
141
  if (addEntities) {
141
142
  console.log(`\n${info("[INFO]")} Entity input (simplified mode)`);
142
- await addCustomEntities(inputs.entitiesData);
143
+ while (true) {
144
+ const entity = await buildEntityInteractive();
145
+ if (entity) {
146
+ inputs.entitiesData.entities.push(entity);
147
+ console.log(
148
+ `${info("[INFO]")} Entity "${entity.name}" added with ${entity.fields.length} field(s)`
149
+ );
150
+ }
151
+ const addMore = readline.keyInYNStrict("Add another entity?");
152
+ if (!addMore) break;
153
+ }
143
154
  }
144
155
 
145
156
  // Demander les relations entre entités
@@ -149,7 +160,7 @@ async function getLightModeInputs(projectName, flags) {
149
160
  );
150
161
  if (wantsRelation) {
151
162
  console.log(`\n${info("[INFO]")} Configuring relationships`);
152
- await addRelations(inputs.entitiesData);
163
+ await buildRelationsInteractive(inputs.entitiesData);
153
164
  }
154
165
  }
155
166
 
@@ -213,248 +224,4 @@ function getDockerChoice(flags) {
213
224
  return readline.keyInYNStrict(`${info("[?]")} Generate Docker files ?`);
214
225
  }
215
226
 
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
227
  module.exports = { getLightModeInputs };