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
package/utils/lightModeInput.js
CHANGED
|
@@ -6,6 +6,7 @@ const { capitalize } = require("./userInput");
|
|
|
6
6
|
const { logWarning } = require("./loggers/logWarning");
|
|
7
7
|
const { logInfo } = require("./loggers/logInfo");
|
|
8
8
|
const { getPackageManager } = require("./utils");
|
|
9
|
+
const { buildEntityInteractive, buildRelationsInteractive } = require("./interactive/entityBuilder");
|
|
9
10
|
const actualInquirer = inquirer.default || inquirer;
|
|
10
11
|
|
|
11
12
|
async function getLightModeInputs(projectName, flags) {
|
|
@@ -139,7 +140,17 @@ async function getLightModeInputs(projectName, flags) {
|
|
|
139
140
|
);
|
|
140
141
|
if (addEntities) {
|
|
141
142
|
console.log(`\n${info("[INFO]")} Entity input (simplified mode)`);
|
|
142
|
-
|
|
143
|
+
while (true) {
|
|
144
|
+
const entity = await buildEntityInteractive();
|
|
145
|
+
if (entity) {
|
|
146
|
+
inputs.entitiesData.entities.push(entity);
|
|
147
|
+
console.log(
|
|
148
|
+
`${info("[INFO]")} Entity "${entity.name}" added with ${entity.fields.length} field(s)`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const addMore = readline.keyInYNStrict("Add another entity?");
|
|
152
|
+
if (!addMore) break;
|
|
153
|
+
}
|
|
143
154
|
}
|
|
144
155
|
|
|
145
156
|
// Demander les relations entre entités
|
|
@@ -149,7 +160,7 @@ async function getLightModeInputs(projectName, flags) {
|
|
|
149
160
|
);
|
|
150
161
|
if (wantsRelation) {
|
|
151
162
|
console.log(`\n${info("[INFO]")} Configuring relationships`);
|
|
152
|
-
await
|
|
163
|
+
await buildRelationsInteractive(inputs.entitiesData);
|
|
153
164
|
}
|
|
154
165
|
}
|
|
155
166
|
|
|
@@ -213,248 +224,4 @@ function getDockerChoice(flags) {
|
|
|
213
224
|
return readline.keyInYNStrict(`${info("[?]")} Generate Docker files ?`);
|
|
214
225
|
}
|
|
215
226
|
|
|
216
|
-
async function addCustomEntities(entitiesData) {
|
|
217
|
-
while (true) {
|
|
218
|
-
let name;
|
|
219
|
-
while (true) {
|
|
220
|
-
name = readline.question("\nEntity name (empty to finish) : ");
|
|
221
|
-
if (!name) return;
|
|
222
|
-
if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
|
|
223
|
-
logWarning("Invalid name. Use letters, numbers, and _ only.");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const fields = [];
|
|
227
|
-
console.log(` Fields for entity "${name}" :`);
|
|
228
|
-
|
|
229
|
-
while (true) {
|
|
230
|
-
const fieldName = readline.question(" Field name (empty to finish) : ");
|
|
231
|
-
if (!fieldName) break;
|
|
232
|
-
|
|
233
|
-
if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fieldName)) {
|
|
234
|
-
logWarning("Invalid field name.");
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const baseTypeChoices = [
|
|
239
|
-
"string",
|
|
240
|
-
"text",
|
|
241
|
-
"number",
|
|
242
|
-
"decimal",
|
|
243
|
-
"boolean",
|
|
244
|
-
"Date",
|
|
245
|
-
"uuid",
|
|
246
|
-
"json",
|
|
247
|
-
"enum",
|
|
248
|
-
"array",
|
|
249
|
-
"object",
|
|
250
|
-
];
|
|
251
|
-
|
|
252
|
-
const typeQuestion = {
|
|
253
|
-
type: "list",
|
|
254
|
-
name: "ftype",
|
|
255
|
-
message: `Type of "${fieldName}"`,
|
|
256
|
-
default: "string",
|
|
257
|
-
choices: baseTypeChoices,
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
const typeAnswer = await actualInquirer.prompt([typeQuestion]);
|
|
261
|
-
let fieldType = typeAnswer.ftype; // --- ADVANCED LOGIC FOR ARRAY, ENUM, AND OBJECT ---
|
|
262
|
-
// Déplace le curseur d'une ligne vers le haut :
|
|
263
|
-
process.stdout.write("\x1B[1A");
|
|
264
|
-
// Efface la ligne (où se trouvait le "√ Type for...") :
|
|
265
|
-
process.stdout.write("\x1B[K");
|
|
266
|
-
|
|
267
|
-
if (fieldType === "array") {
|
|
268
|
-
// Prompt spécifique pour le type interne du tableau
|
|
269
|
-
const arrayInnerQuestion = {
|
|
270
|
-
type: "list",
|
|
271
|
-
name: "innerType",
|
|
272
|
-
message: `Type of elements in "${fieldName}[]"`,
|
|
273
|
-
default: "string", // Exclure array et object du sous-type pour simplifier
|
|
274
|
-
choices: baseTypeChoices.filter(
|
|
275
|
-
(c) => c !== "array" && c !== "object"
|
|
276
|
-
),
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
|
|
280
|
-
fieldType = `${innerAnswer.innerType}[]`;
|
|
281
|
-
} else if (fieldType === "enum") {
|
|
282
|
-
const enumName = capitalize(fieldName) + "Enum";
|
|
283
|
-
logInfo(
|
|
284
|
-
`Enum type selected. Remember to define ${enumName} in your code.`
|
|
285
|
-
);
|
|
286
|
-
fieldType = enumName;
|
|
287
|
-
} else if (fieldType === "object") {
|
|
288
|
-
// Prompt pour nommer l'objet (ou 'json' par défaut)
|
|
289
|
-
const objectNameQuestion = {
|
|
290
|
-
type: "input",
|
|
291
|
-
name: "objectName",
|
|
292
|
-
message: `Complex type name (DTO/Class or leave 'json') :`,
|
|
293
|
-
default: "json",
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
|
|
297
|
-
fieldType = capitalize(objectAnswer.objectName.trim() || "json");
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
console.log(
|
|
301
|
-
` Field type for "${fieldName}" : ${fieldType} ${success("[✓]")}`
|
|
302
|
-
);
|
|
303
|
-
|
|
304
|
-
fields.push({ name: fieldName, type: fieldType });
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
if (fields.length > 0) {
|
|
308
|
-
entitiesData.entities.push({ name, fields });
|
|
309
|
-
console.log(
|
|
310
|
-
`${info("[INFO]")} Entity "${name}" added with ${
|
|
311
|
-
fields.length
|
|
312
|
-
} field(s)`
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const addMore = readline.keyInYNStrict("Add another entity?");
|
|
317
|
-
if (!addMore) break;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Manages adding relationships between entities via an interactive interface.
|
|
323
|
-
* All prompts and logs are in English.
|
|
324
|
-
* @param {Object} entitiesData - The object containing entities and relations.
|
|
325
|
-
*/
|
|
326
|
-
async function addRelations(entitiesData) {
|
|
327
|
-
if (entitiesData.entities.length < 2) {
|
|
328
|
-
logInfo("At least two entities are required to create a relationship.");
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
let configuring = true;
|
|
333
|
-
|
|
334
|
-
while (configuring) {
|
|
335
|
-
const entityNames = entitiesData.entities.map((e) => e.name);
|
|
336
|
-
|
|
337
|
-
// 1. Interactive selection of entities
|
|
338
|
-
const answers = await actualInquirer.prompt([
|
|
339
|
-
{
|
|
340
|
-
type: "list",
|
|
341
|
-
name: "fromName",
|
|
342
|
-
message: "Select the source entity (From) :",
|
|
343
|
-
choices: entityNames,
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
type: "list",
|
|
347
|
-
name: "toName",
|
|
348
|
-
message: (prev) =>
|
|
349
|
-
`To which entity do you want to link ${prev.fromName} ?`,
|
|
350
|
-
choices: (prev) => entityNames.filter((name) => name !== prev.fromName),
|
|
351
|
-
},
|
|
352
|
-
]);
|
|
353
|
-
|
|
354
|
-
// --- CHECK FOR EXISTING RELATIONSHIPS ---
|
|
355
|
-
const alreadyExists = entitiesData.relations.find(
|
|
356
|
-
(rel) =>
|
|
357
|
-
(rel.from === answers.fromName && rel.to === answers.toName) ||
|
|
358
|
-
(rel.from === answers.toName && rel.to === answers.fromName)
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
if (alreadyExists) {
|
|
362
|
-
logWarning(
|
|
363
|
-
`A relationship already exists between ${answers.fromName} and ${answers.toName} (${alreadyExists.type}).`
|
|
364
|
-
);
|
|
365
|
-
|
|
366
|
-
const { tryAgain } = await actualInquirer.prompt([
|
|
367
|
-
{
|
|
368
|
-
type: "confirm",
|
|
369
|
-
name: "tryAgain",
|
|
370
|
-
message: "Would you like to choose different entities ?",
|
|
371
|
-
default: true,
|
|
372
|
-
},
|
|
373
|
-
]);
|
|
374
|
-
|
|
375
|
-
if (!tryAgain) break;
|
|
376
|
-
// Restart the loop
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// 2. Select Relationship Type
|
|
381
|
-
const typeAnswer = await actualInquirer.prompt([
|
|
382
|
-
{
|
|
383
|
-
type: "list",
|
|
384
|
-
name: "relType",
|
|
385
|
-
message: "What is the relationship type?",
|
|
386
|
-
choices: [
|
|
387
|
-
{
|
|
388
|
-
name: `1-1 (One-to-One) : ${answers.fromName} is linked to exactly one ${answers.toName}`,
|
|
389
|
-
value: "1-1",
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
name: `1-n (One-to-Many) : ${answers.fromName} owns multiple ${answers.toName}s`,
|
|
393
|
-
value: "1-n",
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
name: `n-1 (Many-to-One) : Multiple ${answers.fromName}s belong to one ${answers.toName}`,
|
|
397
|
-
value: "n-1",
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
name: `n-n (Many-to-Many) : Multiple ${answers.fromName}s are linked to multiple ${answers.toName}s`,
|
|
401
|
-
value: "n-n",
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
},
|
|
405
|
-
]);
|
|
406
|
-
|
|
407
|
-
const from = entitiesData.entities.find((e) => e.name === answers.fromName);
|
|
408
|
-
const to = entitiesData.entities.find((e) => e.name === answers.toName);
|
|
409
|
-
const relType = typeAnswer.relType;
|
|
410
|
-
|
|
411
|
-
// 3. Register relationship
|
|
412
|
-
entitiesData.relations.push({
|
|
413
|
-
from: from.name,
|
|
414
|
-
to: to.name,
|
|
415
|
-
type: relType,
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
// 4. Automatic field injection
|
|
419
|
-
const fromLow = from.name.toLowerCase();
|
|
420
|
-
const toLow = to.name.toLowerCase();
|
|
421
|
-
if (relType === "1-1") {
|
|
422
|
-
from.fields.push(
|
|
423
|
-
{ name: `${toLow}Id`, type: "string" },
|
|
424
|
-
{ name: toLow, type: to.name }
|
|
425
|
-
);
|
|
426
|
-
} else if (relType === "1-n") {
|
|
427
|
-
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
428
|
-
to.fields.push(
|
|
429
|
-
{ name: `${fromLow}Id`, type: "string" },
|
|
430
|
-
{ name: fromLow, type: from.name }
|
|
431
|
-
);
|
|
432
|
-
} else if (relType === "n-1") {
|
|
433
|
-
from.fields.push(
|
|
434
|
-
{ name: `${toLow}Id`, type: "string" },
|
|
435
|
-
{ name: toLow, type: to.name }
|
|
436
|
-
);
|
|
437
|
-
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
438
|
-
} else if (relType === "n-n") {
|
|
439
|
-
from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
|
|
440
|
-
to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
|
|
441
|
-
}
|
|
442
|
-
console.log(
|
|
443
|
-
`${success("[✓]")} Relationship added: ${from.name} ${relType} ${to.name}`
|
|
444
|
-
);
|
|
445
|
-
|
|
446
|
-
// 5. Ask to continue
|
|
447
|
-
const { addMore } = await actualInquirer.prompt([
|
|
448
|
-
{
|
|
449
|
-
type: "confirm",
|
|
450
|
-
name: "addMore",
|
|
451
|
-
message: "Do you want to add another relationship ?",
|
|
452
|
-
default: false,
|
|
453
|
-
},
|
|
454
|
-
]);
|
|
455
|
-
|
|
456
|
-
configuring = addMore;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
227
|
module.exports = { getLightModeInputs };
|
|
@@ -162,12 +162,39 @@ async function setupTypeORM(inputs) {
|
|
|
162
162
|
columnOptions.push("default: Role.USER");
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
if (field.
|
|
165
|
+
if (field.unique || field.name.toLowerCase() === "email") {
|
|
166
|
+
columnOptions.push("unique: true");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const isNullable = field.nullable || field.optional;
|
|
170
|
+
if (isNullable) {
|
|
166
171
|
columnOptions.push("nullable: true");
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
if (field.
|
|
170
|
-
|
|
174
|
+
if (field.default !== undefined && field.default !== null && field.default !== "") {
|
|
175
|
+
let defaultVal = field.default;
|
|
176
|
+
if (typeof defaultVal === "string") {
|
|
177
|
+
if (defaultVal === "true") defaultVal = true;
|
|
178
|
+
else if (defaultVal === "false") defaultVal = false;
|
|
179
|
+
else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
|
|
180
|
+
defaultVal = "CURRENT_TIMESTAMP";
|
|
181
|
+
} else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
|
|
182
|
+
defaultVal = Number(defaultVal);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (defaultVal === "CURRENT_TIMESTAMP") {
|
|
186
|
+
columnOptions.push(`default: () => 'CURRENT_TIMESTAMP'`);
|
|
187
|
+
} else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
|
|
188
|
+
columnOptions.push(`default: ${defaultVal}`);
|
|
189
|
+
} else if (mapping.type === "enum") {
|
|
190
|
+
if (mapping.tsType && /^[A-Z]/.test(mapping.tsType)) {
|
|
191
|
+
columnOptions.push(`default: ${mapping.tsType}.${defaultVal.toUpperCase()}`);
|
|
192
|
+
} else {
|
|
193
|
+
columnOptions.push(`default: '${defaultVal}'`);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
columnOptions.push(`default: '${defaultVal}'`);
|
|
197
|
+
}
|
|
171
198
|
}
|
|
172
199
|
|
|
173
200
|
// 2. Gestion des Imports d'Enums Custom
|
|
@@ -186,7 +213,7 @@ async function setupTypeORM(inputs) {
|
|
|
186
213
|
// 3. Construction du champ
|
|
187
214
|
fieldsContent += `
|
|
188
215
|
@Column({ ${columnOptions.join(", ")} })
|
|
189
|
-
${field.name}${
|
|
216
|
+
${field.name}${isNullable ? "?" : ""}: ${mapping.tsType};
|
|
190
217
|
`;
|
|
191
218
|
}
|
|
192
219
|
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
const readline = require("readline-sync");
|
|
2
|
-
const { logInfo } = require("../loggers/logInfo");
|
|
3
|
-
const { runCommand } = require("../shell");
|
|
4
2
|
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
5
4
|
const { info } = require("../colors");
|
|
5
|
+
const { logInfo } = require("../loggers/logInfo");
|
|
6
|
+
const { logError } = require("../loggers/logError");
|
|
7
|
+
const { runCommand } = require("../shell");
|
|
8
|
+
|
|
9
|
+
// Pipeline imports
|
|
10
|
+
const { setupCleanArchitecture } = require("../configs/setupCleanArchitecture");
|
|
11
|
+
const { setupLightArchitecture } = require("../configs/setupLightArchitecture");
|
|
12
|
+
const { setupAuth } = require("./setupAuth");
|
|
13
|
+
const { setupSwagger } = require("./setupSwagger");
|
|
14
|
+
const { setupDatabase } = require("./setupDatabase");
|
|
15
|
+
const { configureDocker } = require("../configs/configureDocker");
|
|
16
|
+
const { setupBootstrapLogger } = require("./setupLogger");
|
|
17
|
+
const { generateEnvFile, writeEnvFile } = require("../envGenerator");
|
|
18
|
+
const { saveProjectConfig } = require("../file-utils/saveProjectConfig");
|
|
19
|
+
const { printSetupWarnings } = require("../shell");
|
|
6
20
|
|
|
7
21
|
async function createProject(inputs) {
|
|
8
22
|
if (fs.existsSync(inputs.projectName)) {
|
|
@@ -13,34 +27,90 @@ async function createProject(inputs) {
|
|
|
13
27
|
);
|
|
14
28
|
|
|
15
29
|
if (confirmation) {
|
|
16
|
-
// confirmation is true if the user presses 'y'
|
|
17
30
|
console.log("Deleting existing project...");
|
|
18
31
|
fs.rmSync(inputs.projectName, { recursive: true, force: true });
|
|
19
32
|
} else {
|
|
20
33
|
console.log("Operation cancelled by user. Exiting.");
|
|
21
|
-
|
|
34
|
+
throw new Error("Operation cancelled by user.");
|
|
22
35
|
}
|
|
23
36
|
}
|
|
24
37
|
|
|
25
38
|
logInfo(`Creating NestJS project: ${inputs.projectName}`);
|
|
26
39
|
|
|
27
|
-
// Remains asynchronous as runCommand likely involves external processes (npm/npx)
|
|
28
40
|
await runCommand(
|
|
29
41
|
`npx @nestjs/cli new ${inputs.projectName} --package-manager ${inputs.packageManager}`,
|
|
30
42
|
"Failed to create NestJS project"
|
|
31
43
|
);
|
|
32
44
|
|
|
33
45
|
// Changing the current process directory to the project root
|
|
34
|
-
process.
|
|
46
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
47
|
+
if (!isDryRun) {
|
|
48
|
+
process.chdir(inputs.projectName);
|
|
49
|
+
} else {
|
|
50
|
+
console.log(`[DRY-RUN] Simulated: change directory to ${inputs.projectName}`);
|
|
51
|
+
}
|
|
35
52
|
|
|
36
53
|
// Installing dependencies
|
|
37
54
|
logInfo("Installing dependencies...");
|
|
38
55
|
|
|
39
|
-
// Remains asynchronous as runCommand likely involves external processes (npm/npx)
|
|
40
56
|
await runCommand(
|
|
41
57
|
`${inputs.packageManager} add @nestjs/config class-validator class-transformer dotenv`,
|
|
42
58
|
"Failed to install dependencies"
|
|
43
59
|
);
|
|
44
60
|
}
|
|
45
61
|
|
|
46
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Runs the complete project scaffolding and setup pipeline in sequence.
|
|
64
|
+
* Handles CWD tracking and restores the working directory on failure.
|
|
65
|
+
* @param {object} inputs
|
|
66
|
+
*/
|
|
67
|
+
async function runProjectSetupPipeline(inputs) {
|
|
68
|
+
const originalCwd = process.cwd();
|
|
69
|
+
try {
|
|
70
|
+
logInfo("Starting project generation...");
|
|
71
|
+
|
|
72
|
+
await createProject(inputs);
|
|
73
|
+
|
|
74
|
+
if (inputs.mode === "light") {
|
|
75
|
+
await setupLightArchitecture(inputs);
|
|
76
|
+
} else {
|
|
77
|
+
await setupCleanArchitecture(inputs);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (inputs.useAuth) {
|
|
81
|
+
await setupAuth(inputs);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (inputs.useSwagger) {
|
|
85
|
+
await setupSwagger(inputs.swaggerInputs);
|
|
86
|
+
} else {
|
|
87
|
+
setupBootstrapLogger();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (inputs.useDocker) {
|
|
91
|
+
await configureDocker(inputs);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await setupDatabase(inputs);
|
|
95
|
+
|
|
96
|
+
const envContent = await generateEnvFile(inputs);
|
|
97
|
+
writeEnvFile(envContent);
|
|
98
|
+
|
|
99
|
+
await saveProjectConfig(inputs);
|
|
100
|
+
|
|
101
|
+
// Show warnings
|
|
102
|
+
printSetupWarnings();
|
|
103
|
+
|
|
104
|
+
} catch (error) {
|
|
105
|
+
logError(`Error during project creation: ${error.message}`);
|
|
106
|
+
if (process.cwd() !== originalCwd) {
|
|
107
|
+
process.chdir(originalCwd);
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
createProject,
|
|
115
|
+
runProjectSetupPipeline,
|
|
116
|
+
};
|
|
@@ -805,8 +805,7 @@ import { Session, SessionSchema } from '${schemaRelativePath}';`;
|
|
|
805
805
|
}
|
|
806
806
|
|
|
807
807
|
if (inputs.mode == "full") {
|
|
808
|
-
dbImports
|
|
809
|
-
+"import { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';";
|
|
808
|
+
dbImports += `\nimport { ISessionRepositoryName } from '${paths.interfaces}/session.repository.interface';`;
|
|
810
809
|
}
|
|
811
810
|
|
|
812
811
|
await createFile({
|
|
@@ -68,6 +68,15 @@ async function setupMongoose(inputs) {
|
|
|
68
68
|
"Mongoose and its dependencies successfully installed!"
|
|
69
69
|
);
|
|
70
70
|
|
|
71
|
+
const isDryRun = process.argv.includes("--dry-run");
|
|
72
|
+
if (isDryRun) {
|
|
73
|
+
console.log("[DRY-RUN] Simulated: write Mongoose configuration, app.module.ts config, and schemas");
|
|
74
|
+
logSuccess(
|
|
75
|
+
"Mongoose configuration complete. Schemas are generated in src/schemas!"
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
// --- Base Configuration (app.module.ts and .env) --- // Generating the .env file
|
|
72
81
|
const envPath = ".env";
|
|
73
82
|
const mongoUriLine = `MONGO_URI=${inputs.dbConfig.MONGO_URI}`;
|
|
@@ -78,14 +87,10 @@ async function setupMongoose(inputs) {
|
|
|
78
87
|
await createFile({ path: envPath, contente: envContent });
|
|
79
88
|
} else {
|
|
80
89
|
let content = fs.readFileSync(envPath, "utf8");
|
|
81
|
-
if (content.includes("MONGO_URI=")) {
|
|
82
|
-
content = content.replace(/MONGO_URI=.*/g, mongoUriLine);
|
|
83
|
-
} else {
|
|
90
|
+
if (!content.includes("MONGO_URI=")) {
|
|
84
91
|
content += `\n${mongoUriLine}`;
|
|
85
92
|
}
|
|
86
|
-
if (content.includes("MONGO_DB=")) {
|
|
87
|
-
content = content.replace(/MONGO_DB=.*/g, mongoDbLine);
|
|
88
|
-
} else {
|
|
93
|
+
if (!content.includes("MONGO_DB=")) {
|
|
89
94
|
content += `\n${mongoDbLine}`;
|
|
90
95
|
}
|
|
91
96
|
fs.writeFileSync(envPath, content, "utf8");
|
|
@@ -98,21 +103,30 @@ async function setupMongoose(inputs) {
|
|
|
98
103
|
dbName: process.env.MONGO_DB,
|
|
99
104
|
}),`;
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if (fs.existsSync(appModulePath)) {
|
|
107
|
+
let appModuleContent = fs.readFileSync(appModulePath, "utf8");
|
|
108
|
+
|
|
109
|
+
// 1. Adding MongooseModule import
|
|
110
|
+
if (!appModuleContent.includes("@nestjs/mongoose")) {
|
|
111
|
+
await updateFile({
|
|
112
|
+
path: appModulePath,
|
|
113
|
+
pattern: /import {[\s\S]*?} from '@nestjs\/config';/,
|
|
114
|
+
replacement: (match) => `${match}\n${mongooseImport}`,
|
|
115
|
+
});
|
|
116
|
+
appModuleContent = fs.readFileSync(appModulePath, "utf8");
|
|
117
|
+
}
|
|
107
118
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
119
|
+
// 2. Adding MongooseModule.forRoot() configuration
|
|
120
|
+
if (!appModuleContent.includes("MongooseModule.forRoot")) {
|
|
121
|
+
const importsPattern =
|
|
122
|
+
/imports:\s*\[[\s\S]*?ConfigModule\.forRoot\([\s\S]*?\),/;
|
|
123
|
+
await updateFile({
|
|
124
|
+
path: appModulePath,
|
|
125
|
+
pattern: importsPattern,
|
|
126
|
+
replacement: (match) => `${match}${mongooseForRoot}`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
116
130
|
|
|
117
131
|
// --- Generating Mongoose Entities (Schemas) ---
|
|
118
132
|
logInfo("📁 Generating Mongoose schemas (src/schemas)...");
|
|
@@ -161,10 +175,34 @@ role: Role;
|
|
|
161
175
|
const mongooseType = mapTypeToMongoose(field.type);
|
|
162
176
|
const tsType = field.type;
|
|
163
177
|
|
|
164
|
-
const isRequired =
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
const isRequired = field.nullable !== undefined ? !field.nullable : (field.name.toLowerCase() !== "isactive" && field.name.toLowerCase() !== "password");
|
|
179
|
+
let propOpts = [`type: ${mongooseType}`];
|
|
180
|
+
if (isRequired) {
|
|
181
|
+
propOpts.push("required: true");
|
|
182
|
+
}
|
|
183
|
+
if (field.unique) {
|
|
184
|
+
propOpts.push("unique: true");
|
|
185
|
+
}
|
|
186
|
+
if (field.default !== undefined && field.default !== null && field.default !== "") {
|
|
187
|
+
let defaultVal = field.default;
|
|
188
|
+
if (typeof defaultVal === "string") {
|
|
189
|
+
if (defaultVal === "true") defaultVal = true;
|
|
190
|
+
else if (defaultVal === "false") defaultVal = false;
|
|
191
|
+
else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
|
|
192
|
+
defaultVal = "Date.now";
|
|
193
|
+
} else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
|
|
194
|
+
defaultVal = Number(defaultVal);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (defaultVal === "Date.now") {
|
|
198
|
+
propOpts.push("default: Date.now");
|
|
199
|
+
} else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
|
|
200
|
+
propOpts.push(`default: ${defaultVal}`);
|
|
201
|
+
} else {
|
|
202
|
+
propOpts.push(`default: '${defaultVal}'`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const propOptionsStr = propOpts.join(", ");
|
|
168
206
|
|
|
169
207
|
// Import des enums partagés si nécessaire
|
|
170
208
|
if (
|
|
@@ -176,8 +214,8 @@ role: Role;
|
|
|
176
214
|
}
|
|
177
215
|
|
|
178
216
|
fieldsContent += `
|
|
179
|
-
@Prop({
|
|
180
|
-
${field.name}: ${tsType};
|
|
217
|
+
@Prop({ ${propOptionsStr} })
|
|
218
|
+
${field.name}${field.nullable ? "?" : ""}: ${tsType};
|
|
181
219
|
`;
|
|
182
220
|
}
|
|
183
221
|
|
|
@@ -203,11 +241,22 @@ role: Role;
|
|
|
203
241
|
", "
|
|
204
242
|
)}]),`;
|
|
205
243
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
244
|
+
if (fs.existsSync(appModulePath)) {
|
|
245
|
+
const appModuleContent = fs.readFileSync(appModulePath, "utf8");
|
|
246
|
+
if (appModuleContent.includes("MongooseModule.forFeature")) {
|
|
247
|
+
await updateFile({
|
|
248
|
+
path: appModulePath,
|
|
249
|
+
pattern: /MongooseModule\.forFeature\(\[[\s\S]*?\]\),/,
|
|
250
|
+
replacement: forFeatureBlock,
|
|
251
|
+
});
|
|
252
|
+
} else {
|
|
253
|
+
await updateFile({
|
|
254
|
+
path: appModulePath,
|
|
255
|
+
pattern: new RegExp(mongooseForRoot.trim().replace(/[\n\r]/g, "\\s*"), "g"),
|
|
256
|
+
replacement: (match) => `${match}\n\t${forFeatureBlock}`,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
211
260
|
|
|
212
261
|
if (inputs.isDemo) {
|
|
213
262
|
// The generateSampleData function must be adapted for 'new Date()' usage and Mongoose types
|
|
@@ -147,7 +147,7 @@ async function setupPrisma(inputs) {
|
|
|
147
147
|
!fieldsToExclude.includes(fieldNameLower) &&
|
|
148
148
|
!addedFields.has(fieldNameLower)
|
|
149
149
|
) {
|
|
150
|
-
schemaContent += `\n ${field.name} ${
|
|
150
|
+
schemaContent += `\n ${field.name} ${formatPrismaField(field)}`;
|
|
151
151
|
addedFields.add(fieldNameLower);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
@@ -431,6 +431,44 @@ function mapTypeToPrisma(type) {
|
|
|
431
431
|
}
|
|
432
432
|
}
|
|
433
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Formats a field definition for the Prisma schema including constraints.
|
|
436
|
+
* @param {object} field
|
|
437
|
+
* @returns {string} Formatted field line content (e.g. "String? @unique")
|
|
438
|
+
*/
|
|
439
|
+
function formatPrismaField(field) {
|
|
440
|
+
let prismaType = mapTypeToPrisma(field.type);
|
|
441
|
+
if (field.nullable) {
|
|
442
|
+
if (!prismaType.endsWith("?") && !prismaType.endsWith("[]")) {
|
|
443
|
+
prismaType += "?";
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
let attributes = "";
|
|
447
|
+
if (field.unique) {
|
|
448
|
+
attributes += " @unique";
|
|
449
|
+
}
|
|
450
|
+
if (field.default !== undefined && field.default !== null && field.default !== "") {
|
|
451
|
+
let defaultVal = field.default;
|
|
452
|
+
if (typeof defaultVal === "string") {
|
|
453
|
+
if (defaultVal === "true") defaultVal = true;
|
|
454
|
+
else if (defaultVal === "false") defaultVal = false;
|
|
455
|
+
else if (defaultVal.toLowerCase() === "now" || defaultVal.toLowerCase() === "now()") {
|
|
456
|
+
defaultVal = "now()";
|
|
457
|
+
} else if (!isNaN(defaultVal) && defaultVal.trim() !== "") {
|
|
458
|
+
defaultVal = Number(defaultVal);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (defaultVal === "now()") {
|
|
462
|
+
attributes += ` @default(now())`;
|
|
463
|
+
} else if (typeof defaultVal === "boolean" || typeof defaultVal === "number") {
|
|
464
|
+
attributes += ` @default(${defaultVal})`;
|
|
465
|
+
} else {
|
|
466
|
+
attributes += ` @default("${defaultVal}")`;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return `${prismaType}${attributes}`;
|
|
470
|
+
}
|
|
471
|
+
|
|
434
472
|
async function setupPrismaSeeding(inputs) {
|
|
435
473
|
logInfo("⚙️ Configuring seeding for Prisma...");
|
|
436
474
|
|
|
@@ -681,7 +719,7 @@ model ${capitalizedName} {
|
|
|
681
719
|
updatedAt DateTime @updatedAt${relationFields}`;
|
|
682
720
|
|
|
683
721
|
fields.forEach((field) => {
|
|
684
|
-
modelBlock += `\n ${field.name} ${
|
|
722
|
+
modelBlock += `\n ${field.name} ${formatPrismaField(field)}`;
|
|
685
723
|
});
|
|
686
724
|
|
|
687
725
|
modelBlock += `\n}\n`;
|
|
@@ -738,7 +776,7 @@ model ${capitalizedName} {
|
|
|
738
776
|
);
|
|
739
777
|
|
|
740
778
|
await runCommand(
|
|
741
|
-
`npx prisma migrate dev --name add_module_${entityData.name}
|
|
779
|
+
`npx prisma migrate dev --name add_module_${entityData.name}`,
|
|
742
780
|
`❌ Failed to run prisma migration — run 'npx prisma migrate dev --name add_module_${entityData.name}' manually`,
|
|
743
781
|
{ critical: false }
|
|
744
782
|
);
|