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.
- package/.gitattributes +6 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/ISSUE_TEMPLATE/pull_request_template.md +24 -0
- package/CHANGELOG.fr.md +97 -97
- package/CHANGELOG.md +98 -98
- package/CLI_USAGE.fr.md +331 -331
- package/CLI_USAGE.md +364 -364
- package/DEMO.fr.md +292 -292
- package/DEMO.md +294 -294
- package/LICENSE +21 -21
- package/MIGRATION_GUIDE.fr.md +127 -127
- package/MIGRATION_GUIDE.md +124 -124
- package/QUICK_START.fr.md +152 -152
- package/QUICK_START.md +169 -169
- package/README.fr.md +653 -659
- package/SECURITY.md +10 -0
- package/bin/nestcraft.js +84 -64
- package/commands/demo.js +333 -330
- package/commands/generate.js +93 -0
- package/commands/generateConf.js +91 -0
- package/commands/help.js +78 -78
- package/commands/info.js +48 -48
- package/commands/new.js +338 -335
- package/commands/start.js +19 -19
- package/commands/test.js +7 -7
- package/package.json +41 -41
- package/readme.md +638 -643
- package/utils/cliParser.js +133 -76
- package/utils/colors.js +62 -62
- package/utils/configs/configureDocker.js +120 -120
- package/utils/configs/setupCleanArchitecture.js +563 -557
- package/utils/configs/setupLightArchitecture.js +701 -660
- package/utils/envGenerator.js +122 -122
- package/utils/file-utils/packageJsonUtils.js +49 -55
- package/utils/file-utils/saveProjectConfig.js +36 -0
- package/utils/fullModeInput.js +607 -607
- package/utils/generators/application/dtoUpdater.js +54 -0
- package/utils/generators/cleanModuleGenerator.js +475 -0
- package/utils/generators/database/setupDatabase.js +31 -0
- package/utils/generators/domain/entityUpdater.js +78 -0
- package/utils/generators/infrastructure/mapperUpdater.js +65 -0
- package/utils/generators/lightModuleGenerator.js +131 -0
- package/utils/generators/relation/relation.engine.js +64 -0
- package/utils/interactive/askEntityInputs.js +165 -0
- package/utils/lightModeInput.js +460 -460
- package/utils/loggers/logError.js +7 -7
- package/utils/loggers/logInfo.js +7 -7
- package/utils/loggers/logSuccess.js +7 -7
- package/utils/loggers/logWarning.js +7 -7
- package/utils/setups/orms/typeOrmSetup.js +630 -630
- package/utils/setups/projectSetup.js +46 -46
- package/utils/setups/setupAuth.js +973 -926
- package/utils/setups/setupDatabase.js +75 -75
- package/utils/setups/setupLogger.js +69 -59
- package/utils/setups/setupMongoose.js +377 -432
- package/utils/setups/setupPrisma.js +802 -630
- package/utils/setups/setupSwagger.js +97 -88
- package/utils/shell.js +32 -32
- package/utils/spinner.js +57 -57
- package/utils/systemCheck.js +124 -124
- package/utils/userInput.js +421 -421
- package/utils/utils.js +2197 -1762
package/utils/fullModeInput.js
CHANGED
|
@@ -1,607 +1,607 @@
|
|
|
1
|
-
const readline = require("readline-sync");
|
|
2
|
-
const { info, success, warning } = require("./colors");
|
|
3
|
-
const inquirer = require("inquirer");
|
|
4
|
-
const { capitalize } = require("./userInput");
|
|
5
|
-
const { logWarning } = require("./loggers/logWarning");
|
|
6
|
-
const { getPackageManager } = require("./utils");
|
|
7
|
-
const actualInquirer = inquirer.default || inquirer;
|
|
8
|
-
|
|
9
|
-
async function getFullModeInputs(projectName, flags) {
|
|
10
|
-
console.log(
|
|
11
|
-
`\n${info("[FULL MODE]")} Complete configuration with Clean Architecture\n`
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
const dataBases = [
|
|
15
|
-
{
|
|
16
|
-
name: "postgresql",
|
|
17
|
-
label: "PostgreSQL",
|
|
18
|
-
ormOptions: ["prisma", "typeorm"],
|
|
19
|
-
required: [
|
|
20
|
-
{
|
|
21
|
-
title: "PostgreSQL User",
|
|
22
|
-
envVar: "POSTGRES_USER",
|
|
23
|
-
defaultValue: "postgres",
|
|
24
|
-
hideEchoBack: false,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
title: "PostgreSQL Password",
|
|
28
|
-
envVar: "POSTGRES_PASSWORD",
|
|
29
|
-
defaultValue: "postgres",
|
|
30
|
-
hideEchoBack: true, // Hide password
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
title: "PostgreSQL Database Name",
|
|
34
|
-
envVar: "POSTGRES_DB",
|
|
35
|
-
defaultValue: `${projectName}-db`,
|
|
36
|
-
hideEchoBack: false,
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
title: "PostgreSQL Host",
|
|
40
|
-
envVar: "POSTGRES_HOST",
|
|
41
|
-
defaultValue: "localhost",
|
|
42
|
-
hideEchoBack: false,
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
title: "PostgreSQL Port",
|
|
46
|
-
envVar: "POSTGRES_PORT",
|
|
47
|
-
defaultValue: "5432",
|
|
48
|
-
hideEchoBack: false,
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: "mongodb",
|
|
54
|
-
label: "MongoDB",
|
|
55
|
-
ormOptions: ["mongoose"],
|
|
56
|
-
required: [
|
|
57
|
-
{
|
|
58
|
-
title: "MongoDB URI",
|
|
59
|
-
envVar: "MONGO_URI",
|
|
60
|
-
defaultValue: "mongodb://localhost:27017",
|
|
61
|
-
hideEchoBack: false,
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
title: "MongoDB Database Name",
|
|
65
|
-
envVar: "MONGO_DB",
|
|
66
|
-
defaultValue: `${projectName}-db`,
|
|
67
|
-
hideEchoBack: false,
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
];
|
|
72
|
-
|
|
73
|
-
let currentProjectName = projectName;
|
|
74
|
-
// La validation du nom de projet reste interactive en cas d'échec
|
|
75
|
-
while (true) {
|
|
76
|
-
if (/^[A-Za-z][A-Za-z0-9_-]*$/.test(currentProjectName)) break;
|
|
77
|
-
|
|
78
|
-
currentProjectName = readline.question(`${info("[?]")} Project name : `);
|
|
79
|
-
|
|
80
|
-
logWarning(
|
|
81
|
-
"Invalid name. Use letters, numbers, _ or - (start with a letter)."
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// --- 1. Database Selection ---
|
|
86
|
-
const defaultDB = "postgresql";
|
|
87
|
-
let usedDB;
|
|
88
|
-
|
|
89
|
-
// Prioriser le flag 'db' si présent
|
|
90
|
-
if (flags.db && flags.db !== undefined) {
|
|
91
|
-
usedDB = getFlagValue(flags, "db", defaultDB);
|
|
92
|
-
console.log(
|
|
93
|
-
`${info("[?]")} Database (postgresql, mongodb) : ${usedDB} ${success(
|
|
94
|
-
"[flag]"
|
|
95
|
-
)}`
|
|
96
|
-
);
|
|
97
|
-
} else {
|
|
98
|
-
usedDB = readline.question(
|
|
99
|
-
`${info("[?]")} Database (postgresql, mongodb) [${defaultDB}] : `,
|
|
100
|
-
{ defaultInput: defaultDB }
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
let selectedDB = dataBases.find(
|
|
105
|
-
(db) => db.name.toLowerCase() === usedDB.toLowerCase()
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
// Revenir au mode interactif si le flag fourni est invalide ou si l'utilisateur a saisi une valeur invalide
|
|
109
|
-
while (!selectedDB) {
|
|
110
|
-
logWarning("Database not recognized.");
|
|
111
|
-
|
|
112
|
-
usedDB = readline.question(
|
|
113
|
-
`${info("[?]")} Database (postgresql, mongodb) : `
|
|
114
|
-
);
|
|
115
|
-
selectedDB = dataBases.find(
|
|
116
|
-
(db) => db.name.toLowerCase() === usedDB.toLowerCase()
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// --- 2. DB Configuration (Using flags) ---
|
|
121
|
-
const dbConfig = {};
|
|
122
|
-
console.log(`\n${info("[INFO]")} ${selectedDB.label} Configuration`);
|
|
123
|
-
|
|
124
|
-
selectedDB.required.forEach((field) => {
|
|
125
|
-
// Détermine la clé de flag (ex: 'dbUser' pour POSTGRES_USER ou 'mongoUri' pour MONGO_URI)
|
|
126
|
-
const flagName = field.envVar
|
|
127
|
-
.toLowerCase()
|
|
128
|
-
.replace("postgres_", "db")
|
|
129
|
-
.replace("mongo_", "mongo");
|
|
130
|
-
|
|
131
|
-
const flagValue = flags[flagName];
|
|
132
|
-
let answer;
|
|
133
|
-
|
|
134
|
-
if (flagValue !== undefined) {
|
|
135
|
-
// Flag is present, use its value directly and skip prompt
|
|
136
|
-
answer = getFlagValue(flags, flagName, field.defaultValue);
|
|
137
|
-
|
|
138
|
-
const displayValue = field.hideEchoBack ? "***" : answer;
|
|
139
|
-
|
|
140
|
-
console.log(` ${field.title} : ${displayValue} ${success("[flag]")}`);
|
|
141
|
-
} else {
|
|
142
|
-
// Flag is absent, ask the question
|
|
143
|
-
while (true) {
|
|
144
|
-
answer = readline.question(
|
|
145
|
-
` ${field.title} [${field.defaultValue}] : `,
|
|
146
|
-
{
|
|
147
|
-
hideEchoBack: field.hideEchoBack,
|
|
148
|
-
defaultInput: field.defaultValue,
|
|
149
|
-
}
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
// If the user entered something OR if the default value is non-null, continue
|
|
153
|
-
if (answer || field.defaultValue !== null) break;
|
|
154
|
-
|
|
155
|
-
logWarning("This field is required.");
|
|
156
|
-
}
|
|
157
|
-
// If the user just pressed Enter, use the default value
|
|
158
|
-
answer = answer || field.defaultValue;
|
|
159
|
-
}
|
|
160
|
-
dbConfig[field.envVar] = answer;
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// --- 3. ORM Selection (Full Mode) ---
|
|
164
|
-
if (selectedDB.ormOptions && selectedDB.ormOptions.length > 0) {
|
|
165
|
-
const defaultOrm = selectedDB.ormOptions[0];
|
|
166
|
-
let ormChoice;
|
|
167
|
-
|
|
168
|
-
// 1. Vérification du Flag
|
|
169
|
-
if (flags.orm !== undefined) {
|
|
170
|
-
const flagValue = getFlagValue(flags, "orm", "").toLowerCase();
|
|
171
|
-
|
|
172
|
-
if (selectedDB.ormOptions.includes(flagValue)) {
|
|
173
|
-
ormChoice = flagValue;
|
|
174
|
-
console.log(
|
|
175
|
-
`${info("[?]")} ORM for ${selectedDB.label}: ${ormChoice} ${success(
|
|
176
|
-
"[flag]"
|
|
177
|
-
)}`
|
|
178
|
-
);
|
|
179
|
-
} else {
|
|
180
|
-
logWarning(`ORM flag '${flagValue}' invalid for ${selectedDB.label}.`);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 2. Mode Interactif (si pas de flag ou flag invalide)
|
|
185
|
-
if (!ormChoice) {
|
|
186
|
-
const answers = await actualInquirer.prompt([
|
|
187
|
-
{
|
|
188
|
-
type: "list",
|
|
189
|
-
name: "orm",
|
|
190
|
-
message: `Choose an ORM for ${selectedDB.label}:`,
|
|
191
|
-
choices: selectedDB.ormOptions.map((opt) => ({
|
|
192
|
-
name: opt.charAt(0).toUpperCase() + opt.slice(1), // Capitalize label
|
|
193
|
-
value: opt,
|
|
194
|
-
})),
|
|
195
|
-
default: defaultOrm,
|
|
196
|
-
},
|
|
197
|
-
]);
|
|
198
|
-
ormChoice = answers.orm;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
dbConfig.orm = ormChoice;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const packageManager = await getPackageManager(flags);
|
|
205
|
-
|
|
206
|
-
// --- 4. Boolean Choices (Prioritize flags) ---
|
|
207
|
-
const booleanFlags = [
|
|
208
|
-
{ name: "docker", default: true, prompt: "Generate Docker files?" },
|
|
209
|
-
|
|
210
|
-
{ name: "auth", default: true, prompt: "Add JWT authentication?" },
|
|
211
|
-
|
|
212
|
-
{ name: "swagger", default: true, prompt: "Install Swagger?" },
|
|
213
|
-
];
|
|
214
|
-
|
|
215
|
-
const booleanResults = {};
|
|
216
|
-
|
|
217
|
-
booleanFlags.forEach(({ name, default: defaultValue, prompt }) => {
|
|
218
|
-
let result;
|
|
219
|
-
|
|
220
|
-
if (flags[name] !== undefined) {
|
|
221
|
-
// Flag is present, use its value
|
|
222
|
-
result = getFlagValue(flags, name, defaultValue);
|
|
223
|
-
|
|
224
|
-
const displayValue = result ? "Yes" : "No";
|
|
225
|
-
console.log(
|
|
226
|
-
`${info("[?]")} ${prompt} : ${displayValue} ${success("[flag]")}`
|
|
227
|
-
);
|
|
228
|
-
} else {
|
|
229
|
-
// Flag is absent, ask the question
|
|
230
|
-
const defaultInput = defaultValue ? "y" : "n";
|
|
231
|
-
result = readline.keyInYNStrict(`${info("[?]")} ${prompt}`, {
|
|
232
|
-
defaultInput: defaultInput,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
booleanResults[name] = result;
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const useDocker = booleanResults.docker;
|
|
239
|
-
const useAuth = booleanResults.auth;
|
|
240
|
-
const useSwagger = booleanResults.swagger;
|
|
241
|
-
|
|
242
|
-
// --- 5. Swagger Configuration (Prioritize flags) ---
|
|
243
|
-
let swaggerInputs;
|
|
244
|
-
if (useSwagger) {
|
|
245
|
-
console.log(`\n${info("[INFO]")} Swagger Configuration`);
|
|
246
|
-
const swaggerFields = [
|
|
247
|
-
{
|
|
248
|
-
name: "title",
|
|
249
|
-
flag: "swaggerTitle",
|
|
250
|
-
default: `${currentProjectName} API`,
|
|
251
|
-
prompt: "API Title",
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
name: "description",
|
|
255
|
-
flag: "swaggerDesc",
|
|
256
|
-
default: "API generated by NestCraftX",
|
|
257
|
-
prompt: "Description",
|
|
258
|
-
},
|
|
259
|
-
{
|
|
260
|
-
name: "version",
|
|
261
|
-
flag: "swaggerVersion",
|
|
262
|
-
default: "1.0.0",
|
|
263
|
-
prompt: "Version",
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
name: "endpoint",
|
|
267
|
-
flag: "swaggerEndpoint",
|
|
268
|
-
default: "api/docs",
|
|
269
|
-
prompt: "Endpoint",
|
|
270
|
-
},
|
|
271
|
-
];
|
|
272
|
-
|
|
273
|
-
swaggerInputs = {};
|
|
274
|
-
|
|
275
|
-
swaggerFields.forEach((field) => {
|
|
276
|
-
const flagValue = flags[field.flag];
|
|
277
|
-
const defaultValue = field.default;
|
|
278
|
-
|
|
279
|
-
if (flagValue !== undefined) {
|
|
280
|
-
// Flag is present, use its value
|
|
281
|
-
swaggerInputs[field.name] = flagValue;
|
|
282
|
-
console.log(` ${field.prompt} : ${flagValue} ${success("[flag]")}`);
|
|
283
|
-
} else {
|
|
284
|
-
// Flag is absent, ask the question
|
|
285
|
-
swaggerInputs[field.name] = readline.question(
|
|
286
|
-
` ${field.prompt} [${defaultValue}] : `,
|
|
287
|
-
{ defaultInput: defaultValue }
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// --- 6. Entities (Remains fully interactive) ---
|
|
294
|
-
const entitiesData = { entities: [], relations: [] };
|
|
295
|
-
|
|
296
|
-
if (useAuth) {
|
|
297
|
-
console.log(
|
|
298
|
-
`\n${info("[INFO]")} Auth active: adding User and Session entities`
|
|
299
|
-
);
|
|
300
|
-
|
|
301
|
-
// 1. Entité User
|
|
302
|
-
entitiesData.entities.push({
|
|
303
|
-
name: "user",
|
|
304
|
-
fields: [
|
|
305
|
-
{ name: "email", type: "string", unique: true },
|
|
306
|
-
{ name: "password", type: "string" },
|
|
307
|
-
{ name: "role", type: "Role" },
|
|
308
|
-
{ name: "isActive", type: "boolean", default: true },
|
|
309
|
-
],
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// 2. Entité Session
|
|
313
|
-
entitiesData.entities.push({
|
|
314
|
-
name: "session",
|
|
315
|
-
fields: [
|
|
316
|
-
{ name: "refreshToken", type: "string" },
|
|
317
|
-
{ name: "userId", type: "string" },
|
|
318
|
-
{ name: "expiresAt", type: "Date" },
|
|
319
|
-
{ name: "createdAt", type: "Date", default: "now" },
|
|
320
|
-
],
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// 3. relation user & session
|
|
324
|
-
entitiesData.relations.push({
|
|
325
|
-
from: "user",
|
|
326
|
-
to: "session",
|
|
327
|
-
type: "1-n",
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
console.log(
|
|
332
|
-
`\n${info("[INFO]")} Entity input (FULL Mode - Complete Architecture)`
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
let addEntity = readline.keyInYNStrict(`${info("[?]")} Add an entity?`);
|
|
336
|
-
while (addEntity) {
|
|
337
|
-
let name;
|
|
338
|
-
while (true) {
|
|
339
|
-
name = readline.question(`\n Entity name : `);
|
|
340
|
-
if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
|
|
341
|
-
logWarning("Invalid name. Letters, numbers, _ (start with a letter).");
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const fields = [];
|
|
345
|
-
|
|
346
|
-
console.log(` Fields for "${name}" :`);
|
|
347
|
-
while (true) {
|
|
348
|
-
let fname = readline.question(" Field name (leave empty to finish) : ");
|
|
349
|
-
if (!fname) break;
|
|
350
|
-
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
|
|
351
|
-
logWarning("Invalid field name.");
|
|
352
|
-
continue;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const baseTypeChoices = [
|
|
356
|
-
"string",
|
|
357
|
-
"text",
|
|
358
|
-
"number",
|
|
359
|
-
"decimal",
|
|
360
|
-
"boolean",
|
|
361
|
-
"Date",
|
|
362
|
-
"uuid",
|
|
363
|
-
"json",
|
|
364
|
-
"enum",
|
|
365
|
-
"array",
|
|
366
|
-
"object",
|
|
367
|
-
];
|
|
368
|
-
|
|
369
|
-
const typeQuestion = {
|
|
370
|
-
type: "list",
|
|
371
|
-
name: "ftype",
|
|
372
|
-
message: `Type for "${fname}"`,
|
|
373
|
-
default: "string",
|
|
374
|
-
choices: baseTypeChoices,
|
|
375
|
-
transformer: () => "",
|
|
376
|
-
};
|
|
377
|
-
const typeAnswer = await actualInquirer.prompt([typeQuestion]);
|
|
378
|
-
let ftype = typeAnswer.ftype;
|
|
379
|
-
process.stdout.write("\x1B[1A");
|
|
380
|
-
process.stdout.write("\x1B[K");
|
|
381
|
-
|
|
382
|
-
if (ftype === "array") {
|
|
383
|
-
const arrayInnerQuestion = {
|
|
384
|
-
type: "list",
|
|
385
|
-
name: "innerType",
|
|
386
|
-
message: `Type of elements for "${fname}[]"`,
|
|
387
|
-
default: "string",
|
|
388
|
-
choices: baseTypeChoices.filter(
|
|
389
|
-
(c) => c !== "array" && c !== "object"
|
|
390
|
-
),
|
|
391
|
-
transformer: () => "",
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
|
|
395
|
-
ftype = `${innerAnswer.innerType}[]`;
|
|
396
|
-
} else if (ftype === "enum") {
|
|
397
|
-
const enumName = capitalize(fname) + "Enum";
|
|
398
|
-
console.log(
|
|
399
|
-
` ${info(
|
|
400
|
-
"[INFO]"
|
|
401
|
-
)} Enum type selected. Consider defining ${enumName} in your code.`
|
|
402
|
-
);
|
|
403
|
-
ftype = enumName;
|
|
404
|
-
} else if (ftype === "object") {
|
|
405
|
-
const objectNameQuestion = {
|
|
406
|
-
type: "input",
|
|
407
|
-
name: "objectName",
|
|
408
|
-
|
|
409
|
-
message: `Complex type name (DTO/Class or leave 'json') :`,
|
|
410
|
-
default: "json",
|
|
411
|
-
transformer: () => "",
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
|
|
415
|
-
ftype = capitalize(objectAnswer.objectName.trim() || "json");
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
console.log(` Type for "${fname}" : ${ftype} ${success("[✓]")}`);
|
|
419
|
-
|
|
420
|
-
fields.push({ name: fname, type: ftype });
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
entitiesData.entities.push({ name, fields });
|
|
424
|
-
console.log(
|
|
425
|
-
`${success("[✓]")} Entity "${name}" added with ${fields.length} field(s)`
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
addEntity = readline.keyInYNStrict(`${info("[?]")} Add another entity?`);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const wantsRelation = readline.keyInYNStrict(
|
|
432
|
-
`${info("[?]")} Add relationships between entities?`
|
|
433
|
-
);
|
|
434
|
-
if (wantsRelation) {
|
|
435
|
-
if (entitiesData.entities.length > 1) {
|
|
436
|
-
console.log(`\n${info("[INFO]")} Configuring relationships`);
|
|
437
|
-
|
|
438
|
-
let configuring = true;
|
|
439
|
-
while (configuring) {
|
|
440
|
-
const entityNames = entitiesData.entities.map((e) => e.name);
|
|
441
|
-
|
|
442
|
-
// 1. Select entities first
|
|
443
|
-
const selection = await actualInquirer.prompt([
|
|
444
|
-
{
|
|
445
|
-
type: "list",
|
|
446
|
-
name: "fromName",
|
|
447
|
-
message: "From which entity? (Source)",
|
|
448
|
-
choices: entityNames,
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
type: "list",
|
|
452
|
-
name: "toName",
|
|
453
|
-
message: (prev) =>
|
|
454
|
-
`To which entity should ${prev.fromName} be linked? (Target)`,
|
|
455
|
-
choices: (prev) =>
|
|
456
|
-
entityNames.filter((name) => name !== prev.fromName),
|
|
457
|
-
},
|
|
458
|
-
]);
|
|
459
|
-
|
|
460
|
-
// --- VERIFICATION: Check if link already exists (A->B or B->A) ---
|
|
461
|
-
const alreadyExists = entitiesData.relations.find(
|
|
462
|
-
(rel) =>
|
|
463
|
-
(rel.from === selection.fromName && rel.to === selection.toName) ||
|
|
464
|
-
(rel.from === selection.toName && rel.to === selection.fromName)
|
|
465
|
-
);
|
|
466
|
-
|
|
467
|
-
if (alreadyExists) {
|
|
468
|
-
logWarning(
|
|
469
|
-
`A relationship already exists between ${selection.fromName} and ${selection.toName} (${alreadyExists.type}).`
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
const { tryAgain } = await actualInquirer.prompt([
|
|
473
|
-
{
|
|
474
|
-
type: "confirm",
|
|
475
|
-
name: "tryAgain",
|
|
476
|
-
message: "Do you want to choose different entities?",
|
|
477
|
-
default: true,
|
|
478
|
-
},
|
|
479
|
-
]);
|
|
480
|
-
|
|
481
|
-
if (!tryAgain) break;
|
|
482
|
-
continue; // Restart selection
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// 2. Select Relationship type only if verification passed
|
|
486
|
-
const typeAnswer = await actualInquirer.prompt([
|
|
487
|
-
{
|
|
488
|
-
type: "list",
|
|
489
|
-
name: "relType",
|
|
490
|
-
message: "Relationship type:",
|
|
491
|
-
choices: [
|
|
492
|
-
{
|
|
493
|
-
name: `1-1 (One-to-One) : ${selection.fromName} has one ${selection.toName}`,
|
|
494
|
-
value: "1-1",
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
name: `1-n (One-to-Many) : ${selection.fromName} has many ${selection.toName}s`,
|
|
498
|
-
value: "1-n",
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
name: `n-1 (Many-to-One) : Many ${selection.fromName}s belong to one ${selection.toName}`,
|
|
502
|
-
value: "n-1",
|
|
503
|
-
},
|
|
504
|
-
{
|
|
505
|
-
name: `n-n (Many-to-Many) : Many ${selection.fromName}s linked to many ${selection.toName}s`,
|
|
506
|
-
value: "n-n",
|
|
507
|
-
},
|
|
508
|
-
],
|
|
509
|
-
},
|
|
510
|
-
]);
|
|
511
|
-
|
|
512
|
-
const from = entitiesData.entities.find(
|
|
513
|
-
(e) => e.name === selection.fromName
|
|
514
|
-
);
|
|
515
|
-
const to = entitiesData.entities.find(
|
|
516
|
-
(e) => e.name === selection.toName
|
|
517
|
-
);
|
|
518
|
-
const relType = typeAnswer.relType;
|
|
519
|
-
|
|
520
|
-
// Register Relationship
|
|
521
|
-
entitiesData.relations.push({
|
|
522
|
-
from: from.name,
|
|
523
|
-
to: to.name,
|
|
524
|
-
type: relType,
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
const fromLow = from.name.toLowerCase();
|
|
528
|
-
const toLow = to.name.toLowerCase();
|
|
529
|
-
|
|
530
|
-
// --- Add fields logic ---
|
|
531
|
-
if (relType === "1-1") {
|
|
532
|
-
from.fields.push(
|
|
533
|
-
{ name: `${toLow}Id`, type: "string" },
|
|
534
|
-
{ name: toLow, type: to.name }
|
|
535
|
-
);
|
|
536
|
-
} else if (relType === "1-n") {
|
|
537
|
-
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
538
|
-
to.fields.push(
|
|
539
|
-
{ name: `${fromLow}Id`, type: "string" },
|
|
540
|
-
{ name: fromLow, type: from.name }
|
|
541
|
-
);
|
|
542
|
-
} else if (relType === "n-1") {
|
|
543
|
-
from.fields.push(
|
|
544
|
-
{ name: `${toLow}Id`, type: "string" },
|
|
545
|
-
{ name: toLow, type: to.name }
|
|
546
|
-
);
|
|
547
|
-
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
548
|
-
} else if (relType === "n-n") {
|
|
549
|
-
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
550
|
-
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
console.log(
|
|
554
|
-
`\n${success("[✓]")} Relationship added: ${from.name} ${relType} ${
|
|
555
|
-
to.name
|
|
556
|
-
}`
|
|
557
|
-
);
|
|
558
|
-
|
|
559
|
-
const { addMore } = await actualInquirer.prompt([
|
|
560
|
-
{
|
|
561
|
-
type: "confirm",
|
|
562
|
-
name: "addMore",
|
|
563
|
-
message: "Add another relationship?",
|
|
564
|
-
default: false,
|
|
565
|
-
},
|
|
566
|
-
]);
|
|
567
|
-
configuring = addMore;
|
|
568
|
-
}
|
|
569
|
-
} else {
|
|
570
|
-
logWarning(
|
|
571
|
-
"At least two entities are required to configure a relationship."
|
|
572
|
-
);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
return {
|
|
577
|
-
projectName: currentProjectName,
|
|
578
|
-
useDocker,
|
|
579
|
-
useAuth,
|
|
580
|
-
useSwagger,
|
|
581
|
-
swaggerInputs,
|
|
582
|
-
packageManager,
|
|
583
|
-
entitiesData,
|
|
584
|
-
selectedDB: selectedDB.name,
|
|
585
|
-
dbConfig,
|
|
586
|
-
mode: "full",
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
/**
|
|
591
|
-
* Récupère la valeur d'un flag, ou la valeur par défaut si le flag n'est pas fourni.
|
|
592
|
-
* Convertit les flags 'true'/'false' en booléens si nécessaire.
|
|
593
|
-
* @param {object} flags - L'objet flags (ex: yargs)
|
|
594
|
-
* @param {string} name - Nom du flag (ex: 'auth', 'dbHost')
|
|
595
|
-
* @param {*} defaultValue - Valeur par défaut si le flag est absent.
|
|
596
|
-
*/
|
|
597
|
-
function getFlagValue(flags, name, defaultValue) {
|
|
598
|
-
const value = flags[name];
|
|
599
|
-
if (value !== undefined) {
|
|
600
|
-
// Gérer les cas où yargs (ou autre) renvoie une chaîne pour les booléens
|
|
601
|
-
if (value === "true") return true;
|
|
602
|
-
if (value === "false") return false;
|
|
603
|
-
return value;
|
|
604
|
-
}
|
|
605
|
-
return defaultValue;
|
|
606
|
-
}
|
|
607
|
-
module.exports = { getFullModeInputs };
|
|
1
|
+
const readline = require("readline-sync");
|
|
2
|
+
const { info, success, warning } = require("./colors");
|
|
3
|
+
const inquirer = require("inquirer");
|
|
4
|
+
const { capitalize } = require("./userInput");
|
|
5
|
+
const { logWarning } = require("./loggers/logWarning");
|
|
6
|
+
const { getPackageManager } = require("./utils");
|
|
7
|
+
const actualInquirer = inquirer.default || inquirer;
|
|
8
|
+
|
|
9
|
+
async function getFullModeInputs(projectName, flags) {
|
|
10
|
+
console.log(
|
|
11
|
+
`\n${info("[FULL MODE]")} Complete configuration with Clean Architecture\n`
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const dataBases = [
|
|
15
|
+
{
|
|
16
|
+
name: "postgresql",
|
|
17
|
+
label: "PostgreSQL",
|
|
18
|
+
ormOptions: ["prisma", "typeorm"],
|
|
19
|
+
required: [
|
|
20
|
+
{
|
|
21
|
+
title: "PostgreSQL User",
|
|
22
|
+
envVar: "POSTGRES_USER",
|
|
23
|
+
defaultValue: "postgres",
|
|
24
|
+
hideEchoBack: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
title: "PostgreSQL Password",
|
|
28
|
+
envVar: "POSTGRES_PASSWORD",
|
|
29
|
+
defaultValue: "postgres",
|
|
30
|
+
hideEchoBack: true, // Hide password
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
title: "PostgreSQL Database Name",
|
|
34
|
+
envVar: "POSTGRES_DB",
|
|
35
|
+
defaultValue: `${projectName}-db`,
|
|
36
|
+
hideEchoBack: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
title: "PostgreSQL Host",
|
|
40
|
+
envVar: "POSTGRES_HOST",
|
|
41
|
+
defaultValue: "localhost",
|
|
42
|
+
hideEchoBack: false,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
title: "PostgreSQL Port",
|
|
46
|
+
envVar: "POSTGRES_PORT",
|
|
47
|
+
defaultValue: "5432",
|
|
48
|
+
hideEchoBack: false,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "mongodb",
|
|
54
|
+
label: "MongoDB",
|
|
55
|
+
ormOptions: ["mongoose"],
|
|
56
|
+
required: [
|
|
57
|
+
{
|
|
58
|
+
title: "MongoDB URI",
|
|
59
|
+
envVar: "MONGO_URI",
|
|
60
|
+
defaultValue: "mongodb://localhost:27017",
|
|
61
|
+
hideEchoBack: false,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
title: "MongoDB Database Name",
|
|
65
|
+
envVar: "MONGO_DB",
|
|
66
|
+
defaultValue: `${projectName}-db`,
|
|
67
|
+
hideEchoBack: false,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
let currentProjectName = projectName;
|
|
74
|
+
// La validation du nom de projet reste interactive en cas d'échec
|
|
75
|
+
while (true) {
|
|
76
|
+
if (/^[A-Za-z][A-Za-z0-9_-]*$/.test(currentProjectName)) break;
|
|
77
|
+
|
|
78
|
+
currentProjectName = readline.question(`${info("[?]")} Project name : `);
|
|
79
|
+
|
|
80
|
+
logWarning(
|
|
81
|
+
"Invalid name. Use letters, numbers, _ or - (start with a letter)."
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- 1. Database Selection ---
|
|
86
|
+
const defaultDB = "postgresql";
|
|
87
|
+
let usedDB;
|
|
88
|
+
|
|
89
|
+
// Prioriser le flag 'db' si présent
|
|
90
|
+
if (flags.db && flags.db !== undefined) {
|
|
91
|
+
usedDB = getFlagValue(flags, "db", defaultDB);
|
|
92
|
+
console.log(
|
|
93
|
+
`${info("[?]")} Database (postgresql, mongodb) : ${usedDB} ${success(
|
|
94
|
+
"[flag]"
|
|
95
|
+
)}`
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
usedDB = readline.question(
|
|
99
|
+
`${info("[?]")} Database (postgresql, mongodb) [${defaultDB}] : `,
|
|
100
|
+
{ defaultInput: defaultDB }
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let selectedDB = dataBases.find(
|
|
105
|
+
(db) => db.name.toLowerCase() === usedDB.toLowerCase()
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Revenir au mode interactif si le flag fourni est invalide ou si l'utilisateur a saisi une valeur invalide
|
|
109
|
+
while (!selectedDB) {
|
|
110
|
+
logWarning("Database not recognized.");
|
|
111
|
+
|
|
112
|
+
usedDB = readline.question(
|
|
113
|
+
`${info("[?]")} Database (postgresql, mongodb) : `
|
|
114
|
+
);
|
|
115
|
+
selectedDB = dataBases.find(
|
|
116
|
+
(db) => db.name.toLowerCase() === usedDB.toLowerCase()
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// --- 2. DB Configuration (Using flags) ---
|
|
121
|
+
const dbConfig = {};
|
|
122
|
+
console.log(`\n${info("[INFO]")} ${selectedDB.label} Configuration`);
|
|
123
|
+
|
|
124
|
+
selectedDB.required.forEach((field) => {
|
|
125
|
+
// Détermine la clé de flag (ex: 'dbUser' pour POSTGRES_USER ou 'mongoUri' pour MONGO_URI)
|
|
126
|
+
const flagName = field.envVar
|
|
127
|
+
.toLowerCase()
|
|
128
|
+
.replace("postgres_", "db")
|
|
129
|
+
.replace("mongo_", "mongo");
|
|
130
|
+
|
|
131
|
+
const flagValue = flags[flagName];
|
|
132
|
+
let answer;
|
|
133
|
+
|
|
134
|
+
if (flagValue !== undefined) {
|
|
135
|
+
// Flag is present, use its value directly and skip prompt
|
|
136
|
+
answer = getFlagValue(flags, flagName, field.defaultValue);
|
|
137
|
+
|
|
138
|
+
const displayValue = field.hideEchoBack ? "***" : answer;
|
|
139
|
+
|
|
140
|
+
console.log(` ${field.title} : ${displayValue} ${success("[flag]")}`);
|
|
141
|
+
} else {
|
|
142
|
+
// Flag is absent, ask the question
|
|
143
|
+
while (true) {
|
|
144
|
+
answer = readline.question(
|
|
145
|
+
` ${field.title} [${field.defaultValue}] : `,
|
|
146
|
+
{
|
|
147
|
+
hideEchoBack: field.hideEchoBack,
|
|
148
|
+
defaultInput: field.defaultValue,
|
|
149
|
+
}
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// If the user entered something OR if the default value is non-null, continue
|
|
153
|
+
if (answer || field.defaultValue !== null) break;
|
|
154
|
+
|
|
155
|
+
logWarning("This field is required.");
|
|
156
|
+
}
|
|
157
|
+
// If the user just pressed Enter, use the default value
|
|
158
|
+
answer = answer || field.defaultValue;
|
|
159
|
+
}
|
|
160
|
+
dbConfig[field.envVar] = answer;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// --- 3. ORM Selection (Full Mode) ---
|
|
164
|
+
if (selectedDB.ormOptions && selectedDB.ormOptions.length > 0) {
|
|
165
|
+
const defaultOrm = selectedDB.ormOptions[0];
|
|
166
|
+
let ormChoice;
|
|
167
|
+
|
|
168
|
+
// 1. Vérification du Flag
|
|
169
|
+
if (flags.orm !== undefined) {
|
|
170
|
+
const flagValue = getFlagValue(flags, "orm", "").toLowerCase();
|
|
171
|
+
|
|
172
|
+
if (selectedDB.ormOptions.includes(flagValue)) {
|
|
173
|
+
ormChoice = flagValue;
|
|
174
|
+
console.log(
|
|
175
|
+
`${info("[?]")} ORM for ${selectedDB.label}: ${ormChoice} ${success(
|
|
176
|
+
"[flag]"
|
|
177
|
+
)}`
|
|
178
|
+
);
|
|
179
|
+
} else {
|
|
180
|
+
logWarning(`ORM flag '${flagValue}' invalid for ${selectedDB.label}.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 2. Mode Interactif (si pas de flag ou flag invalide)
|
|
185
|
+
if (!ormChoice) {
|
|
186
|
+
const answers = await actualInquirer.prompt([
|
|
187
|
+
{
|
|
188
|
+
type: "list",
|
|
189
|
+
name: "orm",
|
|
190
|
+
message: `Choose an ORM for ${selectedDB.label}:`,
|
|
191
|
+
choices: selectedDB.ormOptions.map((opt) => ({
|
|
192
|
+
name: opt.charAt(0).toUpperCase() + opt.slice(1), // Capitalize label
|
|
193
|
+
value: opt,
|
|
194
|
+
})),
|
|
195
|
+
default: defaultOrm,
|
|
196
|
+
},
|
|
197
|
+
]);
|
|
198
|
+
ormChoice = answers.orm;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
dbConfig.orm = ormChoice;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const packageManager = await getPackageManager(flags);
|
|
205
|
+
|
|
206
|
+
// --- 4. Boolean Choices (Prioritize flags) ---
|
|
207
|
+
const booleanFlags = [
|
|
208
|
+
{ name: "docker", default: true, prompt: "Generate Docker files?" },
|
|
209
|
+
|
|
210
|
+
{ name: "auth", default: true, prompt: "Add JWT authentication?" },
|
|
211
|
+
|
|
212
|
+
{ name: "swagger", default: true, prompt: "Install Swagger?" },
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
const booleanResults = {};
|
|
216
|
+
|
|
217
|
+
booleanFlags.forEach(({ name, default: defaultValue, prompt }) => {
|
|
218
|
+
let result;
|
|
219
|
+
|
|
220
|
+
if (flags[name] !== undefined) {
|
|
221
|
+
// Flag is present, use its value
|
|
222
|
+
result = getFlagValue(flags, name, defaultValue);
|
|
223
|
+
|
|
224
|
+
const displayValue = result ? "Yes" : "No";
|
|
225
|
+
console.log(
|
|
226
|
+
`${info("[?]")} ${prompt} : ${displayValue} ${success("[flag]")}`
|
|
227
|
+
);
|
|
228
|
+
} else {
|
|
229
|
+
// Flag is absent, ask the question
|
|
230
|
+
const defaultInput = defaultValue ? "y" : "n";
|
|
231
|
+
result = readline.keyInYNStrict(`${info("[?]")} ${prompt}`, {
|
|
232
|
+
defaultInput: defaultInput,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
booleanResults[name] = result;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const useDocker = booleanResults.docker;
|
|
239
|
+
const useAuth = booleanResults.auth;
|
|
240
|
+
const useSwagger = booleanResults.swagger;
|
|
241
|
+
|
|
242
|
+
// --- 5. Swagger Configuration (Prioritize flags) ---
|
|
243
|
+
let swaggerInputs;
|
|
244
|
+
if (useSwagger) {
|
|
245
|
+
console.log(`\n${info("[INFO]")} Swagger Configuration`);
|
|
246
|
+
const swaggerFields = [
|
|
247
|
+
{
|
|
248
|
+
name: "title",
|
|
249
|
+
flag: "swaggerTitle",
|
|
250
|
+
default: `${currentProjectName} API`,
|
|
251
|
+
prompt: "API Title",
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "description",
|
|
255
|
+
flag: "swaggerDesc",
|
|
256
|
+
default: "API generated by NestCraftX",
|
|
257
|
+
prompt: "Description",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "version",
|
|
261
|
+
flag: "swaggerVersion",
|
|
262
|
+
default: "1.0.0",
|
|
263
|
+
prompt: "Version",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: "endpoint",
|
|
267
|
+
flag: "swaggerEndpoint",
|
|
268
|
+
default: "api/docs",
|
|
269
|
+
prompt: "Endpoint",
|
|
270
|
+
},
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
swaggerInputs = {};
|
|
274
|
+
|
|
275
|
+
swaggerFields.forEach((field) => {
|
|
276
|
+
const flagValue = flags[field.flag];
|
|
277
|
+
const defaultValue = field.default;
|
|
278
|
+
|
|
279
|
+
if (flagValue !== undefined) {
|
|
280
|
+
// Flag is present, use its value
|
|
281
|
+
swaggerInputs[field.name] = flagValue;
|
|
282
|
+
console.log(` ${field.prompt} : ${flagValue} ${success("[flag]")}`);
|
|
283
|
+
} else {
|
|
284
|
+
// Flag is absent, ask the question
|
|
285
|
+
swaggerInputs[field.name] = readline.question(
|
|
286
|
+
` ${field.prompt} [${defaultValue}] : `,
|
|
287
|
+
{ defaultInput: defaultValue }
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// --- 6. Entities (Remains fully interactive) ---
|
|
294
|
+
const entitiesData = { entities: [], relations: [] };
|
|
295
|
+
|
|
296
|
+
if (useAuth) {
|
|
297
|
+
console.log(
|
|
298
|
+
`\n${info("[INFO]")} Auth active: adding User and Session entities`
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// 1. Entité User
|
|
302
|
+
entitiesData.entities.push({
|
|
303
|
+
name: "user",
|
|
304
|
+
fields: [
|
|
305
|
+
{ name: "email", type: "string", unique: true },
|
|
306
|
+
{ name: "password", type: "string" },
|
|
307
|
+
{ name: "role", type: "Role" },
|
|
308
|
+
{ name: "isActive", type: "boolean", default: true },
|
|
309
|
+
],
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// 2. Entité Session
|
|
313
|
+
entitiesData.entities.push({
|
|
314
|
+
name: "session",
|
|
315
|
+
fields: [
|
|
316
|
+
{ name: "refreshToken", type: "string" },
|
|
317
|
+
{ name: "userId", type: "string" },
|
|
318
|
+
{ name: "expiresAt", type: "Date" },
|
|
319
|
+
{ name: "createdAt", type: "Date", default: "now" },
|
|
320
|
+
],
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// 3. relation user & session
|
|
324
|
+
entitiesData.relations.push({
|
|
325
|
+
from: "user",
|
|
326
|
+
to: "session",
|
|
327
|
+
type: "1-n",
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
console.log(
|
|
332
|
+
`\n${info("[INFO]")} Entity input (FULL Mode - Complete Architecture)`
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
let addEntity = readline.keyInYNStrict(`${info("[?]")} Add an entity?`);
|
|
336
|
+
while (addEntity) {
|
|
337
|
+
let name;
|
|
338
|
+
while (true) {
|
|
339
|
+
name = readline.question(`\n Entity name : `);
|
|
340
|
+
if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
|
|
341
|
+
logWarning("Invalid name. Letters, numbers, _ (start with a letter).");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const fields = [];
|
|
345
|
+
|
|
346
|
+
console.log(` Fields for "${name}" :`);
|
|
347
|
+
while (true) {
|
|
348
|
+
let fname = readline.question(" Field name (leave empty to finish) : ");
|
|
349
|
+
if (!fname) break;
|
|
350
|
+
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fname)) {
|
|
351
|
+
logWarning("Invalid field name.");
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const baseTypeChoices = [
|
|
356
|
+
"string",
|
|
357
|
+
"text",
|
|
358
|
+
"number",
|
|
359
|
+
"decimal",
|
|
360
|
+
"boolean",
|
|
361
|
+
"Date",
|
|
362
|
+
"uuid",
|
|
363
|
+
"json",
|
|
364
|
+
"enum",
|
|
365
|
+
"array",
|
|
366
|
+
"object",
|
|
367
|
+
];
|
|
368
|
+
|
|
369
|
+
const typeQuestion = {
|
|
370
|
+
type: "list",
|
|
371
|
+
name: "ftype",
|
|
372
|
+
message: `Type for "${fname}"`,
|
|
373
|
+
default: "string",
|
|
374
|
+
choices: baseTypeChoices,
|
|
375
|
+
transformer: () => "",
|
|
376
|
+
};
|
|
377
|
+
const typeAnswer = await actualInquirer.prompt([typeQuestion]);
|
|
378
|
+
let ftype = typeAnswer.ftype;
|
|
379
|
+
process.stdout.write("\x1B[1A");
|
|
380
|
+
process.stdout.write("\x1B[K");
|
|
381
|
+
|
|
382
|
+
if (ftype === "array") {
|
|
383
|
+
const arrayInnerQuestion = {
|
|
384
|
+
type: "list",
|
|
385
|
+
name: "innerType",
|
|
386
|
+
message: `Type of elements for "${fname}[]"`,
|
|
387
|
+
default: "string",
|
|
388
|
+
choices: baseTypeChoices.filter(
|
|
389
|
+
(c) => c !== "array" && c !== "object"
|
|
390
|
+
),
|
|
391
|
+
transformer: () => "",
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
|
|
395
|
+
ftype = `${innerAnswer.innerType}[]`;
|
|
396
|
+
} else if (ftype === "enum") {
|
|
397
|
+
const enumName = capitalize(fname) + "Enum";
|
|
398
|
+
console.log(
|
|
399
|
+
` ${info(
|
|
400
|
+
"[INFO]"
|
|
401
|
+
)} Enum type selected. Consider defining ${enumName} in your code.`
|
|
402
|
+
);
|
|
403
|
+
ftype = enumName;
|
|
404
|
+
} else if (ftype === "object") {
|
|
405
|
+
const objectNameQuestion = {
|
|
406
|
+
type: "input",
|
|
407
|
+
name: "objectName",
|
|
408
|
+
|
|
409
|
+
message: `Complex type name (DTO/Class or leave 'json') :`,
|
|
410
|
+
default: "json",
|
|
411
|
+
transformer: () => "",
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
|
|
415
|
+
ftype = capitalize(objectAnswer.objectName.trim() || "json");
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
console.log(` Type for "${fname}" : ${ftype} ${success("[✓]")}`);
|
|
419
|
+
|
|
420
|
+
fields.push({ name: fname, type: ftype });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
entitiesData.entities.push({ name, fields });
|
|
424
|
+
console.log(
|
|
425
|
+
`${success("[✓]")} Entity "${name}" added with ${fields.length} field(s)`
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
addEntity = readline.keyInYNStrict(`${info("[?]")} Add another entity?`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const wantsRelation = readline.keyInYNStrict(
|
|
432
|
+
`${info("[?]")} Add relationships between entities?`
|
|
433
|
+
);
|
|
434
|
+
if (wantsRelation) {
|
|
435
|
+
if (entitiesData.entities.length > 1) {
|
|
436
|
+
console.log(`\n${info("[INFO]")} Configuring relationships`);
|
|
437
|
+
|
|
438
|
+
let configuring = true;
|
|
439
|
+
while (configuring) {
|
|
440
|
+
const entityNames = entitiesData.entities.map((e) => e.name);
|
|
441
|
+
|
|
442
|
+
// 1. Select entities first
|
|
443
|
+
const selection = await actualInquirer.prompt([
|
|
444
|
+
{
|
|
445
|
+
type: "list",
|
|
446
|
+
name: "fromName",
|
|
447
|
+
message: "From which entity? (Source)",
|
|
448
|
+
choices: entityNames,
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
type: "list",
|
|
452
|
+
name: "toName",
|
|
453
|
+
message: (prev) =>
|
|
454
|
+
`To which entity should ${prev.fromName} be linked? (Target)`,
|
|
455
|
+
choices: (prev) =>
|
|
456
|
+
entityNames.filter((name) => name !== prev.fromName),
|
|
457
|
+
},
|
|
458
|
+
]);
|
|
459
|
+
|
|
460
|
+
// --- VERIFICATION: Check if link already exists (A->B or B->A) ---
|
|
461
|
+
const alreadyExists = entitiesData.relations.find(
|
|
462
|
+
(rel) =>
|
|
463
|
+
(rel.from === selection.fromName && rel.to === selection.toName) ||
|
|
464
|
+
(rel.from === selection.toName && rel.to === selection.fromName)
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
if (alreadyExists) {
|
|
468
|
+
logWarning(
|
|
469
|
+
`A relationship already exists between ${selection.fromName} and ${selection.toName} (${alreadyExists.type}).`
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const { tryAgain } = await actualInquirer.prompt([
|
|
473
|
+
{
|
|
474
|
+
type: "confirm",
|
|
475
|
+
name: "tryAgain",
|
|
476
|
+
message: "Do you want to choose different entities?",
|
|
477
|
+
default: true,
|
|
478
|
+
},
|
|
479
|
+
]);
|
|
480
|
+
|
|
481
|
+
if (!tryAgain) break;
|
|
482
|
+
continue; // Restart selection
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// 2. Select Relationship type only if verification passed
|
|
486
|
+
const typeAnswer = await actualInquirer.prompt([
|
|
487
|
+
{
|
|
488
|
+
type: "list",
|
|
489
|
+
name: "relType",
|
|
490
|
+
message: "Relationship type:",
|
|
491
|
+
choices: [
|
|
492
|
+
{
|
|
493
|
+
name: `1-1 (One-to-One) : ${selection.fromName} has one ${selection.toName}`,
|
|
494
|
+
value: "1-1",
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
name: `1-n (One-to-Many) : ${selection.fromName} has many ${selection.toName}s`,
|
|
498
|
+
value: "1-n",
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: `n-1 (Many-to-One) : Many ${selection.fromName}s belong to one ${selection.toName}`,
|
|
502
|
+
value: "n-1",
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: `n-n (Many-to-Many) : Many ${selection.fromName}s linked to many ${selection.toName}s`,
|
|
506
|
+
value: "n-n",
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
},
|
|
510
|
+
]);
|
|
511
|
+
|
|
512
|
+
const from = entitiesData.entities.find(
|
|
513
|
+
(e) => e.name === selection.fromName
|
|
514
|
+
);
|
|
515
|
+
const to = entitiesData.entities.find(
|
|
516
|
+
(e) => e.name === selection.toName
|
|
517
|
+
);
|
|
518
|
+
const relType = typeAnswer.relType;
|
|
519
|
+
|
|
520
|
+
// Register Relationship
|
|
521
|
+
entitiesData.relations.push({
|
|
522
|
+
from: from.name,
|
|
523
|
+
to: to.name,
|
|
524
|
+
type: relType,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
const fromLow = from.name.toLowerCase();
|
|
528
|
+
const toLow = to.name.toLowerCase();
|
|
529
|
+
|
|
530
|
+
// --- Add fields logic ---
|
|
531
|
+
if (relType === "1-1") {
|
|
532
|
+
from.fields.push(
|
|
533
|
+
{ name: `${toLow}Id`, type: "string" },
|
|
534
|
+
{ name: toLow, type: to.name }
|
|
535
|
+
);
|
|
536
|
+
} else if (relType === "1-n") {
|
|
537
|
+
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
538
|
+
to.fields.push(
|
|
539
|
+
{ name: `${fromLow}Id`, type: "string" },
|
|
540
|
+
{ name: fromLow, type: from.name }
|
|
541
|
+
);
|
|
542
|
+
} else if (relType === "n-1") {
|
|
543
|
+
from.fields.push(
|
|
544
|
+
{ name: `${toLow}Id`, type: "string" },
|
|
545
|
+
{ name: toLow, type: to.name }
|
|
546
|
+
);
|
|
547
|
+
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
548
|
+
} else if (relType === "n-n") {
|
|
549
|
+
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
550
|
+
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
console.log(
|
|
554
|
+
`\n${success("[✓]")} Relationship added: ${from.name} ${relType} ${
|
|
555
|
+
to.name
|
|
556
|
+
}`
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const { addMore } = await actualInquirer.prompt([
|
|
560
|
+
{
|
|
561
|
+
type: "confirm",
|
|
562
|
+
name: "addMore",
|
|
563
|
+
message: "Add another relationship?",
|
|
564
|
+
default: false,
|
|
565
|
+
},
|
|
566
|
+
]);
|
|
567
|
+
configuring = addMore;
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
logWarning(
|
|
571
|
+
"At least two entities are required to configure a relationship."
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return {
|
|
577
|
+
projectName: currentProjectName,
|
|
578
|
+
useDocker,
|
|
579
|
+
useAuth,
|
|
580
|
+
useSwagger,
|
|
581
|
+
swaggerInputs,
|
|
582
|
+
packageManager,
|
|
583
|
+
entitiesData,
|
|
584
|
+
selectedDB: selectedDB.name,
|
|
585
|
+
dbConfig,
|
|
586
|
+
mode: "full",
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Récupère la valeur d'un flag, ou la valeur par défaut si le flag n'est pas fourni.
|
|
592
|
+
* Convertit les flags 'true'/'false' en booléens si nécessaire.
|
|
593
|
+
* @param {object} flags - L'objet flags (ex: yargs)
|
|
594
|
+
* @param {string} name - Nom du flag (ex: 'auth', 'dbHost')
|
|
595
|
+
* @param {*} defaultValue - Valeur par défaut si le flag est absent.
|
|
596
|
+
*/
|
|
597
|
+
function getFlagValue(flags, name, defaultValue) {
|
|
598
|
+
const value = flags[name];
|
|
599
|
+
if (value !== undefined) {
|
|
600
|
+
// Gérer les cas où yargs (ou autre) renvoie une chaîne pour les booléens
|
|
601
|
+
if (value === "true") return true;
|
|
602
|
+
if (value === "false") return false;
|
|
603
|
+
return value;
|
|
604
|
+
}
|
|
605
|
+
return defaultValue;
|
|
606
|
+
}
|
|
607
|
+
module.exports = { getFullModeInputs };
|