@ifecodes/backend-template 1.1.8 ā 1.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/LICENSE +201 -0
- package/README.md +423 -365
- package/bin/cli.js +1276 -836
- package/bin/lib/microservice-config.js +155 -150
- package/bin/lib/prompts.js +277 -241
- package/bin/lib/readme-generator.js +364 -335
- package/bin/lib/service-setup.js +901 -679
- package/package.json +64 -55
- package/template/base/js/.env.example +5 -5
- package/template/base/js/.eslintrc.json +10 -13
- package/template/base/js/.husky/pre-commit +1 -7
- package/template/base/js/.prettierrc +7 -7
- package/template/base/js/eslint.config.js +33 -31
- package/template/base/js/package.json +29 -28
- package/template/base/js/src/app.js +20 -15
- package/template/base/js/src/config/env.js +32 -2
- package/template/base/js/src/config/index.js +2 -2
- package/template/base/js/src/docs/index.js +5 -0
- package/template/base/js/src/docs/route-registry.js +63 -0
- package/template/base/js/src/middlewares/error-handler.middleware.js +22 -0
- package/template/base/js/src/middlewares/index.js +9 -3
- package/template/base/js/src/middlewares/method-not-allowed.middleware.js +8 -2
- package/template/base/js/src/middlewares/not-found.middleware.js +4 -1
- package/template/base/js/src/middlewares/observability.middleware.js +24 -0
- package/template/base/js/src/middlewares/root.middleware.js +7 -5
- package/template/base/js/src/middlewares/validation.middleware.js +39 -0
- package/template/base/js/src/modules/index.js +8 -8
- package/template/base/js/src/modules/v1/health/health.controller.auth.js +29 -0
- package/template/base/js/src/modules/v1/health/health.controller.js +4 -4
- package/template/base/js/src/modules/v1/health/health.route.js +70 -5
- package/template/base/js/src/modules/v1/health/index.js +1 -1
- package/template/base/js/src/modules/v1/index.js +3 -3
- package/template/base/js/src/routes.js +13 -6
- package/template/base/js/src/server.js +18 -18
- package/template/base/js/src/utils/http-error.js +27 -6
- package/template/base/js/src/utils/index.js +28 -22
- package/template/base/js/src/utils/logger.js +57 -67
- package/template/base/ts/.eslintrc.json +13 -17
- package/template/base/ts/.prettierrc +7 -7
- package/template/base/ts/eslint.config.js +33 -33
- package/template/base/ts/package.json +41 -39
- package/template/base/ts/src/app.ts +20 -15
- package/template/base/ts/src/config/db.ts +4 -4
- package/template/base/ts/src/config/env.ts +40 -10
- package/template/base/ts/src/config/index.ts +2 -2
- package/template/base/ts/src/docs/index.ts +3 -0
- package/template/base/ts/src/docs/route-registry.ts +98 -0
- package/template/base/ts/src/middlewares/error-handler.middleware.ts +26 -0
- package/template/base/ts/src/middlewares/index.ts +6 -3
- package/template/base/ts/src/middlewares/method-not-allowed.middleware.ts +23 -16
- package/template/base/ts/src/middlewares/not-found.middleware.ts +10 -8
- package/template/base/ts/src/middlewares/observability.middleware.ts +25 -0
- package/template/base/ts/src/middlewares/root.middleware.ts +16 -14
- package/template/base/ts/src/middlewares/validation.middleware.ts +46 -0
- package/template/base/ts/src/modules/index.ts +8 -8
- package/template/base/ts/src/modules/v1/health/health.controller.auth.ts +26 -0
- package/template/base/ts/src/modules/v1/health/health.controller.ts +18 -18
- package/template/base/ts/src/modules/v1/health/health.route.ts +68 -9
- package/template/base/ts/src/modules/v1/health/index.ts +1 -1
- package/template/base/ts/src/modules/v1/index.ts +8 -8
- package/template/base/ts/src/routes.ts +23 -15
- package/template/base/ts/src/server.ts +19 -19
- package/template/base/ts/src/utils/http-error.ts +63 -45
- package/template/base/ts/src/utils/index.ts +14 -11
- package/template/base/ts/src/utils/logger.ts +58 -68
- package/template/base/ts/tsconfig.json +21 -22
- package/template/features/auth/argon2/inject.js +50 -50
- package/template/features/auth/base/health-openapi.ts +62 -0
- package/template/features/auth/base/inject.js +174 -172
- package/template/features/auth/bcrypt/inject.js +40 -40
- package/template/features/auth/models/user.model.js +24 -24
- package/template/features/auth/models/user.model.ts +1 -1
- package/template/features/auth/modules/auth.controller.js +21 -21
- package/template/features/auth/modules/auth.controller.ts +28 -20
- package/template/features/auth/modules/auth.routes.js +89 -10
- package/template/features/auth/modules/auth.routes.ts +86 -11
- package/template/features/auth/modules/auth.service.js +29 -29
- package/template/features/auth/modules/auth.service.ts +38 -38
- package/template/features/auth/modules/index.js +1 -1
- package/template/features/auth/utils/hash.ts +20 -20
- package/template/features/auth/utils/jwt.js +12 -12
- package/template/features/cors/inject.js +14 -13
- package/template/features/helmet/inject.js +7 -6
- package/template/features/morgan/inject.js +8 -7
- package/template/features/rate-limit/inject.js +7 -6
- package/template/gateway/js/app.js +42 -42
- package/template/gateway/js/inject.js +33 -31
- package/template/gateway/js/server.js +19 -19
- package/template/gateway/ts/app.ts +43 -43
- package/template/gateway/ts/inject.js +33 -33
- package/template/gateway/ts/server.ts +19 -19
- package/template/microservice/docker/docker-compose.yml +5 -5
- package/template/microservice/nodocker/pm2.config.js +3 -3
package/bin/cli.js
CHANGED
|
@@ -1,836 +1,1276 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { execSync } from "child_process";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import pc from "picocolors";
|
|
7
|
-
import { getProjectConfig } from "./lib/prompts.js";
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
target,
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const
|
|
487
|
-
if (fs.existsSync(
|
|
488
|
-
fs.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
const
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
)
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
if (
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
)
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
//
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import { getProjectConfig } from "./lib/prompts.js";
|
|
8
|
+
import prompts from "prompts";
|
|
9
|
+
import { setupService } from "./lib/service-setup.js";
|
|
10
|
+
import { generateReadme } from "./lib/readme-generator.js";
|
|
11
|
+
// No TS->JS transform: templates contain language-specific folders (base/js, base/ts)
|
|
12
|
+
import {
|
|
13
|
+
generateDockerCompose,
|
|
14
|
+
generatePm2Config,
|
|
15
|
+
copyDockerfile,
|
|
16
|
+
copyDockerignore,
|
|
17
|
+
} from "./lib/microservice-config.js";
|
|
18
|
+
|
|
19
|
+
function writeStarterWorkflow(target, config) {
|
|
20
|
+
if (config.isInMicroserviceProject || !config.cicd) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const workflowsDir = path.join(target, ".github", "workflows");
|
|
25
|
+
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
26
|
+
|
|
27
|
+
const workflowName = config.sanitizedName
|
|
28
|
+
? `${config.sanitizedName} CI/CD`
|
|
29
|
+
: "Build CI/CD";
|
|
30
|
+
|
|
31
|
+
const workflowContent = `name: ${workflowName}
|
|
32
|
+
|
|
33
|
+
on:
|
|
34
|
+
workflow_dispatch:
|
|
35
|
+
push:
|
|
36
|
+
branches:
|
|
37
|
+
- main
|
|
38
|
+
pull_request:
|
|
39
|
+
branches:
|
|
40
|
+
- main
|
|
41
|
+
- dev
|
|
42
|
+
|
|
43
|
+
jobs:
|
|
44
|
+
# ------------------------
|
|
45
|
+
# 1ļøā£ BUILD & CHECK JOB
|
|
46
|
+
# ------------------------
|
|
47
|
+
build:
|
|
48
|
+
name: Build & Check
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
- name: Checkout code
|
|
52
|
+
uses: actions/checkout@v4
|
|
53
|
+
|
|
54
|
+
- name: Setup Node.js
|
|
55
|
+
uses: actions/setup-node@v4
|
|
56
|
+
with:
|
|
57
|
+
node-version: 20.x
|
|
58
|
+
|
|
59
|
+
- name: Install dependencies
|
|
60
|
+
run: npm ci
|
|
61
|
+
|
|
62
|
+
- name: Check code format
|
|
63
|
+
run: npm run check-format
|
|
64
|
+
|
|
65
|
+
- name: Run linter
|
|
66
|
+
run: npm run lint
|
|
67
|
+
|
|
68
|
+
- name: Run build
|
|
69
|
+
run: npm run build
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
fs.writeFileSync(path.join(workflowsDir, "ci-cd.yml"), workflowContent);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writePullRequestTemplate(target, config) {
|
|
76
|
+
if (config.isInMicroserviceProject || !config.cicd) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const githubDir = path.join(target, ".github");
|
|
81
|
+
fs.mkdirSync(githubDir, { recursive: true });
|
|
82
|
+
|
|
83
|
+
const prTemplateContent = `# Description
|
|
84
|
+
|
|
85
|
+
<!--- Describe your changes in detail -->
|
|
86
|
+
|
|
87
|
+
This PR ...
|
|
88
|
+
|
|
89
|
+
# Changes Proposed
|
|
90
|
+
|
|
91
|
+
## What were you told to do?
|
|
92
|
+
|
|
93
|
+
## What did you do?
|
|
94
|
+
|
|
95
|
+
## Types of changes
|
|
96
|
+
|
|
97
|
+
<!--- What types of changes does your code introduce? Put an x in all the boxes that apply: -->
|
|
98
|
+
|
|
99
|
+
- [ ] Bug fix (non-breaking change which fixes an issue)
|
|
100
|
+
- [ ] New feature (non-breaking change which adds functionality)
|
|
101
|
+
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
|
102
|
+
- [ ] Chore (changes that do not relate to a fix or feature and don't modify src or test files)
|
|
103
|
+
|
|
104
|
+
# Check List
|
|
105
|
+
|
|
106
|
+
<!--- Go over all the following points, and put an x in all the boxes that apply. -->
|
|
107
|
+
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
|
108
|
+
|
|
109
|
+
- [x] My code follows the code style of this project.
|
|
110
|
+
- [x] This PR does not contain plagiarized content.
|
|
111
|
+
- [x] The title and description of the PR are clear and explain the approach.
|
|
112
|
+
- [x] I am making a pull request against the dev branch (left side).
|
|
113
|
+
- [x] My commit message style matches our requested structure.
|
|
114
|
+
- [x] My code additions will not fail code linting checks or unit tests.
|
|
115
|
+
- [x] I am only making changes to files I was requested to.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
# Images
|
|
120
|
+
|
|
121
|
+
<!-- Add Screenshots of: -->
|
|
122
|
+
|
|
123
|
+
- Linting check (run npm lint)
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
fs.writeFileSync(
|
|
127
|
+
path.join(githubDir, "pull_request_template.md"),
|
|
128
|
+
prTemplateContent
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeContributingGuide(target, config) {
|
|
133
|
+
if (config.isInMicroserviceProject || !config.cicd) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const contributingContent = `# Contributing
|
|
138
|
+
|
|
139
|
+
Thank you for your interest in contributing to **${config.sanitizedName}**! We appreciate your help in making this project better.
|
|
140
|
+
|
|
141
|
+
## Getting Started
|
|
142
|
+
|
|
143
|
+
1. **Fork the repository** ā Click the fork button on GitHub to create your own copy.
|
|
144
|
+
2. **Clone your fork** ā Clone your forked repository to your local machine:
|
|
145
|
+
\`\`\`bash
|
|
146
|
+
git clone https://github.com/YOUR_USERNAME/${config.sanitizedName}.git
|
|
147
|
+
cd ${config.sanitizedName}
|
|
148
|
+
\`\`\`
|
|
149
|
+
3. **Add upstream remote** ā Keep a reference to the original repository:
|
|
150
|
+
\`\`\`bash
|
|
151
|
+
git remote add upstream https://github.com/ORIGINAL_OWNER/${config.sanitizedName}.git
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
## Development Setup
|
|
155
|
+
|
|
156
|
+
1. **Install dependencies**:
|
|
157
|
+
\`\`\`bash
|
|
158
|
+
npm install
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
2. **Create a feature branch**:
|
|
162
|
+
\`\`\`bash
|
|
163
|
+
git checkout -b feature/your-feature-name
|
|
164
|
+
\`\`\`
|
|
165
|
+
|
|
166
|
+
3. **Make your changes** and commit with clear, descriptive messages:
|
|
167
|
+
\`\`\`bash
|
|
168
|
+
git add .
|
|
169
|
+
git commit -m "feat: add your feature description"
|
|
170
|
+
\`\`\`
|
|
171
|
+
|
|
172
|
+
## Code Quality
|
|
173
|
+
|
|
174
|
+
Before submitting a pull request, ensure your code meets our standards:
|
|
175
|
+
|
|
176
|
+
### Linting
|
|
177
|
+
|
|
178
|
+
Run the linter to check for code style issues:
|
|
179
|
+
\`\`\`bash
|
|
180
|
+
npm run lint
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
Fix any issues automatically (when possible):
|
|
184
|
+
\`\`\`bash
|
|
185
|
+
npm run lint -- --fix
|
|
186
|
+
\`\`\`
|
|
187
|
+
|
|
188
|
+
### Formatting
|
|
189
|
+
|
|
190
|
+
We use Prettier for code formatting. Format your code before committing:
|
|
191
|
+
\`\`\`bash
|
|
192
|
+
npm run format
|
|
193
|
+
\`\`\`
|
|
194
|
+
|
|
195
|
+
### Building
|
|
196
|
+
|
|
197
|
+
Ensure your changes build successfully:
|
|
198
|
+
\`\`\`bash
|
|
199
|
+
npm run build
|
|
200
|
+
\`\`\`
|
|
201
|
+
|
|
202
|
+
### Testing
|
|
203
|
+
|
|
204
|
+
Run tests to verify your changes:
|
|
205
|
+
\`\`\`bash
|
|
206
|
+
npm run test
|
|
207
|
+
\`\`\`
|
|
208
|
+
|
|
209
|
+
## Commit Message Guidelines
|
|
210
|
+
|
|
211
|
+
Follow these conventions for commit messages:
|
|
212
|
+
|
|
213
|
+
- \`feat:\` for new features
|
|
214
|
+
- \`fix:\` for bug fixes
|
|
215
|
+
- \`docs:\` for documentation changes
|
|
216
|
+
- \`style:\` for code style changes (formatting, missing semicolons, etc.)
|
|
217
|
+
- \`refactor:\` for code refactoring
|
|
218
|
+
- \`test:\` for testing changes
|
|
219
|
+
- \`chore:\` for dependency updates, build changes, etc.
|
|
220
|
+
|
|
221
|
+
**Example:**
|
|
222
|
+
\`\`\`
|
|
223
|
+
feat: add user authentication endpoint
|
|
224
|
+
fix: resolve null pointer exception in service
|
|
225
|
+
docs: update API documentation
|
|
226
|
+
\`\`\`
|
|
227
|
+
|
|
228
|
+
## Pull Request Process
|
|
229
|
+
|
|
230
|
+
1. **Sync your branch** with the main repository:
|
|
231
|
+
\`\`\`bash
|
|
232
|
+
git fetch upstream
|
|
233
|
+
git rebase upstream/main
|
|
234
|
+
\`\`\`
|
|
235
|
+
|
|
236
|
+
2. **Push your changes**:
|
|
237
|
+
\`\`\`bash
|
|
238
|
+
git push origin feature/your-feature-name
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
3. **Create a Pull Request** on GitHub with:
|
|
242
|
+
- A clear, concise title describing your changes
|
|
243
|
+
- A detailed description using our PR template
|
|
244
|
+
- Reference to any related issues (e.g., "Closes #123")
|
|
245
|
+
|
|
246
|
+
4. **Address feedback** ā Respond to code review comments and make requested changes
|
|
247
|
+
|
|
248
|
+
5. **Merge** ā Once approved and all checks pass, your PR will be merged
|
|
249
|
+
|
|
250
|
+
## Code Style
|
|
251
|
+
|
|
252
|
+
- Use **consistent indentation** (2 spaces)
|
|
253
|
+
- Follow **ESLint** and **Prettier** configurations
|
|
254
|
+
- Write **descriptive variable and function names**
|
|
255
|
+
- Add **comments** for complex logic
|
|
256
|
+
- Keep **functions small** and focused
|
|
257
|
+
|
|
258
|
+
## Reporting Issues
|
|
259
|
+
|
|
260
|
+
Found a bug? Please open an issue on GitHub with:
|
|
261
|
+
- A clear, descriptive title
|
|
262
|
+
- Steps to reproduce the issue
|
|
263
|
+
- Expected vs. actual behavior
|
|
264
|
+
- Your environment (OS, Node version, etc.)
|
|
265
|
+
- Any relevant error messages or logs
|
|
266
|
+
|
|
267
|
+
## Questions or Need Help?
|
|
268
|
+
|
|
269
|
+
- Check the [README.md](../README.md) for project overview
|
|
270
|
+
- Review existing issues and documentation
|
|
271
|
+
- Open a discussion if you have questions
|
|
272
|
+
|
|
273
|
+
## Code of Conduct
|
|
274
|
+
|
|
275
|
+
Please be respectful and welcoming to all contributors. We are committed to providing a harassment-free environment.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
Thank you for contributing! Your efforts help make this project better for everyone. š
|
|
280
|
+
`;
|
|
281
|
+
|
|
282
|
+
fs.writeFileSync(path.join(target, "CONTRIBUTING.md"), contributingContent);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
286
|
+
|
|
287
|
+
// Get project configuration from user
|
|
288
|
+
const config = await getProjectConfig();
|
|
289
|
+
const {
|
|
290
|
+
sanitizedName,
|
|
291
|
+
target,
|
|
292
|
+
isExistingProject,
|
|
293
|
+
mode,
|
|
294
|
+
isInMicroserviceProject,
|
|
295
|
+
} = config;
|
|
296
|
+
|
|
297
|
+
const baseRoot =
|
|
298
|
+
config.language === "javascript"
|
|
299
|
+
? path.join(__dirname, "../template/base/js")
|
|
300
|
+
: path.join(__dirname, "../template/base/ts");
|
|
301
|
+
const base = baseRoot;
|
|
302
|
+
|
|
303
|
+
// Determine which services to create
|
|
304
|
+
const servicesToCreate = [];
|
|
305
|
+
const servicesToSetup = [];
|
|
306
|
+
if (isInMicroserviceProject) {
|
|
307
|
+
const newServiceName = config.serviceName.replace(/\s+/g, "-");
|
|
308
|
+
servicesToCreate.push(newServiceName);
|
|
309
|
+
} else if (config.projectType === "microservice") {
|
|
310
|
+
servicesToCreate.push("gateway");
|
|
311
|
+
servicesToCreate.push("health-service");
|
|
312
|
+
if (config.auth) {
|
|
313
|
+
servicesToCreate.push("auth-service");
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Validate and prepare project
|
|
318
|
+
if (!isInMicroserviceProject && config.projectType === "microservice") {
|
|
319
|
+
if (isExistingProject) {
|
|
320
|
+
console.error(
|
|
321
|
+
`\n${pc.red("ā Error:")} Project ${pc.bold(
|
|
322
|
+
sanitizedName
|
|
323
|
+
)} already exists!`
|
|
324
|
+
);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
console.log(
|
|
328
|
+
`\n${pc.cyan("šļø Creating microservices:")} ${pc.bold(
|
|
329
|
+
servicesToCreate.join(", ")
|
|
330
|
+
)}...\n`
|
|
331
|
+
);
|
|
332
|
+
} else if (!isInMicroserviceProject && config.projectType === "monolith") {
|
|
333
|
+
if (isExistingProject) {
|
|
334
|
+
console.error(
|
|
335
|
+
`\n${pc.red("ā Error:")} Project ${pc.bold(
|
|
336
|
+
sanitizedName
|
|
337
|
+
)} already exists!`
|
|
338
|
+
);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
fs.cpSync(base, target, { recursive: true });
|
|
342
|
+
|
|
343
|
+
// Remove db file and remove connectDB export/import if auth is not enabled
|
|
344
|
+
if (!config.auth) {
|
|
345
|
+
const ext = config.language === "javascript" ? "js" : "ts";
|
|
346
|
+
const dbPath = path.join(target, `src/config/db.${ext}`);
|
|
347
|
+
if (fs.existsSync(dbPath)) {
|
|
348
|
+
fs.rmSync(dbPath);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Update index.(js|ts) to not export or require connectDB
|
|
352
|
+
const indexPath = path.join(target, `src/config/index.${ext}`);
|
|
353
|
+
if (fs.existsSync(indexPath)) {
|
|
354
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
|
355
|
+
if (ext === "ts") {
|
|
356
|
+
indexContent = indexContent.replace(
|
|
357
|
+
'export { connectDB } from "./db";\n',
|
|
358
|
+
""
|
|
359
|
+
);
|
|
360
|
+
// also remove any trailing references like `connectDB,` in exported objects
|
|
361
|
+
indexContent = indexContent.replace(/connectDB,?/g, "");
|
|
362
|
+
} else {
|
|
363
|
+
indexContent = indexContent
|
|
364
|
+
.replace('const { connectDB } = require("./db");', "")
|
|
365
|
+
.replace(/connectDB,?/g, "");
|
|
366
|
+
}
|
|
367
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// No TypeScript-to-JavaScript conversion ā templates include language-specific variants
|
|
372
|
+
} else if (isInMicroserviceProject) {
|
|
373
|
+
console.log(
|
|
374
|
+
`\n${pc.cyan("šļø Adding service:")} ${pc.bold(servicesToCreate[0])}...\n`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Process services
|
|
379
|
+
if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
380
|
+
// Create shared folder for config and utils (only once)
|
|
381
|
+
if (!isInMicroserviceProject) {
|
|
382
|
+
const sharedDir = path.join(target, "shared");
|
|
383
|
+
if (!fs.existsSync(sharedDir)) {
|
|
384
|
+
console.log(
|
|
385
|
+
`\n${pc.cyan("š¦ Creating shared folder for config and utils...")}`
|
|
386
|
+
);
|
|
387
|
+
fs.mkdirSync(sharedDir, { recursive: true });
|
|
388
|
+
|
|
389
|
+
// Copy config and utils from base template
|
|
390
|
+
const baseConfigDir = path.join(base, "src", "config");
|
|
391
|
+
const baseUtilsDir = path.join(base, "src", "utils");
|
|
392
|
+
const sharedConfigDir = path.join(sharedDir, "config");
|
|
393
|
+
const sharedUtilsDir = path.join(sharedDir, "utils");
|
|
394
|
+
|
|
395
|
+
fs.cpSync(baseConfigDir, sharedConfigDir, { recursive: true });
|
|
396
|
+
fs.cpSync(baseUtilsDir, sharedUtilsDir, { recursive: true });
|
|
397
|
+
|
|
398
|
+
// Remove db files and strip connectDB exports/imports when auth is not enabled
|
|
399
|
+
if (!config.auth) {
|
|
400
|
+
for (const ext of ["ts", "js"]) {
|
|
401
|
+
const sharedDbPath = path.join(sharedConfigDir, `db.${ext}`);
|
|
402
|
+
if (fs.existsSync(sharedDbPath)) fs.rmSync(sharedDbPath);
|
|
403
|
+
|
|
404
|
+
const sharedIndexPath = path.join(sharedConfigDir, `index.${ext}`);
|
|
405
|
+
if (fs.existsSync(sharedIndexPath)) {
|
|
406
|
+
let idx = fs.readFileSync(sharedIndexPath, "utf8");
|
|
407
|
+
// Remove various export/import patterns referencing connectDB
|
|
408
|
+
idx = idx.replace(
|
|
409
|
+
/export\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
410
|
+
""
|
|
411
|
+
);
|
|
412
|
+
idx = idx.replace(
|
|
413
|
+
/const\s*\{\s*connectDB\s*\}\s*=\s*require\(["']\.\/db["']\);?/g,
|
|
414
|
+
""
|
|
415
|
+
);
|
|
416
|
+
idx = idx.replace(
|
|
417
|
+
/import\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
418
|
+
""
|
|
419
|
+
);
|
|
420
|
+
idx = idx.replace(/\bconnectDB,?\b/g, "");
|
|
421
|
+
idx = idx.replace(/\n{3,}/g, "\n\n");
|
|
422
|
+
fs.writeFileSync(sharedIndexPath, idx);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const ext = config.language === "javascript" ? "js" : "ts";
|
|
427
|
+
|
|
428
|
+
// Update shared env.ts to include all service port environment variables
|
|
429
|
+
const sharedEnvPath = path.join(sharedConfigDir, `env.${ext}`);
|
|
430
|
+
if (fs.existsSync(sharedEnvPath)) {
|
|
431
|
+
let envContent = fs.readFileSync(sharedEnvPath, "utf8");
|
|
432
|
+
console.log(`\n${pc.cyan("š§ Updating shared env configuration...")}`);
|
|
433
|
+
|
|
434
|
+
// Build port environment variables for all services
|
|
435
|
+
const allServices = ["gateway", "health-service"];
|
|
436
|
+
if (config.auth) allServices.push("auth-service");
|
|
437
|
+
|
|
438
|
+
const portEnvVars = allServices
|
|
439
|
+
.map((service) => {
|
|
440
|
+
const envVarName = `${service
|
|
441
|
+
.toUpperCase()
|
|
442
|
+
.replace(/-/g, "_")}_PORT`;
|
|
443
|
+
// Don't add ! for JavaScript projects - it will cause syntax errors
|
|
444
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
445
|
+
return ` ${envVarName}: process.env.${envVarName}${assertion},`;
|
|
446
|
+
})
|
|
447
|
+
.join("\n");
|
|
448
|
+
|
|
449
|
+
// Replace PORT with service-specific ports
|
|
450
|
+
envContent = envContent.replace(
|
|
451
|
+
" PORT: process.env.PORT!,",
|
|
452
|
+
portEnvVars
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
// Add ALLOWED_ORIGIN if CORS is selected
|
|
456
|
+
if (config.features && config.features.includes("cors")) {
|
|
457
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
458
|
+
envContent = envContent.replace(
|
|
459
|
+
"/*__ALLOWED_ORIGIN__*/",
|
|
460
|
+
`ALLOWED_ORIGIN: process.env.ALLOWED_ORIGIN${assertion},`
|
|
461
|
+
);
|
|
462
|
+
} else {
|
|
463
|
+
envContent = envContent.replace("/*__ALLOWED_ORIGIN__*/", "");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Add MONGO_URI and JWT_SECRET if auth is enabled
|
|
467
|
+
if (config.auth) {
|
|
468
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
469
|
+
envContent = envContent.replace(
|
|
470
|
+
"/*__MONGO_URI__*/",
|
|
471
|
+
`MONGO_URI: process.env.MONGO_URI${assertion},`
|
|
472
|
+
);
|
|
473
|
+
envContent = envContent.replace(
|
|
474
|
+
"/*__JWT_SECRET__*/",
|
|
475
|
+
`JWT_SECRET: process.env.JWT_SECRET${assertion},`
|
|
476
|
+
);
|
|
477
|
+
} else {
|
|
478
|
+
envContent = envContent.replace("/*__MONGO_URI__*/", "");
|
|
479
|
+
envContent = envContent.replace("/*__JWT_SECRET__*/", "");
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
fs.writeFileSync(sharedEnvPath, envContent);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Update shared config/index to conditionally export connectDB
|
|
486
|
+
const sharedConfigIndexPath = path.join(sharedConfigDir, `index.${ext}`);
|
|
487
|
+
if (fs.existsSync(sharedConfigIndexPath)) {
|
|
488
|
+
let indexContent = fs.readFileSync(sharedConfigIndexPath, "utf8");
|
|
489
|
+
if (!config.auth) {
|
|
490
|
+
if (ext === "ts") {
|
|
491
|
+
indexContent = indexContent.replace(
|
|
492
|
+
'export { connectDB } from "./db";\n',
|
|
493
|
+
""
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
indexContent = indexContent
|
|
497
|
+
.replace('const { connectDB } = require("./db");', "")
|
|
498
|
+
.replace("connectDB,", "");
|
|
499
|
+
fs.writeFileSync(sharedConfigIndexPath, indexContent);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Update shared utils/logger to use shared config
|
|
504
|
+
const sharedLoggerPath = path.join(sharedUtilsDir, `logger.${ext}`);
|
|
505
|
+
if (fs.existsSync(sharedLoggerPath)) {
|
|
506
|
+
console.log(
|
|
507
|
+
`\n${pc.cyan("š§ Updating shared logger to use shared config...")}`
|
|
508
|
+
);
|
|
509
|
+
let loggerContent = fs.readFileSync(sharedLoggerPath, "utf8");
|
|
510
|
+
// Replace imports like: from '@/config'; or from "@/config" with relative import to shared config
|
|
511
|
+
loggerContent = loggerContent.replace(
|
|
512
|
+
/from\s+["']@\/config["'];?/g,
|
|
513
|
+
"from '../config';"
|
|
514
|
+
);
|
|
515
|
+
fs.writeFileSync(sharedLoggerPath, loggerContent);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Create shared package.json
|
|
519
|
+
const sharedPackageJson = {
|
|
520
|
+
name: "@shared/common",
|
|
521
|
+
version: "1.0.0",
|
|
522
|
+
type: "commonjs",
|
|
523
|
+
exports: {
|
|
524
|
+
"./config/*": "./config/*",
|
|
525
|
+
"./utils/*": "./utils/*",
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
fs.writeFileSync(
|
|
529
|
+
path.join(sharedDir, "package.json"),
|
|
530
|
+
JSON.stringify(sharedPackageJson, null, 2)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
for (const serviceName of servicesToCreate) {
|
|
536
|
+
const serviceRoot = path.join(target, "services", serviceName);
|
|
537
|
+
|
|
538
|
+
// Check for exact or alternate folder conflicts (e.g., 'order' vs 'order-service')
|
|
539
|
+
const altName = serviceName.endsWith("-service")
|
|
540
|
+
? serviceName.replace(/-service$/, "")
|
|
541
|
+
: `${serviceName}-service`;
|
|
542
|
+
const altPath = path.join(target, "services", altName);
|
|
543
|
+
let conflictPath = null;
|
|
544
|
+
if (fs.existsSync(serviceRoot)) conflictPath = serviceRoot;
|
|
545
|
+
else if (fs.existsSync(altPath)) conflictPath = altPath;
|
|
546
|
+
|
|
547
|
+
if (conflictPath) {
|
|
548
|
+
const rel = path.relative(target, conflictPath);
|
|
549
|
+
const resp = await prompts({
|
|
550
|
+
type: "select",
|
|
551
|
+
name: "action",
|
|
552
|
+
message: `Service directory '${rel}' already exists and conflicts with requested '${serviceName}'. Choose action:`,
|
|
553
|
+
choices: [
|
|
554
|
+
{ title: "Abort generation", value: "abort" },
|
|
555
|
+
{ title: `Skip creating '${serviceName}'`, value: "skip" },
|
|
556
|
+
{
|
|
557
|
+
title: `Overwrite '${rel}' with '${serviceName}'`,
|
|
558
|
+
value: "overwrite",
|
|
559
|
+
},
|
|
560
|
+
],
|
|
561
|
+
initial: 0,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
if (!resp.action || resp.action === "abort") {
|
|
565
|
+
console.log(pc.red("Aborting."));
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
if (resp.action === "skip") {
|
|
569
|
+
console.log(pc.yellow(`Skipping ${serviceName}`));
|
|
570
|
+
continue;
|
|
571
|
+
}
|
|
572
|
+
if (resp.action === "overwrite") {
|
|
573
|
+
try {
|
|
574
|
+
fs.rmSync(conflictPath, { recursive: true, force: true });
|
|
575
|
+
} catch (e) {
|
|
576
|
+
console.error(
|
|
577
|
+
pc.red(`Failed to remove existing path: ${conflictPath}`)
|
|
578
|
+
);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
console.log(`\nšØ Setting up ${serviceName}...`);
|
|
585
|
+
fs.cpSync(base, serviceRoot, { recursive: true });
|
|
586
|
+
// track which services we actually created/overwrote so we can run setupService for them
|
|
587
|
+
servicesToSetup.push(serviceName);
|
|
588
|
+
|
|
589
|
+
// Remove .env and .env.example from microservices (environment variables come from docker-compose/pm2)
|
|
590
|
+
const envPath = path.join(serviceRoot, ".env");
|
|
591
|
+
const envExamplePath = path.join(serviceRoot, ".env.example");
|
|
592
|
+
if (fs.existsSync(envPath)) fs.rmSync(envPath);
|
|
593
|
+
if (fs.existsSync(envExamplePath)) fs.rmSync(envExamplePath);
|
|
594
|
+
|
|
595
|
+
// Remove config and utils from service (they'll use shared) - except gateway handles it differently
|
|
596
|
+
if (serviceName !== "gateway") {
|
|
597
|
+
const serviceConfigDir = path.join(serviceRoot, "src", "config");
|
|
598
|
+
const serviceUtilsDir = path.join(serviceRoot, "src", "utils");
|
|
599
|
+
if (fs.existsSync(serviceConfigDir))
|
|
600
|
+
fs.rmSync(serviceConfigDir, { recursive: true });
|
|
601
|
+
if (fs.existsSync(serviceUtilsDir))
|
|
602
|
+
fs.rmSync(serviceUtilsDir, { recursive: true });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Get all services first (needed for gateway routing)
|
|
607
|
+
const servicesDir = path.join(target, "services");
|
|
608
|
+
const existingServices = fs.existsSync(servicesDir)
|
|
609
|
+
? fs
|
|
610
|
+
.readdirSync(servicesDir)
|
|
611
|
+
.filter((f) => fs.statSync(path.join(servicesDir, f)).isDirectory())
|
|
612
|
+
: [];
|
|
613
|
+
// Include services we're about to create so port computation and gateway routing
|
|
614
|
+
// are aware of newly added services when setting up files.
|
|
615
|
+
const allServices = Array.from(
|
|
616
|
+
new Set([...existingServices, ...servicesToSetup])
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
// Step 1: Setup all service files first (without installing dependencies)
|
|
620
|
+
console.log(pc.cyan("\nāļø Setting up service files...\n"));
|
|
621
|
+
const serviceConfigs = [];
|
|
622
|
+
|
|
623
|
+
for (const serviceName of servicesToSetup) {
|
|
624
|
+
const serviceRoot = path.join(target, "services", serviceName);
|
|
625
|
+
const shouldIncludeAuth = isInMicroserviceProject
|
|
626
|
+
? config.auth
|
|
627
|
+
: serviceName === "auth-service";
|
|
628
|
+
const result = await setupService(
|
|
629
|
+
config,
|
|
630
|
+
serviceName,
|
|
631
|
+
serviceRoot,
|
|
632
|
+
shouldIncludeAuth,
|
|
633
|
+
allServices,
|
|
634
|
+
true // Skip install for now
|
|
635
|
+
);
|
|
636
|
+
serviceConfigs.push({
|
|
637
|
+
serviceName,
|
|
638
|
+
serviceRoot,
|
|
639
|
+
deps: result.deps,
|
|
640
|
+
devDeps: result.devDeps,
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Remove per-service husky hooks and ensure a single root pre-commit hook
|
|
645
|
+
try {
|
|
646
|
+
const servicesDirPath = path.join(target, "services");
|
|
647
|
+
const allServicesList = fs.existsSync(servicesDirPath)
|
|
648
|
+
? fs
|
|
649
|
+
.readdirSync(servicesDirPath)
|
|
650
|
+
.filter((f) =>
|
|
651
|
+
fs.statSync(path.join(servicesDirPath, f)).isDirectory()
|
|
652
|
+
)
|
|
653
|
+
: [];
|
|
654
|
+
|
|
655
|
+
// Remove `.husky` folders from each service
|
|
656
|
+
for (const svc of allServicesList) {
|
|
657
|
+
const svcHusky = path.join(servicesDirPath, svc, ".husky");
|
|
658
|
+
if (fs.existsSync(svcHusky)) {
|
|
659
|
+
fs.rmSync(svcHusky, { recursive: true, force: true });
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Ensure root .husky/pre-commit exists at target
|
|
664
|
+
const rootHuskyDir = path.join(target, ".husky");
|
|
665
|
+
if (!fs.existsSync(rootHuskyDir))
|
|
666
|
+
fs.mkdirSync(rootHuskyDir, { recursive: true });
|
|
667
|
+
const preCommitPath = path.join(rootHuskyDir, "pre-commit");
|
|
668
|
+
const preCommitContent =
|
|
669
|
+
config.language === "typescript"
|
|
670
|
+
? 'set -e\n\necho "Checking format (prettier)..."\nnpm run check-format\n\necho "Running TypeScript type-check..."\nnpx tsc --noEmit\n\necho "Checking lint..."\nnpm run lint -- --max-warnings=0\n'
|
|
671
|
+
: 'set -e\n\necho "Checking format (prettier)..."\nnpm run check-format\n\necho "Checking lint..."\nnpm run lint -- --max-warnings=0\n';
|
|
672
|
+
fs.writeFileSync(preCommitPath, preCommitContent);
|
|
673
|
+
} catch (err) {
|
|
674
|
+
// Non-fatal; continue setup even if husky files couldn't be created/removed
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Step 2: Generate docker-compose/pm2 config and root files
|
|
678
|
+
if (mode === "docker") {
|
|
679
|
+
generateDockerCompose(target, allServices, config.sanitizedName);
|
|
680
|
+
copyDockerfile(target, servicesToSetup);
|
|
681
|
+
copyDockerignore(target, servicesToSetup);
|
|
682
|
+
} else {
|
|
683
|
+
generatePm2Config(target, allServices);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Create root package.json for microservice monorepo if it doesn't exist
|
|
687
|
+
const rootPackageJsonPath = path.join(target, "package.json");
|
|
688
|
+
if (!fs.existsSync(rootPackageJsonPath)) {
|
|
689
|
+
const rootPackageJson = {
|
|
690
|
+
name: sanitizedName,
|
|
691
|
+
version: config.version || "1.0.0",
|
|
692
|
+
description: config.description || "",
|
|
693
|
+
private: true,
|
|
694
|
+
scripts: {
|
|
695
|
+
dev:
|
|
696
|
+
mode === "docker"
|
|
697
|
+
? "docker-compose up"
|
|
698
|
+
: "npx pm2 start pm2.config.js && npx pm2 logs",
|
|
699
|
+
stop: mode === "docker" ? "docker-compose down" : "npx pm2 kill",
|
|
700
|
+
restart:
|
|
701
|
+
mode === "docker"
|
|
702
|
+
? "docker-compose restart"
|
|
703
|
+
: "npx pm2 restart all && npx pm2 logs",
|
|
704
|
+
lint: 'eslint "services/**/*.{js,ts,tsx}" "shared/**/*.{js,ts,tsx}"',
|
|
705
|
+
format:
|
|
706
|
+
'prettier --write "services/**/*.{js,ts,json}" "shared/**/*.{js,ts,json}"',
|
|
707
|
+
"check-format":
|
|
708
|
+
'prettier --check "services/**/*.{js,ts,json}" "shared/**/*.{js,ts,json}"',
|
|
709
|
+
prepare: "husky install",
|
|
710
|
+
},
|
|
711
|
+
devDependencies: {
|
|
712
|
+
husky: "^9.1.7",
|
|
713
|
+
prettier: "^3.7.4",
|
|
714
|
+
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
715
|
+
"@typescript-eslint/parser": "^8.50.1",
|
|
716
|
+
eslint: "^9.39.2",
|
|
717
|
+
"eslint-config-prettier": "^10.1.8",
|
|
718
|
+
},
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
// Add runtime dependencies for non-Docker (PM2) mode
|
|
722
|
+
if (mode !== "docker") {
|
|
723
|
+
rootPackageJson.dependencies = {
|
|
724
|
+
dotenv: "^17.2.3",
|
|
725
|
+
pm2: "^6.0.14",
|
|
726
|
+
"ts-node": "^10.9.2",
|
|
727
|
+
"tsconfig-paths": "^4.2.0",
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
fs.writeFileSync(
|
|
731
|
+
rootPackageJsonPath,
|
|
732
|
+
JSON.stringify(rootPackageJson, null, 2) + "\n"
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Ensure root lint/format config files exist (copy from template base if available), and remove any per-service copies
|
|
737
|
+
try {
|
|
738
|
+
const rootFiles = [".prettierrc", ".prettierignore", ".eslintrc.json"];
|
|
739
|
+
for (const f of rootFiles) {
|
|
740
|
+
const src = path.join(base, f);
|
|
741
|
+
const dest = path.join(target, f);
|
|
742
|
+
if (fs.existsSync(src)) {
|
|
743
|
+
fs.copyFileSync(src, dest);
|
|
744
|
+
} else if (!fs.existsSync(dest)) {
|
|
745
|
+
// create minimal defaults
|
|
746
|
+
if (f === ".prettierignore")
|
|
747
|
+
fs.writeFileSync(dest, "node_modules\n" + "dist\n");
|
|
748
|
+
else if (f === ".eslintrc.json")
|
|
749
|
+
fs.writeFileSync(dest, JSON.stringify({ root: true }, null, 2));
|
|
750
|
+
else fs.writeFileSync(dest, "{}");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Write eslint.config.js with recommended workspace config (overwrite)
|
|
755
|
+
const eslintConfigPath = path.join(target, "eslint.config.js");
|
|
756
|
+
|
|
757
|
+
// Build dynamic project list for TypeScript projects based on the services present
|
|
758
|
+
const projectPaths = ["./tsconfig.json"];
|
|
759
|
+
try {
|
|
760
|
+
if (typeof allServices !== "undefined" && Array.isArray(allServices)) {
|
|
761
|
+
for (const svc of allServices) {
|
|
762
|
+
const svcTsPath = `./services/${svc}/tsconfig.json`;
|
|
763
|
+
if (fs.existsSync(path.join(target, svcTsPath))) {
|
|
764
|
+
projectPaths.push(svcTsPath);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
} catch (e) {
|
|
769
|
+
// non-fatal; fall back to default projectPaths containing only root tsconfig
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
const projectEntries = projectPaths
|
|
773
|
+
.map((p) => ` "${p}",`)
|
|
774
|
+
.join("\n");
|
|
775
|
+
|
|
776
|
+
const eslintConfigContent = `const tsParser = require("@typescript-eslint/parser");\nconst tsPlugin = require("@typescript-eslint/eslint-plugin");\n\nmodule.exports = [\n // Files/paths to ignore (replaces .eslintignore usage in flat config)\n {\n ignores: ["node_modules/**", "dist/**"],\n },\n\n // TypeScript rules for source files\n {\n files: ["services/**/*.{js,ts,tsx}", "shared/**/*.{js,ts,tsx}"],\n languageOptions: {\n parser: tsParser,\n parserOptions: {\n project: [\n${projectEntries}\n ],\n tsconfigRootDir: __dirname,\n ecmaVersion: 2020,\n sourceType: "module",\n },\n },\n plugins: {\n "@typescript-eslint": tsPlugin,\n },\n rules: {\n // Disallow explicit 'any'\n "@typescript-eslint/no-explicit-any": "error",\n\n // You can add or tune more TypeScript rules here\n "@typescript-eslint/explicit-module-boundary-types": "off",\n },\n },\n];\n`;
|
|
777
|
+
fs.writeFileSync(eslintConfigPath, eslintConfigContent);
|
|
778
|
+
|
|
779
|
+
// Remove per-service copies if they exist (already removed in setupService, but double-check)
|
|
780
|
+
const servicesDirPath = path.join(target, "services");
|
|
781
|
+
if (fs.existsSync(servicesDirPath)) {
|
|
782
|
+
const svcs = fs
|
|
783
|
+
.readdirSync(servicesDirPath)
|
|
784
|
+
.filter((f) =>
|
|
785
|
+
fs.statSync(path.join(servicesDirPath, f)).isDirectory()
|
|
786
|
+
);
|
|
787
|
+
for (const svc of svcs) {
|
|
788
|
+
for (const f of [...rootFiles, "eslint.config.js"]) {
|
|
789
|
+
const p = path.join(servicesDirPath, svc, f);
|
|
790
|
+
if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
} catch (err) {
|
|
795
|
+
// non-fatal
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// Step 3: Generate README and create root configuration files
|
|
799
|
+
if (!isInMicroserviceProject) {
|
|
800
|
+
console.log(`\n${pc.cyan("š Generating README.md...")}\n`);
|
|
801
|
+
const readmeContent = generateReadme(config);
|
|
802
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
803
|
+
writeStarterWorkflow(target, config);
|
|
804
|
+
writePullRequestTemplate(target, config);
|
|
805
|
+
writeContributingGuide(target, config);
|
|
806
|
+
|
|
807
|
+
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
808
|
+
for (const service of allServices) {
|
|
809
|
+
const gitignorePath = path.join(servicesDir, service, "gitignore");
|
|
810
|
+
const dotGitignorePath = path.join(servicesDir, service, ".gitignore");
|
|
811
|
+
if (fs.existsSync(gitignorePath)) {
|
|
812
|
+
fs.renameSync(gitignorePath, dotGitignorePath);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// Create root .gitignore for microservices
|
|
817
|
+
const rootGitignoreContent = `.env\nnode_modules\n`;
|
|
818
|
+
fs.writeFileSync(path.join(target, ".gitignore"), rootGitignoreContent);
|
|
819
|
+
|
|
820
|
+
// Create root .env and .env.example for microservices
|
|
821
|
+
let rootENVContent = `# Environment Configuration\nNODE_ENV=development\n\n`;
|
|
822
|
+
|
|
823
|
+
// Add port configuration for each service
|
|
824
|
+
allServices.forEach((service, index) => {
|
|
825
|
+
const isGateway = service === "gateway";
|
|
826
|
+
const port = isGateway
|
|
827
|
+
? 4000
|
|
828
|
+
: 4001 +
|
|
829
|
+
allServices.filter((s, i) => s !== "gateway" && i < index).length;
|
|
830
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
831
|
+
const serviceName = service
|
|
832
|
+
.split("-")
|
|
833
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
834
|
+
.join(" ");
|
|
835
|
+
rootENVContent += `# ${serviceName}\n${envVarName}=${port}\n\n`;
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
fs.writeFileSync(path.join(target, ".env"), rootENVContent);
|
|
839
|
+
fs.writeFileSync(path.join(target, ".env.example"), rootENVContent);
|
|
840
|
+
|
|
841
|
+
// Create root tsconfig.json for microservices workspace
|
|
842
|
+
const rootTsConfigContent = {
|
|
843
|
+
compilerOptions: {
|
|
844
|
+
target: "ES2020",
|
|
845
|
+
module: "CommonJS",
|
|
846
|
+
lib: ["ES2020"],
|
|
847
|
+
moduleResolution: "node",
|
|
848
|
+
esModuleInterop: true,
|
|
849
|
+
skipLibCheck: true,
|
|
850
|
+
strict: true,
|
|
851
|
+
baseUrl: ".",
|
|
852
|
+
paths: {
|
|
853
|
+
"@/*": ["./*"],
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
include: [],
|
|
857
|
+
exclude: ["node_modules", "dist"],
|
|
858
|
+
references: allServices.map((service) => ({
|
|
859
|
+
path: `./services/${service}`,
|
|
860
|
+
})),
|
|
861
|
+
};
|
|
862
|
+
fs.writeFileSync(
|
|
863
|
+
path.join(target, "tsconfig.json"),
|
|
864
|
+
JSON.stringify(rootTsConfigContent, null, 2) + "\n"
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// If we're adding a service into an existing microservice project,
|
|
869
|
+
// ensure shared config and gateway are updated to reference the new service.
|
|
870
|
+
if (isInMicroserviceProject) {
|
|
871
|
+
try {
|
|
872
|
+
const sharedConfigDir = path.join(target, "shared", "config");
|
|
873
|
+
const languageExt = config.language === "javascript" ? "js" : "ts";
|
|
874
|
+
const sharedEnvPath = path.join(sharedConfigDir, `env.${languageExt}`);
|
|
875
|
+
|
|
876
|
+
if (fs.existsSync(sharedEnvPath)) {
|
|
877
|
+
let envContent = fs.readFileSync(sharedEnvPath, "utf8");
|
|
878
|
+
|
|
879
|
+
// Build port environment variables for all services
|
|
880
|
+
const portEnvVars = allServices
|
|
881
|
+
.map((service) => {
|
|
882
|
+
const envVarName = `${service
|
|
883
|
+
.toUpperCase()
|
|
884
|
+
.replace(/-/g, "_")}_PORT`;
|
|
885
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
886
|
+
return ` ${envVarName}: process.env.${envVarName}${assertion},`;
|
|
887
|
+
})
|
|
888
|
+
.join("\n");
|
|
889
|
+
|
|
890
|
+
// Remove any existing *_PORT lines to avoid duplication
|
|
891
|
+
envContent = envContent.replace(
|
|
892
|
+
/^[ \t]*[A-Z0-9_]+_PORT:\s*process\.env\.[A-Z0-9_]+!?\,?\s*$/gim,
|
|
893
|
+
""
|
|
894
|
+
);
|
|
895
|
+
// Normalize multiple consecutive blank lines
|
|
896
|
+
envContent = envContent.replace(/\n{2,}/g, "\n\n");
|
|
897
|
+
|
|
898
|
+
// Attempt several fallback strategies to inject port variables:
|
|
899
|
+
// 1. Replace explicit placeholder if present in template
|
|
900
|
+
// 2. Insert right after the first object opening brace (or replace placeholder)
|
|
901
|
+
if (envContent.includes("/*__PORTS__*/")) {
|
|
902
|
+
envContent = envContent.replace(
|
|
903
|
+
"/*__PORTS__*/",
|
|
904
|
+
"/*__PORTS__*/\n" + portEnvVars
|
|
905
|
+
);
|
|
906
|
+
} else {
|
|
907
|
+
// Fallback: find the opening brace of the exported ENV object and insert after it
|
|
908
|
+
const braceIndex = envContent.indexOf("{");
|
|
909
|
+
if (braceIndex !== -1) {
|
|
910
|
+
const insertPos =
|
|
911
|
+
envContent.indexOf("\n", braceIndex) + 1 || braceIndex + 1;
|
|
912
|
+
// insert a stable placeholder comment followed by the ports block
|
|
913
|
+
envContent =
|
|
914
|
+
envContent.slice(0, insertPos) +
|
|
915
|
+
" /*__PORTS__*/\n" +
|
|
916
|
+
portEnvVars +
|
|
917
|
+
"\n" +
|
|
918
|
+
envContent.slice(insertPos);
|
|
919
|
+
} else {
|
|
920
|
+
// Final fallback: append a placeholder and the ports to the end
|
|
921
|
+
envContent = envContent + "\n/*__PORTS__*/\n" + portEnvVars;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
fs.writeFileSync(sharedEnvPath, envContent);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Re-generate gateway routes if gateway exists (so new service gets proxied)
|
|
929
|
+
const gatewayRoot = path.join(target, "services", "gateway");
|
|
930
|
+
if (fs.existsSync(gatewayRoot)) {
|
|
931
|
+
// Re-run setupService for gateway to rewrite app/server/env files
|
|
932
|
+
await setupService(
|
|
933
|
+
config,
|
|
934
|
+
"gateway",
|
|
935
|
+
gatewayRoot,
|
|
936
|
+
config.auth,
|
|
937
|
+
allServices,
|
|
938
|
+
true
|
|
939
|
+
);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Update root .env and .env.example so newly added services have port entries
|
|
943
|
+
try {
|
|
944
|
+
const servicesDirPath = path.join(target, "services");
|
|
945
|
+
const svcList = fs.existsSync(servicesDirPath)
|
|
946
|
+
? fs
|
|
947
|
+
.readdirSync(servicesDirPath)
|
|
948
|
+
.filter((f) =>
|
|
949
|
+
fs.statSync(path.join(servicesDirPath, f)).isDirectory()
|
|
950
|
+
)
|
|
951
|
+
: allServices;
|
|
952
|
+
|
|
953
|
+
// Update only .env.example: preserve runtime .env (don't overwrite user changes)
|
|
954
|
+
try {
|
|
955
|
+
const envExamplePath = path.join(target, ".env.example");
|
|
956
|
+
const servicesPorts = svcList.map((service, index) => {
|
|
957
|
+
const isGateway = service === "gateway";
|
|
958
|
+
const port = isGateway
|
|
959
|
+
? 4000
|
|
960
|
+
: 4001 +
|
|
961
|
+
svcList.filter((s, i) => s !== "gateway" && i < index).length;
|
|
962
|
+
const envVarName = `${service
|
|
963
|
+
.toUpperCase()
|
|
964
|
+
.replace(/-/g, "_")}_PORT`;
|
|
965
|
+
const serviceNamePretty = service
|
|
966
|
+
.split("-")
|
|
967
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
968
|
+
.join(" ");
|
|
969
|
+
return `# ${serviceNamePretty}\n${envVarName}=${port}\n`;
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
const portsBlock = servicesPorts.join("\n");
|
|
973
|
+
|
|
974
|
+
let exampleContent = "";
|
|
975
|
+
if (fs.existsSync(envExamplePath)) {
|
|
976
|
+
exampleContent = fs.readFileSync(envExamplePath, "utf8");
|
|
977
|
+
|
|
978
|
+
// Remove existing *_PORT lines and any immediate preceding single-line comment
|
|
979
|
+
const lines = exampleContent.split(/\r?\n/);
|
|
980
|
+
const filtered = [];
|
|
981
|
+
for (let i = 0; i < lines.length; i++) {
|
|
982
|
+
const line = lines[i];
|
|
983
|
+
const next = lines[i + 1];
|
|
984
|
+
if (next && /^[A-Z0-9_]+_PORT=/.test(next.trim())) {
|
|
985
|
+
// skip this line if it's a comment immediately preceding a _PORT assignment
|
|
986
|
+
i++; // skip next as well
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (/^[A-Z0-9_]+_PORT=/.test(line.trim())) {
|
|
990
|
+
// skip existing port assignment
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
filtered.push(line);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
exampleContent = filtered.join("\n");
|
|
997
|
+
} else {
|
|
998
|
+
// create minimal header if example file doesn't exist
|
|
999
|
+
exampleContent = `# Environment Configuration\nNODE_ENV=development\n\n`;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Ensure NODE_ENV line exists and insert portsBlock after it
|
|
1003
|
+
const nodeEnvRegex = /^NODE_ENV=.*$/m;
|
|
1004
|
+
if (nodeEnvRegex.test(exampleContent)) {
|
|
1005
|
+
exampleContent = exampleContent.replace(
|
|
1006
|
+
nodeEnvRegex,
|
|
1007
|
+
(m) => `${m}\n\n${portsBlock}\n`
|
|
1008
|
+
);
|
|
1009
|
+
} else {
|
|
1010
|
+
// Prepend header and ports
|
|
1011
|
+
exampleContent =
|
|
1012
|
+
`# Environment Configuration\nNODE_ENV=development\n\n${portsBlock}\n` +
|
|
1013
|
+
exampleContent;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
fs.writeFileSync(envExamplePath, exampleContent);
|
|
1017
|
+
// Inform the user about the new ports and remind them to update their runtime .env if needed
|
|
1018
|
+
try {
|
|
1019
|
+
console.log(
|
|
1020
|
+
pc.cyan("\nš§ Updated .env.example with service port entries:\n")
|
|
1021
|
+
);
|
|
1022
|
+
console.log(pc.green(portsBlock));
|
|
1023
|
+
console.log(
|
|
1024
|
+
pc.dim(
|
|
1025
|
+
"If you keep a runtime .env with custom overrides, do NOT overwrite it.\nPlease copy any new *_PORT entries from .env.example into .env as appropriate."
|
|
1026
|
+
)
|
|
1027
|
+
);
|
|
1028
|
+
} catch (e) {
|
|
1029
|
+
// non-fatal if logging fails
|
|
1030
|
+
}
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
// non-fatal
|
|
1033
|
+
}
|
|
1034
|
+
} catch (e) {
|
|
1035
|
+
// non-fatal
|
|
1036
|
+
}
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
// non-fatal; continue even if updating shared/gateway fails
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Step 5: Install dependencies for all services
|
|
1043
|
+
|
|
1044
|
+
console.log(pc.cyan("\nš¦ Installing dependencies for all services...\n"));
|
|
1045
|
+
let allInstallsSucceeded = true;
|
|
1046
|
+
|
|
1047
|
+
for (const { serviceName, serviceRoot, deps, devDeps } of serviceConfigs) {
|
|
1048
|
+
console.log(
|
|
1049
|
+
pc.cyan(`\nš¦ Installing dependencies for ${serviceName}...\n`)
|
|
1050
|
+
);
|
|
1051
|
+
|
|
1052
|
+
try {
|
|
1053
|
+
if (deps.length) {
|
|
1054
|
+
execSync(`npm install ${deps.join(" ")}`, {
|
|
1055
|
+
cwd: serviceRoot,
|
|
1056
|
+
stdio: "inherit",
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
if (devDeps.length) {
|
|
1060
|
+
execSync(`npm install -D ${devDeps.join(" ")}`, {
|
|
1061
|
+
cwd: serviceRoot,
|
|
1062
|
+
stdio: "inherit",
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
execSync("npm install", { cwd: serviceRoot, stdio: "inherit" });
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
allInstallsSucceeded = false;
|
|
1068
|
+
console.error(
|
|
1069
|
+
pc.red(`\nā Failed to install dependencies for ${serviceName}`)
|
|
1070
|
+
);
|
|
1071
|
+
console.error(pc.dim(`\nYou can install them later by running:`));
|
|
1072
|
+
console.error(pc.cyan(` cd services/${serviceName} && npm install\n`));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// Store for later use
|
|
1077
|
+
config.allInstallsSucceeded = allInstallsSucceeded;
|
|
1078
|
+
} else {
|
|
1079
|
+
const result = await setupService(config, null, target, true);
|
|
1080
|
+
config.installSucceeded = result.installSucceeded;
|
|
1081
|
+
|
|
1082
|
+
try {
|
|
1083
|
+
if (result.deps && result.deps.length) {
|
|
1084
|
+
execSync(`npm install ${result.deps.join(" ")}`, {
|
|
1085
|
+
cwd: target,
|
|
1086
|
+
stdio: "inherit",
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
if (result.devDeps && result.devDeps.length) {
|
|
1090
|
+
execSync(`npm install -D ${result.devDeps.join(" ")}`, {
|
|
1091
|
+
cwd: target,
|
|
1092
|
+
stdio: "inherit",
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
execSync("npm install", { cwd: target, stdio: "inherit" });
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
console.error(pc.red("\nā Failed to install monolith dependencies"));
|
|
1098
|
+
console.error(pc.dim(`\nYou can install them later by running:`));
|
|
1099
|
+
console.error(pc.cyan(` cd ${target} && npm install\n`));
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Safety net: ensure eslint.config.js exists in generated monolith projects
|
|
1103
|
+
const templateEslintConfig = path.join(base, "eslint.config.js");
|
|
1104
|
+
const generatedEslintConfig = path.join(target, "eslint.config.js");
|
|
1105
|
+
if (
|
|
1106
|
+
config.projectType === "monolith" &&
|
|
1107
|
+
fs.existsSync(templateEslintConfig) &&
|
|
1108
|
+
!fs.existsSync(generatedEslintConfig)
|
|
1109
|
+
) {
|
|
1110
|
+
fs.copyFileSync(templateEslintConfig, generatedEslintConfig);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// Generate README.md for monolith (microservices already done above)
|
|
1115
|
+
if (!isInMicroserviceProject && config.projectType === "monolith") {
|
|
1116
|
+
console.log(`\n${pc.cyan("š Generating README.md...")}\n`);
|
|
1117
|
+
const readmeContent = generateReadme(config);
|
|
1118
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
1119
|
+
writeStarterWorkflow(target, config);
|
|
1120
|
+
writePullRequestTemplate(target, config);
|
|
1121
|
+
writeContributingGuide(target, config);
|
|
1122
|
+
|
|
1123
|
+
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
1124
|
+
const gitignorePath = path.join(target, "gitignore");
|
|
1125
|
+
const dotGitignorePath = path.join(target, ".gitignore");
|
|
1126
|
+
if (fs.existsSync(gitignorePath)) {
|
|
1127
|
+
fs.renameSync(gitignorePath, dotGitignorePath);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Generate .env from .env.example for monolith only
|
|
1131
|
+
console.log(`${pc.cyan("š Setting up environment files...")}\n`);
|
|
1132
|
+
try {
|
|
1133
|
+
const rootEnvExamplePath = path.join(target, ".env.example");
|
|
1134
|
+
const rootEnvPath = path.join(target, ".env");
|
|
1135
|
+
if (fs.existsSync(rootEnvExamplePath) && !fs.existsSync(rootEnvPath)) {
|
|
1136
|
+
fs.copyFileSync(rootEnvExamplePath, rootEnvPath);
|
|
1137
|
+
}
|
|
1138
|
+
} catch (err) {
|
|
1139
|
+
// Non-fatal; proceed even if we fail to write env files
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Initialize git and Husky
|
|
1144
|
+
if (!isInMicroserviceProject) {
|
|
1145
|
+
execSync("git init", { cwd: target, stdio: "inherit" });
|
|
1146
|
+
|
|
1147
|
+
// Install husky and other devDeps and setup at root level
|
|
1148
|
+
if (config.projectType === "microservice") {
|
|
1149
|
+
console.log("\nš¦ Installing dependencies at root level...\n");
|
|
1150
|
+
if (config.allInstallsSucceeded) {
|
|
1151
|
+
try {
|
|
1152
|
+
execSync("npm install", { cwd: target, stdio: "inherit" });
|
|
1153
|
+
console.log("\nš§ Setting up Husky...\n");
|
|
1154
|
+
execSync("npm run prepare", { cwd: target, stdio: "inherit" });
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
console.log("\nā ļø Husky setup failed\n");
|
|
1157
|
+
}
|
|
1158
|
+
// Run format after successful install
|
|
1159
|
+
console.log(pc.cyan("\nšØ Formatting code...\n"));
|
|
1160
|
+
try {
|
|
1161
|
+
execSync("npm run format", { cwd: target, stdio: "inherit" });
|
|
1162
|
+
} catch (formatError) {
|
|
1163
|
+
console.warn(
|
|
1164
|
+
pc.yellow(
|
|
1165
|
+
"ā ļø Warning: Code formatting failed. You can run it manually later with: npm run format\n"
|
|
1166
|
+
)
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
} else {
|
|
1170
|
+
console.log(
|
|
1171
|
+
"\nā ļø Husky setup skipped (run 'npm install && npm run prepare' after fixing service dependencies)\n"
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
} else if (config.projectType === "monolith") {
|
|
1175
|
+
// Only setup Husky if installation succeeded
|
|
1176
|
+
if (config.installSucceeded) {
|
|
1177
|
+
console.log(`\n${pc.cyan("š§ Setting up Husky...")}\n`);
|
|
1178
|
+
try {
|
|
1179
|
+
execSync("npm run prepare", { cwd: target, stdio: "inherit" });
|
|
1180
|
+
} catch (error) {
|
|
1181
|
+
console.log(
|
|
1182
|
+
`\n${pc.yellow("ā ļø Husky setup failed")} ${pc.dim(
|
|
1183
|
+
"(run 'npm run prepare' manually after fixing dependencies)"
|
|
1184
|
+
)}\n`
|
|
1185
|
+
);
|
|
1186
|
+
}
|
|
1187
|
+
} else {
|
|
1188
|
+
console.log(
|
|
1189
|
+
`\n${pc.yellow("ā ļø Husky setup skipped")} ${pc.dim(
|
|
1190
|
+
"(run 'npm install && npm run prepare' to set up git hooks)"
|
|
1191
|
+
)}\n`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// Success messages
|
|
1198
|
+
const servicesDir = path.join(target, "services");
|
|
1199
|
+
const allServices = fs.existsSync(servicesDir)
|
|
1200
|
+
? fs
|
|
1201
|
+
.readdirSync(servicesDir)
|
|
1202
|
+
.filter((f) => fs.statSync(path.join(servicesDir, f)).isDirectory())
|
|
1203
|
+
: servicesToCreate;
|
|
1204
|
+
// Update root README when adding services to an existing microservice project
|
|
1205
|
+
if (isInMicroserviceProject) {
|
|
1206
|
+
try {
|
|
1207
|
+
const readmeContent = generateReadme({ ...config, allServices });
|
|
1208
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
1209
|
+
console.log(`\n${pc.cyan("š Updated README.md with new services")}`);
|
|
1210
|
+
} catch (e) {
|
|
1211
|
+
// non-fatal
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
if (isInMicroserviceProject) {
|
|
1216
|
+
if (servicesToSetup.length > 0) {
|
|
1217
|
+
console.log(
|
|
1218
|
+
`\n${pc.green("ā
Service")} ${pc.bold(servicesToSetup[0])} ${pc.green(
|
|
1219
|
+
"added successfully!"
|
|
1220
|
+
)}`
|
|
1221
|
+
);
|
|
1222
|
+
} else {
|
|
1223
|
+
console.log(
|
|
1224
|
+
pc.yellow("\nā ļø No new service was created (skipped by your selection).")
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
console.log(`\n${pc.cyan("š¦ All services:")} ${allServices.join(", ")}`);
|
|
1228
|
+
console.log(`\n${pc.blue("š” Next steps:")}`);
|
|
1229
|
+
console.log(` ${pc.dim("1.")} Start services: ${pc.bold("npm run dev")}`);
|
|
1230
|
+
} else if (config.projectType === "microservice") {
|
|
1231
|
+
console.log(`\n${pc.green("ā
Microservice Backend created successfully!")}`);
|
|
1232
|
+
console.log(
|
|
1233
|
+
`\n${pc.cyan("š¦ Created services:")} ${servicesToCreate.join(", ")}`
|
|
1234
|
+
);
|
|
1235
|
+
console.log(`\n${pc.blue("š” Next steps:")}`);
|
|
1236
|
+
console.log(` ${pc.dim("1.")} cd ${pc.bold(sanitizedName)}`);
|
|
1237
|
+
console.log(` ${pc.dim("2.")} Start services: ${pc.bold("npm run dev")}`);
|
|
1238
|
+
} else {
|
|
1239
|
+
console.log(`\n${pc.green("ā
Monolith Backend created successfully!")}`);
|
|
1240
|
+
console.log(`\n${pc.blue("š” Next steps:")}`);
|
|
1241
|
+
console.log(` ${pc.dim("1.")} cd ${pc.bold(sanitizedName)}`);
|
|
1242
|
+
console.log(` ${pc.dim("2.")} npm run dev`);
|
|
1243
|
+
}
|
|
1244
|
+
// Post-processing: ensure shared config does not export/connect to DB when auth is disabled
|
|
1245
|
+
try {
|
|
1246
|
+
if (!config.auth) {
|
|
1247
|
+
const sharedConfigDir = path.join(target, "shared", "config");
|
|
1248
|
+
if (fs.existsSync(sharedConfigDir)) {
|
|
1249
|
+
for (const ext of ["ts", "js"]) {
|
|
1250
|
+
const idxPath = path.join(sharedConfigDir, `index.${ext}`);
|
|
1251
|
+
if (!fs.existsSync(idxPath)) continue;
|
|
1252
|
+
let idxContent = fs.readFileSync(idxPath, "utf8");
|
|
1253
|
+
// Remove various connectDB export/import patterns
|
|
1254
|
+
idxContent = idxContent.replace(
|
|
1255
|
+
/export\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
1256
|
+
""
|
|
1257
|
+
);
|
|
1258
|
+
idxContent = idxContent.replace(
|
|
1259
|
+
/import\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
1260
|
+
""
|
|
1261
|
+
);
|
|
1262
|
+
idxContent = idxContent.replace(
|
|
1263
|
+
/const\s*\{\s*connectDB\s*\}\s*=\s*require\(["']\.\/db["']\);?/g,
|
|
1264
|
+
""
|
|
1265
|
+
);
|
|
1266
|
+
// Remove any remaining connectDB identifiers (commas/newlines)
|
|
1267
|
+
idxContent = idxContent.replace(/connectDB,?/g, "");
|
|
1268
|
+
// Normalize multiple blank lines
|
|
1269
|
+
idxContent = idxContent.replace(/\n{3,}/g, "\n\n");
|
|
1270
|
+
fs.writeFileSync(idxPath, idxContent);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
} catch (e) {
|
|
1275
|
+
// non-fatal
|
|
1276
|
+
}
|