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