@ifecodes/backend-template 1.1.3 → 1.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -18
- package/bin/cli.js +602 -101
- package/bin/lib/microservice-config.js +68 -14
- package/bin/lib/prompts.js +25 -6
- package/bin/lib/readme-generator.js +119 -29
- package/bin/lib/service-setup.js +401 -132
- package/package.json +2 -2
- 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 -45
- package/template/base/{src → ts/src}/utils/logger.ts +23 -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/ts/app.ts +43 -0
- package/template/gateway/ts/inject.js +32 -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 -342
- package/template/base/src/app.ts +0 -8
- package/template/gateway/app.ts +0 -26
- 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/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import fs from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
@@ -7,11 +7,12 @@ import pc from "picocolors";
|
|
|
7
7
|
import { getProjectConfig } from "./lib/prompts.js";
|
|
8
8
|
import { setupService } from "./lib/service-setup.js";
|
|
9
9
|
import { generateReadme } from "./lib/readme-generator.js";
|
|
10
|
-
|
|
10
|
+
// No TS->JS transform: templates contain language-specific folders (base/js, base/ts)
|
|
11
11
|
import {
|
|
12
12
|
generateDockerCompose,
|
|
13
13
|
generatePm2Config,
|
|
14
14
|
copyDockerfile,
|
|
15
|
+
copyDockerignore,
|
|
15
16
|
} from "./lib/microservice-config.js";
|
|
16
17
|
|
|
17
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -26,7 +27,11 @@ const {
|
|
|
26
27
|
isInMicroserviceProject,
|
|
27
28
|
} = config;
|
|
28
29
|
|
|
29
|
-
const
|
|
30
|
+
const baseRoot =
|
|
31
|
+
config.language === "javascript"
|
|
32
|
+
? path.join(__dirname, "../template/base/js")
|
|
33
|
+
: path.join(__dirname, "../template/base/ts");
|
|
34
|
+
const base = baseRoot;
|
|
30
35
|
|
|
31
36
|
// Determine which services to create
|
|
32
37
|
const servicesToCreate = [];
|
|
@@ -44,27 +49,56 @@ if (isInMicroserviceProject) {
|
|
|
44
49
|
// Validate and prepare project
|
|
45
50
|
if (!isInMicroserviceProject && config.projectType === "microservice") {
|
|
46
51
|
if (isExistingProject) {
|
|
47
|
-
console.error(
|
|
52
|
+
console.error(
|
|
53
|
+
`\n${pc.red("❌ Error:")} Project ${pc.bold(sanitizedName)} already exists!`,
|
|
54
|
+
);
|
|
48
55
|
process.exit(1);
|
|
49
56
|
}
|
|
50
57
|
console.log(
|
|
51
|
-
`\n${pc.cyan("🏗️ Creating microservices:")} ${pc.bold(servicesToCreate.join(", "))}...\n
|
|
58
|
+
`\n${pc.cyan("🏗️ Creating microservices:")} ${pc.bold(servicesToCreate.join(", "))}...\n`,
|
|
52
59
|
);
|
|
53
60
|
} else if (!isInMicroserviceProject && config.projectType === "monolith") {
|
|
54
61
|
if (isExistingProject) {
|
|
55
|
-
console.error(
|
|
62
|
+
console.error(
|
|
63
|
+
`\n${pc.red("❌ Error:")} Project ${pc.bold(sanitizedName)} already exists!`,
|
|
64
|
+
);
|
|
56
65
|
process.exit(1);
|
|
57
66
|
}
|
|
58
67
|
fs.cpSync(base, target, { recursive: true });
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
if (config.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
|
|
69
|
+
// Remove db file and remove connectDB export/import if auth is not enabled
|
|
70
|
+
if (!config.auth) {
|
|
71
|
+
const ext = config.language === "javascript" ? "js" : "ts";
|
|
72
|
+
const dbPath = path.join(target, `src/config/db.${ext}`);
|
|
73
|
+
if (fs.existsSync(dbPath)) {
|
|
74
|
+
fs.rmSync(dbPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Update index.(js|ts) to not export or require connectDB
|
|
78
|
+
const indexPath = path.join(target, `src/config/index.${ext}`);
|
|
79
|
+
if (fs.existsSync(indexPath)) {
|
|
80
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
|
81
|
+
if (ext === "ts") {
|
|
82
|
+
indexContent = indexContent.replace(
|
|
83
|
+
'export { connectDB } from "./db";\n',
|
|
84
|
+
"",
|
|
85
|
+
);
|
|
86
|
+
// also remove any trailing references like `connectDB,` in exported objects
|
|
87
|
+
indexContent = indexContent.replace(/connectDB,?/g, "");
|
|
88
|
+
} else {
|
|
89
|
+
indexContent = indexContent
|
|
90
|
+
.replace('const { connectDB } = require("./db");', "")
|
|
91
|
+
.replace(/connectDB,?/g, "");
|
|
92
|
+
}
|
|
93
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
94
|
+
}
|
|
65
95
|
}
|
|
96
|
+
|
|
97
|
+
// No TypeScript-to-JavaScript conversion — templates include language-specific variants
|
|
66
98
|
} else if (isInMicroserviceProject) {
|
|
67
|
-
console.log(
|
|
99
|
+
console.log(
|
|
100
|
+
`\n${pc.cyan("🏗️ Adding service:")} ${pc.bold(servicesToCreate[0])}...\n`,
|
|
101
|
+
);
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
// Process services
|
|
@@ -73,7 +107,9 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
73
107
|
if (!isInMicroserviceProject) {
|
|
74
108
|
const sharedDir = path.join(target, "shared");
|
|
75
109
|
if (!fs.existsSync(sharedDir)) {
|
|
76
|
-
console.log(
|
|
110
|
+
console.log(
|
|
111
|
+
`\n${pc.cyan("📦 Creating shared folder for config and utils...")}`,
|
|
112
|
+
);
|
|
77
113
|
fs.mkdirSync(sharedDir, { recursive: true });
|
|
78
114
|
|
|
79
115
|
// Copy config and utils from base template
|
|
@@ -85,11 +121,129 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
85
121
|
fs.cpSync(baseConfigDir, sharedConfigDir, { recursive: true });
|
|
86
122
|
fs.cpSync(baseUtilsDir, sharedUtilsDir, { recursive: true });
|
|
87
123
|
|
|
124
|
+
// Remove db files and strip connectDB exports/imports when auth is not enabled
|
|
125
|
+
if (!config.auth) {
|
|
126
|
+
for (const ext of ["ts", "js"]) {
|
|
127
|
+
const sharedDbPath = path.join(sharedConfigDir, `db.${ext}`);
|
|
128
|
+
if (fs.existsSync(sharedDbPath)) fs.rmSync(sharedDbPath);
|
|
129
|
+
|
|
130
|
+
const sharedIndexPath = path.join(sharedConfigDir, `index.${ext}`);
|
|
131
|
+
if (fs.existsSync(sharedIndexPath)) {
|
|
132
|
+
let idx = fs.readFileSync(sharedIndexPath, "utf8");
|
|
133
|
+
// Remove various export/import patterns referencing connectDB
|
|
134
|
+
idx = idx.replace(
|
|
135
|
+
/export\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
136
|
+
"",
|
|
137
|
+
);
|
|
138
|
+
idx = idx.replace(
|
|
139
|
+
/const\s*\{\s*connectDB\s*\}\s*=\s*require\(["']\.\/db["']\);?/g,
|
|
140
|
+
"",
|
|
141
|
+
);
|
|
142
|
+
idx = idx.replace(
|
|
143
|
+
/import\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
144
|
+
"",
|
|
145
|
+
);
|
|
146
|
+
idx = idx.replace(/\bconnectDB,?\b/g, "");
|
|
147
|
+
idx = idx.replace(/\n{3,}/g, "\n\n");
|
|
148
|
+
fs.writeFileSync(sharedIndexPath, idx);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const ext = config.language === "javascript" ? "js" : "ts";
|
|
153
|
+
|
|
154
|
+
// Update shared env.ts to include all service port environment variables
|
|
155
|
+
const sharedEnvPath = path.join(sharedConfigDir, `env.${ext}`);
|
|
156
|
+
if (fs.existsSync(sharedEnvPath)) {
|
|
157
|
+
let envContent = fs.readFileSync(sharedEnvPath, "utf8");
|
|
158
|
+
console.log(`\n${pc.cyan("🔧 Updating shared env configuration...")}`);
|
|
159
|
+
|
|
160
|
+
// Build port environment variables for all services
|
|
161
|
+
const allServices = ["gateway", "health-service"];
|
|
162
|
+
if (config.auth) allServices.push("auth-service");
|
|
163
|
+
|
|
164
|
+
const portEnvVars = allServices
|
|
165
|
+
.map((service) => {
|
|
166
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
167
|
+
// Don't add ! for JavaScript projects - it will cause syntax errors
|
|
168
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
169
|
+
return ` ${envVarName}: process.env.${envVarName}${assertion},`;
|
|
170
|
+
})
|
|
171
|
+
.join("\n");
|
|
172
|
+
|
|
173
|
+
// Replace PORT with service-specific ports
|
|
174
|
+
envContent = envContent.replace(
|
|
175
|
+
" PORT: process.env.PORT!,",
|
|
176
|
+
portEnvVars,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Add ALLOWED_ORIGIN if CORS is selected
|
|
180
|
+
if (config.features && config.features.includes("cors")) {
|
|
181
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
182
|
+
envContent = envContent.replace(
|
|
183
|
+
"/*__ALLOWED_ORIGIN__*/",
|
|
184
|
+
`ALLOWED_ORIGIN: process.env.ALLOWED_ORIGIN${assertion},`,
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
envContent = envContent.replace("/*__ALLOWED_ORIGIN__*/", "");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Add MONGO_URI and JWT_SECRET if auth is enabled
|
|
191
|
+
if (config.auth) {
|
|
192
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
193
|
+
envContent = envContent.replace(
|
|
194
|
+
"/*__MONGO_URI__*/",
|
|
195
|
+
`MONGO_URI: process.env.MONGO_URI${assertion},`,
|
|
196
|
+
);
|
|
197
|
+
envContent = envContent.replace(
|
|
198
|
+
"/*__JWT_SECRET__*/",
|
|
199
|
+
`JWT_SECRET: process.env.JWT_SECRET${assertion},`,
|
|
200
|
+
);
|
|
201
|
+
} else {
|
|
202
|
+
envContent = envContent.replace("/*__MONGO_URI__*/", "");
|
|
203
|
+
envContent = envContent.replace("/*__JWT_SECRET__*/", "");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fs.writeFileSync(sharedEnvPath, envContent);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Update shared config/index to conditionally export connectDB
|
|
210
|
+
const sharedConfigIndexPath = path.join(sharedConfigDir, `index.${ext}`);
|
|
211
|
+
if (fs.existsSync(sharedConfigIndexPath)) {
|
|
212
|
+
let indexContent = fs.readFileSync(sharedConfigIndexPath, "utf8");
|
|
213
|
+
if (!config.auth) {
|
|
214
|
+
if (ext === "ts") {
|
|
215
|
+
indexContent = indexContent.replace(
|
|
216
|
+
'export { connectDB } from "./db";\n',
|
|
217
|
+
"",
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
indexContent = indexContent
|
|
221
|
+
.replace('const { connectDB } = require("./db");', "")
|
|
222
|
+
.replace("connectDB,", "");
|
|
223
|
+
fs.writeFileSync(sharedConfigIndexPath, indexContent);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Update shared utils/logger to use shared config
|
|
228
|
+
const sharedLoggerPath = path.join(sharedUtilsDir, `logger.${ext}`);
|
|
229
|
+
if (fs.existsSync(sharedLoggerPath)) {
|
|
230
|
+
console.log(
|
|
231
|
+
`\n${pc.cyan("🔧 Updating shared logger to use shared config...")}`,
|
|
232
|
+
);
|
|
233
|
+
let loggerContent = fs.readFileSync(sharedLoggerPath, "utf8");
|
|
234
|
+
// Replace imports like: from '@/config'; or from "@/config" with relative import to shared config
|
|
235
|
+
loggerContent = loggerContent.replace(
|
|
236
|
+
/from\s+["']@\/config["'];?/g,
|
|
237
|
+
"from '../config';",
|
|
238
|
+
);
|
|
239
|
+
fs.writeFileSync(sharedLoggerPath, loggerContent);
|
|
240
|
+
}
|
|
241
|
+
|
|
88
242
|
// Create shared package.json
|
|
89
243
|
const sharedPackageJson = {
|
|
90
244
|
name: "@shared/common",
|
|
91
245
|
version: "1.0.0",
|
|
92
|
-
type: "
|
|
246
|
+
type: "commonjs",
|
|
93
247
|
exports: {
|
|
94
248
|
"./config/*": "./config/*",
|
|
95
249
|
"./utils/*": "./utils/*",
|
|
@@ -97,7 +251,7 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
97
251
|
};
|
|
98
252
|
fs.writeFileSync(
|
|
99
253
|
path.join(sharedDir, "package.json"),
|
|
100
|
-
JSON.stringify(sharedPackageJson, null, 2)
|
|
254
|
+
JSON.stringify(sharedPackageJson, null, 2),
|
|
101
255
|
);
|
|
102
256
|
}
|
|
103
257
|
}
|
|
@@ -113,6 +267,12 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
113
267
|
console.log(`\n🔨 Setting up ${serviceName}...`);
|
|
114
268
|
fs.cpSync(base, serviceRoot, { recursive: true });
|
|
115
269
|
|
|
270
|
+
// Remove .env and .env.example from microservices (environment variables come from docker-compose/pm2)
|
|
271
|
+
const envPath = path.join(serviceRoot, ".env");
|
|
272
|
+
const envExamplePath = path.join(serviceRoot, ".env.example");
|
|
273
|
+
if (fs.existsSync(envPath)) fs.rmSync(envPath);
|
|
274
|
+
if (fs.existsSync(envExamplePath)) fs.rmSync(envExamplePath);
|
|
275
|
+
|
|
116
276
|
// Remove config and utils from service (they'll use shared) - except gateway handles it differently
|
|
117
277
|
if (serviceName !== "gateway") {
|
|
118
278
|
const serviceConfigDir = path.join(serviceRoot, "src", "config");
|
|
@@ -126,16 +286,21 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
126
286
|
|
|
127
287
|
// Get all services first (needed for gateway routing)
|
|
128
288
|
const servicesDir = path.join(target, "services");
|
|
129
|
-
const
|
|
289
|
+
const existingServices = fs.existsSync(servicesDir)
|
|
130
290
|
? fs
|
|
131
291
|
.readdirSync(servicesDir)
|
|
132
292
|
.filter((f) => fs.statSync(path.join(servicesDir, f)).isDirectory())
|
|
133
|
-
:
|
|
293
|
+
: [];
|
|
294
|
+
// Include services we're about to create so port computation and gateway routing
|
|
295
|
+
// are aware of newly added services when setting up files.
|
|
296
|
+
const allServices = Array.from(
|
|
297
|
+
new Set([...existingServices, ...servicesToCreate]),
|
|
298
|
+
);
|
|
134
299
|
|
|
135
|
-
//
|
|
136
|
-
|
|
300
|
+
// Step 1: Setup all service files first (without installing dependencies)
|
|
301
|
+
console.log(pc.cyan("\n⚙️ Setting up service files...\n"));
|
|
302
|
+
const serviceConfigs = [];
|
|
137
303
|
|
|
138
|
-
// Now setup each service with knowledge of all services
|
|
139
304
|
for (const serviceName of servicesToCreate) {
|
|
140
305
|
const serviceRoot = path.join(target, "services", serviceName);
|
|
141
306
|
const shouldIncludeAuth = isInMicroserviceProject
|
|
@@ -146,39 +311,53 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
146
311
|
serviceName,
|
|
147
312
|
serviceRoot,
|
|
148
313
|
shouldIncludeAuth,
|
|
149
|
-
allServices
|
|
314
|
+
allServices,
|
|
315
|
+
true, // Skip install for now
|
|
150
316
|
);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
317
|
+
serviceConfigs.push({
|
|
318
|
+
serviceName,
|
|
319
|
+
serviceRoot,
|
|
320
|
+
deps: result.deps,
|
|
321
|
+
devDeps: result.devDeps,
|
|
322
|
+
});
|
|
154
323
|
}
|
|
155
324
|
|
|
156
|
-
//
|
|
157
|
-
|
|
325
|
+
// Remove per-service husky hooks and ensure a single root pre-commit hook
|
|
326
|
+
try {
|
|
327
|
+
const servicesDirPath = path.join(target, "services");
|
|
328
|
+
const allServicesList = fs.existsSync(servicesDirPath)
|
|
329
|
+
? fs
|
|
330
|
+
.readdirSync(servicesDirPath)
|
|
331
|
+
.filter((f) =>
|
|
332
|
+
fs.statSync(path.join(servicesDirPath, f)).isDirectory(),
|
|
333
|
+
)
|
|
334
|
+
: [];
|
|
158
335
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (fs.existsSync(sharedDir)) {
|
|
166
|
-
transformDirectory(sharedDir);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Transform each service
|
|
170
|
-
for (const serviceName of allServices) {
|
|
171
|
-
const serviceRoot = path.join(target, "services", serviceName);
|
|
172
|
-
console.log(pc.dim(` Transforming ${serviceName}...`));
|
|
173
|
-
transformToJavaScript(serviceRoot);
|
|
336
|
+
// Remove `.husky` folders from each service
|
|
337
|
+
for (const svc of allServicesList) {
|
|
338
|
+
const svcHusky = path.join(servicesDirPath, svc, ".husky");
|
|
339
|
+
if (fs.existsSync(svcHusky)) {
|
|
340
|
+
fs.rmSync(svcHusky, { recursive: true, force: true });
|
|
341
|
+
}
|
|
174
342
|
}
|
|
175
|
-
|
|
176
|
-
|
|
343
|
+
|
|
344
|
+
// Ensure root .husky/pre-commit exists at target
|
|
345
|
+
const rootHuskyDir = path.join(target, ".husky");
|
|
346
|
+
if (!fs.existsSync(rootHuskyDir))
|
|
347
|
+
fs.mkdirSync(rootHuskyDir, { recursive: true });
|
|
348
|
+
const preCommitPath = path.join(rootHuskyDir, "pre-commit");
|
|
349
|
+
const preCommitContent =
|
|
350
|
+
'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';
|
|
351
|
+
fs.writeFileSync(preCommitPath, preCommitContent);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
// Non-fatal; continue setup even if husky files couldn't be created/removed
|
|
177
354
|
}
|
|
178
355
|
|
|
356
|
+
// Step 2: Generate docker-compose/pm2 config and root files
|
|
179
357
|
if (mode === "docker") {
|
|
180
|
-
generateDockerCompose(target, allServices);
|
|
358
|
+
generateDockerCompose(target, allServices, config.sanitizedName);
|
|
181
359
|
copyDockerfile(target, servicesToCreate);
|
|
360
|
+
copyDockerignore(target, servicesToCreate);
|
|
182
361
|
} else {
|
|
183
362
|
generatePm2Config(target, allServices);
|
|
184
363
|
}
|
|
@@ -188,38 +367,104 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
188
367
|
if (!fs.existsSync(rootPackageJsonPath)) {
|
|
189
368
|
const rootPackageJson = {
|
|
190
369
|
name: sanitizedName,
|
|
191
|
-
version: "1.0.0",
|
|
370
|
+
version: config.version || "1.0.0",
|
|
371
|
+
description: config.description || "",
|
|
192
372
|
private: true,
|
|
193
373
|
scripts: {
|
|
374
|
+
dev: "docker-compose up",
|
|
375
|
+
stop: "docker-compose down",
|
|
376
|
+
restart: "docker-compose restart",
|
|
377
|
+
lint: 'eslint "services/**/*.{js,ts,tsx}" "shared/**/*.{js,ts,tsx}"',
|
|
378
|
+
format:
|
|
379
|
+
'prettier --write "services/**/*.{js,ts,json}" "shared/**/*.{js,ts,json}"',
|
|
380
|
+
"check-format":
|
|
381
|
+
'prettier --check "services/**/*.{js,ts,json}" "shared/**/*.{js,ts,json}"',
|
|
194
382
|
prepare: "husky install",
|
|
195
383
|
},
|
|
196
384
|
devDependencies: {
|
|
197
|
-
husky: "^
|
|
385
|
+
husky: "^9.1.7",
|
|
386
|
+
prettier: "^3.7.4",
|
|
387
|
+
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
388
|
+
"@typescript-eslint/parser": "^8.50.1",
|
|
389
|
+
eslint: "^9.39.2",
|
|
390
|
+
"eslint-config-prettier": "^10.1.8",
|
|
198
391
|
},
|
|
199
392
|
};
|
|
200
393
|
fs.writeFileSync(
|
|
201
394
|
rootPackageJsonPath,
|
|
202
|
-
JSON.stringify(rootPackageJson, null, 2) + "\n"
|
|
395
|
+
JSON.stringify(rootPackageJson, null, 2) + "\n",
|
|
203
396
|
);
|
|
204
397
|
}
|
|
205
|
-
} else {
|
|
206
|
-
const result = await setupService(config, null, target, true);
|
|
207
|
-
config.installSucceeded = result.installSucceeded;
|
|
208
|
-
}
|
|
209
398
|
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
399
|
+
// Ensure root lint/format config files exist (copy from template base if available), and remove any per-service copies
|
|
400
|
+
try {
|
|
401
|
+
const rootFiles = [".prettierrc", ".prettierignore", ".eslintrc.json"];
|
|
402
|
+
for (const f of rootFiles) {
|
|
403
|
+
const src = path.join(base, f);
|
|
404
|
+
const dest = path.join(target, f);
|
|
405
|
+
if (fs.existsSync(src)) {
|
|
406
|
+
fs.copyFileSync(src, dest);
|
|
407
|
+
} else if (!fs.existsSync(dest)) {
|
|
408
|
+
// create minimal defaults
|
|
409
|
+
if (f === ".prettierignore")
|
|
410
|
+
fs.writeFileSync(dest, "node_modules\n" + "dist\n");
|
|
411
|
+
else if (f === ".eslintrc.json")
|
|
412
|
+
fs.writeFileSync(dest, JSON.stringify({ root: true }, null, 2));
|
|
413
|
+
else fs.writeFileSync(dest, "{}");
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Write eslint.config.js with recommended workspace config (overwrite)
|
|
418
|
+
const eslintConfigPath = path.join(target, "eslint.config.js");
|
|
419
|
+
|
|
420
|
+
// Build dynamic project list for TypeScript projects based on the services present
|
|
421
|
+
const projectPaths = ["./tsconfig.json"];
|
|
422
|
+
try {
|
|
423
|
+
if (typeof allServices !== "undefined" && Array.isArray(allServices)) {
|
|
424
|
+
for (const svc of allServices) {
|
|
425
|
+
const svcTsPath = `./services/${svc}/tsconfig.json`;
|
|
426
|
+
if (fs.existsSync(path.join(target, svcTsPath))) {
|
|
427
|
+
projectPaths.push(svcTsPath);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
} catch (e) {
|
|
432
|
+
// non-fatal; fall back to default projectPaths containing only root tsconfig
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const projectEntries = projectPaths
|
|
436
|
+
.map((p) => ` "${p}",`)
|
|
437
|
+
.join("\n");
|
|
438
|
+
|
|
439
|
+
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`;
|
|
440
|
+
fs.writeFileSync(eslintConfigPath, eslintConfigContent);
|
|
441
|
+
|
|
442
|
+
// Remove per-service copies if they exist (already removed in setupService, but double-check)
|
|
443
|
+
const servicesDirPath = path.join(target, "services");
|
|
444
|
+
if (fs.existsSync(servicesDirPath)) {
|
|
445
|
+
const svcs = fs
|
|
446
|
+
.readdirSync(servicesDirPath)
|
|
447
|
+
.filter((f) =>
|
|
448
|
+
fs.statSync(path.join(servicesDirPath, f)).isDirectory(),
|
|
449
|
+
);
|
|
450
|
+
for (const svc of svcs) {
|
|
451
|
+
for (const f of [...rootFiles, "eslint.config.js"]) {
|
|
452
|
+
const p = path.join(servicesDirPath, svc, f);
|
|
453
|
+
if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
} catch (err) {
|
|
458
|
+
// non-fatal
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Step 3: Generate README and create root configuration files
|
|
462
|
+
if (!isInMicroserviceProject) {
|
|
463
|
+
console.log(`\n${pc.cyan("📝 Generating README.md...")}\n`);
|
|
464
|
+
const readmeContent = generateReadme(config);
|
|
465
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
466
|
+
|
|
467
|
+
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
223
468
|
for (const service of allServices) {
|
|
224
469
|
const gitignorePath = path.join(servicesDir, service, "gitignore");
|
|
225
470
|
const dotGitignorePath = path.join(servicesDir, service, ".gitignore");
|
|
@@ -227,35 +472,227 @@ if (!isInMicroserviceProject) {
|
|
|
227
472
|
fs.renameSync(gitignorePath, dotGitignorePath);
|
|
228
473
|
}
|
|
229
474
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
475
|
+
|
|
476
|
+
// Create root .gitignore for microservices
|
|
477
|
+
const rootGitignoreContent = `.env\nnode_modules\n`;
|
|
478
|
+
fs.writeFileSync(path.join(target, ".gitignore"), rootGitignoreContent);
|
|
479
|
+
|
|
480
|
+
// Create root .env and .env.example for microservices
|
|
481
|
+
let rootENVContent = `# Environment Configuration\nNODE_ENV=development\n\n`;
|
|
482
|
+
|
|
483
|
+
// Add port configuration for each service
|
|
484
|
+
allServices.forEach((service, index) => {
|
|
485
|
+
const isGateway = service === "gateway";
|
|
486
|
+
const port = isGateway
|
|
487
|
+
? 4000
|
|
488
|
+
: 4001 +
|
|
489
|
+
allServices.filter((s, i) => s !== "gateway" && i < index).length;
|
|
490
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
491
|
+
const serviceName = service
|
|
492
|
+
.split("-")
|
|
493
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
494
|
+
.join(" ");
|
|
495
|
+
rootENVContent += `# ${serviceName}\n${envVarName}=${port}\n\n`;
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
fs.writeFileSync(path.join(target, ".env"), rootENVContent);
|
|
499
|
+
fs.writeFileSync(path.join(target, ".env.example"), rootENVContent);
|
|
500
|
+
|
|
501
|
+
// Create root tsconfig.json for microservices workspace
|
|
502
|
+
const rootTsConfigContent = {
|
|
503
|
+
compilerOptions: {
|
|
504
|
+
target: "ES2020",
|
|
505
|
+
module: "CommonJS",
|
|
506
|
+
lib: ["ES2020"],
|
|
507
|
+
moduleResolution: "node",
|
|
508
|
+
esModuleInterop: true,
|
|
509
|
+
skipLibCheck: true,
|
|
510
|
+
strict: true,
|
|
511
|
+
baseUrl: ".",
|
|
512
|
+
paths: {
|
|
513
|
+
"@/*": ["./*"],
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
include: [],
|
|
517
|
+
exclude: ["node_modules", "dist"],
|
|
518
|
+
references: allServices.map((service) => ({
|
|
519
|
+
path: `./services/${service}`,
|
|
520
|
+
})),
|
|
521
|
+
};
|
|
522
|
+
fs.writeFileSync(
|
|
523
|
+
path.join(target, "tsconfig.json"),
|
|
524
|
+
JSON.stringify(rootTsConfigContent, null, 2) + "\n",
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// If we're adding a service into an existing microservice project,
|
|
529
|
+
// ensure shared config and gateway are updated to reference the new service.
|
|
530
|
+
if (isInMicroserviceProject) {
|
|
531
|
+
try {
|
|
532
|
+
const sharedConfigDir = path.join(target, "shared", "config");
|
|
533
|
+
const languageExt = config.language === "javascript" ? "js" : "ts";
|
|
534
|
+
const sharedEnvPath = path.join(sharedConfigDir, `env.${languageExt}`);
|
|
535
|
+
|
|
536
|
+
if (fs.existsSync(sharedEnvPath)) {
|
|
537
|
+
let envContent = fs.readFileSync(sharedEnvPath, "utf8");
|
|
538
|
+
|
|
539
|
+
// Build port environment variables for all services
|
|
540
|
+
const portEnvVars = allServices
|
|
541
|
+
.map((service) => {
|
|
542
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
543
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
544
|
+
return ` ${envVarName}: process.env.${envVarName}${assertion},`;
|
|
545
|
+
})
|
|
546
|
+
.join("\n");
|
|
547
|
+
|
|
548
|
+
// Remove any existing *_PORT lines to avoid duplication
|
|
549
|
+
envContent = envContent.replace(
|
|
550
|
+
/^[ \t]*[A-Z0-9_]+_PORT:\s*process\.env\.[A-Z0-9_]+!?\,?\s*$/gim,
|
|
551
|
+
"",
|
|
552
|
+
);
|
|
553
|
+
// Normalize multiple consecutive blank lines
|
|
554
|
+
envContent = envContent.replace(/\n{2,}/g, "\n\n");
|
|
555
|
+
|
|
556
|
+
// Attempt several fallback strategies to inject port variables:
|
|
557
|
+
// 1. Replace explicit placeholder if present in template
|
|
558
|
+
// 2. Insert right after the first object opening brace
|
|
559
|
+
// 3. Append to the end as a last resort
|
|
560
|
+
if (envContent.includes("/*__PORTS__*/")) {
|
|
561
|
+
envContent = envContent.replace("/*__PORTS__*/", portEnvVars);
|
|
562
|
+
} else {
|
|
563
|
+
// Fallback: find the opening brace of the exported ENV object and insert after it
|
|
564
|
+
const braceIndex = envContent.indexOf("{");
|
|
565
|
+
if (braceIndex !== -1) {
|
|
566
|
+
const insertPos =
|
|
567
|
+
envContent.indexOf("\n", braceIndex) + 1 || braceIndex + 1;
|
|
568
|
+
envContent =
|
|
569
|
+
envContent.slice(0, insertPos) +
|
|
570
|
+
portEnvVars +
|
|
571
|
+
"\n" +
|
|
572
|
+
envContent.slice(insertPos);
|
|
573
|
+
} else {
|
|
574
|
+
// Final fallback: append to the end
|
|
575
|
+
envContent = envContent + "\n" + portEnvVars;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
fs.writeFileSync(sharedEnvPath, envContent);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// Re-generate gateway routes if gateway exists (so new service gets proxied)
|
|
583
|
+
const gatewayRoot = path.join(target, "services", "gateway");
|
|
584
|
+
if (fs.existsSync(gatewayRoot)) {
|
|
585
|
+
// Re-run setupService for gateway to rewrite app/server/env files
|
|
586
|
+
await setupService(
|
|
587
|
+
config,
|
|
588
|
+
"gateway",
|
|
589
|
+
gatewayRoot,
|
|
590
|
+
config.auth,
|
|
591
|
+
allServices,
|
|
592
|
+
true,
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Update root .env and .env.example so newly added services have port entries
|
|
597
|
+
try {
|
|
598
|
+
const servicesDirPath = path.join(target, "services");
|
|
599
|
+
const svcList = fs.existsSync(servicesDirPath)
|
|
600
|
+
? fs
|
|
601
|
+
.readdirSync(servicesDirPath)
|
|
602
|
+
.filter((f) =>
|
|
603
|
+
fs.statSync(path.join(servicesDirPath, f)).isDirectory(),
|
|
604
|
+
)
|
|
605
|
+
: allServices;
|
|
606
|
+
|
|
607
|
+
let rootENVContent = `# Environment Configuration\nNODE_ENV=development\n\n`;
|
|
608
|
+
svcList.forEach((service, index) => {
|
|
609
|
+
const isGateway = service === "gateway";
|
|
610
|
+
const port = isGateway
|
|
611
|
+
? 4000
|
|
612
|
+
: 4001 +
|
|
613
|
+
svcList.filter((s, i) => s !== "gateway" && i < index).length;
|
|
614
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
615
|
+
const serviceNamePretty = service
|
|
616
|
+
.split("-")
|
|
617
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
618
|
+
.join(" ");
|
|
619
|
+
rootENVContent += `# ${serviceNamePretty}\n${envVarName}=${port}\n\n`;
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
fs.writeFileSync(path.join(target, ".env"), rootENVContent);
|
|
623
|
+
fs.writeFileSync(path.join(target, ".env.example"), rootENVContent);
|
|
624
|
+
} catch (e) {
|
|
625
|
+
// non-fatal
|
|
626
|
+
}
|
|
627
|
+
} catch (e) {
|
|
628
|
+
// non-fatal; continue even if updating shared/gateway fails
|
|
235
629
|
}
|
|
236
630
|
}
|
|
237
|
-
|
|
238
|
-
//
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
631
|
+
|
|
632
|
+
// Step 5: Install dependencies for all services
|
|
633
|
+
|
|
634
|
+
console.log(pc.cyan("\n📦 Installing dependencies for all services...\n"));
|
|
635
|
+
let allInstallsSucceeded = true;
|
|
636
|
+
|
|
637
|
+
for (const { serviceName, serviceRoot, deps, devDeps } of serviceConfigs) {
|
|
638
|
+
console.log(
|
|
639
|
+
pc.cyan(`\n📦 Installing dependencies for ${serviceName}...\n`),
|
|
244
640
|
);
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
if (deps.length) {
|
|
644
|
+
execSync(`npm install ${deps.join(" ")}`, {
|
|
645
|
+
cwd: serviceRoot,
|
|
646
|
+
stdio: "inherit",
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
if (devDeps.length) {
|
|
650
|
+
execSync(`npm install -D ${devDeps.join(" ")}`, {
|
|
651
|
+
cwd: serviceRoot,
|
|
652
|
+
stdio: "inherit",
|
|
653
|
+
});
|
|
251
654
|
}
|
|
655
|
+
execSync("npm install", { cwd: serviceRoot, stdio: "inherit" });
|
|
656
|
+
} catch (error) {
|
|
657
|
+
allInstallsSucceeded = false;
|
|
658
|
+
console.error(
|
|
659
|
+
pc.red(`\n❌ Failed to install dependencies for ${serviceName}`),
|
|
660
|
+
);
|
|
661
|
+
console.error(pc.dim(`\nYou can install them later by running:`));
|
|
662
|
+
console.error(pc.cyan(` cd services/${serviceName} && npm install\n`));
|
|
252
663
|
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Store for later use
|
|
667
|
+
config.allInstallsSucceeded = allInstallsSucceeded;
|
|
668
|
+
} else {
|
|
669
|
+
const result = await setupService(config, null, target, true);
|
|
670
|
+
config.installSucceeded = result.installSucceeded;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Generate README.md for monolith (microservices already done above)
|
|
674
|
+
if (!isInMicroserviceProject && config.projectType === "monolith") {
|
|
675
|
+
console.log(`\n${pc.cyan("📝 Generating README.md...")}\n`);
|
|
676
|
+
const readmeContent = generateReadme(config);
|
|
677
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
678
|
+
|
|
679
|
+
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
680
|
+
const gitignorePath = path.join(target, "gitignore");
|
|
681
|
+
const dotGitignorePath = path.join(target, ".gitignore");
|
|
682
|
+
if (fs.existsSync(gitignorePath)) {
|
|
683
|
+
fs.renameSync(gitignorePath, dotGitignorePath);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Generate .env from .env.example for monolith only
|
|
687
|
+
console.log(`${pc.cyan("📄 Setting up environment files...")}\n`);
|
|
688
|
+
try {
|
|
689
|
+
const rootEnvExamplePath = path.join(target, ".env.example");
|
|
690
|
+
const rootEnvPath = path.join(target, ".env");
|
|
691
|
+
if (fs.existsSync(rootEnvExamplePath) && !fs.existsSync(rootEnvPath)) {
|
|
692
|
+
fs.copyFileSync(rootEnvExamplePath, rootEnvPath);
|
|
258
693
|
}
|
|
694
|
+
} catch (err) {
|
|
695
|
+
// Non-fatal; proceed even if we fail to write env files
|
|
259
696
|
}
|
|
260
697
|
}
|
|
261
698
|
|
|
@@ -263,9 +700,9 @@ if (!isInMicroserviceProject) {
|
|
|
263
700
|
if (!isInMicroserviceProject) {
|
|
264
701
|
execSync("git init", { cwd: target, stdio: "inherit" });
|
|
265
702
|
|
|
266
|
-
// Install husky and setup at root level
|
|
703
|
+
// Install husky and other devDeps and setup at root level
|
|
267
704
|
if (config.projectType === "microservice") {
|
|
268
|
-
console.log("\n📦 Installing
|
|
705
|
+
console.log("\n📦 Installing dependencies at root level...\n");
|
|
269
706
|
if (config.allInstallsSucceeded) {
|
|
270
707
|
try {
|
|
271
708
|
execSync("npm install", { cwd: target, stdio: "inherit" });
|
|
@@ -274,8 +711,21 @@ if (!isInMicroserviceProject) {
|
|
|
274
711
|
} catch (error) {
|
|
275
712
|
console.log("\n⚠️ Husky setup failed\n");
|
|
276
713
|
}
|
|
714
|
+
// Run format after successful install
|
|
715
|
+
console.log(pc.cyan("\n🎨 Formatting code...\n"));
|
|
716
|
+
try {
|
|
717
|
+
execSync("npm run format", { cwd: target, stdio: "inherit" });
|
|
718
|
+
} catch (formatError) {
|
|
719
|
+
console.warn(
|
|
720
|
+
pc.yellow(
|
|
721
|
+
"⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n",
|
|
722
|
+
),
|
|
723
|
+
);
|
|
724
|
+
}
|
|
277
725
|
} else {
|
|
278
|
-
console.log(
|
|
726
|
+
console.log(
|
|
727
|
+
"\n⚠️ Husky setup skipped (run 'npm install && npm run prepare' after fixing service dependencies)\n",
|
|
728
|
+
);
|
|
279
729
|
}
|
|
280
730
|
} else if (config.projectType === "monolith") {
|
|
281
731
|
// Only setup Husky if installation succeeded
|
|
@@ -284,10 +734,14 @@ if (!isInMicroserviceProject) {
|
|
|
284
734
|
try {
|
|
285
735
|
execSync("npm run prepare", { cwd: target, stdio: "inherit" });
|
|
286
736
|
} catch (error) {
|
|
287
|
-
console.log(
|
|
737
|
+
console.log(
|
|
738
|
+
`\n${pc.yellow("⚠️ Husky setup failed")} ${pc.dim("(run 'npm run prepare' manually after fixing dependencies)")}\n`,
|
|
739
|
+
);
|
|
288
740
|
}
|
|
289
741
|
} else {
|
|
290
|
-
console.log(
|
|
742
|
+
console.log(
|
|
743
|
+
`\n${pc.yellow("⚠️ Husky setup skipped")} ${pc.dim("(run 'npm install && npm run prepare' to set up git hooks)")}\n`,
|
|
744
|
+
);
|
|
291
745
|
}
|
|
292
746
|
}
|
|
293
747
|
}
|
|
@@ -299,29 +753,76 @@ const allServices = fs.existsSync(servicesDir)
|
|
|
299
753
|
.readdirSync(servicesDir)
|
|
300
754
|
.filter((f) => fs.statSync(path.join(servicesDir, f)).isDirectory())
|
|
301
755
|
: servicesToCreate;
|
|
756
|
+
// Update root README when adding services to an existing microservice project
|
|
757
|
+
if (isInMicroserviceProject) {
|
|
758
|
+
try {
|
|
759
|
+
const readmeContent = generateReadme({ ...config, allServices });
|
|
760
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
761
|
+
console.log(`\n${pc.cyan("📝 Updated README.md with new services")}`);
|
|
762
|
+
} catch (e) {
|
|
763
|
+
// non-fatal
|
|
764
|
+
}
|
|
765
|
+
}
|
|
302
766
|
|
|
303
767
|
if (isInMicroserviceProject) {
|
|
304
|
-
console.log(
|
|
768
|
+
console.log(
|
|
769
|
+
`\n${pc.green("✅ Service")} ${pc.bold(servicesToCreate[0])} ${pc.green("added successfully!")}`,
|
|
770
|
+
);
|
|
305
771
|
console.log(`\n${pc.cyan("📦 All services:")} ${allServices.join(", ")}`);
|
|
306
772
|
console.log(`\n${pc.blue("💡 Next steps:")}`);
|
|
307
773
|
console.log(
|
|
308
774
|
mode === "docker"
|
|
309
|
-
? ` ${pc.dim("1.")} Start services: ${pc.bold("
|
|
310
|
-
: ` ${pc.dim("1.")} Start services: ${pc.bold("pm2 start pm2.config.js")}
|
|
775
|
+
? ` ${pc.dim("1.")} Start services: ${pc.bold("npm run dev")}`
|
|
776
|
+
: ` ${pc.dim("1.")} Start services: ${pc.bold("pm2 start pm2.config.js")}`,
|
|
311
777
|
);
|
|
312
778
|
} else if (config.projectType === "microservice") {
|
|
313
|
-
console.log(`\n${pc.green("✅ Backend created successfully!")}`);
|
|
314
|
-
console.log(
|
|
779
|
+
console.log(`\n${pc.green("✅ Microservice Backend created successfully!")}`);
|
|
780
|
+
console.log(
|
|
781
|
+
`\n${pc.cyan("📦 Created services:")} ${servicesToCreate.join(", ")}`,
|
|
782
|
+
);
|
|
315
783
|
console.log(`\n${pc.blue("💡 Next steps:")}`);
|
|
316
784
|
console.log(` ${pc.dim("1.")} cd ${pc.bold(sanitizedName)}`);
|
|
317
785
|
console.log(
|
|
318
786
|
mode === "docker"
|
|
319
|
-
? ` ${pc.dim("2.")} Start services: ${pc.bold("
|
|
320
|
-
: ` ${pc.dim("2.")} Start services: ${pc.bold("pm2 start pm2.config.js")}
|
|
787
|
+
? ` ${pc.dim("2.")} Start services: ${pc.bold("npm run dev")}`
|
|
788
|
+
: ` ${pc.dim("2.")} Start services: ${pc.bold("pm2 start pm2.config.js")}`,
|
|
321
789
|
);
|
|
322
790
|
} else {
|
|
323
|
-
console.log(`\n${pc.green("✅ Backend created successfully!")}`);
|
|
791
|
+
console.log(`\n${pc.green("✅ Monolith Backend created successfully!")}`);
|
|
324
792
|
console.log(`\n${pc.blue("💡 Next steps:")}`);
|
|
325
793
|
console.log(` ${pc.dim("1.")} cd ${pc.bold(sanitizedName)}`);
|
|
326
794
|
console.log(` ${pc.dim("2.")} npm run dev`);
|
|
327
795
|
}
|
|
796
|
+
// Post-processing: ensure shared config does not export/connect to DB when auth is disabled
|
|
797
|
+
try {
|
|
798
|
+
if (!config.auth) {
|
|
799
|
+
const sharedConfigDir = path.join(target, "shared", "config");
|
|
800
|
+
if (fs.existsSync(sharedConfigDir)) {
|
|
801
|
+
for (const ext of ["ts", "js"]) {
|
|
802
|
+
const idxPath = path.join(sharedConfigDir, `index.${ext}`);
|
|
803
|
+
if (!fs.existsSync(idxPath)) continue;
|
|
804
|
+
let idxContent = fs.readFileSync(idxPath, "utf8");
|
|
805
|
+
// Remove various connectDB export/import patterns
|
|
806
|
+
idxContent = idxContent.replace(
|
|
807
|
+
/export\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
808
|
+
"",
|
|
809
|
+
);
|
|
810
|
+
idxContent = idxContent.replace(
|
|
811
|
+
/import\s*\{\s*connectDB\s*\}\s*from\s*["']\.\/db["'];?/g,
|
|
812
|
+
"",
|
|
813
|
+
);
|
|
814
|
+
idxContent = idxContent.replace(
|
|
815
|
+
/const\s*\{\s*connectDB\s*\}\s*=\s*require\(["']\.\/db["']\);?/g,
|
|
816
|
+
"",
|
|
817
|
+
);
|
|
818
|
+
// Remove any remaining connectDB identifiers (commas/newlines)
|
|
819
|
+
idxContent = idxContent.replace(/connectDB,?/g, "");
|
|
820
|
+
// Normalize multiple blank lines
|
|
821
|
+
idxContent = idxContent.replace(/\n{3,}/g, "\n\n");
|
|
822
|
+
fs.writeFileSync(idxPath, idxContent);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
} catch (e) {
|
|
827
|
+
// non-fatal
|
|
828
|
+
}
|