nestcraftx 0.3.0 → 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.
- package/PROGRESS.md +13 -12
- package/commands/demo.js +2 -46
- package/commands/new.js +2 -47
- package/package.json +1 -1
- package/utils/app-module.updater.js +6 -0
- package/utils/envGenerator.js +6 -0
- package/utils/file-system.js +15 -0
- package/utils/file-utils/packageJsonUtils.js +6 -0
- package/utils/file-utils/saveProjectConfig.js +6 -0
- package/utils/fullModeInput.js +8 -229
- package/utils/generators/application/dtoGenerator.js +6 -5
- package/utils/generators/application/dtoUpdater.js +5 -0
- package/utils/generators/domain/entityUpdater.js +5 -0
- package/utils/generators/infrastructure/mapperUpdater.js +5 -0
- package/utils/generators/infrastructure/mongooseSchemaGenerator.js +36 -3
- package/utils/helpers.js +8 -2
- package/utils/interactive/askEntityInputs.js +5 -68
- package/utils/interactive/entityBuilder.js +400 -0
- package/utils/lightModeInput.js +13 -246
- package/utils/setups/orms/typeOrmSetup.js +31 -4
- package/utils/setups/projectSetup.js +78 -8
- package/utils/setups/setupAuth.js +1 -2
- package/utils/setups/setupMongoose.js +80 -31
- package/utils/setups/setupPrisma.js +41 -3
- package/utils/setups/setupSwagger.js +12 -8
- package/utils/shell.js +44 -9
|
@@ -119,6 +119,7 @@ async function generateMongooseSchemaFileContent(
|
|
|
119
119
|
"number",
|
|
120
120
|
"int",
|
|
121
121
|
"float",
|
|
122
|
+
"decimal",
|
|
122
123
|
"boolean",
|
|
123
124
|
"date",
|
|
124
125
|
"uuid",
|
|
@@ -144,35 +145,67 @@ async function generateMongooseSchemaFileContent(
|
|
|
144
145
|
|
|
145
146
|
// 2. Mapping des types CLI -> TypeScript/Mongoose
|
|
146
147
|
let tsType = "string";
|
|
147
|
-
let
|
|
148
|
+
let propOpts = [];
|
|
148
149
|
|
|
149
150
|
switch (rawType) {
|
|
150
151
|
case "text":
|
|
151
152
|
case "uuid":
|
|
152
153
|
case "string":
|
|
153
154
|
tsType = "string";
|
|
155
|
+
propOpts.push("type: String");
|
|
154
156
|
break;
|
|
155
157
|
case "int":
|
|
156
158
|
case "number":
|
|
157
159
|
case "float":
|
|
158
160
|
case "decimal":
|
|
159
161
|
tsType = "number";
|
|
162
|
+
propOpts.push("type: Number");
|
|
160
163
|
break;
|
|
161
164
|
case "boolean":
|
|
162
165
|
tsType = "boolean";
|
|
166
|
+
propOpts.push("type: Boolean");
|
|
163
167
|
break;
|
|
164
168
|
case "date":
|
|
165
169
|
tsType = "Date";
|
|
170
|
+
propOpts.push("type: Date");
|
|
166
171
|
break;
|
|
167
172
|
case "json":
|
|
168
173
|
tsType = "Record<string, any>";
|
|
169
|
-
|
|
174
|
+
propOpts.push("type: Object");
|
|
170
175
|
break;
|
|
171
176
|
default:
|
|
172
177
|
tsType = "any";
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
|
|
180
|
+
const isRequired = f.nullable !== undefined ? !f.nullable : (fieldName.toLowerCase() !== "isactive" && fieldName.toLowerCase() !== "password");
|
|
181
|
+
if (isRequired) {
|
|
182
|
+
propOpts.push("required: true");
|
|
183
|
+
}
|
|
184
|
+
if (f.unique) {
|
|
185
|
+
propOpts.push("unique: true");
|
|
186
|
+
}
|
|
187
|
+
if (f.default !== undefined && f.default !== null && f.default !== "") {
|
|
188
|
+
let defaultVal = f.default;
|
|
189
|
+
if (typeof defaultVal === "string") {
|
|
190
|
+
if (defaultVal === "true") defaultVal = true;
|
|
191
|
+
else if (defaultVal === "false") defaultVal = false;
|
|
192
|
+
else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
|
|
193
|
+
defaultVal = "Date.now";
|
|
194
|
+
} else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
|
|
195
|
+
defaultVal = Number(defaultVal);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (defaultVal === "Date.now") {
|
|
199
|
+
propOpts.push("default: Date.now");
|
|
200
|
+
} else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
|
|
201
|
+
propOpts.push(`default: ${defaultVal}`);
|
|
202
|
+
} else {
|
|
203
|
+
propOpts.push(`default: '${defaultVal}'`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const propOptions = propOpts.join(", ");
|
|
208
|
+
return ` @Prop({ ${propOptions} })\n ${fieldName}${f.nullable ? "?" : ""}: ${tsType};`;
|
|
176
209
|
});
|
|
177
210
|
|
|
178
211
|
const allFields = [...new Set([...directFields, ...dynamicRelations])].join(
|
package/utils/helpers.js
CHANGED
|
@@ -94,10 +94,16 @@ function formatType(type) {
|
|
|
94
94
|
* @returns {string} Le type final formaté
|
|
95
95
|
*/
|
|
96
96
|
function getFormattedType(field) {
|
|
97
|
+
let typeStr;
|
|
97
98
|
if (field.name === "role" && field.type.toLowerCase().startsWith("string")) {
|
|
98
|
-
|
|
99
|
+
typeStr = "Role";
|
|
100
|
+
} else {
|
|
101
|
+
typeStr = formatType(field.type);
|
|
99
102
|
}
|
|
100
|
-
|
|
103
|
+
if (field.nullable) {
|
|
104
|
+
typeStr += " | null";
|
|
105
|
+
}
|
|
106
|
+
return typeStr;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
module.exports = {
|
|
@@ -6,78 +6,15 @@ const inquirer = require("inquirer");
|
|
|
6
6
|
const { info, success } = require("../colors");
|
|
7
7
|
const { logWarning } = require("../loggers/logWarning");
|
|
8
8
|
const { capitalize } = require("../userInput");
|
|
9
|
+
const { buildEntityInteractive } = require("./entityBuilder");
|
|
9
10
|
const actualInquirer = inquirer.default || inquirer;
|
|
10
11
|
|
|
11
12
|
async function askEntityInputs(targetName) {
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
`\n${info("[ENTITY DESIGN]")} Define fields for "${targetName}" :`,
|
|
16
|
-
);
|
|
17
|
-
|
|
18
|
-
while (true) {
|
|
19
|
-
let fname = readline.question(" Field name (leave empty to finish) : ");
|
|
20
|
-
if (!fname) break;
|
|
21
|
-
|
|
22
|
-
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
|
|
23
|
-
logWarning("Invalid field name.");
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const baseTypeChoices = [
|
|
28
|
-
"string",
|
|
29
|
-
"text",
|
|
30
|
-
"number",
|
|
31
|
-
"boolean",
|
|
32
|
-
"Date",
|
|
33
|
-
"uuid",
|
|
34
|
-
"json",
|
|
35
|
-
"enum",
|
|
36
|
-
"array",
|
|
37
|
-
"object",
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
const typeAnswer = await actualInquirer.prompt([
|
|
41
|
-
{
|
|
42
|
-
type: "list",
|
|
43
|
-
name: "ftype",
|
|
44
|
-
message: `Type for "${fname}"`,
|
|
45
|
-
choices: baseTypeChoices,
|
|
46
|
-
},
|
|
47
|
-
]);
|
|
48
|
-
|
|
49
|
-
let ftype = typeAnswer.ftype;
|
|
50
|
-
|
|
51
|
-
// Logique de raffinement des types (identique à ton script original)
|
|
52
|
-
if (ftype === "array") {
|
|
53
|
-
const inner = await actualInquirer.prompt([
|
|
54
|
-
{
|
|
55
|
-
type: "list",
|
|
56
|
-
name: "innerType",
|
|
57
|
-
message: `Type of elements for "${fname}[]"`,
|
|
58
|
-
choices: baseTypeChoices.filter(
|
|
59
|
-
(c) => c !== "array" && c !== "object",
|
|
60
|
-
),
|
|
61
|
-
},
|
|
62
|
-
]);
|
|
63
|
-
ftype = `${inner.innerType}[]`;
|
|
64
|
-
} else if (ftype === "enum") {
|
|
65
|
-
ftype = capitalize(fname) + "Enum";
|
|
66
|
-
} else if (ftype === "object") {
|
|
67
|
-
const obj = await actualInquirer.prompt([
|
|
68
|
-
{
|
|
69
|
-
type: "input",
|
|
70
|
-
name: "val",
|
|
71
|
-
message: "Complex type name :",
|
|
72
|
-
default: "json",
|
|
73
|
-
},
|
|
74
|
-
]);
|
|
75
|
-
ftype = capitalize(obj.val);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
entity.fields.push({ name: fname, type: ftype });
|
|
79
|
-
console.log(` Type for "${fname}" : ${ftype} ${success("[✓]")}`);
|
|
13
|
+
const result = await buildEntityInteractive(targetName);
|
|
14
|
+
if (!result) {
|
|
15
|
+
throw new Error("Module generation cancelled by user.");
|
|
80
16
|
}
|
|
17
|
+
const entity = { name: result.name, fields: result.fields, relation: null };
|
|
81
18
|
|
|
82
19
|
const relationData = await askRelationInputs(entity.name);
|
|
83
20
|
entity.relation = relationData;
|
|
@@ -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
|
+
};
|