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
|
@@ -1,630 +1,802 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const {
|
|
3
|
-
const {
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (relation.type === "1
|
|
96
|
-
// 'Many' side: exclude the foreign key (e.g., '
|
|
97
|
-
fieldsToExcludeMap.get(
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
provider = "
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
await
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
await
|
|
293
|
-
|
|
294
|
-
//
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
async
|
|
314
|
-
await this.$
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
);
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (fs.existsSync(prismaConfigPath)) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
case "
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
"
|
|
433
|
-
|
|
434
|
-
await runCommand(
|
|
435
|
-
`${inputs.packageManager}
|
|
436
|
-
"❌ Failed to install
|
|
437
|
-
);
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
{ content: '
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const { execSync } = require("child_process");
|
|
3
|
+
const { logInfo } = require("../loggers/logInfo");
|
|
4
|
+
const { runCommand } = require("../shell");
|
|
5
|
+
const { logSuccess } = require("../loggers/logSuccess");
|
|
6
|
+
const {
|
|
7
|
+
createDirectory,
|
|
8
|
+
createFile,
|
|
9
|
+
updateFile,
|
|
10
|
+
capitalize,
|
|
11
|
+
} = require("../userInput");
|
|
12
|
+
const { updatePackageJson } = require("../file-utils/packageJsonUtils");
|
|
13
|
+
const { success, warning, error, info } = require("../colors");
|
|
14
|
+
|
|
15
|
+
const { logWarning } = require("../loggers/logWarning");
|
|
16
|
+
|
|
17
|
+
async function setupPrisma(inputs) {
|
|
18
|
+
logInfo("Configuring Prisma...");
|
|
19
|
+
|
|
20
|
+
const dbConfig = inputs.dbConfig; // 📌 Path to schema.prisma
|
|
21
|
+
const schemaPath = "prisma/schema.prisma"; // 📦 Step 1: Install Prisma and its client at version 6.5.0
|
|
22
|
+
|
|
23
|
+
const prismaVersion = "6.5.0"; // Stable version for the CLI
|
|
24
|
+
logInfo(`Installing prisma and client...`);
|
|
25
|
+
await runCommand(
|
|
26
|
+
`${inputs.packageManager} add -D prisma@${prismaVersion} @prisma/client@${prismaVersion}`,
|
|
27
|
+
"❌ Prisma installation failed",
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
// Step 2: Initialize Prisma
|
|
31
|
+
logInfo("Initializing Prisma");
|
|
32
|
+
await runCommand("npx prisma init", "❌ Prisma initialization failed");
|
|
33
|
+
|
|
34
|
+
await updateFile({
|
|
35
|
+
path: schemaPath,
|
|
36
|
+
pattern: /generator client \{[^}]*\}/g,
|
|
37
|
+
replacement: `generator client {
|
|
38
|
+
provider = "prisma-client-js"
|
|
39
|
+
output = "../node_modules/.prisma/client" //
|
|
40
|
+
}`,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Step 3: Environment Configuration (.env and .env.example files)
|
|
44
|
+
const envPath = ".env";
|
|
45
|
+
const exampleEnvPath = ".env.example";
|
|
46
|
+
const databaseUrl = `DATABASE_URL="postgresql://${dbConfig.POSTGRES_USER}:${dbConfig.POSTGRES_PASSWORD}@${dbConfig.POSTGRES_HOST}:${dbConfig.POSTGRES_PORT}/${dbConfig.POSTGRES_DB}?schema=public"`;
|
|
47
|
+
const exampleDatabaseUrl = `DATABASE_URL="postgresql://user:password@localhost:5432/dbName?schema=public"`;
|
|
48
|
+
|
|
49
|
+
await createFile({
|
|
50
|
+
path: envPath,
|
|
51
|
+
contente: databaseUrl,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await createFile({
|
|
55
|
+
path: exampleEnvPath,
|
|
56
|
+
contente: exampleDatabaseUrl,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Step 4: Generating Prisma models from provided entities
|
|
60
|
+
logInfo("Adding entities");
|
|
61
|
+
let schemaContent = "";
|
|
62
|
+
const hasUserEntity = inputs.entitiesData.entities.some(
|
|
63
|
+
(entity) => entity.name.toLowerCase() === "user",
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Adding the Role enum block if User is present
|
|
67
|
+
if (hasUserEntity) {
|
|
68
|
+
schemaContent += `
|
|
69
|
+
/**
|
|
70
|
+
* Role enumeration
|
|
71
|
+
*/
|
|
72
|
+
enum Role {
|
|
73
|
+
USER
|
|
74
|
+
ADMIN
|
|
75
|
+
SUPER_ADMIN
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const fieldsToExcludeMap = new Map();
|
|
81
|
+
for (const entity of inputs.entitiesData.entities) {
|
|
82
|
+
fieldsToExcludeMap.set(entity.name.toLowerCase(), []);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (inputs.entitiesData.relations?.length > 0) {
|
|
86
|
+
for (const relation of inputs.entitiesData.relations) {
|
|
87
|
+
const fromLower = relation.from.toLowerCase();
|
|
88
|
+
const toLower = relation.to.toLowerCase();
|
|
89
|
+
const fromCapitalized = capitalize(relation.from);
|
|
90
|
+
const toCapitalized = capitalize(relation.to); // 'from' side (source)
|
|
91
|
+
|
|
92
|
+
if (relation.type === "1-n") {
|
|
93
|
+
// 'One' side: exclude the name of the other entity's list (e.g., 'articles')
|
|
94
|
+
fieldsToExcludeMap.get(fromLower).push(`${toLower}s`);
|
|
95
|
+
} else if (relation.type === "n-1") {
|
|
96
|
+
// 'Many' side: exclude the foreign key (e.g., 'articleId') and the relation name (e.g., 'article')
|
|
97
|
+
fieldsToExcludeMap.get(fromLower).push(`${toLower}id`, toLower);
|
|
98
|
+
} // Add other relation types (1-1, n-n) if necessary here... // 'to' side (target)
|
|
99
|
+
if (relation.type === "1-n") {
|
|
100
|
+
// 'Many' side: exclude the foreign key (e.g., 'userId') and the relation name (e.g., 'user')
|
|
101
|
+
fieldsToExcludeMap.get(toLower).push(`${fromLower}id`, fromLower);
|
|
102
|
+
} else if (relation.type === "n-1") {
|
|
103
|
+
// 'One' side: exclude the name of the other entity's list (e.g., 'comments')
|
|
104
|
+
fieldsToExcludeMap.get(toLower).push(`${fromLower}s`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 2. Initial generation of models WITHOUT incorrect relationship fields
|
|
109
|
+
for (const entity of inputs.entitiesData.entities) {
|
|
110
|
+
const entityNameLower = entity.name.toLowerCase();
|
|
111
|
+
// On utilise un Set pour suivre les noms de champs déjà écrits dans ce modèle
|
|
112
|
+
const addedFields = new Set(["id", "createdat", "updatedat"]);
|
|
113
|
+
|
|
114
|
+
schemaContent += `
|
|
115
|
+
/**
|
|
116
|
+
* ${entity.name} Model
|
|
117
|
+
*/
|
|
118
|
+
model ${entity.name} {
|
|
119
|
+
id String @id @default(uuid())
|
|
120
|
+
createdAt DateTime @default(now())
|
|
121
|
+
updatedAt DateTime @updatedAt`;
|
|
122
|
+
|
|
123
|
+
const fieldsToExclude = fieldsToExcludeMap.get(entityNameLower) || [];
|
|
124
|
+
|
|
125
|
+
for (const field of entity.fields) {
|
|
126
|
+
const fieldNameLower = field.name.toLowerCase();
|
|
127
|
+
|
|
128
|
+
// Ajout du rôle SEULEMENT s'il n'a pas été ajouté durant la boucle ci-dessus
|
|
129
|
+
if (entityNameLower == "user" && !addedFields.has("role")) {
|
|
130
|
+
schemaContent += `\n role Role @default(USER)`;
|
|
131
|
+
addedFields.add("role");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Add email field ONLY if it was not added previously
|
|
135
|
+
if (entityNameLower === "user" && !addedFields.has("email")) {
|
|
136
|
+
schemaContent += `\n email String @unique`;
|
|
137
|
+
addedFields.add("email");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// On n'ajoute le champ que s'il n'est pas exclu ET pas déjà présent (comme id/createdAt)
|
|
141
|
+
if (
|
|
142
|
+
!fieldsToExclude.includes(fieldNameLower) &&
|
|
143
|
+
!addedFields.has(fieldNameLower)
|
|
144
|
+
) {
|
|
145
|
+
schemaContent += `\n ${field.name} ${mapTypeToPrisma(field.type)}`;
|
|
146
|
+
addedFields.add(fieldNameLower);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
schemaContent += `\n}\n`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 3. Applying relationship logic to add the CORRECT fields
|
|
154
|
+
logInfo("Applying Prisma relations...");
|
|
155
|
+
|
|
156
|
+
if (inputs.entitiesData.relations?.length > 0) {
|
|
157
|
+
for (const relation of inputs.entitiesData.relations) {
|
|
158
|
+
const from = relation.from;
|
|
159
|
+
const to = relation.to;
|
|
160
|
+
const type = relation.type;
|
|
161
|
+
|
|
162
|
+
// The replacement must be done on the entire generated schemaContent // Using a replacement function to update the content of `schemaContent`
|
|
163
|
+
if (type === "1-n") {
|
|
164
|
+
// "One" side (source): adds the list (to[])
|
|
165
|
+
schemaContent = schemaContent.replace(
|
|
166
|
+
new RegExp(`model ${from} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
167
|
+
(match, content) => {
|
|
168
|
+
const fieldLine = ` ${to}s ${to}[]`;
|
|
169
|
+
return match.includes(fieldLine)
|
|
170
|
+
? match
|
|
171
|
+
: `model ${from} {${content}\n${fieldLine}\n}`;
|
|
172
|
+
},
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// "Many" side (target): adds the relation and the foreign key
|
|
176
|
+
schemaContent = schemaContent.replace(
|
|
177
|
+
new RegExp(`model ${to} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
178
|
+
(match, content) => {
|
|
179
|
+
const relationLine = ` ${from} ${from} @relation(fields: [${from}Id], references: [id])`;
|
|
180
|
+
const fkLine = ` ${from}Id String`;
|
|
181
|
+
let result = match.includes(relationLine)
|
|
182
|
+
? content
|
|
183
|
+
: `${content}\n${relationLine}`;
|
|
184
|
+
result = result.includes(fkLine) ? result : `${result}\n${fkLine}`;
|
|
185
|
+
return `model ${to} {${result}\n}`;
|
|
186
|
+
},
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (type === "n-1") {
|
|
191
|
+
// n-1 is the inverse of 1-n: from is the "many" and to is the "one"
|
|
192
|
+
|
|
193
|
+
// "Many" side (source = from): adds the relation and the foreign key
|
|
194
|
+
schemaContent = schemaContent.replace(
|
|
195
|
+
new RegExp(`model ${from} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
196
|
+
(match, content) => {
|
|
197
|
+
const relationLine = ` ${to} ${to} @relation(fields: [${to}Id], references: [id])`;
|
|
198
|
+
const fkLine = ` ${to}Id String`;
|
|
199
|
+
let result = match.includes(relationLine)
|
|
200
|
+
? content
|
|
201
|
+
: `${content}\n${relationLine}`;
|
|
202
|
+
result = result.includes(fkLine) ? result : `${result}\n${fkLine}`;
|
|
203
|
+
return `model ${from} {${result}\n}`;
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// "One" side (target = to): adds the list (from[])
|
|
208
|
+
schemaContent = schemaContent.replace(
|
|
209
|
+
new RegExp(`model ${to} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
210
|
+
(match, content) => {
|
|
211
|
+
const fromCapitalized = capitalize(from);
|
|
212
|
+
const fieldLine = ` ${from}s ${from}[]`;
|
|
213
|
+
return match.includes(fieldLine)
|
|
214
|
+
? match
|
|
215
|
+
: `model ${to} {${content}\n${fieldLine}\n}`;
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (type === "1-1") {
|
|
221
|
+
// 'from' side (source): adds the relation, foreign key, and @unique attribute
|
|
222
|
+
schemaContent = schemaContent.replace(
|
|
223
|
+
new RegExp(`model ${from} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
224
|
+
(match, content) => {
|
|
225
|
+
const relationLine = ` ${to} ${to}? @relation(fields: [${to}Id], references: [id])`; // The foreign key must be unique in a 1-1 relationship, and optional for flexibility
|
|
226
|
+
const fkLine = ` ${to}Id String? @unique`;
|
|
227
|
+
|
|
228
|
+
let result = match.includes(relationLine)
|
|
229
|
+
? content
|
|
230
|
+
: `${content}\n${relationLine}`;
|
|
231
|
+
result = result.includes(fkLine) ? result : `${result}\n${fkLine}`;
|
|
232
|
+
return `model ${from} {${result}\n}`;
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// 'to' side (target): adds the inverse relation (optional)
|
|
237
|
+
schemaContent = schemaContent.replace(
|
|
238
|
+
new RegExp(`model ${to} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
239
|
+
(match, content) => {
|
|
240
|
+
// Inverse relation (optional because 'from' holds the FK)
|
|
241
|
+
const fieldLine = ` ${from} ${from}?`;
|
|
242
|
+
return match.includes(fieldLine)
|
|
243
|
+
? match
|
|
244
|
+
: `model ${to} {${content}\n${fieldLine}\n}`;
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (type === "n-n") {
|
|
250
|
+
// 'from' side (source): adds the list (to[])
|
|
251
|
+
schemaContent = schemaContent.replace(
|
|
252
|
+
new RegExp(`model ${from} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
253
|
+
(match, content) => {
|
|
254
|
+
const fieldLine = ` ${to}s ${to}[]`;
|
|
255
|
+
return match.includes(fieldLine)
|
|
256
|
+
? match
|
|
257
|
+
: `model ${from} {${content}\n${fieldLine}\n}`;
|
|
258
|
+
},
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
// 'to' side (target): adds the list (from[])
|
|
262
|
+
schemaContent = schemaContent.replace(
|
|
263
|
+
new RegExp(`model ${to} \{([\\s\\S]*?)\n\\}`, "m"),
|
|
264
|
+
(match, content) => {
|
|
265
|
+
const fieldLine = ` ${from}s ${from}[]`;
|
|
266
|
+
return match.includes(fieldLine)
|
|
267
|
+
? match
|
|
268
|
+
: `model ${to} {${content}\n${fieldLine}\n}`;
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
logInfo("Updating schema.prisma");
|
|
276
|
+
const baseSchema = `
|
|
277
|
+
generator client {
|
|
278
|
+
provider = "prisma-client-js"
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
datasource db {
|
|
282
|
+
provider = "${inputs.dbConfig.orm === "mongodb" ? "mongodb" : "postgresql"}"
|
|
283
|
+
url = env("DATABASE_URL")
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
${schemaContent}
|
|
287
|
+
`;
|
|
288
|
+
await createFile({
|
|
289
|
+
path: schemaPath,
|
|
290
|
+
contente: baseSchema,
|
|
291
|
+
});
|
|
292
|
+
await runCommand(`npx prisma format`, "❌ Failed to format prisma schema");
|
|
293
|
+
|
|
294
|
+
// 📁 Step 6: Creating the `src/prisma` structure
|
|
295
|
+
const defaultPatch = "src/prisma";
|
|
296
|
+
await createDirectory(defaultPatch);
|
|
297
|
+
|
|
298
|
+
// 🧩 Prisma Service
|
|
299
|
+
await createFile({
|
|
300
|
+
path: `${defaultPatch}/prisma.service.ts`,
|
|
301
|
+
contente: `import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
302
|
+
import { PrismaClient } from '@prisma/client';
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Prisma Service to expose a global instance of the Prisma client
|
|
306
|
+
*/
|
|
307
|
+
@Injectable()
|
|
308
|
+
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
|
309
|
+
constructor() {
|
|
310
|
+
super();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async onModuleInit() {
|
|
314
|
+
await this.$connect();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async onModuleDestroy() {
|
|
318
|
+
await this.$disconnect();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
`,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// 🧩 Prisma Module
|
|
325
|
+
await createFile({
|
|
326
|
+
path: `${defaultPatch}/prisma.module.ts`,
|
|
327
|
+
contente: `import { Global, Module } from '@nestjs/common';
|
|
328
|
+
import { PrismaService } from './prisma.service';
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Global Prisma Module to provide the service to the entire application
|
|
332
|
+
*/
|
|
333
|
+
@Global()
|
|
334
|
+
@Module({
|
|
335
|
+
providers: [PrismaService],
|
|
336
|
+
exports: [PrismaService],
|
|
337
|
+
})
|
|
338
|
+
export class PrismaModule {}
|
|
339
|
+
`,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// Installing dotenv if necessary
|
|
343
|
+
logInfo("📦 Installing dotenv...");
|
|
344
|
+
await runCommand(
|
|
345
|
+
`${inputs.packageManager} add dotenv`,
|
|
346
|
+
"❌ Failed to install dotenv",
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
// Creating prisma.config.ts file to load environment variables
|
|
350
|
+
let prismaConfigPath = "prisma.config.ts";
|
|
351
|
+
if (!fs.existsSync(prismaConfigPath)) {
|
|
352
|
+
prismaConfigPath = "prisma/prisma.config.ts";
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (fs.existsSync(prismaConfigPath)) {
|
|
356
|
+
logInfo(" Updating prisma.config.ts with dotenv import...");
|
|
357
|
+
await updateFile({
|
|
358
|
+
path: prismaConfigPath,
|
|
359
|
+
pattern: /^/,
|
|
360
|
+
replacement: `import 'dotenv/config';\n\n`,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Step 7: Generating the Prisma client
|
|
365
|
+
await runCommand("npx prisma generate", "❌ Prisma generation failed");
|
|
366
|
+
|
|
367
|
+
// Step 8: Migration (ONLY in 'new' mode)
|
|
368
|
+
if (inputs.isDemo) {
|
|
369
|
+
setupPrismaSeeding(inputs);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
logSuccess(" Prisma configured successfully!");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Maps generic entity types to Prisma data types.
|
|
377
|
+
* @param {string} type - Generic type (e.g., 'string', 'number', 'Date', 'string[]', 'MonEnum')
|
|
378
|
+
* @returns {string} The corresponding type in the Prisma schema.
|
|
379
|
+
*/
|
|
380
|
+
function mapTypeToPrisma(type) {
|
|
381
|
+
// Handles the case of arrays (e.g., 'string[]')
|
|
382
|
+
if (type.endsWith("[]")) {
|
|
383
|
+
const innerType = type.slice(0, -2); // Removes '[]' // Recursively calls for the inner type
|
|
384
|
+
return `${mapTypeToPrisma(innerType)}[]`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const typeLower = type.toLowerCase();
|
|
388
|
+
|
|
389
|
+
switch (typeLower) {
|
|
390
|
+
case "string":
|
|
391
|
+
case "text": // Mapped to String because Prisma does not have a distinct TEXT type for PostgreSQL
|
|
392
|
+
return "String";
|
|
393
|
+
|
|
394
|
+
case "number": // A simple "number" field can be Int or Float. We default to Float.
|
|
395
|
+
return "Float";
|
|
396
|
+
case "int":
|
|
397
|
+
return "Int";
|
|
398
|
+
|
|
399
|
+
case "decimal": // Use Decimal for high precision, or Float for simplicity
|
|
400
|
+
return "Decimal";
|
|
401
|
+
|
|
402
|
+
case "boolean":
|
|
403
|
+
return "Boolean";
|
|
404
|
+
|
|
405
|
+
case "date":
|
|
406
|
+
return "DateTime";
|
|
407
|
+
|
|
408
|
+
case "uuid": // We use String by default for storage, the @id @default(uuid()) attribute will be managed by the ID logic. // For non-ID fields, String is the appropriate choice.
|
|
409
|
+
return "String";
|
|
410
|
+
|
|
411
|
+
case "json":
|
|
412
|
+
return "Json";
|
|
413
|
+
case "role":
|
|
414
|
+
return "Role";
|
|
415
|
+
|
|
416
|
+
default: // Handles cases of custom enumerations (e.g., 'StatusEnum') or named object types (e.g., 'Address')
|
|
417
|
+
// Prisma will use the exact type name if it matches a defined 'enum' or other 'model'.
|
|
418
|
+
// In the context of a simple non-persistent DTO/object field, it is better to revert to Json if unrecognized.
|
|
419
|
+
// If the type is capitalized (e.g., 'Address'), we return it as is (assuming it's another Model/Enum)
|
|
420
|
+
return type.charAt(0) === type.charAt(0).toUpperCase() ? type : "Json";
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function setupPrismaSeeding(inputs) {
|
|
425
|
+
logInfo("⚙️ Configuring seeding for Prisma...");
|
|
426
|
+
|
|
427
|
+
// --- Dependencies ---
|
|
428
|
+
const prismaDevDeps = [
|
|
429
|
+
"ts-node",
|
|
430
|
+
"@types/node",
|
|
431
|
+
"@types/bcrypt",
|
|
432
|
+
"dotenv-cli",
|
|
433
|
+
];
|
|
434
|
+
await runCommand(
|
|
435
|
+
`${inputs.packageManager} add -D ${prismaDevDeps.join(" ")}`,
|
|
436
|
+
"❌ Failed to install Prisma seeding dependencies",
|
|
437
|
+
); // Bcrypt is often a production dependency for hashing
|
|
438
|
+
await runCommand(
|
|
439
|
+
`${inputs.packageManager} install bcrypt`,
|
|
440
|
+
"❌ Failed to install bcrypt",
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
// --- Scripts in package.json ---
|
|
444
|
+
const prismaScripts = {
|
|
445
|
+
"prisma:migrate": "prisma migrate dev",
|
|
446
|
+
"prisma:seed": "prisma db seed",
|
|
447
|
+
"prisma:reset": "prisma migrate reset",
|
|
448
|
+
"prisma:migrate:prod": "prisma migrate deploy",
|
|
449
|
+
seed: "ts-node prisma/seed.ts",
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
await updatePackageJson(inputs, prismaScripts);
|
|
453
|
+
|
|
454
|
+
// --- Configuration in schema.prisma ---
|
|
455
|
+
await updateFile({
|
|
456
|
+
path: "prisma/schema.prisma",
|
|
457
|
+
pattern: /generator client \{[^}]*\}/g,
|
|
458
|
+
replacement: `generator client {
|
|
459
|
+
provider = "prisma-client-js"
|
|
460
|
+
output = "../node_modules/.prisma/client"
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
`,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// --- Creating seed.ts file ---
|
|
467
|
+
const seedTsContent = generatePrismaSeedContent(inputs.entitiesData.entities);
|
|
468
|
+
await createFile({
|
|
469
|
+
path: `prisma/seed.ts`,
|
|
470
|
+
contente: seedTsContent,
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
// logSuccess("✅ Prisma seeding configured.");
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function generatePrismaSeedContent(entities) {
|
|
477
|
+
const requiresBcrypt = entities.some((e) => e.name.toLowerCase() === "user");
|
|
478
|
+
|
|
479
|
+
return `
|
|
480
|
+
import { PrismaClient } from '@prisma/client';
|
|
481
|
+
${requiresBcrypt ? "import * as bcrypt from 'bcrypt';" : ""}
|
|
482
|
+
|
|
483
|
+
const prisma = new PrismaClient();
|
|
484
|
+
|
|
485
|
+
async function main() {
|
|
486
|
+
console.log('🌱 Starting Prisma seeding...');
|
|
487
|
+
|
|
488
|
+
// --- 1. ADMIN USER ---
|
|
489
|
+
${
|
|
490
|
+
requiresBcrypt
|
|
491
|
+
? `const salt = await bcrypt.genSalt(10);
|
|
492
|
+
const hashedPassword = await bcrypt.hash('password123', salt);`
|
|
493
|
+
: ""
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const exists = await prisma.user.findFirst({
|
|
497
|
+
where: { email: 'admin@nestcraft.com' },
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (!exists) {
|
|
501
|
+
const adminUser = await prisma.user.create({
|
|
502
|
+
data: {
|
|
503
|
+
email: 'admin@nestcraft.com',
|
|
504
|
+
${
|
|
505
|
+
requiresBcrypt
|
|
506
|
+
? "password: hashedPassword,"
|
|
507
|
+
: "// Default password: password123"
|
|
508
|
+
}
|
|
509
|
+
username: 'NestCraftAdmin',
|
|
510
|
+
role: 'SUPER_ADMIN',
|
|
511
|
+
isActive: true,
|
|
512
|
+
},
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
console.log(\`👑 Admin created: \${adminUser.email}\`);
|
|
516
|
+
} else {
|
|
517
|
+
console.log(\`👑 Admin realy exists: \${exists.email}\`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
// --- 2. DEMO USERS ---
|
|
522
|
+
const demoUsersData = [
|
|
523
|
+
{ email: 'emma.jones@demo.com', ${
|
|
524
|
+
requiresBcrypt ? "password: hashedPassword," : ""
|
|
525
|
+
} username: 'EmmaJones', isActive: true },
|
|
526
|
+
{ email: 'lucas.martin@demo.com', ${
|
|
527
|
+
requiresBcrypt ? "password: hashedPassword," : ""
|
|
528
|
+
} username: 'LucasMartin', isActive: true },
|
|
529
|
+
{ email: 'sophia.bernard@demo.com', ${
|
|
530
|
+
requiresBcrypt ? "password: hashedPassword," : ""
|
|
531
|
+
} username: 'SophiaBernard', isActive: true },
|
|
532
|
+
{ email: 'alexandre.dubois@demo.com', ${
|
|
533
|
+
requiresBcrypt ? "password: hashedPassword," : ""
|
|
534
|
+
} username: 'AlexandreDubois', isActive: true },
|
|
535
|
+
{ email: 'chloe.moreau@demo.com', ${
|
|
536
|
+
requiresBcrypt ? "password: hashedPassword," : ""
|
|
537
|
+
} username: 'ChloeMoreau', isActive: true },
|
|
538
|
+
];
|
|
539
|
+
|
|
540
|
+
const existingUsers = await prisma.user.findMany({
|
|
541
|
+
where: {
|
|
542
|
+
email: {
|
|
543
|
+
in: demoUsersData.map((u) => u.email),
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
select: { email: true },
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
const existingEmails = new Set(existingUsers.map((u) => u.email));
|
|
550
|
+
|
|
551
|
+
const usersToCreate = demoUsersData.filter(
|
|
552
|
+
(u) => !existingEmails.has(u.email),
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
if (usersToCreate.length === 0) {
|
|
556
|
+
console.log(
|
|
557
|
+
'! Demo users already exist. Reset the database if you want to reseed.',
|
|
558
|
+
);
|
|
559
|
+
} else {
|
|
560
|
+
await prisma.user.createMany({
|
|
561
|
+
data: usersToCreate,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
console.log(\`👥 \${usersToCreate.length} demo users created\`);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const allUsers = await prisma.user.findMany({ select: { id: true } });
|
|
568
|
+
const userIds = allUsers.map(u => u.id);
|
|
569
|
+
|
|
570
|
+
// --- 3. BLOG POSTS ---
|
|
571
|
+
const postsData = [
|
|
572
|
+
{
|
|
573
|
+
title: 'The Basics of NestJS for Modern Developers',
|
|
574
|
+
content: 'Discover how to build a robust and maintainable API with NestJS...',
|
|
575
|
+
published: true,
|
|
576
|
+
userId: userIds[1],
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
title: 'How to Secure Your API with JWT',
|
|
580
|
+
content: 'JWT authentication is a standard for securing APIs...',
|
|
581
|
+
published: true,
|
|
582
|
+
userId: userIds[2],
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
title: 'Optimizing Node.js API Performance',
|
|
586
|
+
content: 'Discover best practices for improving performance...',
|
|
587
|
+
published: true,
|
|
588
|
+
userId: userIds[3],
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
title: 'Introduction to Prisma ORM',
|
|
592
|
+
content: 'Prisma is a modern ORM that simplifies interactions with the database...',
|
|
593
|
+
published: true,
|
|
594
|
+
userId: userIds[4],
|
|
595
|
+
},
|
|
596
|
+
{
|
|
597
|
+
title: 'Understanding Clean Architecture',
|
|
598
|
+
content: 'Clean Architecture helps separate business logic from the rest of the code...',
|
|
599
|
+
published: false,
|
|
600
|
+
userId: userIds[0],
|
|
601
|
+
},
|
|
602
|
+
];
|
|
603
|
+
await prisma.post.createMany({ data: postsData, skipDuplicates: true });
|
|
604
|
+
console.log('📝 5 Articles created.');
|
|
605
|
+
|
|
606
|
+
const allPosts = await prisma.post.findMany({ select: { id: true } });
|
|
607
|
+
const postIds = allPosts.map(p => p.id);
|
|
608
|
+
|
|
609
|
+
// --- 4. DEMO COMMENTS ---
|
|
610
|
+
const commentsData = [
|
|
611
|
+
{ content: 'Excellent article! I was able to apply these tips directly to my NestJS project.', postId: postIds[0], userId: userIds[2] },
|
|
612
|
+
{ content: 'Very clear and well explained, thank you for sharing about Prisma 👏', postId: postIds[3], userId: userIds[0] },
|
|
613
|
+
{ content: "I didn\'t know about JWT before this article, it\'s a real revelation.", postId: postIds[1], userId: userIds[4] },
|
|
614
|
+
{ content: 'Clean Architecture always seemed blurry to me, this article finally enlightened me.', postId: postIds[4], userId: userIds[1] },
|
|
615
|
+
{ content: 'Thanks for the content! I would like to see a complete tutorial with NestJS + Prisma.', postId: postIds[2], userId: userIds[3] },
|
|
616
|
+
];
|
|
617
|
+
await prisma.comment.createMany({ data: commentsData, skipDuplicates: true });
|
|
618
|
+
console.log('💬 5 Comments created.');
|
|
619
|
+
|
|
620
|
+
console.log('✅ Seeding finished successfully! 🚀');
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
main()
|
|
624
|
+
.catch((e) => {
|
|
625
|
+
console.error('❌ Error during Prisma seeding:', e);
|
|
626
|
+
process.exit(1);
|
|
627
|
+
})
|
|
628
|
+
.finally(async () => {
|
|
629
|
+
await prisma.$disconnect();
|
|
630
|
+
});
|
|
631
|
+
`;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
async function updatePrismaSchema(entityData) {
|
|
635
|
+
const schemaPath = "prisma/schema.prisma";
|
|
636
|
+
const { name, fields, relation } = entityData;
|
|
637
|
+
const capitalizedName = capitalize(name);
|
|
638
|
+
const lowercasedName = name.toLowerCase();
|
|
639
|
+
|
|
640
|
+
// 1️⃣ Préparation des champs de relation pour le NOUVEAU modèle
|
|
641
|
+
let relationFields = "";
|
|
642
|
+
if (relation) {
|
|
643
|
+
const targetCap = capitalize(relation.target);
|
|
644
|
+
const targetLow = relation.target.toLowerCase();
|
|
645
|
+
|
|
646
|
+
switch (relation.type) {
|
|
647
|
+
case "n-1": // "Many to One" : Le nouveau module appartient à une cible
|
|
648
|
+
relationFields += `\n ${targetLow} ${targetLow} @relation(fields: [${targetLow}Id], references: [id])`;
|
|
649
|
+
relationFields += `\n ${targetLow}Id String`;
|
|
650
|
+
break;
|
|
651
|
+
case "1-n": // "One to Many" : Le nouveau module possède plusieurs cibles
|
|
652
|
+
relationFields += `\n ${targetLow}s ${targetLow}[]`;
|
|
653
|
+
break;
|
|
654
|
+
case "1-1":
|
|
655
|
+
relationFields += `\n ${targetLow} ${targetLow}? @relation(fields: [${targetLow}Id], references: [id])`;
|
|
656
|
+
relationFields += `\n ${targetLow}Id String? @unique`;
|
|
657
|
+
break;
|
|
658
|
+
case "n-n":
|
|
659
|
+
relationFields += `\n ${targetLow}s ${targetLow}[]`;
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// 2️⃣ Construction du bloc pour le nouveau modèle
|
|
665
|
+
let modelBlock = `\n/**
|
|
666
|
+
* ${capitalizedName} Model
|
|
667
|
+
*/
|
|
668
|
+
model ${lowercasedName} {
|
|
669
|
+
id String @id @default(uuid())
|
|
670
|
+
createdAt DateTime @default(now())
|
|
671
|
+
updatedAt DateTime @updatedAt${relationFields}`;
|
|
672
|
+
|
|
673
|
+
fields.forEach((field) => {
|
|
674
|
+
modelBlock += `\n ${field.name} ${mapTypeToPrisma(field.type)}`;
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
modelBlock += `\n}\n`;
|
|
678
|
+
|
|
679
|
+
// 3️⃣ Injection du nouveau modèle
|
|
680
|
+
await updateFile({
|
|
681
|
+
path: schemaPath,
|
|
682
|
+
pattern: /$/,
|
|
683
|
+
replacement: modelBlock,
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// 4️⃣ Injection de l'INVERSE dans le modèle cible (Target)
|
|
687
|
+
if (relation) {
|
|
688
|
+
const targetCap = capitalize(relation.target);
|
|
689
|
+
const targetLow = capitalize(relation.target);
|
|
690
|
+
const newCap = capitalizedName;
|
|
691
|
+
const newLow = name.toLowerCase();
|
|
692
|
+
|
|
693
|
+
let inverseField = "";
|
|
694
|
+
switch (relation.type) {
|
|
695
|
+
case "n-1": // Inverse d'un Many-to-One est un One-to-Many
|
|
696
|
+
inverseField = ` ${newLow}s ${newLow}[]`;
|
|
697
|
+
break;
|
|
698
|
+
case "1-n": // Inverse d'un One-to-Many est un Many-to-One
|
|
699
|
+
inverseField = ` ${newLow} ${newLow} @relation(fields: [${newLow}Id], references: [id])\n ${newLow}Id String`;
|
|
700
|
+
break;
|
|
701
|
+
case "1-1":
|
|
702
|
+
inverseField = ` ${newLow} ${newLow}?`;
|
|
703
|
+
break;
|
|
704
|
+
case "n-n":
|
|
705
|
+
inverseField = ` ${newLow}s ${newLow}[]`;
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// On utilise une RegEx pour trouver le bloc du modèle cible et insérer avant la fermeture "}"
|
|
710
|
+
await updateFile({
|
|
711
|
+
path: schemaPath,
|
|
712
|
+
pattern: new RegExp(`model ${targetLow} \\{([\\s\\S]*?)\\}`, "g"),
|
|
713
|
+
replacement: `model ${targetLow} {$1\n${inverseField}\n}`,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// 3️⃣ Prisma Commands Sync (meilleur sur Windows)
|
|
718
|
+
try {
|
|
719
|
+
console.log(info("Running: prisma format..."));
|
|
720
|
+
execSync("npx prisma format", { stdio: "pipe" });
|
|
721
|
+
|
|
722
|
+
console.log(info("Running: prisma generate..."));
|
|
723
|
+
execSync("npx prisma generate", { stdio: "pipe" });
|
|
724
|
+
|
|
725
|
+
console.log(info("Running: prisma migrate dev..."));
|
|
726
|
+
execSync(
|
|
727
|
+
`npx prisma migrate dev --name add_module_${entityData.name} --force`,
|
|
728
|
+
{
|
|
729
|
+
stdio: "pipe",
|
|
730
|
+
},
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
// console.log(success("🎉 Prisma migration completed successfully!"));
|
|
734
|
+
} catch (err) {
|
|
735
|
+
// 4️⃣ Gestion spécifique Windows EPERM
|
|
736
|
+
if (
|
|
737
|
+
err.message.includes("operation not permitted") ||
|
|
738
|
+
err.message.includes("EPERM") ||
|
|
739
|
+
err.message.includes("EBUSY")
|
|
740
|
+
) {
|
|
741
|
+
logWarning("System Lock Detected!");
|
|
742
|
+
console.log(
|
|
743
|
+
`${info("Prisma Client binary is locked because your server is running.")}`,
|
|
744
|
+
);
|
|
745
|
+
console.log(
|
|
746
|
+
`${info("ACTION:")} Stop NestJS (Ctrl+C) and run manually:\n`,
|
|
747
|
+
);
|
|
748
|
+
console.log(`${success("npx prisma generate")}`);
|
|
749
|
+
console.log(`${success("npx prisma migrate dev")}\n`);
|
|
750
|
+
} else {
|
|
751
|
+
console.log(error("❌ Prisma error: " + err.message));
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/* async function updatePrismaSchema(entityData) {
|
|
757
|
+
const schemaPath = "prisma/schema.prisma";
|
|
758
|
+
|
|
759
|
+
// Construction du bloc modèle
|
|
760
|
+
let modelBlock = `\nmodel ${capitalize(entityData.name)} {
|
|
761
|
+
id String @id @default(uuid())
|
|
762
|
+
createdAt DateTime @default(now())
|
|
763
|
+
updatedAt DateTime @updatedAt`;
|
|
764
|
+
|
|
765
|
+
entityData.fields.forEach((field) => {
|
|
766
|
+
// Note: On réutilise ta fonction mapTypeToPrisma ici
|
|
767
|
+
modelBlock += `\n ${field.name} ${mapTypeToPrisma(field.type)}`;
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
modelBlock += `\n}\n`;
|
|
771
|
+
|
|
772
|
+
// Injection à la fin du fichier
|
|
773
|
+
await updateFile({
|
|
774
|
+
path: schemaPath,
|
|
775
|
+
pattern: /$/, // On ajoute à la fin
|
|
776
|
+
replacement: modelBlock,
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// ⚠️ IMPORTANT : On formate et on régénère le client pour supprimer les erreurs TS
|
|
780
|
+
try {
|
|
781
|
+
await runCommand("npx prisma format");
|
|
782
|
+
await runCommand("npx prisma generate");
|
|
783
|
+
await runCommand(
|
|
784
|
+
`npx prisma migrate dev --name add_module_${entityData.name}`,
|
|
785
|
+
);
|
|
786
|
+
} catch (err) {
|
|
787
|
+
if (err.message.includes("EPERM")) {
|
|
788
|
+
console.log(
|
|
789
|
+
`\n${warning("⚠️ System Lock:")} Could not update Prisma Client binary because your app is currently running.`,
|
|
790
|
+
);
|
|
791
|
+
console.log(
|
|
792
|
+
`${info("ACTION REQUIRED:")} Stop your dev server (Ctrl+C) and run: ${success("| npx prisma generate | and | npx prisma migrate dev |")}\n`,
|
|
793
|
+
);
|
|
794
|
+
} else {
|
|
795
|
+
logError("Prisma error: " + err.message);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
throw new Error(err);
|
|
800
|
+
} */
|
|
801
|
+
|
|
802
|
+
module.exports = { setupPrisma, updatePrismaSchema };
|