@ifecodes/backend-template 1.1.1 → 1.1.5
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 +39 -18
- package/bin/cli.js +205 -149
- package/bin/lib/microservice-config.js +57 -13
- package/bin/lib/prompts.js +42 -11
- package/bin/lib/readme-generator.js +75 -14
- package/bin/lib/service-setup.js +277 -123
- package/package.json +1 -1
- package/template/base/js/.eslintrc.json +13 -0
- package/template/base/js/.prettierrc +7 -0
- package/template/base/js/eslint.config.js +31 -0
- package/template/base/js/package.json +28 -0
- package/template/base/js/src/app.js +15 -0
- package/template/base/js/src/config/db.js +8 -0
- package/template/base/js/src/config/env.js +14 -0
- package/template/base/js/src/config/index.js +7 -0
- package/template/base/js/src/middlewares/index.js +9 -0
- package/template/base/js/src/middlewares/method-not-allowed.middleware.js +13 -0
- package/template/base/js/src/middlewares/not-found.middleware.js +10 -0
- package/template/base/js/src/middlewares/root.middleware.js +16 -0
- package/template/base/js/src/modules/index.js +8 -0
- package/template/base/js/src/modules/v1/health/health.controller.js +21 -0
- package/template/base/js/src/modules/v1/health/health.route.js +9 -0
- package/template/base/js/src/modules/v1/health/index.js +5 -0
- package/template/base/js/src/modules/v1/index.js +8 -0
- package/template/base/js/src/routes.js +16 -0
- package/template/base/js/src/server.js +18 -0
- package/template/base/js/src/utils/http-error.js +53 -0
- package/template/base/js/src/utils/index.js +22 -0
- package/template/base/js/src/utils/logger.js +67 -0
- package/template/base/ts/.env.example +5 -0
- package/template/base/ts/.husky/pre-commit +13 -0
- package/template/base/ts/.prettierignore +47 -0
- package/template/base/ts/gitignore +31 -0
- package/template/base/ts/src/app.ts +15 -0
- package/template/base/{src → ts/src}/config/env.ts +10 -10
- package/template/base/{src → ts/src}/middlewares/index.ts +3 -3
- package/template/base/{src → ts/src}/middlewares/method-not-allowed.middleware.ts +16 -17
- package/template/base/{src → ts/src}/middlewares/root.middleware.ts +2 -2
- package/template/base/{src → ts/src}/modules/v1/health/health.controller.ts +18 -18
- package/template/base/{src → ts/src}/modules/v1/health/health.route.ts +9 -9
- package/template/base/{src → ts/src}/modules/v1/health/index.ts +1 -1
- package/template/base/{src → ts/src}/modules/v1/index.ts +8 -8
- package/template/base/{src → ts/src}/routes.ts +15 -15
- package/template/base/{src → ts/src}/utils/http-error.ts +45 -47
- package/template/base/ts/src/utils/logger.ts +68 -0
- package/template/features/auth/argon2/inject.js +29 -4
- package/template/features/auth/base/inject.js +108 -26
- package/template/features/auth/bcrypt/inject.js +25 -5
- package/template/features/auth/models/user.model.js +24 -0
- package/template/features/auth/modules/auth.controller.js +21 -0
- package/template/features/auth/modules/auth.routes.js +10 -0
- package/template/features/auth/modules/auth.service.js +29 -0
- package/template/features/auth/modules/index.js +1 -0
- package/template/features/auth/utils/jwt.js +12 -0
- package/template/features/cors/inject.js +4 -1
- package/template/features/helmet/inject.js +4 -1
- package/template/features/morgan/inject.js +4 -1
- package/template/features/rate-limit/inject.js +4 -1
- package/template/gateway/js/app.js +42 -0
- package/template/gateway/js/inject.js +31 -0
- package/template/gateway/js/server.js +19 -0
- package/template/gateway/{app.ts → ts/app.ts} +19 -3
- package/template/gateway/ts/inject.js +31 -0
- package/template/gateway/{server.ts → ts/server.ts} +3 -3
- package/template/microservice/docker/.dockerignore +10 -0
- package/template/microservice/docker/Dockerfile +2 -1
- package/template/microservice/docker/docker-compose.yml +0 -1
- package/bin/lib/ts-to-js.js +0 -123
- package/template/base/src/app.ts +0 -8
- package/template/base/src/utils/logger.ts +0 -35
- package/template/gateway/inject.js +0 -27
- /package/template/base/{.env.example → js/.env.example} +0 -0
- /package/template/base/{.husky → js/.husky}/pre-commit +0 -0
- /package/template/base/{.prettierignore → js/.prettierignore} +0 -0
- /package/template/base/{gitignore → js/gitignore} +0 -0
- /package/template/base/{.eslintrc.json → ts/.eslintrc.json} +0 -0
- /package/template/base/{.prettierrc → ts/.prettierrc} +0 -0
- /package/template/base/{eslint.config.js → ts/eslint.config.js} +0 -0
- /package/template/base/{package.json → ts/package.json} +0 -0
- /package/template/base/{src → ts/src}/config/db.ts +0 -0
- /package/template/base/{src → ts/src}/config/index.ts +0 -0
- /package/template/base/{src → ts/src}/middlewares/not-found.middleware.ts +0 -0
- /package/template/base/{src → ts/src}/modules/index.ts +0 -0
- /package/template/base/{src → ts/src}/server.ts +0 -0
- /package/template/base/{src → ts/src}/utils/index.ts +0 -0
- /package/template/base/{tsconfig.json → ts/tsconfig.json} +0 -0
package/bin/lib/service-setup.js
CHANGED
|
@@ -8,14 +8,14 @@ import { fileURLToPath } from "url";
|
|
|
8
8
|
// Helper function to get the correct file extension (.ts or .js)
|
|
9
9
|
function getFileExtension(dir) {
|
|
10
10
|
// Check if .ts files exist, otherwise use .js
|
|
11
|
-
const sampleFiles = [
|
|
11
|
+
const sampleFiles = ["src/app.ts", "src/server.ts", "src/routes.ts"];
|
|
12
12
|
for (const file of sampleFiles) {
|
|
13
13
|
const tsPath = path.join(dir, file);
|
|
14
|
-
if (fs.existsSync(tsPath)) return
|
|
15
|
-
const jsPath = path.join(dir, file.replace(
|
|
16
|
-
if (fs.existsSync(jsPath)) return
|
|
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
17
|
}
|
|
18
|
-
return
|
|
18
|
+
return "ts"; // default to ts
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export const setupService = async (
|
|
@@ -23,7 +23,8 @@ export const setupService = async (
|
|
|
23
23
|
serviceName,
|
|
24
24
|
serviceRoot,
|
|
25
25
|
shouldIncludeAuth,
|
|
26
|
-
allServices = []
|
|
26
|
+
allServices = [],
|
|
27
|
+
skipInstall = false,
|
|
27
28
|
) => {
|
|
28
29
|
let imports = [];
|
|
29
30
|
let middlewares = [];
|
|
@@ -31,13 +32,26 @@ export const setupService = async (
|
|
|
31
32
|
let devDeps = [];
|
|
32
33
|
let v1Imports = [];
|
|
33
34
|
let v1Routes = [];
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
// Detect file extension (ts or js)
|
|
36
37
|
const ext = getFileExtension(serviceRoot);
|
|
37
38
|
|
|
39
|
+
// Ensure service-level gitignore is renamed immediately after template copy
|
|
40
|
+
try {
|
|
41
|
+
const serviceGitignore = path.join(serviceRoot, "gitignore");
|
|
42
|
+
const serviceDotGitignore = path.join(serviceRoot, ".gitignore");
|
|
43
|
+
if (fs.existsSync(serviceGitignore) && !fs.existsSync(serviceDotGitignore)) {
|
|
44
|
+
fs.renameSync(serviceGitignore, serviceDotGitignore);
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// Non-fatal; continue setup
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
// Special handling for gateway service
|
|
39
51
|
if (serviceName === "gateway") {
|
|
40
|
-
const gatewayModule = await import(
|
|
52
|
+
const gatewayModule = await import(
|
|
53
|
+
`../../template/gateway/${res.language}/inject.js`
|
|
54
|
+
);
|
|
41
55
|
deps.push(...gatewayModule.gatewayDeps);
|
|
42
56
|
|
|
43
57
|
// Copy gateway-specific files
|
|
@@ -45,17 +59,24 @@ export const setupService = async (
|
|
|
45
59
|
const gatewayServerPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
46
60
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
47
61
|
|
|
62
|
+
// Read gateway template files according to selected language
|
|
63
|
+
const templateExt = res.language === "javascript" ? ".js" : ".ts";
|
|
64
|
+
const templateDir = path.join(
|
|
65
|
+
__dirname,
|
|
66
|
+
`../../template/gateway/${res.language}`,
|
|
67
|
+
);
|
|
48
68
|
const gatewayAppContent = fs.readFileSync(
|
|
49
|
-
path.join(
|
|
50
|
-
"utf8"
|
|
69
|
+
path.join(templateDir, `app${templateExt}`),
|
|
70
|
+
"utf8",
|
|
51
71
|
);
|
|
52
72
|
const gatewayServerContent = fs.readFileSync(
|
|
53
|
-
path.join(
|
|
54
|
-
"utf8"
|
|
73
|
+
path.join(templateDir, `server${templateExt}`),
|
|
74
|
+
"utf8",
|
|
55
75
|
);
|
|
56
76
|
|
|
57
|
-
// Generate routes for all services
|
|
58
|
-
const
|
|
77
|
+
// Generate routes for all services with mode (docker or nodocker)
|
|
78
|
+
const mode = res.mode || "docker";
|
|
79
|
+
const routes = gatewayModule.generateGatewayRoutes(allServices, mode);
|
|
59
80
|
const finalAppContent = gatewayAppContent.replace("/*__ROUTES__*/", routes);
|
|
60
81
|
|
|
61
82
|
fs.writeFileSync(gatewayAppPath, finalAppContent);
|
|
@@ -76,29 +97,48 @@ export const setupService = async (
|
|
|
76
97
|
if (res.projectType === "monolith" || serviceName === "health-service") {
|
|
77
98
|
for (const f of res.features) {
|
|
78
99
|
const feature = await import(`../../template/features/${f}/inject.js`);
|
|
79
|
-
|
|
100
|
+
const featureImports = feature.getImports
|
|
101
|
+
? feature.getImports(res.language)
|
|
102
|
+
: feature.imports;
|
|
103
|
+
imports.push(featureImports);
|
|
80
104
|
middlewares.push(feature.middleware);
|
|
81
105
|
deps.push(...feature.deps);
|
|
82
|
-
if (feature.devDeps) {
|
|
106
|
+
if (feature.devDeps && res.language === "typescript") {
|
|
83
107
|
devDeps.push(...feature.devDeps);
|
|
84
108
|
}
|
|
109
|
+
|
|
110
|
+
// If the feature provides files for the selected language, write them
|
|
111
|
+
const featureFiles = feature.getFiles
|
|
112
|
+
? feature.getFiles(res.language)
|
|
113
|
+
: feature.files;
|
|
114
|
+
if (featureFiles) {
|
|
115
|
+
for (const file in featureFiles) {
|
|
116
|
+
const filePath = file.replace(/\.ts$/, `.${ext}`);
|
|
117
|
+
const fullPath = path.join(serviceRoot, filePath);
|
|
118
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
119
|
+
fs.writeFileSync(fullPath, featureFiles[file]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
85
122
|
}
|
|
86
123
|
}
|
|
87
124
|
|
|
88
125
|
// Add authentication (only for monolith or auth-service)
|
|
89
126
|
if (shouldIncludeAuth && res.auth) {
|
|
90
|
-
const baseAuth =
|
|
91
|
-
"../../template/features/auth/base/inject.js"
|
|
92
|
-
);
|
|
127
|
+
const baseAuth =
|
|
128
|
+
await import("../../template/features/auth/base/inject.js");
|
|
93
129
|
deps.push(...baseAuth.deps);
|
|
94
|
-
if (baseAuth.devDeps) {
|
|
130
|
+
if (baseAuth.devDeps && res.language === "typescript") {
|
|
95
131
|
devDeps.push(...baseAuth.devDeps);
|
|
96
132
|
}
|
|
97
133
|
|
|
98
|
-
|
|
99
|
-
|
|
134
|
+
const authFiles = baseAuth.getFiles
|
|
135
|
+
? baseAuth.getFiles(res.language)
|
|
136
|
+
: baseAuth.files;
|
|
137
|
+
for (const file in authFiles) {
|
|
138
|
+
const filePath = file.replace(/\.ts$/, `.${ext}`);
|
|
139
|
+
const fullPath = path.join(serviceRoot, filePath);
|
|
100
140
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
101
|
-
fs.writeFileSync(fullPath,
|
|
141
|
+
fs.writeFileSync(fullPath, authFiles[file]);
|
|
102
142
|
}
|
|
103
143
|
|
|
104
144
|
const algo = await prompts({
|
|
@@ -108,17 +148,19 @@ export const setupService = async (
|
|
|
108
148
|
serviceName ? ` for ${serviceName}` : ""
|
|
109
149
|
}`,
|
|
110
150
|
choices: [
|
|
111
|
-
{
|
|
112
|
-
title:
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
151
|
+
{
|
|
152
|
+
title:
|
|
153
|
+
process.platform === "win32"
|
|
154
|
+
? "bcrypt (recommended for Windows)"
|
|
155
|
+
: "argon2 (recommended)",
|
|
156
|
+
value: process.platform === "win32" ? "bcrypt" : "argon2",
|
|
116
157
|
},
|
|
117
|
-
{
|
|
118
|
-
title:
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
158
|
+
{
|
|
159
|
+
title:
|
|
160
|
+
process.platform === "win32"
|
|
161
|
+
? "argon2 (requires build tools)"
|
|
162
|
+
: "bcrypt",
|
|
163
|
+
value: process.platform === "win32" ? "argon2" : "bcrypt",
|
|
122
164
|
},
|
|
123
165
|
],
|
|
124
166
|
});
|
|
@@ -127,17 +169,26 @@ export const setupService = async (
|
|
|
127
169
|
`../../template/features/auth/${algo.hasher}/inject.js`
|
|
128
170
|
);
|
|
129
171
|
deps.push(...hashFeature.deps);
|
|
130
|
-
if (hashFeature.devDeps) {
|
|
172
|
+
if (hashFeature.devDeps && res.language === "typescript") {
|
|
131
173
|
devDeps.push(...hashFeature.devDeps);
|
|
132
174
|
}
|
|
133
175
|
|
|
134
|
-
|
|
135
|
-
|
|
176
|
+
const hashFiles = hashFeature.getFiles
|
|
177
|
+
? hashFeature.getFiles(res.language)
|
|
178
|
+
: hashFeature.files;
|
|
179
|
+
for (const file in hashFiles) {
|
|
180
|
+
const filePath = file.replace(/\.ts$/, `.${ext}`);
|
|
181
|
+
const fullPath = path.join(serviceRoot, filePath);
|
|
136
182
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
137
|
-
|
|
183
|
+
const content = hashFiles[file];
|
|
184
|
+
fs.writeFileSync(fullPath, content);
|
|
138
185
|
}
|
|
139
186
|
|
|
140
|
-
v1Imports.push(
|
|
187
|
+
v1Imports.push(
|
|
188
|
+
baseAuth.getImports
|
|
189
|
+
? baseAuth.getImports(res.language)
|
|
190
|
+
: baseAuth.imports,
|
|
191
|
+
);
|
|
141
192
|
v1Routes.push(baseAuth.middleware);
|
|
142
193
|
}
|
|
143
194
|
|
|
@@ -149,22 +200,31 @@ export const setupService = async (
|
|
|
149
200
|
fs.writeFileSync(appPath, content);
|
|
150
201
|
|
|
151
202
|
// Update root endpoint middleware with project info
|
|
152
|
-
const rootMiddlewarePath = path.join(
|
|
203
|
+
const rootMiddlewarePath = path.join(
|
|
204
|
+
serviceRoot,
|
|
205
|
+
`src/middlewares/root.middleware.${ext}`,
|
|
206
|
+
);
|
|
153
207
|
if (fs.existsSync(rootMiddlewarePath)) {
|
|
154
208
|
let rootContent = fs.readFileSync(rootMiddlewarePath, "utf8");
|
|
155
|
-
rootContent = rootContent.replace(
|
|
156
|
-
|
|
157
|
-
|
|
209
|
+
rootContent = rootContent.replace(
|
|
210
|
+
"/*__PROJECT_NAME__*/",
|
|
211
|
+
serviceName || res.sanitizedName,
|
|
212
|
+
);
|
|
213
|
+
rootContent = rootContent.replace(
|
|
214
|
+
"/*__PROJECT_TYPE__*/",
|
|
215
|
+
res.projectType,
|
|
216
|
+
);
|
|
217
|
+
|
|
158
218
|
// Add auth endpoint if auth is enabled
|
|
159
219
|
if (shouldIncludeAuth && res.auth) {
|
|
160
220
|
rootContent = rootContent.replace(
|
|
161
221
|
"/*__AUTH_ENDPOINT__*/",
|
|
162
|
-
'auth: "/v1/auth",'
|
|
222
|
+
'auth: "/api/v1/auth",',
|
|
163
223
|
);
|
|
164
224
|
} else {
|
|
165
225
|
rootContent = rootContent.replace("/*__AUTH_ENDPOINT__*/", "");
|
|
166
226
|
}
|
|
167
|
-
|
|
227
|
+
|
|
168
228
|
fs.writeFileSync(rootMiddlewarePath, rootContent);
|
|
169
229
|
}
|
|
170
230
|
|
|
@@ -190,106 +250,126 @@ export const setupService = async (
|
|
|
190
250
|
|
|
191
251
|
fs.writeFileSync(v1IndexPath, v1Content);
|
|
192
252
|
}
|
|
193
|
-
|
|
253
|
+
|
|
194
254
|
// Update env file to conditionally include ALLOWED_ORIGIN and MONGO_URI
|
|
195
255
|
const envPath = path.join(serviceRoot, `src/config/env.${ext}`);
|
|
196
256
|
if (fs.existsSync(envPath)) {
|
|
197
257
|
let envContent = fs.readFileSync(envPath, "utf8");
|
|
198
|
-
|
|
258
|
+
|
|
199
259
|
// Import ENV in app.ts if CORS is selected
|
|
200
260
|
if (res.features && res.features.includes("cors")) {
|
|
201
261
|
let appContent = fs.readFileSync(appPath, "utf8");
|
|
202
262
|
if (!appContent.includes("import { ENV } from")) {
|
|
203
263
|
appContent = appContent.replace(
|
|
204
264
|
"/*__IMPORTS__*/",
|
|
205
|
-
"import { ENV } from '@/config';\n/*__IMPORTS__*/"
|
|
265
|
+
"import { ENV } from '@/config';\n/*__IMPORTS__*/",
|
|
206
266
|
);
|
|
207
267
|
fs.writeFileSync(appPath, appContent);
|
|
208
268
|
}
|
|
209
269
|
}
|
|
210
|
-
|
|
270
|
+
|
|
211
271
|
// Add ALLOWED_ORIGIN if CORS is selected
|
|
212
272
|
if (res.features && res.features.includes("cors")) {
|
|
273
|
+
const assertion = res.language === "javascript" ? "" : "!";
|
|
213
274
|
envContent = envContent.replace(
|
|
214
275
|
"/*__ALLOWED_ORIGIN__*/",
|
|
215
|
-
|
|
276
|
+
`ALLOWED_ORIGIN: process.env.ALLOWED_ORIGIN${assertion},`,
|
|
216
277
|
);
|
|
217
278
|
} else {
|
|
218
279
|
envContent = envContent.replace("/*__ALLOWED_ORIGIN__*/", "");
|
|
219
280
|
}
|
|
220
|
-
|
|
281
|
+
|
|
221
282
|
// Add MONGO_URI if auth is enabled
|
|
222
283
|
if (shouldIncludeAuth && res.auth) {
|
|
284
|
+
const assertion = res.language === "javascript" ? "" : "!";
|
|
223
285
|
envContent = envContent.replace(
|
|
224
286
|
"/*__MONGO_URI__*/",
|
|
225
|
-
|
|
287
|
+
`MONGO_URI: process.env.MONGO_URI${assertion},`,
|
|
226
288
|
);
|
|
227
289
|
envContent = envContent.replace(
|
|
228
290
|
"/*__JWT_SECRET__*/",
|
|
229
|
-
|
|
291
|
+
`JWT_SECRET: process.env.JWT_SECRET${assertion},`,
|
|
230
292
|
);
|
|
231
293
|
} else {
|
|
232
294
|
envContent = envContent.replace("/*__MONGO_URI__*/", "");
|
|
233
295
|
envContent = envContent.replace("/*__JWT_SECRET__*/", "");
|
|
234
296
|
}
|
|
235
|
-
|
|
297
|
+
|
|
236
298
|
fs.writeFileSync(envPath, envContent);
|
|
237
299
|
}
|
|
238
|
-
|
|
300
|
+
|
|
239
301
|
// Update server file to connect to DB if auth is enabled
|
|
240
302
|
const serverPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
241
303
|
if (fs.existsSync(serverPath)) {
|
|
242
304
|
let serverContent = fs.readFileSync(serverPath, "utf8");
|
|
243
|
-
|
|
305
|
+
|
|
244
306
|
if (shouldIncludeAuth && res.auth) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
307
|
+
const language = res.language;
|
|
308
|
+
if (language === "javascript") {
|
|
309
|
+
serverContent = serverContent.replace(
|
|
310
|
+
"/*__DB_IMPORT__*/",
|
|
311
|
+
", connectDB",
|
|
312
|
+
);
|
|
313
|
+
} else {
|
|
314
|
+
serverContent = serverContent.replace(
|
|
315
|
+
"/*__DB_IMPORT__*/",
|
|
316
|
+
'import { connectDB } from "./config";',
|
|
317
|
+
);
|
|
318
|
+
}
|
|
249
319
|
serverContent = serverContent.replace(
|
|
250
320
|
"/*__DB_CONNECT__*/",
|
|
251
|
-
`// Connect to MongoDB
|
|
252
|
-
await connectDB();`
|
|
321
|
+
`// Connect to MongoDB\nawait connectDB();`,
|
|
253
322
|
);
|
|
254
323
|
} else {
|
|
255
324
|
serverContent = serverContent.replace("/*__DB_IMPORT__*/", "");
|
|
256
325
|
serverContent = serverContent.replace("/*__DB_CONNECT__*/", "");
|
|
257
326
|
}
|
|
258
|
-
|
|
327
|
+
|
|
259
328
|
fs.writeFileSync(serverPath, serverContent);
|
|
260
329
|
}
|
|
261
|
-
|
|
262
|
-
// Update .env.example to conditionally include environment variables
|
|
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
|
-
|
|
330
|
+
|
|
331
|
+
// Update .env.example to conditionally include environment variables (only for monolith)
|
|
332
|
+
if (res.projectType !== "microservice") {
|
|
333
|
+
const envExamplePath = path.join(serviceRoot, ".env.example");
|
|
334
|
+
if (fs.existsSync(envExamplePath)) {
|
|
335
|
+
let envExampleContent = fs.readFileSync(envExamplePath, "utf8");
|
|
336
|
+
|
|
337
|
+
// Add ALLOWED_ORIGIN if CORS is selected
|
|
338
|
+
if (res.features && res.features.includes("cors")) {
|
|
339
|
+
envExampleContent = envExampleContent.replace(
|
|
340
|
+
"/*__ALLOWED_ORIGIN_ENV__*/",
|
|
341
|
+
"ALLOWED_ORIGIN=http://localhost:3000",
|
|
342
|
+
);
|
|
343
|
+
} else {
|
|
344
|
+
envExampleContent = envExampleContent.replace(
|
|
345
|
+
"/*__ALLOWED_ORIGIN_ENV__*/",
|
|
346
|
+
"",
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Add MONGO_URI and JWT_SECRET if auth is enabled
|
|
351
|
+
if (shouldIncludeAuth && res.auth) {
|
|
352
|
+
envExampleContent = envExampleContent.replace(
|
|
353
|
+
"/*__MONGO_URI_ENV__*/",
|
|
354
|
+
"MONGO_URI=mongodb://localhost:27017/your-database-name",
|
|
355
|
+
);
|
|
356
|
+
envExampleContent = envExampleContent.replace(
|
|
357
|
+
"/*__JWT_SECRET_ENV__*/",
|
|
358
|
+
"JWT_SECRET=your-super-secret-jwt-key-change-this-in-production",
|
|
359
|
+
);
|
|
360
|
+
} else {
|
|
361
|
+
envExampleContent = envExampleContent.replace(
|
|
362
|
+
"/*__MONGO_URI_ENV__*/",
|
|
363
|
+
"",
|
|
364
|
+
);
|
|
365
|
+
envExampleContent = envExampleContent.replace(
|
|
366
|
+
"/*__JWT_SECRET_ENV__*/",
|
|
367
|
+
"",
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
fs.writeFileSync(envExamplePath, envExampleContent);
|
|
290
372
|
}
|
|
291
|
-
|
|
292
|
-
fs.writeFileSync(envExamplePath, envExampleContent);
|
|
293
373
|
}
|
|
294
374
|
} // End of else block for non-gateway services
|
|
295
375
|
|
|
@@ -297,28 +377,71 @@ await connectDB();`
|
|
|
297
377
|
if (res.projectType === "microservice") {
|
|
298
378
|
const tsconfigPath = path.join(serviceRoot, "tsconfig.json");
|
|
299
379
|
let tsconfigContent = fs.readFileSync(tsconfigPath, "utf8");
|
|
300
|
-
|
|
380
|
+
|
|
301
381
|
// Remove comments from JSON (strip-json-comments approach)
|
|
302
382
|
tsconfigContent = tsconfigContent
|
|
303
|
-
.replace(/\/\/.*$/gm,
|
|
304
|
-
.replace(/\/\*[\s\S]*?\*\//g,
|
|
305
|
-
|
|
383
|
+
.replace(/\/\/.*$/gm, "") // Remove single-line comments
|
|
384
|
+
.replace(/\/\*[\s\S]*?\*\//g, ""); // Remove multi-line comments
|
|
385
|
+
|
|
306
386
|
const tsconfig = JSON.parse(tsconfigContent);
|
|
307
387
|
|
|
308
|
-
// Update
|
|
388
|
+
// Update baseUrl to allow import from the shared folder
|
|
389
|
+
tsconfig.compilerOptions.baseUrl = ".";
|
|
390
|
+
|
|
391
|
+
// Update paths to include shared folder (works in both Docker and VS Code)
|
|
309
392
|
tsconfig.compilerOptions.paths = {
|
|
310
|
-
"@/*": ["
|
|
311
|
-
"@/
|
|
312
|
-
"@/utils/*": ["../../shared/utils/*"],
|
|
393
|
+
"@/*": ["src/*"],
|
|
394
|
+
"@/shared/*": ["shared/*", "../../shared/*"],
|
|
313
395
|
};
|
|
314
396
|
|
|
397
|
+
// Remove rootDir restriction to allow imports from outside src/
|
|
398
|
+
delete tsconfig.compilerOptions.rootDir;
|
|
399
|
+
|
|
315
400
|
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2) + "\n");
|
|
401
|
+
|
|
402
|
+
// Update imports in service files to use @/shared/* instead of @/* for utils/config
|
|
403
|
+
// This applies to non-gateway services
|
|
404
|
+
if (serviceName !== "gateway") {
|
|
405
|
+
const healthControllerPath = path.join(
|
|
406
|
+
serviceRoot,
|
|
407
|
+
`src/modules/v1/health/health.controller.${ext}`,
|
|
408
|
+
);
|
|
409
|
+
if (fs.existsSync(healthControllerPath)) {
|
|
410
|
+
let healthControllerContent = fs.readFileSync(
|
|
411
|
+
healthControllerPath,
|
|
412
|
+
"utf8",
|
|
413
|
+
);
|
|
414
|
+
healthControllerContent = healthControllerContent.replace(
|
|
415
|
+
'from "@/utils"',
|
|
416
|
+
'from "@/shared/utils"',
|
|
417
|
+
);
|
|
418
|
+
fs.writeFileSync(healthControllerPath, healthControllerContent);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Update server.ts to use shared imports
|
|
422
|
+
const serverPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
423
|
+
if (fs.existsSync(serverPath)) {
|
|
424
|
+
let serverContent = fs.readFileSync(serverPath, "utf8");
|
|
425
|
+
serverContent = serverContent
|
|
426
|
+
.replace('from "@/utils"', 'from "@/shared/utils"')
|
|
427
|
+
.replace('from "@/config"', 'from "@/shared/config"');
|
|
428
|
+
|
|
429
|
+
// Update PORT to use service-specific environment variable
|
|
430
|
+
const portEnvVar = `${serviceName.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
431
|
+
serverContent = serverContent.replace(
|
|
432
|
+
/const PORT = ENV\.PORT \|\| (\d+);/,
|
|
433
|
+
`const PORT = ENV.${portEnvVar} || $1;`,
|
|
434
|
+
);
|
|
435
|
+
|
|
436
|
+
fs.writeFileSync(serverPath, serverContent);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
316
439
|
}
|
|
317
440
|
|
|
318
441
|
// Update package.json
|
|
319
442
|
const packageJsonPath = path.join(serviceRoot, "package.json");
|
|
320
443
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
321
|
-
|
|
444
|
+
|
|
322
445
|
// Create new package.json with name at the top
|
|
323
446
|
const orderedPackageJson = {
|
|
324
447
|
name: serviceName || res.sanitizedName,
|
|
@@ -326,12 +449,12 @@ await connectDB();`
|
|
|
326
449
|
description: res.description || packageJson.description,
|
|
327
450
|
...packageJson,
|
|
328
451
|
};
|
|
329
|
-
|
|
452
|
+
|
|
330
453
|
// Remove duplicate keys that were moved to the top
|
|
331
454
|
delete orderedPackageJson.name;
|
|
332
455
|
delete orderedPackageJson.version;
|
|
333
456
|
delete orderedPackageJson.description;
|
|
334
|
-
|
|
457
|
+
|
|
335
458
|
// Re-add them at the top in correct order
|
|
336
459
|
const finalPackageJson = {
|
|
337
460
|
name: serviceName || res.sanitizedName,
|
|
@@ -339,29 +462,57 @@ await connectDB();`
|
|
|
339
462
|
description: res.description || packageJson.description,
|
|
340
463
|
...orderedPackageJson,
|
|
341
464
|
};
|
|
342
|
-
|
|
465
|
+
|
|
343
466
|
// Add author if provided
|
|
344
467
|
if (res.author) {
|
|
345
468
|
finalPackageJson.author = res.author;
|
|
346
469
|
}
|
|
347
|
-
|
|
470
|
+
|
|
348
471
|
// Add keywords if provided
|
|
349
472
|
if (res.keywords && res.keywords.trim()) {
|
|
350
|
-
finalPackageJson.keywords = res.keywords
|
|
473
|
+
finalPackageJson.keywords = res.keywords
|
|
474
|
+
.split(",")
|
|
475
|
+
.map((k) => k.trim())
|
|
476
|
+
.filter(Boolean);
|
|
351
477
|
}
|
|
352
|
-
|
|
478
|
+
|
|
479
|
+
// Add Node.js native path aliasing for JavaScript projects
|
|
480
|
+
// This replaces TypeScript's tsconfig paths with Node's native imports field
|
|
481
|
+
if (res.language === "javascript") {
|
|
482
|
+
finalPackageJson.imports = {
|
|
483
|
+
"#/*": "./src/*",
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Add --poll flag to dev script for Docker mode (fixes watch mode in Docker on Windows)
|
|
488
|
+
if (res.projectType === "microservice" && res.mode === "docker") {
|
|
489
|
+
if (finalPackageJson.scripts && finalPackageJson.scripts.dev) {
|
|
490
|
+
finalPackageJson.scripts.dev = finalPackageJson.scripts.dev.replace(
|
|
491
|
+
"ts-node-dev --respawn --transpile-only",
|
|
492
|
+
"ts-node-dev --respawn --transpile-only --poll",
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
353
497
|
fs.writeFileSync(
|
|
354
498
|
packageJsonPath,
|
|
355
|
-
JSON.stringify(finalPackageJson, null, 2) + "\n"
|
|
499
|
+
JSON.stringify(finalPackageJson, null, 2) + "\n",
|
|
356
500
|
);
|
|
357
501
|
|
|
502
|
+
// Skip installation if skipInstall is true (will be done later in batch)
|
|
503
|
+
if (skipInstall) {
|
|
504
|
+
return { deps, devDeps, installSucceeded: true };
|
|
505
|
+
}
|
|
506
|
+
|
|
358
507
|
// Install dependencies
|
|
359
508
|
console.log(
|
|
360
|
-
pc.cyan(
|
|
509
|
+
pc.cyan(
|
|
510
|
+
`\n📦 Installing dependencies for ${serviceName || "project"}...\n`,
|
|
511
|
+
),
|
|
361
512
|
);
|
|
362
|
-
|
|
513
|
+
|
|
363
514
|
let installSucceeded = false;
|
|
364
|
-
|
|
515
|
+
|
|
365
516
|
try {
|
|
366
517
|
if (deps.length) {
|
|
367
518
|
execSync(`npm install ${deps.join(" ")}`, {
|
|
@@ -377,26 +528,29 @@ await connectDB();`
|
|
|
377
528
|
}
|
|
378
529
|
execSync("npm install", { cwd: serviceRoot, stdio: "inherit" });
|
|
379
530
|
installSucceeded = true;
|
|
380
|
-
|
|
531
|
+
|
|
381
532
|
// Run format after successful install
|
|
382
533
|
console.log(pc.cyan("\n🎨 Formatting code...\n"));
|
|
383
534
|
try {
|
|
384
535
|
execSync("npm run format", { cwd: serviceRoot, stdio: "inherit" });
|
|
385
536
|
} catch (formatError) {
|
|
386
|
-
console.warn(
|
|
537
|
+
console.warn(
|
|
538
|
+
pc.yellow(
|
|
539
|
+
"⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n",
|
|
540
|
+
),
|
|
541
|
+
);
|
|
387
542
|
}
|
|
388
543
|
} catch (error) {
|
|
389
|
-
console.error(pc.
|
|
390
|
-
console.error(pc.
|
|
391
|
-
console.error(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
console.error(pc.dim("
|
|
395
|
-
|
|
396
|
-
|
|
544
|
+
console.error(pc.red("\n❌ Failed to install dependencies"));
|
|
545
|
+
console.error(pc.dim(`\nYou can install them later by running:`));
|
|
546
|
+
console.error(
|
|
547
|
+
pc.cyan(` cd ${serviceName || res.sanitizedName} && npm install`),
|
|
548
|
+
);
|
|
549
|
+
console.error(pc.dim(" Then run: npm run format\n"));
|
|
550
|
+
|
|
397
551
|
// Don't exit - let the project be created anyway
|
|
398
552
|
console.log(pc.cyan("⏭️ Continuing with project creation...\n"));
|
|
399
553
|
}
|
|
400
554
|
|
|
401
|
-
return { deps, installSucceeded };
|
|
555
|
+
return { deps, devDeps, installSucceeded };
|
|
402
556
|
};
|
package/package.json
CHANGED