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.
@@ -6,6 +6,7 @@ const { capitalize } = require("./userInput");
6
6
  const { logWarning } = require("./loggers/logWarning");
7
7
  const { logInfo } = require("./loggers/logInfo");
8
8
  const { getPackageManager } = require("./utils");
9
+ const { buildEntityInteractive, buildRelationsInteractive } = require("./interactive/entityBuilder");
9
10
  const actualInquirer = inquirer.default || inquirer;
10
11
 
11
12
  async function getLightModeInputs(projectName, flags) {
@@ -139,7 +140,17 @@ async function getLightModeInputs(projectName, flags) {
139
140
  );
140
141
  if (addEntities) {
141
142
  console.log(`\n${info("[INFO]")} Entity input (simplified mode)`);
142
- await addCustomEntities(inputs.entitiesData);
143
+ while (true) {
144
+ const entity = await buildEntityInteractive();
145
+ if (entity) {
146
+ inputs.entitiesData.entities.push(entity);
147
+ console.log(
148
+ `${info("[INFO]")} Entity "${entity.name}" added with ${entity.fields.length} field(s)`
149
+ );
150
+ }
151
+ const addMore = readline.keyInYNStrict("Add another entity?");
152
+ if (!addMore) break;
153
+ }
143
154
  }
144
155
 
145
156
  // Demander les relations entre entités
@@ -149,7 +160,7 @@ async function getLightModeInputs(projectName, flags) {
149
160
  );
150
161
  if (wantsRelation) {
151
162
  console.log(`\n${info("[INFO]")} Configuring relationships`);
152
- await addRelations(inputs.entitiesData);
163
+ await buildRelationsInteractive(inputs.entitiesData);
153
164
  }
154
165
  }
155
166
 
@@ -213,248 +224,4 @@ function getDockerChoice(flags) {
213
224
  return readline.keyInYNStrict(`${info("[?]")} Generate Docker files ?`);
214
225
  }
215
226
 
