@ifecodes/backend-template 1.1.9 → 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/README.md +423 -383
- package/bin/cli.js +1276 -964
- package/bin/lib/microservice-config.js +155 -150
- package/bin/lib/prompts.js +277 -241
- package/bin/lib/readme-generator.js +364 -329
- package/bin/lib/service-setup.js +901 -684
- package/package.json +64 -55
- package/template/base/js/.eslintrc.json +10 -13
- 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 -18
- package/template/base/js/src/config/db.js +8 -8
- package/template/base/js/src/config/env.js +44 -14
- package/template/base/js/src/config/index.js +7 -7
- 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 -19
- package/template/base/js/src/middlewares/index.js +15 -11
- package/template/base/js/src/middlewares/method-not-allowed.middleware.js +19 -13
- package/template/base/js/src/middlewares/not-found.middleware.js +13 -10
- package/template/base/js/src/middlewares/observability.middleware.js +24 -0
- package/template/base/js/src/middlewares/root.middleware.js +18 -16
- 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 +21 -21
- package/template/base/js/src/modules/v1/health/health.route.js +74 -9
- package/template/base/js/src/modules/v1/health/index.js +5 -5
- package/template/base/js/src/modules/v1/index.js +8 -8
- package/template/base/js/src/routes.js +23 -16
- package/template/base/js/src/server.js +18 -18
- package/template/base/js/src/utils/http-error.js +74 -74
- package/template/base/js/src/utils/index.js +28 -28
- 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 -18
- 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 +4 -1
- package/template/base/ts/src/middlewares/index.ts +6 -4
- package/template/base/ts/src/middlewares/method-not-allowed.middleware.ts +23 -18
- 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 -63
- package/template/base/ts/src/utils/index.ts +14 -14
- package/template/base/ts/src/utils/logger.ts +58 -68
- package/template/base/ts/tsconfig.json +21 -21
- 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/index.ts +1 -1
- package/template/features/auth/models/user.model.js +24 -24
- package/template/features/auth/models/user.model.ts +28 -28
- 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/modules/index.ts +1 -1
- package/template/features/auth/utils/hash.ts +20 -20
- package/template/features/auth/utils/jwt.js +12 -12
- package/template/features/auth/utils/jwt.ts +15 -15
- 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 -33
- 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/lib/service-setup.js
CHANGED
|
@@ -1,684 +1,901 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import prompts from "prompts";
|
|
4
|
-
import pc from "picocolors";
|
|
5
|
-
import { execSync } from "child_process";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
|
|
8
|
-
// Helper function to get the correct file extension (.ts or .js)
|
|
9
|
-
function getFileExtension(dir) {
|
|
10
|
-
// Check if .ts files exist, otherwise use .js
|
|
11
|
-
const sampleFiles = ["src/app.ts", "src/server.ts", "src/routes.ts"];
|
|
12
|
-
for (const file of sampleFiles) {
|
|
13
|
-
const tsPath = path.join(dir, file);
|
|
14
|
-
if (fs.existsSync(tsPath)) return "ts";
|
|
15
|
-
const jsPath = path.join(dir, file.replace(".ts", ".js"));
|
|
16
|
-
if (fs.existsSync(jsPath)) return "js";
|
|
17
|
-
}
|
|
18
|
-
return "ts"; // default to ts
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export const setupService = async (
|
|
22
|
-
res,
|
|
23
|
-
serviceName,
|
|
24
|
-
serviceRoot,
|
|
25
|
-
shouldIncludeAuth,
|
|
26
|
-
allServices = [],
|
|
27
|
-
skipInstall = false,
|
|
28
|
-
) => {
|
|
29
|
-
let imports = [];
|
|
30
|
-
let middlewares = [];
|
|
31
|
-
let deps = [];
|
|
32
|
-
let devDeps = [];
|
|
33
|
-
let v1Imports = [];
|
|
34
|
-
let v1Routes = [];
|
|
35
|
-
|
|
36
|
-
// Detect file extension (ts or js)
|
|
37
|
-
const ext = getFileExtension(serviceRoot);
|
|
38
|
-
|
|
39
|
-
// Remove workspace-level config files from service only in microservice mode (they should live at root)
|
|
40
|
-
// (monolith projects keep these files at project root)
|
|
41
|
-
if (res.projectType === "microservice" || res.isInMicroserviceProject) {
|
|
42
|
-
try {
|
|
43
|
-
const serviceConfigFiles = [
|
|
44
|
-
".prettierrc",
|
|
45
|
-
".prettierignore",
|
|
46
|
-
".eslintrc.json",
|
|
47
|
-
"eslint.config.js",
|
|
48
|
-
".husky",
|
|
49
|
-
"husky",
|
|
50
|
-
];
|
|
51
|
-
for (const f of serviceConfigFiles) {
|
|
52
|
-
const p = path.join(serviceRoot, f);
|
|
53
|
-
if (fs.existsSync(p)) {
|
|
54
|
-
// Remove file or directory
|
|
55
|
-
const stat = fs.statSync(p);
|
|
56
|
-
if (stat.isDirectory())
|
|
57
|
-
fs.rmSync(p, { recursive: true, force: true });
|
|
58
|
-
else fs.rmSync(p, { force: true });
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} catch (err) {
|
|
62
|
-
// Non-fatal
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Ensure service-level gitignore is renamed immediately after template copy
|
|
67
|
-
try {
|
|
68
|
-
const serviceGitignore = path.join(serviceRoot, "gitignore");
|
|
69
|
-
const serviceDotGitignore = path.join(serviceRoot, ".gitignore");
|
|
70
|
-
if (
|
|
71
|
-
fs.existsSync(serviceGitignore) &&
|
|
72
|
-
!fs.existsSync(serviceDotGitignore)
|
|
73
|
-
) {
|
|
74
|
-
fs.renameSync(serviceGitignore, serviceDotGitignore);
|
|
75
|
-
}
|
|
76
|
-
} catch (err) {
|
|
77
|
-
// Non-fatal; continue setup
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Special handling for gateway service
|
|
81
|
-
if (serviceName === "gateway") {
|
|
82
|
-
const tmplLang = res.language === "javascript" ? "js" : "ts";
|
|
83
|
-
const gatewayModule = await import(
|
|
84
|
-
`../../template/gateway/${tmplLang}/inject.js`
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
deps.push(...gatewayModule.gatewayDeps);
|
|
88
|
-
|
|
89
|
-
// Copy gateway-specific files
|
|
90
|
-
const gatewayAppPath = path.join(serviceRoot, `src/app.${ext}`);
|
|
91
|
-
const gatewayServerPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
92
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
93
|
-
|
|
94
|
-
// Read gateway template files according to selected language
|
|
95
|
-
const templateExt = res.language === "javascript" ? ".js" : ".ts";
|
|
96
|
-
const templateDir = path.join(
|
|
97
|
-
__dirname,
|
|
98
|
-
`../../template/gateway/${tmplLang}`,
|
|
99
|
-
);
|
|
100
|
-
const gatewayAppContent = fs.readFileSync(
|
|
101
|
-
path.join(templateDir, `app${templateExt}`),
|
|
102
|
-
"utf8",
|
|
103
|
-
);
|
|
104
|
-
const gatewayServerContent = fs.readFileSync(
|
|
105
|
-
path.join(templateDir, `server${templateExt}`),
|
|
106
|
-
"utf8",
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// Generate routes for all services with mode (docker or nodocker)
|
|
110
|
-
const mode = res.mode || "docker";
|
|
111
|
-
const routes = gatewayModule.generateGatewayRoutes(allServices, mode);
|
|
112
|
-
const finalAppContent = gatewayAppContent.replace("/*__ROUTES__*/", routes);
|
|
113
|
-
|
|
114
|
-
fs.writeFileSync(gatewayAppPath, finalAppContent);
|
|
115
|
-
fs.writeFileSync(gatewayServerPath, gatewayServerContent);
|
|
116
|
-
|
|
117
|
-
// Remove unnecessary files for gateway
|
|
118
|
-
const routesPath = path.join(serviceRoot, `src/routes.${ext}`);
|
|
119
|
-
const modulesPath = path.join(serviceRoot, "src/modules");
|
|
120
|
-
const middlewaresPath = path.join(serviceRoot, "src/middlewares");
|
|
121
|
-
const configPath = path.join(serviceRoot, `src/config`);
|
|
122
|
-
const utilPath = path.join(serviceRoot, `src/utils`);
|
|
123
|
-
|
|
124
|
-
if (fs.existsSync(routesPath)) fs.rmSync(routesPath);
|
|
125
|
-
if (fs.existsSync(modulesPath)) fs.rmSync(modulesPath, { recursive: true });
|
|
126
|
-
if (fs.existsSync(configPath)) fs.rmSync(configPath, { recursive: true });
|
|
127
|
-
if (fs.existsSync(utilPath)) fs.rmSync(utilPath, { recursive: true });
|
|
128
|
-
if (fs.existsSync(middlewaresPath))
|
|
129
|
-
fs.rmSync(middlewaresPath, { recursive: true });
|
|
130
|
-
} else {
|
|
131
|
-
// Regular service setup (existing code)
|
|
132
|
-
// Add
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
? feature.
|
|
149
|
-
: feature.
|
|
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
|
-
if
|
|
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
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
549
|
-
|
|
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
|
-
if
|
|
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
|
-
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
|
|
8
|
+
// Helper function to get the correct file extension (.ts or .js)
|
|
9
|
+
function getFileExtension(dir) {
|
|
10
|
+
// Check if .ts files exist, otherwise use .js
|
|
11
|
+
const sampleFiles = ["src/app.ts", "src/server.ts", "src/routes.ts"];
|
|
12
|
+
for (const file of sampleFiles) {
|
|
13
|
+
const tsPath = path.join(dir, file);
|
|
14
|
+
if (fs.existsSync(tsPath)) return "ts";
|
|
15
|
+
const jsPath = path.join(dir, file.replace(".ts", ".js"));
|
|
16
|
+
if (fs.existsSync(jsPath)) return "js";
|
|
17
|
+
}
|
|
18
|
+
return "ts"; // default to ts
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const setupService = async (
|
|
22
|
+
res,
|
|
23
|
+
serviceName,
|
|
24
|
+
serviceRoot,
|
|
25
|
+
shouldIncludeAuth,
|
|
26
|
+
allServices = [],
|
|
27
|
+
skipInstall = false,
|
|
28
|
+
) => {
|
|
29
|
+
let imports = [];
|
|
30
|
+
let middlewares = [];
|
|
31
|
+
let deps = [];
|
|
32
|
+
let devDeps = [];
|
|
33
|
+
let v1Imports = [];
|
|
34
|
+
let v1Routes = [];
|
|
35
|
+
|
|
36
|
+
// Detect file extension (ts or js)
|
|
37
|
+
const ext = getFileExtension(serviceRoot);
|
|
38
|
+
|
|
39
|
+
// Remove workspace-level config files from service only in microservice mode (they should live at root)
|
|
40
|
+
// (monolith projects keep these files at project root)
|
|
41
|
+
if (res.projectType === "microservice" || res.isInMicroserviceProject) {
|
|
42
|
+
try {
|
|
43
|
+
const serviceConfigFiles = [
|
|
44
|
+
".prettierrc",
|
|
45
|
+
".prettierignore",
|
|
46
|
+
".eslintrc.json",
|
|
47
|
+
"eslint.config.js",
|
|
48
|
+
".husky",
|
|
49
|
+
"husky",
|
|
50
|
+
];
|
|
51
|
+
for (const f of serviceConfigFiles) {
|
|
52
|
+
const p = path.join(serviceRoot, f);
|
|
53
|
+
if (fs.existsSync(p)) {
|
|
54
|
+
// Remove file or directory
|
|
55
|
+
const stat = fs.statSync(p);
|
|
56
|
+
if (stat.isDirectory())
|
|
57
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
58
|
+
else fs.rmSync(p, { force: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
// Non-fatal
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Ensure service-level gitignore is renamed immediately after template copy
|
|
67
|
+
try {
|
|
68
|
+
const serviceGitignore = path.join(serviceRoot, "gitignore");
|
|
69
|
+
const serviceDotGitignore = path.join(serviceRoot, ".gitignore");
|
|
70
|
+
if (
|
|
71
|
+
fs.existsSync(serviceGitignore) &&
|
|
72
|
+
!fs.existsSync(serviceDotGitignore)
|
|
73
|
+
) {
|
|
74
|
+
fs.renameSync(serviceGitignore, serviceDotGitignore);
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
// Non-fatal; continue setup
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Special handling for gateway service
|
|
81
|
+
if (serviceName === "gateway") {
|
|
82
|
+
const tmplLang = res.language === "javascript" ? "js" : "ts";
|
|
83
|
+
const gatewayModule = await import(
|
|
84
|
+
`../../template/gateway/${tmplLang}/inject.js`
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
deps.push(...gatewayModule.gatewayDeps);
|
|
88
|
+
|
|
89
|
+
// Copy gateway-specific files
|
|
90
|
+
const gatewayAppPath = path.join(serviceRoot, `src/app.${ext}`);
|
|
91
|
+
const gatewayServerPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
92
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
93
|
+
|
|
94
|
+
// Read gateway template files according to selected language
|
|
95
|
+
const templateExt = res.language === "javascript" ? ".js" : ".ts";
|
|
96
|
+
const templateDir = path.join(
|
|
97
|
+
__dirname,
|
|
98
|
+
`../../template/gateway/${tmplLang}`,
|
|
99
|
+
);
|
|
100
|
+
const gatewayAppContent = fs.readFileSync(
|
|
101
|
+
path.join(templateDir, `app${templateExt}`),
|
|
102
|
+
"utf8",
|
|
103
|
+
);
|
|
104
|
+
const gatewayServerContent = fs.readFileSync(
|
|
105
|
+
path.join(templateDir, `server${templateExt}`),
|
|
106
|
+
"utf8",
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Generate routes for all services with mode (docker or nodocker)
|
|
110
|
+
const mode = res.mode || "docker";
|
|
111
|
+
const routes = gatewayModule.generateGatewayRoutes(allServices, mode);
|
|
112
|
+
const finalAppContent = gatewayAppContent.replace("/*__ROUTES__*/", routes);
|
|
113
|
+
|
|
114
|
+
fs.writeFileSync(gatewayAppPath, finalAppContent);
|
|
115
|
+
fs.writeFileSync(gatewayServerPath, gatewayServerContent);
|
|
116
|
+
|
|
117
|
+
// Remove unnecessary files for gateway
|
|
118
|
+
const routesPath = path.join(serviceRoot, `src/routes.${ext}`);
|
|
119
|
+
const modulesPath = path.join(serviceRoot, "src/modules");
|
|
120
|
+
const middlewaresPath = path.join(serviceRoot, "src/middlewares");
|
|
121
|
+
const configPath = path.join(serviceRoot, `src/config`);
|
|
122
|
+
const utilPath = path.join(serviceRoot, `src/utils`);
|
|
123
|
+
|
|
124
|
+
if (fs.existsSync(routesPath)) fs.rmSync(routesPath);
|
|
125
|
+
if (fs.existsSync(modulesPath)) fs.rmSync(modulesPath, { recursive: true });
|
|
126
|
+
if (fs.existsSync(configPath)) fs.rmSync(configPath, { recursive: true });
|
|
127
|
+
if (fs.existsSync(utilPath)) fs.rmSync(utilPath, { recursive: true });
|
|
128
|
+
if (fs.existsSync(middlewaresPath))
|
|
129
|
+
fs.rmSync(middlewaresPath, { recursive: true });
|
|
130
|
+
} else {
|
|
131
|
+
// Regular service setup (existing code)
|
|
132
|
+
// Add Swagger UI as runtime dependency (used by routes for /api-docs)
|
|
133
|
+
deps.push("swagger-ui-express");
|
|
134
|
+
if (res.language === "typescript") {
|
|
135
|
+
devDeps.push("@types/swagger-ui-express");
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Add Zod only when validation is enabled
|
|
139
|
+
if (res.validation) {
|
|
140
|
+
deps.push("zod");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add features (only for monolith or health-service)
|
|
144
|
+
if (res.projectType === "monolith" || serviceName === "health-service") {
|
|
145
|
+
for (const f of res.features) {
|
|
146
|
+
const feature = await import(`../../template/features/${f}/inject.js`);
|
|
147
|
+
const featureImports = feature.getImports
|
|
148
|
+
? feature.getImports(res.language)
|
|
149
|
+
: feature.imports;
|
|
150
|
+
imports.push(featureImports);
|
|
151
|
+
middlewares.push(feature.middleware);
|
|
152
|
+
deps.push(...feature.deps);
|
|
153
|
+
if (feature.devDeps && res.language === "typescript") {
|
|
154
|
+
devDeps.push(...feature.devDeps);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// If the feature provides files for the selected language, write them
|
|
158
|
+
const featureFiles = feature.getFiles
|
|
159
|
+
? feature.getFiles(res.language)
|
|
160
|
+
: feature.files;
|
|
161
|
+
if (featureFiles) {
|
|
162
|
+
for (const file in featureFiles) {
|
|
163
|
+
const filePath = file.replace(/\.ts$/, `.${ext}`);
|
|
164
|
+
const fullPath = path.join(serviceRoot, filePath);
|
|
165
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
166
|
+
fs.writeFileSync(fullPath, featureFiles[file]);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Add authentication (only for monolith or auth-service)
|
|
173
|
+
if (shouldIncludeAuth && res.auth) {
|
|
174
|
+
const baseAuth = await import(
|
|
175
|
+
"../../template/features/auth/base/inject.js"
|
|
176
|
+
);
|
|
177
|
+
deps.push(...baseAuth.deps);
|
|
178
|
+
if (baseAuth.devDeps && res.language === "typescript") {
|
|
179
|
+
devDeps.push(...baseAuth.devDeps);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const authFiles = baseAuth.getFiles
|
|
183
|
+
? baseAuth.getFiles(res.language)
|
|
184
|
+
: baseAuth.files;
|
|
185
|
+
for (const file in authFiles) {
|
|
186
|
+
const filePath = file.replace(/\.ts$/, `.${ext}`);
|
|
187
|
+
const fullPath = path.join(serviceRoot, filePath);
|
|
188
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
189
|
+
fs.writeFileSync(fullPath, authFiles[file]);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const algo = await prompts({
|
|
193
|
+
type: "select",
|
|
194
|
+
name: "hasher",
|
|
195
|
+
message: `Password hashing method${
|
|
196
|
+
serviceName ? ` for ${serviceName}` : ""
|
|
197
|
+
}`,
|
|
198
|
+
choices: [
|
|
199
|
+
{
|
|
200
|
+
title:
|
|
201
|
+
process.platform === "win32"
|
|
202
|
+
? "bcrypt (recommended for Windows)"
|
|
203
|
+
: "argon2 (recommended)",
|
|
204
|
+
value: process.platform === "win32" ? "bcrypt" : "argon2",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
title:
|
|
208
|
+
process.platform === "win32"
|
|
209
|
+
? "argon2 (requires build tools)"
|
|
210
|
+
: "bcrypt",
|
|
211
|
+
value: process.platform === "win32" ? "argon2" : "bcrypt",
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const hashFeature = await import(
|
|
217
|
+
`../../template/features/auth/${algo.hasher}/inject.js`
|
|
218
|
+
);
|
|
219
|
+
deps.push(...hashFeature.deps);
|
|
220
|
+
if (hashFeature.devDeps && res.language === "typescript") {
|
|
221
|
+
devDeps.push(...hashFeature.devDeps);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const hashFiles = hashFeature.getFiles
|
|
225
|
+
? hashFeature.getFiles(res.language)
|
|
226
|
+
: hashFeature.files;
|
|
227
|
+
for (const file in hashFiles) {
|
|
228
|
+
const filePath = file.replace(/\.ts$/, `.${ext}`);
|
|
229
|
+
const fullPath = path.join(serviceRoot, filePath);
|
|
230
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
231
|
+
const content = hashFiles[file];
|
|
232
|
+
fs.writeFileSync(fullPath, content);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
v1Imports.push(
|
|
236
|
+
baseAuth.getImports
|
|
237
|
+
? baseAuth.getImports(res.language)
|
|
238
|
+
: baseAuth.imports,
|
|
239
|
+
);
|
|
240
|
+
v1Routes.push(baseAuth.middleware);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Update health route to remove validation if not enabled
|
|
244
|
+
if (!res.validation) {
|
|
245
|
+
const healthRoutePath = path.join(
|
|
246
|
+
serviceRoot,
|
|
247
|
+
`src/modules/v1/health/health.route.${ext}`,
|
|
248
|
+
);
|
|
249
|
+
if (fs.existsSync(healthRoutePath)) {
|
|
250
|
+
let healthContent = fs.readFileSync(healthRoutePath, "utf8");
|
|
251
|
+
// Remove validation-related imports, schema definitions and usages more robustly
|
|
252
|
+
// Remove z import, validateRequest import, any const <name> = z... schema blocks,
|
|
253
|
+
// and usages like ", validateRequest({ query: <name> })".
|
|
254
|
+
if (ext === "ts") {
|
|
255
|
+
// Remove validateRequest from middlewares import, keep methodNotAllowedHandler
|
|
256
|
+
healthContent = healthContent.replace(
|
|
257
|
+
/import\s+\{([\s\S]*?)\}\s+from\s+["']@\/middlewares["'];?/m,
|
|
258
|
+
(m, p1) => {
|
|
259
|
+
const parts = p1
|
|
260
|
+
.split(",")
|
|
261
|
+
.map((s) => s.trim())
|
|
262
|
+
.filter(Boolean)
|
|
263
|
+
.filter((name) => name !== "validateRequest");
|
|
264
|
+
return `import { ${parts.join(", ")} } from "@/middlewares";\n`;
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
// Remove any import of z from zod
|
|
269
|
+
healthContent = healthContent.replace(
|
|
270
|
+
/import\s+\{\s*z\s*\}\s+from\s+["']zod["'];?\s*/g,
|
|
271
|
+
"",
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
// Remove any const <identifier> = z...; schema blocks (non-greedy)
|
|
275
|
+
// Be tolerant of CRLF (Windows) and LF line endings
|
|
276
|
+
healthContent = healthContent.replace(
|
|
277
|
+
/const\s+\w+\s*=\s*z[\s\S]*?;\s*(?:\r?\n){2,}/gm,
|
|
278
|
+
"",
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// Remove validateRequest usage patterns
|
|
282
|
+
healthContent = healthContent.replace(
|
|
283
|
+
/,\s*validateRequest\s*\(\{[\s\S]*?\}\)/g,
|
|
284
|
+
"",
|
|
285
|
+
);
|
|
286
|
+
} else {
|
|
287
|
+
// JS: remove require('zod') and adjust middleware require
|
|
288
|
+
healthContent = healthContent.replace(
|
|
289
|
+
/const\s+\{\s*z\s*\}\s*=\s*require\(['\"]zod['\"]\);?\s*/g,
|
|
290
|
+
"",
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
healthContent = healthContent.replace(
|
|
294
|
+
/const\s+\{([\s\S]*?)\}\s*=\s*require\(['\"][\s\S]*?middlewares['\"]\);?/m,
|
|
295
|
+
(m, p1) => {
|
|
296
|
+
const parts = p1
|
|
297
|
+
.split(",")
|
|
298
|
+
.map((s) => s.trim())
|
|
299
|
+
.filter(Boolean)
|
|
300
|
+
.filter((name) => name !== "validateRequest");
|
|
301
|
+
return `const { ${parts.join(
|
|
302
|
+
", ",
|
|
303
|
+
)} } = require('../../../middlewares');`;
|
|
304
|
+
},
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// Remove schema const blocks and validateRequest usages
|
|
308
|
+
healthContent = healthContent.replace(
|
|
309
|
+
/const\s+\w+\s*=\s*z[\s\S]*?;\s*\n\n/gm,
|
|
310
|
+
"",
|
|
311
|
+
);
|
|
312
|
+
healthContent = healthContent.replace(
|
|
313
|
+
/,\s*validateRequest\s*\(\{[\s\S]*?\}\)/g,
|
|
314
|
+
"",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
fs.writeFileSync(healthRoutePath, healthContent);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
|
322
|
+
|
|
323
|
+
// Replace health controller with auth version if auth is enabled
|
|
324
|
+
if (shouldIncludeAuth && res.auth) {
|
|
325
|
+
const baseDir = path.dirname(fileURLToPath(import.meta.url));
|
|
326
|
+
const healthControllerSourcePath = path.join(
|
|
327
|
+
baseDir,
|
|
328
|
+
`../../template/base/${ext}/src/modules/v1/health/health.controller.auth.${ext}`,
|
|
329
|
+
);
|
|
330
|
+
const healthControllerTargetPath = path.join(
|
|
331
|
+
serviceRoot,
|
|
332
|
+
`src/modules/v1/health/health.controller.${ext}`,
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (fs.existsSync(healthControllerSourcePath)) {
|
|
336
|
+
const healthControllerContent = fs.readFileSync(
|
|
337
|
+
healthControllerSourcePath,
|
|
338
|
+
"utf8",
|
|
339
|
+
);
|
|
340
|
+
fs.writeFileSync(healthControllerTargetPath, healthControllerContent);
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
const healthControllerSourcePath = path.join(
|
|
344
|
+
baseDir,
|
|
345
|
+
`../../template/base/${ext}/src/modules/v1/health/health.controller.${ext}`,
|
|
346
|
+
);
|
|
347
|
+
const healthControllerTargetPath = path.join(
|
|
348
|
+
serviceRoot,
|
|
349
|
+
`src/modules/v1/health/health.controller.${ext}`,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
if (fs.existsSync(healthControllerSourcePath)) {
|
|
353
|
+
const healthControllerContent = fs.readFileSync(
|
|
354
|
+
healthControllerSourcePath,
|
|
355
|
+
"utf8",
|
|
356
|
+
);
|
|
357
|
+
fs.writeFileSync(healthControllerTargetPath, healthControllerContent);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Remove the auth-aware health controller when auth is disabled so TypeScript
|
|
362
|
+
// does not try to compile a mongoose-dependent file in non-auth projects.
|
|
363
|
+
if (!res.auth) {
|
|
364
|
+
const authHealthControllerPath = path.join(
|
|
365
|
+
serviceRoot,
|
|
366
|
+
`src/modules/v1/health/health.controller.auth.${ext}`,
|
|
367
|
+
);
|
|
368
|
+
if (fs.existsSync(authHealthControllerPath)) {
|
|
369
|
+
fs.rmSync(authHealthControllerPath, { force: true });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Update auth routes to remove validation if not enabled
|
|
374
|
+
if (!res.validation && shouldIncludeAuth && res.auth) {
|
|
375
|
+
const authRoutePath = path.join(
|
|
376
|
+
serviceRoot,
|
|
377
|
+
`src/modules/v1/auth/auth.routes.${ext}`,
|
|
378
|
+
);
|
|
379
|
+
if (fs.existsSync(authRoutePath)) {
|
|
380
|
+
let authContent = fs.readFileSync(authRoutePath, "utf8");
|
|
381
|
+
// Remove validation-related imports, zod schema blocks and usages robustly
|
|
382
|
+
if (ext === "ts") {
|
|
383
|
+
authContent = authContent.replace(
|
|
384
|
+
/import\s+\{([\s\S]*?)\}\s+from\s+["']@\/middlewares["'];?/m,
|
|
385
|
+
(m, p1) => {
|
|
386
|
+
const parts = p1
|
|
387
|
+
.split(",")
|
|
388
|
+
.map((s) => s.trim())
|
|
389
|
+
.filter(Boolean)
|
|
390
|
+
.filter((name) => name !== "validateRequest");
|
|
391
|
+
return parts.length
|
|
392
|
+
? `import { ${parts.join(", ")} } from "@/middlewares";\n`
|
|
393
|
+
: "";
|
|
394
|
+
},
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
authContent = authContent.replace(
|
|
398
|
+
/import\s+\{\s*z\s*\}\s+from\s+["']zod["'];?\s*/g,
|
|
399
|
+
"",
|
|
400
|
+
);
|
|
401
|
+
authContent = authContent.replace(
|
|
402
|
+
/const\s+\w+Schema\s*=\s*z[\s\S]*?;\s*(?:\r?\n){2,}/gm,
|
|
403
|
+
"",
|
|
404
|
+
);
|
|
405
|
+
authContent = authContent.replace(
|
|
406
|
+
/,\s*validateRequest\s*\(\{[\s\S]*?\}\)/g,
|
|
407
|
+
"",
|
|
408
|
+
);
|
|
409
|
+
} else {
|
|
410
|
+
authContent = authContent.replace(
|
|
411
|
+
/const\s+\{\s*z\s*\}\s*=\s*require\(['\"]zod['\"]\);?\s*/g,
|
|
412
|
+
"",
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
authContent = authContent.replace(
|
|
416
|
+
/const\s+\{([\s\S]*?)\}\s*=\s*require\(['\"](?:\.\.\/)*middlewares['\"]\);?/m,
|
|
417
|
+
(m, p1) => {
|
|
418
|
+
const parts = p1
|
|
419
|
+
.split(",")
|
|
420
|
+
.map((s) => s.trim())
|
|
421
|
+
.filter(Boolean)
|
|
422
|
+
.filter((name) => name !== "validateRequest");
|
|
423
|
+
return parts.length
|
|
424
|
+
? `const { ${parts.join(
|
|
425
|
+
", ",
|
|
426
|
+
)} } = require('../../../middlewares');`
|
|
427
|
+
: "const { methodNotAllowedHandler } = require('../../../middlewares');";
|
|
428
|
+
},
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
authContent = authContent.replace(
|
|
432
|
+
/const\s+\w+Schema\s*=\s*z[\s\S]*?;\s*(?:\r?\n){2,}/gm,
|
|
433
|
+
"",
|
|
434
|
+
);
|
|
435
|
+
authContent = authContent.replace(
|
|
436
|
+
/,\s*validateRequest\s*\(\{[\s\S]*?\}\)/g,
|
|
437
|
+
"",
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
fs.writeFileSync(authRoutePath, authContent);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// OpenAPI is generated at runtime from routeRegistry; static spec patching removed
|
|
445
|
+
|
|
446
|
+
// Update app file
|
|
447
|
+
const appPath = path.join(serviceRoot, `src/app.${ext}`);
|
|
448
|
+
let content = fs.readFileSync(appPath, "utf8");
|
|
449
|
+
content = content.replace("/*__IMPORTS__*/", imports.join("\n"));
|
|
450
|
+
content = content.replace("/*__MIDDLEWARE__*/", middlewares.join("\n"));
|
|
451
|
+
fs.writeFileSync(appPath, content);
|
|
452
|
+
|
|
453
|
+
// Update root endpoint middleware with project info
|
|
454
|
+
const rootMiddlewarePath = path.join(
|
|
455
|
+
serviceRoot,
|
|
456
|
+
`src/middlewares/root.middleware.${ext}`,
|
|
457
|
+
);
|
|
458
|
+
if (fs.existsSync(rootMiddlewarePath)) {
|
|
459
|
+
let rootContent = fs.readFileSync(rootMiddlewarePath, "utf8");
|
|
460
|
+
rootContent = rootContent.replace(
|
|
461
|
+
"/*__PROJECT_NAME__*/",
|
|
462
|
+
serviceName || res.sanitizedName,
|
|
463
|
+
);
|
|
464
|
+
rootContent = rootContent.replace(
|
|
465
|
+
"/*__PROJECT_TYPE__*/",
|
|
466
|
+
res.projectType,
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
// Add auth endpoint if auth is enabled
|
|
470
|
+
if (shouldIncludeAuth && res.auth) {
|
|
471
|
+
rootContent = rootContent.replace(
|
|
472
|
+
"/*__AUTH_ENDPOINT__*/",
|
|
473
|
+
'auth: "/api/v1/auth",',
|
|
474
|
+
);
|
|
475
|
+
} else {
|
|
476
|
+
rootContent = rootContent.replace("/*__AUTH_ENDPOINT__*/", "");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
fs.writeFileSync(rootMiddlewarePath, rootContent);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Update v1 index file if needed
|
|
483
|
+
if (v1Imports.length || v1Routes.length) {
|
|
484
|
+
const v1IndexPath = path.join(serviceRoot, `src/modules/v1/index.${ext}`);
|
|
485
|
+
let v1Content = fs.readFileSync(v1IndexPath, "utf8");
|
|
486
|
+
|
|
487
|
+
const lastImportIndex = v1Content.lastIndexOf("import");
|
|
488
|
+
const importEndIndex = v1Content.indexOf("\n", lastImportIndex) + 1;
|
|
489
|
+
v1Content =
|
|
490
|
+
v1Content.slice(0, importEndIndex) +
|
|
491
|
+
v1Imports.join("\n") +
|
|
492
|
+
"\n" +
|
|
493
|
+
v1Content.slice(importEndIndex);
|
|
494
|
+
|
|
495
|
+
const exportIndex = v1Content.lastIndexOf("export default");
|
|
496
|
+
v1Content =
|
|
497
|
+
v1Content.slice(0, exportIndex) +
|
|
498
|
+
v1Routes.join("\n") +
|
|
499
|
+
"\n\n" +
|
|
500
|
+
v1Content.slice(exportIndex);
|
|
501
|
+
|
|
502
|
+
fs.writeFileSync(v1IndexPath, v1Content);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Update env file to conditionally include ALLOWED_ORIGIN and MONGO_URI
|
|
506
|
+
const envPath = path.join(serviceRoot, `src/config/env.${ext}`);
|
|
507
|
+
if (fs.existsSync(envPath)) {
|
|
508
|
+
let envContent = fs.readFileSync(envPath, "utf8");
|
|
509
|
+
|
|
510
|
+
// Import ENV in app.ts if CORS is selected
|
|
511
|
+
if (res.features && res.features.includes("cors")) {
|
|
512
|
+
let appContent = fs.readFileSync(appPath, "utf8");
|
|
513
|
+
if (!appContent.includes("import { ENV } from")) {
|
|
514
|
+
appContent = appContent.replace(
|
|
515
|
+
"/*__IMPORTS__*/",
|
|
516
|
+
"import { ENV } from '@/config';\n/*__IMPORTS__*/",
|
|
517
|
+
);
|
|
518
|
+
fs.writeFileSync(appPath, appContent);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Add ALLOWED_ORIGIN if CORS is selected
|
|
523
|
+
if (res.features && res.features.includes("cors")) {
|
|
524
|
+
const assertion = res.language === "javascript" ? "" : "!";
|
|
525
|
+
envContent = envContent.replace(
|
|
526
|
+
"/*__ALLOWED_ORIGIN__*/",
|
|
527
|
+
`ALLOWED_ORIGIN: process.env.ALLOWED_ORIGIN${assertion},`,
|
|
528
|
+
);
|
|
529
|
+
} else {
|
|
530
|
+
envContent = envContent.replace("/*__ALLOWED_ORIGIN__*/", "");
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Add MONGO_URI and JWT_SECRET if auth is enabled
|
|
534
|
+
if (shouldIncludeAuth && res.auth) {
|
|
535
|
+
const assertion = res.language === "javascript" ? "" : "!";
|
|
536
|
+
envContent = envContent.replace(
|
|
537
|
+
"/*__MONGO_URI__*/",
|
|
538
|
+
`MONGO_URI: process.env.MONGO_URI${assertion},`,
|
|
539
|
+
);
|
|
540
|
+
envContent = envContent.replace(
|
|
541
|
+
"/*__JWT_SECRET__*/",
|
|
542
|
+
`JWT_SECRET: process.env.JWT_SECRET${assertion},`,
|
|
543
|
+
);
|
|
544
|
+
} else {
|
|
545
|
+
envContent = envContent.replace("/*__MONGO_URI__*/", "");
|
|
546
|
+
envContent = envContent.replace("/*__JWT_SECRET__*/", "");
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
fs.writeFileSync(envPath, envContent);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Update server file to connect to DB if auth is enabled
|
|
553
|
+
const serverPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
554
|
+
if (fs.existsSync(serverPath)) {
|
|
555
|
+
let serverContent = fs.readFileSync(serverPath, "utf8");
|
|
556
|
+
|
|
557
|
+
if (shouldIncludeAuth && res.auth) {
|
|
558
|
+
const language = res.language;
|
|
559
|
+
if (language === "javascript") {
|
|
560
|
+
serverContent = serverContent.replace(
|
|
561
|
+
"/*__DB_IMPORT__*/",
|
|
562
|
+
", connectDB",
|
|
563
|
+
);
|
|
564
|
+
} else {
|
|
565
|
+
serverContent = serverContent.replace(
|
|
566
|
+
"/*__DB_IMPORT__*/",
|
|
567
|
+
'import { connectDB } from "./config";',
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
serverContent = serverContent.replace(
|
|
571
|
+
"/*__DB_CONNECT__*/",
|
|
572
|
+
`// Connect to MongoDB\nawait connectDB();`,
|
|
573
|
+
);
|
|
574
|
+
} else {
|
|
575
|
+
serverContent = serverContent.replace("/*__DB_IMPORT__*/", "");
|
|
576
|
+
serverContent = serverContent.replace("/*__DB_CONNECT__*/", "");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
fs.writeFileSync(serverPath, serverContent);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Update .env.example to conditionally include environment variables (only for monolith)
|
|
583
|
+
if (res.projectType !== "microservice") {
|
|
584
|
+
const envExamplePath = path.join(serviceRoot, ".env.example");
|
|
585
|
+
if (fs.existsSync(envExamplePath)) {
|
|
586
|
+
let envExampleContent = fs.readFileSync(envExamplePath, "utf8");
|
|
587
|
+
|
|
588
|
+
// Add ALLOWED_ORIGIN if CORS is selected
|
|
589
|
+
if (res.features && res.features.includes("cors")) {
|
|
590
|
+
envExampleContent = envExampleContent.replace(
|
|
591
|
+
"/*__ALLOWED_ORIGIN_ENV__*/",
|
|
592
|
+
"ALLOWED_ORIGIN=http://localhost:3000",
|
|
593
|
+
);
|
|
594
|
+
} else {
|
|
595
|
+
envExampleContent = envExampleContent.replace(
|
|
596
|
+
"/*__ALLOWED_ORIGIN_ENV__*/",
|
|
597
|
+
"",
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Add MONGO_URI and JWT_SECRET if auth is enabled
|
|
602
|
+
if (shouldIncludeAuth && res.auth) {
|
|
603
|
+
envExampleContent = envExampleContent.replace(
|
|
604
|
+
"/*__MONGO_URI_ENV__*/",
|
|
605
|
+
"MONGO_URI=mongodb://localhost:27017/your-database-name",
|
|
606
|
+
);
|
|
607
|
+
envExampleContent = envExampleContent.replace(
|
|
608
|
+
"/*__JWT_SECRET_ENV__*/",
|
|
609
|
+
"JWT_SECRET=your-super-secret-jwt-key-change-this-in-production",
|
|
610
|
+
);
|
|
611
|
+
} else {
|
|
612
|
+
envExampleContent = envExampleContent.replace(
|
|
613
|
+
"/*__MONGO_URI_ENV__*/",
|
|
614
|
+
"",
|
|
615
|
+
);
|
|
616
|
+
envExampleContent = envExampleContent.replace(
|
|
617
|
+
"/*__JWT_SECRET_ENV__*/",
|
|
618
|
+
"",
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
fs.writeFileSync(envExamplePath, envExampleContent);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
} // End of else block for non-gateway services
|
|
626
|
+
|
|
627
|
+
// Update tsconfig.json for microservices to support @/ alias with shared folder
|
|
628
|
+
// Also run when adding a service into an existing microservice project
|
|
629
|
+
if (res.projectType === "microservice" || res.isInMicroserviceProject) {
|
|
630
|
+
const tsconfigPath = path.join(serviceRoot, "tsconfig.json");
|
|
631
|
+
let tsconfigContent = fs.readFileSync(tsconfigPath, "utf8");
|
|
632
|
+
|
|
633
|
+
// Remove comments from JSON (strip-json-comments approach)
|
|
634
|
+
tsconfigContent = tsconfigContent
|
|
635
|
+
.replace(/\/\/.*$/gm, "") // Remove single-line comments
|
|
636
|
+
.replace(/\/\*[\s\S]*?\*\//g, ""); // Remove multi-line comments
|
|
637
|
+
|
|
638
|
+
const tsconfig = JSON.parse(tsconfigContent);
|
|
639
|
+
|
|
640
|
+
// Update baseUrl to allow import from the shared folder
|
|
641
|
+
tsconfig.compilerOptions.baseUrl = ".";
|
|
642
|
+
|
|
643
|
+
// Update paths to include shared folder (works in both Docker and VS Code)
|
|
644
|
+
tsconfig.compilerOptions.paths = {
|
|
645
|
+
"@/*": ["src/*"],
|
|
646
|
+
"@/shared/*": ["shared/*", "../../shared/*"],
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
// Remove rootDir restriction to allow imports from outside src/
|
|
650
|
+
delete tsconfig.compilerOptions.rootDir;
|
|
651
|
+
|
|
652
|
+
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
653
|
+
|
|
654
|
+
// Update imports in service files to use @/shared/* instead of @/* for utils/config
|
|
655
|
+
// This applies to non-gateway services
|
|
656
|
+
if (serviceName !== "gateway") {
|
|
657
|
+
const healthControllerPath = path.join(
|
|
658
|
+
serviceRoot,
|
|
659
|
+
`src/modules/v1/health/health.controller.${ext}`,
|
|
660
|
+
);
|
|
661
|
+
if (fs.existsSync(healthControllerPath)) {
|
|
662
|
+
let healthControllerContent = fs.readFileSync(
|
|
663
|
+
healthControllerPath,
|
|
664
|
+
"utf8",
|
|
665
|
+
);
|
|
666
|
+
healthControllerContent = healthControllerContent.replace(
|
|
667
|
+
'from "@/utils"',
|
|
668
|
+
'from "@/shared/utils"',
|
|
669
|
+
);
|
|
670
|
+
fs.writeFileSync(healthControllerPath, healthControllerContent);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Update server.ts to use shared imports
|
|
674
|
+
const serverPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
675
|
+
if (fs.existsSync(serverPath)) {
|
|
676
|
+
// Determine a single port string for this specific service.
|
|
677
|
+
// Gateway should use 4000; other services use 4001, 4002, ...
|
|
678
|
+
let serverPort = "3000";
|
|
679
|
+
if (Array.isArray(allServices) && allServices.length) {
|
|
680
|
+
if (serviceName === "gateway") {
|
|
681
|
+
serverPort = "4000";
|
|
682
|
+
} else {
|
|
683
|
+
const idx = allServices.indexOf(serviceName);
|
|
684
|
+
if (idx !== -1) {
|
|
685
|
+
// Count non-gateway services before this one to compute offset
|
|
686
|
+
const nonGatewayBefore = allServices
|
|
687
|
+
.slice(0, idx)
|
|
688
|
+
.filter((s) => s !== "gateway").length;
|
|
689
|
+
serverPort = `${4001 + nonGatewayBefore}`;
|
|
690
|
+
} else {
|
|
691
|
+
// Fallback: assign next available port after 4000
|
|
692
|
+
serverPort = `${4001 + allServices.length - 1}`;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
let serverContent = fs.readFileSync(serverPath, "utf8");
|
|
698
|
+
|
|
699
|
+
// Normalize imports: accept @/ or relative imports and rewrite to shared imports
|
|
700
|
+
serverContent = serverContent
|
|
701
|
+
.replace(
|
|
702
|
+
/from\s+["'](?:@\/utils|\.\/utils|\.\.\/utils)["']/g,
|
|
703
|
+
'from "@/shared/utils"',
|
|
704
|
+
)
|
|
705
|
+
.replace(
|
|
706
|
+
/from\s+["'](?:@\/config|\.\/config|\.\.\/config)["']/g,
|
|
707
|
+
'from "@/shared/config"',
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
// Update PORT to use service-specific environment variable and a correct default port.
|
|
711
|
+
const portEnvVar = `${serviceName
|
|
712
|
+
.toUpperCase()
|
|
713
|
+
.replace(/-/g, "_")}_PORT`;
|
|
714
|
+
const portRegex = /const\s+PORT\s*=\s*ENV\.PORT\s*\|\|\s*(\d+)\s*;/;
|
|
715
|
+
if (portRegex.test(serverContent)) {
|
|
716
|
+
serverContent = serverContent.replace(
|
|
717
|
+
portRegex,
|
|
718
|
+
`const PORT = ENV.${portEnvVar} || ${serverPort};`,
|
|
719
|
+
);
|
|
720
|
+
} else {
|
|
721
|
+
// Fallback: replace a simple numeric default or a bare PORT assignment
|
|
722
|
+
const simplePortRegex = /const\s+PORT\s*=\s*(\d+)\s*;/;
|
|
723
|
+
if (simplePortRegex.test(serverContent)) {
|
|
724
|
+
serverContent = serverContent.replace(
|
|
725
|
+
simplePortRegex,
|
|
726
|
+
`const PORT = ENV.${portEnvVar} || ${serverPort};`,
|
|
727
|
+
);
|
|
728
|
+
} else {
|
|
729
|
+
// Last resort: append a PORT assignment near the top after imports
|
|
730
|
+
const importEnd = serverContent.indexOf("\n\n");
|
|
731
|
+
const insertPos = importEnd === -1 ? 0 : importEnd + 2;
|
|
732
|
+
const portLine = `const PORT = ENV.${portEnvVar} || ${serverPort};\n\n`;
|
|
733
|
+
serverContent =
|
|
734
|
+
serverContent.slice(0, insertPos) +
|
|
735
|
+
portLine +
|
|
736
|
+
serverContent.slice(insertPos);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
fs.writeFileSync(serverPath, serverContent);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// Update package.json
|
|
746
|
+
const packageJsonPath = path.join(serviceRoot, "package.json");
|
|
747
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
748
|
+
|
|
749
|
+
// Create new package.json with name at the top
|
|
750
|
+
const orderedPackageJson = {
|
|
751
|
+
name: serviceName || res.sanitizedName,
|
|
752
|
+
version: packageJson.version,
|
|
753
|
+
description: res.description || packageJson.description,
|
|
754
|
+
...packageJson,
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
// Remove duplicate keys that were moved to the top
|
|
758
|
+
delete orderedPackageJson.name;
|
|
759
|
+
delete orderedPackageJson.version;
|
|
760
|
+
delete orderedPackageJson.description;
|
|
761
|
+
|
|
762
|
+
// Re-add them at the top in correct order
|
|
763
|
+
const finalPackageJson = {
|
|
764
|
+
name: serviceName || res.sanitizedName,
|
|
765
|
+
version: packageJson.version,
|
|
766
|
+
description: res.description || packageJson.description,
|
|
767
|
+
...orderedPackageJson,
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
// Add author if provided
|
|
771
|
+
if (res.author) {
|
|
772
|
+
finalPackageJson.author = res.author;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Add keywords if provided
|
|
776
|
+
if (res.keywords && res.keywords.trim()) {
|
|
777
|
+
finalPackageJson.keywords = res.keywords
|
|
778
|
+
.split(",")
|
|
779
|
+
.map((k) => k.trim())
|
|
780
|
+
.filter(Boolean);
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Add --poll flag to dev script for Docker mode (fixes watch mode in Docker on Windows)
|
|
784
|
+
if (res.projectType === "microservice" && res.mode === "docker") {
|
|
785
|
+
if (finalPackageJson.scripts && finalPackageJson.scripts.dev) {
|
|
786
|
+
finalPackageJson.scripts.dev = finalPackageJson.scripts.dev.replace(
|
|
787
|
+
"ts-node-dev --respawn --transpile-only",
|
|
788
|
+
"ts-node-dev --respawn --transpile-only --poll",
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// If creating microservices, do not install workspace-level devDependencies per service
|
|
794
|
+
if (res.projectType === "microservice") {
|
|
795
|
+
if (finalPackageJson.devDependencies) {
|
|
796
|
+
const toRemove = [
|
|
797
|
+
"prettier",
|
|
798
|
+
"eslint",
|
|
799
|
+
"eslint-config-prettier",
|
|
800
|
+
"@typescript-eslint/eslint-plugin",
|
|
801
|
+
"@typescript-eslint/parser",
|
|
802
|
+
"husky",
|
|
803
|
+
];
|
|
804
|
+
for (const dep of toRemove) {
|
|
805
|
+
if (finalPackageJson.devDependencies[dep]) {
|
|
806
|
+
delete finalPackageJson.devDependencies[dep];
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// Remove @types/* from JavaScript services; keep for TypeScript
|
|
811
|
+
if (res.language === "javascript") {
|
|
812
|
+
for (const key of Object.keys(finalPackageJson.devDependencies)) {
|
|
813
|
+
if (key.startsWith("@types/"))
|
|
814
|
+
delete finalPackageJson.devDependencies[key];
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// If devDependencies becomes empty, remove the field
|
|
819
|
+
if (Object.keys(finalPackageJson.devDependencies).length === 0) {
|
|
820
|
+
delete finalPackageJson.devDependencies;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Remove per-service prepare script (which runs husky) for microservice workspaces
|
|
826
|
+
if (res.projectType === "microservice" || res.isInMicroserviceProject) {
|
|
827
|
+
if (finalPackageJson.scripts && finalPackageJson.scripts.prepare) {
|
|
828
|
+
delete finalPackageJson.scripts.prepare;
|
|
829
|
+
}
|
|
830
|
+
// Also remove per-service lint/format/check-format scripts (workspace-level tooling lives at root)
|
|
831
|
+
if (finalPackageJson.scripts) {
|
|
832
|
+
delete finalPackageJson.scripts.lint;
|
|
833
|
+
delete finalPackageJson.scripts.format;
|
|
834
|
+
delete finalPackageJson.scripts["check-format"];
|
|
835
|
+
// If scripts becomes empty, remove the field
|
|
836
|
+
if (Object.keys(finalPackageJson.scripts).length === 0) {
|
|
837
|
+
delete finalPackageJson.scripts;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
fs.writeFileSync(
|
|
843
|
+
packageJsonPath,
|
|
844
|
+
JSON.stringify(finalPackageJson, null, 2) + "\n",
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
// Skip installation if skipInstall is true (will be done later in batch)
|
|
848
|
+
if (skipInstall) {
|
|
849
|
+
return { deps, devDeps, installSucceeded: true };
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Install dependencies
|
|
853
|
+
console.log(
|
|
854
|
+
pc.cyan(
|
|
855
|
+
`\n📦 Installing dependencies for ${serviceName || "project"}...\n`,
|
|
856
|
+
),
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
let installSucceeded = false;
|
|
860
|
+
|
|
861
|
+
try {
|
|
862
|
+
if (deps.length) {
|
|
863
|
+
execSync(`npm install ${deps.join(" ")}`, {
|
|
864
|
+
cwd: serviceRoot,
|
|
865
|
+
stdio: "inherit",
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
if (devDeps.length) {
|
|
869
|
+
execSync(`npm install -D ${devDeps.join(" ")}`, {
|
|
870
|
+
cwd: serviceRoot,
|
|
871
|
+
stdio: "inherit",
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
execSync("npm install", { cwd: serviceRoot, stdio: "inherit" });
|
|
875
|
+
installSucceeded = true;
|
|
876
|
+
|
|
877
|
+
// Run format after successful install
|
|
878
|
+
console.log(pc.cyan("\n🎨 Formatting code...\n"));
|
|
879
|
+
try {
|
|
880
|
+
execSync("npm run format", { cwd: serviceRoot, stdio: "inherit" });
|
|
881
|
+
} catch (formatError) {
|
|
882
|
+
console.warn(
|
|
883
|
+
pc.yellow(
|
|
884
|
+
"⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n",
|
|
885
|
+
),
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
} catch (error) {
|
|
889
|
+
console.error(pc.red("\n❌ Failed to install dependencies"));
|
|
890
|
+
console.error(pc.dim(`\nYou can install them later by running:`));
|
|
891
|
+
console.error(
|
|
892
|
+
pc.cyan(` cd ${serviceName || res.sanitizedName} && npm install`),
|
|
893
|
+
);
|
|
894
|
+
console.error(pc.dim(" Then run: npm run format\n"));
|
|
895
|
+
|
|
896
|
+
// Don't exit - let the project be created anyway
|
|
897
|
+
console.log(pc.cyan("⏭️ Continuing with project creation...\n"));
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return { deps, devDeps, installSucceeded };
|
|
901
|
+
};
|