216
- async function addCustomEntities(entitiesData) {
217
- while (true) {
218
- let name;
219
- while (true) {
220
- name = readline.question("\nEntity name (empty to finish) : ");
221
- if (!name) return;
222
- if (/^[A-Za-z][A-Za-z0-9_]*$/.test(name)) break;
223
- logWarning("Invalid name. Use letters, numbers, and _ only.");
224
- }
225
-
226
- const fields = [];
227
- console.log(` Fields for entity "${name}" :`);
228
-
229
- while (true) {
230
- const fieldName = readline.question(" Field name (empty to finish) : ");
231
- if (!fieldName) break;
232
-
233
- if (!/^[A-Za-z][A-Za-z0-9_]*$/.test(fieldName)) {
234
- logWarning("Invalid field name.");
235
- continue;
236
- }
237
-
238
- const baseTypeChoices = [
239
- "string",
240
- "text",
241
- "number",
242
- "decimal",
243
- "boolean",
244
- "Date",
245
- "uuid",
246
- "json",
247
- "enum",
248
- "array",
249
- "object",
250
- ];
251
-
252
- const typeQuestion = {
253
- type: "list",
254
- name: "ftype",
255
- message: `Type of "${fieldName}"`,
256
- default: "string",
257
- choices: baseTypeChoices,
258
- };
259
-
260
- const typeAnswer = await actualInquirer.prompt([typeQuestion]);
261
- let fieldType = typeAnswer.ftype; // --- ADVANCED LOGIC FOR ARRAY, ENUM, AND OBJECT ---
262
- // Déplace le curseur d'une ligne vers le haut :
263
- process.stdout.write("\x1B[1A");
264
- // Efface la ligne (où se trouvait le "√ Type for...") :
265
- process.stdout.write("\x1B[K");
266
-
267
- if (fieldType === "array") {
268
- // Prompt spécifique pour le type interne du tableau
269
- const arrayInnerQuestion = {
270
- type: "list",
271
- name: "innerType",
272
- message: `Type of elements in "${fieldName}[]"`,
273
- default: "string", // Exclure array et object du sous-type pour simplifier
274
- choices: baseTypeChoices.filter(
275
- (c) => c !== "array" && c !== "object"
276
- ),
277
- };
278
-
279
- const innerAnswer = await actualInquirer.prompt([arrayInnerQuestion]);
280
- fieldType = `${innerAnswer.innerType}[]`;
281
- } else if (fieldType === "enum") {
282
- const enumName = capitalize(fieldName) + "Enum";
283
- logInfo(
284
- `Enum type selected. Remember to define ${enumName} in your code.`
285
- );
286
- fieldType = enumName;
287
- } else if (fieldType === "object") {
288
- // Prompt pour nommer l'objet (ou 'json' par défaut)
289
- const objectNameQuestion = {
290
- type: "input",
291
- name: "objectName",
292
- message: `Complex type name (DTO/Class or leave 'json') :`,
293
- default: "json",
294
- };
295
-
296
- const objectAnswer = await actualInquirer.prompt([objectNameQuestion]);
297
- fieldType = capitalize(objectAnswer.objectName.trim() || "json");
298
- }
299
-
300
- console.log(
301
- ` Field type for "${fieldName}" : ${fieldType} ${success("[✓]")}`
302
- );
303
-
304
- fields.push({ name: fieldName, type: fieldType });
305
- }
306
-
307
- if (fields.length > 0) {
308
- entitiesData.entities.push({ name, fields });
309
- console.log(
310
- `${info("[INFO]")} Entity "${name}" added with ${
311
- fields.length
312
- } field(s)`
313
- );
314
- }
315
-
316
- const addMore = readline.keyInYNStrict("Add another entity?");
317
- if (!addMore) break;
318
- }
319
- }
320
-
321
- /**
322
- * Manages adding relationships between entities via an interactive interface.
323
- * All prompts and logs are in English.
324
- * @param {Object} entitiesData - The object containing entities and relations.
325
- */
326
- async function addRelations(entitiesData) {
327
- if (entitiesData.entities.length < 2) {
328
- logInfo("At least two entities are required to create a relationship.");
329
- return;
330
- }
331
-
332
- let configuring = true;
333
-
334
- while (configuring) {
335
- const entityNames = entitiesData.entities.map((e) => e.name);
336
-
337
- // 1. Interactive selection of entities
338
- const answers = await actualInquirer.prompt([
339
- {
340
- type: "list",
341
- name: "fromName",
342
- message: "Select the source entity (From) :",
343
- choices: entityNames,
344
- },
345
- {
346
- type: "list",
347
- name: "toName",
348
- message: (prev) =>
349
- `To which entity do you want to link ${prev.fromName} ?`,
350
- choices: (prev) => entityNames.filter((name) => name !== prev.fromName),
351
- },
352
- ]);
353
-
354
- // --- CHECK FOR EXISTING RELATIONSHIPS ---
355
- const alreadyExists = entitiesData.relations.find(
356
- (rel) =>
357
- (rel.from === answers.fromName && rel.to === answers.toName) ||
358
- (rel.from === answers.toName && rel.to === answers.fromName)
359
- );
360
-
361
- if (alreadyExists) {
362
- logWarning(
363
- `A relationship already exists between ${answers.fromName} and ${answers.toName} (${alreadyExists.type}).`
364
- );
365
-
366
- const { tryAgain } = await actualInquirer.prompt([
367
- {
368
- type: "confirm",
369
- name: "tryAgain",
370
- message: "Would you like to choose different entities ?",
371
- default: true,
372
- },
373
- ]);
374
-
375
- if (!tryAgain) break;
376
- // Restart the loop
377
- continue;
378
- }
379
-
380
- // 2. Select Relationship Type
381
- const typeAnswer = await actualInquirer.prompt([
382
- {
383
- type: "list",
384
- name: "relType",
385
- message: "What is the relationship type?",
386
- choices: [
387
- {
388
- name: `1-1 (One-to-One) : ${answers.fromName} is linked to exactly one ${answers.toName}`,
389
- value: "1-1",
390
- },
391
- {
392
- name: `1-n (One-to-Many) : ${answers.fromName} owns multiple ${answers.toName}s`,
393
- value: "1-n",
394
- },
395
- {
396
- name: `n-1 (Many-to-One) : Multiple ${answers.fromName}s belong to one ${answers.toName}`,
397
- value: "n-1",
398
- },
399
- {
400
- name: `n-n (Many-to-Many) : Multiple ${answers.fromName}s are linked to multiple ${answers.toName}s`,
401
- value: "n-n",
402
- },
403
- ],
404
- },
405
- ]);
406
-
407
- const from = entitiesData.entities.find((e) => e.name === answers.fromName);
408
- const to = entitiesData.entities.find((e) => e.name === answers.toName);
409
- const relType = typeAnswer.relType;
410
-
411
- // 3. Register relationship
412
- entitiesData.relations.push({
413
- from: from.name,
414
- to: to.name,
415
- type: relType,
416
- });
417
-
418
- // 4. Automatic field injection
419
- const fromLow = from.name.toLowerCase();
420
- const toLow = to.name.toLowerCase();
421
- if (relType === "1-1") {
422
- from.fields.push(
423
- { name: `${toLow}Id`, type: "string" },
424
- { name: toLow, type: to.name }
425
- );
426
- } else if (relType === "1-n") {
427
- from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
428
- to.fields.push(
429
- { name: `${fromLow}Id`, type: "string" },
430
- { name: fromLow, type: from.name }
431
- );
432
- } else if (relType === "n-1") {
433
- from.fields.push(
434
- { name: `${toLow}Id`, type: "string" },
435
- { name: toLow, type: to.name }
436
- );
437
- to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
438
- } else if (relType === "n-n") {
439
- from.fields.push({ name: `${toLow}s`, type: `${to.name}[]` });
440
- to.fields.push({ name: `${fromLow}s`, type: `${from.name}[]` });
441
- }
442
- console.log(
443
- `${success("[✓]")} Relationship added: ${from.name} ${relType} ${to.name}`
444
- );
445
-
446
- // 5. Ask to continue
447
- const { addMore } = await actualInquirer.prompt([
448
- {
449
- type: "confirm",
450
- name: "addMore",
451
- message: "Do you want to add another relationship ?",
452
- default: false,
453
- },
454
- ]);
455
-
456
- configuring = addMore;
457
- }
458
- }
459
-
460
227
  module.exports = { getLightModeInputs };
@@ -162,12 +162,39 @@ async function setupTypeORM(inputs) {
162
162
  columnOptions.push("default: Role.USER");
163
163
  }
164
164
 
165
- if (field.optional) {
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.name.toLowerCase() === "email") {
170
- columnOptions.push("unique: true");
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}${field.optional ? "?" : ""}: ${mapping.tsType};
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
- return; // Stop function execution if the user cancels
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.chdir(inputs.projectName);
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
- module.exports = { createProject };
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
- // 1. Adding MongooseModule import
102
- await updateFile({
103
- path: appModulePath,
104
- pattern: /import {[\s\S]*?} from '@nestjs\/config';/,
105
- replacement: (match) => `${match}\n${mongooseImport}`,
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
- // 2. Adding MongooseModule.forRoot() configuration
109
- const importsPattern =
110
- /imports:\s*\[[\s\S]*?ConfigModule\.forRoot\([\s\S]*?\),/;
111
- await updateFile({
112
- path: appModulePath,
113
- pattern: importsPattern,
114
- replacement: (match) => `${match}${mongooseForRoot}`,
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
- field.name.toLowerCase() !== "isactive" &&
166
- field.name.toLowerCase() !== "password";
167
- const requiredOption = isRequired ? ", required: true" : "";
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({ type: ${mongooseType}${requiredOption} })
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
- await updateFile({
207
- path: appModulePath,
208
- pattern: new RegExp(mongooseForRoot.trim().replace(/[\n\r]/g, "\\s*"), "g"),
209
- replacement: (match) => `${match}\n\t${forFeatureBlock}`,
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} ${mapTypeToPrisma(field.type)}`;
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} ${mapTypeToPrisma(field.type)}`;
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} --force`,
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
  );