@ifecodes/backend-template 1.1.3 → 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 +207 -80
- package/bin/lib/microservice-config.js +57 -13
- package/bin/lib/prompts.js +10 -6
- package/bin/lib/readme-generator.js +75 -14
- package/bin/lib/service-setup.js +272 -126
- 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 -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/{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 -342
- package/template/base/src/app.ts +0 -8
- 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/README.md
CHANGED
|
@@ -113,18 +113,23 @@ my-backend/
|
|
|
113
113
|
```
|
|
114
114
|
my-project/
|
|
115
115
|
├── shared/ # Shared utilities across services
|
|
116
|
-
│ ├── config/ #
|
|
116
|
+
│ ├── config/ # Environment configs (db.ts only if auth enabled)
|
|
117
117
|
│ └── utils/ # Logger, error handlers
|
|
118
118
|
├── services/
|
|
119
119
|
│ ├── gateway/ # API Gateway (port 4000)
|
|
120
120
|
│ ├── health-service/ # Health checks (port 4001)
|
|
121
|
-
│ └── auth-service/ # Authentication (port 4002)
|
|
121
|
+
│ └── auth-service/ # Authentication (port 4002, if enabled)
|
|
122
122
|
├── docker-compose.yml # Docker setup (if selected)
|
|
123
123
|
├── pm2.config.js # PM2 setup (if selected)
|
|
124
|
+
├── .env # Root environment variables
|
|
125
|
+
├── .gitignore # Git ignore (includes .env and node_modules)
|
|
126
|
+
├── tsconfig.json # Root TypeScript config with project references
|
|
124
127
|
├── .husky/ # Git hooks
|
|
125
128
|
└── package.json # Root package.json
|
|
126
129
|
```
|
|
127
130
|
|
|
131
|
+
**Note**: Each microservice does NOT have its own `.env` file. Environment variables are managed at the root level through `docker-compose.yml` or `pm2.config.js`.
|
|
132
|
+
|
|
128
133
|
---
|
|
129
134
|
|
|
130
135
|
## ▶️ Running the Application
|
|
@@ -194,9 +199,9 @@ pm2 stop all
|
|
|
194
199
|
|
|
195
200
|
---
|
|
196
201
|
|
|
197
|
-
##
|
|
202
|
+
## TypeScript vs JavaScript
|
|
198
203
|
|
|
199
|
-
This CLI generates **TypeScript** projects by default but
|
|
204
|
+
This CLI generates **TypeScript** projects by default but also includes explicit **JavaScript** templates. There is no fragile, on-the-fly TypeScript → JavaScript transform at runtime — the project templates include language-specific variants so the output is predictable and parseable in Node.js.
|
|
200
205
|
|
|
201
206
|
### TypeScript (Default)
|
|
202
207
|
|
|
@@ -207,12 +212,10 @@ This CLI generates **TypeScript** projects by default but fully supports **JavaS
|
|
|
207
212
|
|
|
208
213
|
### JavaScript
|
|
209
214
|
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
-
|
|
213
|
-
- Same functionality
|
|
214
|
-
|
|
215
|
-
**Note**: When selecting JavaScript, the CLI transforms the TypeScript template on-the-fly, ensuring you get a production-ready JavaScript project with all the same features.
|
|
215
|
+
- Pre-authored JavaScript (CommonJS) templates are included
|
|
216
|
+
- No TypeScript annotations remain in generated `.js` files
|
|
217
|
+
- DevDependencies that are TypeScript-only are omitted for JS projects
|
|
218
|
+
- Same functionality with simpler runtime setup
|
|
216
219
|
|
|
217
220
|
---
|
|
218
221
|
|
|
@@ -246,10 +249,10 @@ This CLI generates **TypeScript** projects by default but fully supports **JavaS
|
|
|
246
249
|
### Monolith
|
|
247
250
|
|
|
248
251
|
```
|
|
249
|
-
GET /
|
|
250
|
-
GET /v1/health - Health check
|
|
251
|
-
POST /v1/auth/register - Register user (if auth enabled)
|
|
252
|
-
POST /v1/auth/login - Login user (if auth enabled)
|
|
252
|
+
GET / - API information
|
|
253
|
+
GET /api/v1/health - Health check
|
|
254
|
+
POST /api/v1/auth/register - Register user (if auth enabled)
|
|
255
|
+
POST /api/v1/auth/login - Login user (if auth enabled)
|
|
253
256
|
```
|
|
254
257
|
|
|
255
258
|
### Microservice
|
|
@@ -257,22 +260,40 @@ POST /v1/auth/login - Login user (if auth enabled)
|
|
|
257
260
|
All requests go through the API Gateway at `http://localhost:4000`
|
|
258
261
|
|
|
259
262
|
```
|
|
260
|
-
GET /health
|
|
261
|
-
|
|
262
|
-
POST /auth/
|
|
263
|
+
GET /health - Gateway health check
|
|
264
|
+
GET /api/v1/health - Health service check
|
|
265
|
+
POST /api/v1/auth/register - Auth service (if enabled)
|
|
266
|
+
POST /api/v1/auth/login - Auth service (if enabled)
|
|
263
267
|
```
|
|
264
268
|
|
|
265
269
|
---
|
|
266
270
|
|
|
267
271
|
## 🔧 Environment Variables
|
|
268
272
|
|
|
269
|
-
###
|
|
273
|
+
### Monolith
|
|
270
274
|
|
|
271
275
|
```env
|
|
272
276
|
PORT=4000
|
|
273
277
|
NODE_ENV=development
|
|
274
278
|
```
|
|
275
279
|
|
|
280
|
+
### Microservice (Root .env)
|
|
281
|
+
|
|
282
|
+
```env
|
|
283
|
+
NODE_ENV=development
|
|
284
|
+
|
|
285
|
+
# Gateway Service
|
|
286
|
+
GATEWAY_PORT=4000
|
|
287
|
+
|
|
288
|
+
# Health Service
|
|
289
|
+
HEALTH_SERVICE_PORT=4001
|
|
290
|
+
|
|
291
|
+
# Auth Service (if enabled)
|
|
292
|
+
AUTH_SERVICE_PORT=4002
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Note**: Microservices use environment variables from `docker-compose.yml` or `pm2.config.js`. Individual services don't have `.env` files.
|
|
296
|
+
|
|
276
297
|
### With CORS
|
|
277
298
|
|
|
278
299
|
```env
|
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,10 @@ const {
|
|
|
26
27
|
isInMicroserviceProject,
|
|
27
28
|
} = config;
|
|
28
29
|
|
|
29
|
-
const
|
|
30
|
+
const baseRoot = (config.language === "javascript")
|
|
31
|
+
? path.join(__dirname, "../template/base/js")
|
|
32
|
+
: path.join(__dirname, "../template/base/ts");
|
|
33
|
+
const base = baseRoot;
|
|
30
34
|
|
|
31
35
|
// Determine which services to create
|
|
32
36
|
const servicesToCreate = [];
|
|
@@ -57,12 +61,23 @@ if (!isInMicroserviceProject && config.projectType === "microservice") {
|
|
|
57
61
|
}
|
|
58
62
|
fs.cpSync(base, target, { recursive: true });
|
|
59
63
|
|
|
60
|
-
//
|
|
61
|
-
if (config.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
// Remove db.ts from config if auth is not enabled
|
|
65
|
+
if (!config.auth) {
|
|
66
|
+
const dbPath = path.join(target, "src/config/db.ts");
|
|
67
|
+
if (fs.existsSync(dbPath)) {
|
|
68
|
+
fs.rmSync(dbPath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Update index.ts to not export connectDB
|
|
72
|
+
const indexPath = path.join(target, "src/config/index.ts");
|
|
73
|
+
if (fs.existsSync(indexPath)) {
|
|
74
|
+
let indexContent = fs.readFileSync(indexPath, "utf8");
|
|
75
|
+
indexContent = indexContent.replace('export { connectDB } from "./db";\n', '');
|
|
76
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
77
|
+
}
|
|
65
78
|
}
|
|
79
|
+
|
|
80
|
+
// No TypeScript-to-JavaScript conversion — templates include language-specific variants
|
|
66
81
|
} else if (isInMicroserviceProject) {
|
|
67
82
|
console.log(`\n${pc.cyan("🏗️ Adding service:")} ${pc.bold(servicesToCreate[0])}...\n`);
|
|
68
83
|
}
|
|
@@ -85,11 +100,53 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
85
100
|
fs.cpSync(baseConfigDir, sharedConfigDir, { recursive: true });
|
|
86
101
|
fs.cpSync(baseUtilsDir, sharedUtilsDir, { recursive: true });
|
|
87
102
|
|
|
103
|
+
// Remove db.ts from shared config if auth is not enabled
|
|
104
|
+
if (!config.auth) {
|
|
105
|
+
const sharedDbPath = path.join(sharedConfigDir, "db.ts");
|
|
106
|
+
if (fs.existsSync(sharedDbPath)) {
|
|
107
|
+
fs.rmSync(sharedDbPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Update index.ts to not export connectDB
|
|
111
|
+
const sharedIndexPath = path.join(sharedConfigDir, "index.ts");
|
|
112
|
+
if (fs.existsSync(sharedIndexPath)) {
|
|
113
|
+
let indexContent = fs.readFileSync(sharedIndexPath, "utf8");
|
|
114
|
+
indexContent = indexContent.replace('export { connectDB } from "./db";\n', '');
|
|
115
|
+
fs.writeFileSync(sharedIndexPath, indexContent);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update shared env.ts to include all service port environment variables
|
|
120
|
+
const sharedEnvPath = path.join(sharedConfigDir, "env.ts");
|
|
121
|
+
if (fs.existsSync(sharedEnvPath)) {
|
|
122
|
+
let envContent = fs.readFileSync(sharedEnvPath, "utf8");
|
|
123
|
+
|
|
124
|
+
// Build port environment variables for all services
|
|
125
|
+
const allServices = ["gateway", "health-service"];
|
|
126
|
+
if (config.auth) allServices.push("auth-service");
|
|
127
|
+
|
|
128
|
+
const portEnvVars = allServices.map((service) => {
|
|
129
|
+
const isGateway = service === "gateway";
|
|
130
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
131
|
+
// Don't add ! for JavaScript projects - it will cause syntax errors
|
|
132
|
+
const assertion = config.language === "javascript" ? "" : "!";
|
|
133
|
+
return ` ${envVarName}: process.env.${envVarName}${assertion},`;
|
|
134
|
+
}).join("\n");
|
|
135
|
+
|
|
136
|
+
// Replace PORT with service-specific ports
|
|
137
|
+
envContent = envContent.replace(
|
|
138
|
+
" PORT: process.env.PORT!,",
|
|
139
|
+
portEnvVars
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
fs.writeFileSync(sharedEnvPath, envContent);
|
|
143
|
+
}
|
|
144
|
+
|
|
88
145
|
// Create shared package.json
|
|
89
146
|
const sharedPackageJson = {
|
|
90
147
|
name: "@shared/common",
|
|
91
148
|
version: "1.0.0",
|
|
92
|
-
type: "
|
|
149
|
+
type: "commonjs",
|
|
93
150
|
exports: {
|
|
94
151
|
"./config/*": "./config/*",
|
|
95
152
|
"./utils/*": "./utils/*",
|
|
@@ -113,6 +170,12 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
113
170
|
console.log(`\n🔨 Setting up ${serviceName}...`);
|
|
114
171
|
fs.cpSync(base, serviceRoot, { recursive: true });
|
|
115
172
|
|
|
173
|
+
// Remove .env and .env.example from microservices (environment variables come from docker-compose/pm2)
|
|
174
|
+
const envPath = path.join(serviceRoot, ".env");
|
|
175
|
+
const envExamplePath = path.join(serviceRoot, ".env.example");
|
|
176
|
+
if (fs.existsSync(envPath)) fs.rmSync(envPath);
|
|
177
|
+
if (fs.existsSync(envExamplePath)) fs.rmSync(envExamplePath);
|
|
178
|
+
|
|
116
179
|
// Remove config and utils from service (they'll use shared) - except gateway handles it differently
|
|
117
180
|
if (serviceName !== "gateway") {
|
|
118
181
|
const serviceConfigDir = path.join(serviceRoot, "src", "config");
|
|
@@ -132,10 +195,10 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
132
195
|
.filter((f) => fs.statSync(path.join(servicesDir, f)).isDirectory())
|
|
133
196
|
: servicesToCreate;
|
|
134
197
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
198
|
+
// Step 1: Setup all service files first (without installing dependencies)
|
|
199
|
+
console.log(pc.cyan("\n⚙️ Setting up service files...\n"));
|
|
200
|
+
const serviceConfigs = [];
|
|
201
|
+
|
|
139
202
|
for (const serviceName of servicesToCreate) {
|
|
140
203
|
const serviceRoot = path.join(target, "services", serviceName);
|
|
141
204
|
const shouldIncludeAuth = isInMicroserviceProject
|
|
@@ -146,39 +209,22 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
146
209
|
serviceName,
|
|
147
210
|
serviceRoot,
|
|
148
211
|
shouldIncludeAuth,
|
|
149
|
-
allServices
|
|
212
|
+
allServices,
|
|
213
|
+
true // Skip install for now
|
|
150
214
|
);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
config.allInstallsSucceeded = allInstallsSucceeded;
|
|
158
|
-
|
|
159
|
-
// Transform to JavaScript if selected (for microservices)
|
|
160
|
-
if (config.language === "javascript") {
|
|
161
|
-
console.log(`\n${pc.cyan("⚙️ Converting microservices to JavaScript...")}\n`);
|
|
162
|
-
|
|
163
|
-
// Transform shared folder
|
|
164
|
-
const sharedDir = path.join(target, "shared");
|
|
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);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log(pc.green("✓ JavaScript transformation complete\n"));
|
|
215
|
+
serviceConfigs.push({
|
|
216
|
+
serviceName,
|
|
217
|
+
serviceRoot,
|
|
218
|
+
deps: result.deps,
|
|
219
|
+
devDeps: result.devDeps
|
|
220
|
+
});
|
|
177
221
|
}
|
|
178
222
|
|
|
223
|
+
// Step 2: Generate docker-compose/pm2 config and root files
|
|
179
224
|
if (mode === "docker") {
|
|
180
225
|
generateDockerCompose(target, allServices);
|
|
181
226
|
copyDockerfile(target, servicesToCreate);
|
|
227
|
+
copyDockerignore(target, servicesToCreate);
|
|
182
228
|
} else {
|
|
183
229
|
generatePm2Config(target, allServices);
|
|
184
230
|
}
|
|
@@ -202,24 +248,14 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
202
248
|
JSON.stringify(rootPackageJson, null, 2) + "\n"
|
|
203
249
|
);
|
|
204
250
|
}
|
|
205
|
-
} else {
|
|
206
|
-
const result = await setupService(config, null, target, true);
|
|
207
|
-
config.installSucceeded = result.installSucceeded;
|
|
208
|
-
}
|
|
209
251
|
|
|
210
|
-
// Generate README
|
|
211
|
-
if (!isInMicroserviceProject) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
217
|
-
if (config.projectType === "microservice") {
|
|
218
|
-
const servicesDir = path.join(target, "services");
|
|
219
|
-
const allServices = fs.readdirSync(servicesDir).filter((f) =>
|
|
220
|
-
fs.statSync(path.join(servicesDir, f)).isDirectory()
|
|
221
|
-
);
|
|
252
|
+
// Step 3: Generate README and create root configuration files
|
|
253
|
+
if (!isInMicroserviceProject) {
|
|
254
|
+
console.log(`\n${pc.cyan("📝 Generating README.md...")}\n`);
|
|
255
|
+
const readmeContent = generateReadme(config);
|
|
256
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
222
257
|
|
|
258
|
+
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
223
259
|
for (const service of allServices) {
|
|
224
260
|
const gitignorePath = path.join(servicesDir, service, "gitignore");
|
|
225
261
|
const dotGitignorePath = path.join(servicesDir, service, ".gitignore");
|
|
@@ -227,35 +263,126 @@ if (!isInMicroserviceProject) {
|
|
|
227
263
|
fs.renameSync(gitignorePath, dotGitignorePath);
|
|
228
264
|
}
|
|
229
265
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
266
|
+
|
|
267
|
+
// Create root .gitignore for microservices
|
|
268
|
+
const rootGitignoreContent = `.env\nnode_modules\n`;
|
|
269
|
+
fs.writeFileSync(path.join(target, ".gitignore"), rootGitignoreContent);
|
|
270
|
+
|
|
271
|
+
// Create root .env and .env.example for microservices
|
|
272
|
+
let rootENVContent = `# Environment Configuration\nNODE_ENV=development\n\n`;
|
|
273
|
+
|
|
274
|
+
// Add port configuration for each service
|
|
275
|
+
allServices.forEach((service, index) => {
|
|
276
|
+
const isGateway = service === "gateway";
|
|
277
|
+
const port = isGateway ? 4000 : 4001 + allServices.filter((s, i) => s !== "gateway" && i < index).length;
|
|
278
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
279
|
+
const serviceName = service.split("-").map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
280
|
+
rootENVContent += `# ${serviceName}\n${envVarName}=${port}\n\n`;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
fs.writeFileSync(path.join(target, ".env"), rootENVContent);
|
|
284
|
+
fs.writeFileSync(path.join(target, ".env.example"), rootENVContent);
|
|
285
|
+
|
|
286
|
+
// Create root tsconfig.json for microservices workspace
|
|
287
|
+
const rootTsConfigContent = {
|
|
288
|
+
"compilerOptions": {
|
|
289
|
+
"target": "ES2020",
|
|
290
|
+
"module": "CommonJS",
|
|
291
|
+
"lib": ["ES2020"],
|
|
292
|
+
"moduleResolution": "node",
|
|
293
|
+
"esModuleInterop": true,
|
|
294
|
+
"skipLibCheck": true,
|
|
295
|
+
"strict": true,
|
|
296
|
+
"baseUrl": ".",
|
|
297
|
+
"paths": {
|
|
298
|
+
"@/*": ["./*"]
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
"include": [],
|
|
302
|
+
"references": allServices.map(service => ({
|
|
303
|
+
"path": `./services/${service}`
|
|
304
|
+
}))
|
|
305
|
+
};
|
|
306
|
+
fs.writeFileSync(
|
|
307
|
+
path.join(target, "tsconfig.json"),
|
|
308
|
+
JSON.stringify(rootTsConfigContent, null, 2) + "\n"
|
|
244
309
|
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Step 5: Install dependencies for all services
|
|
313
|
+
|
|
314
|
+
console.log(pc.cyan("\n📦 Installing dependencies for all services...\n"));
|
|
315
|
+
let allInstallsSucceeded = true;
|
|
316
|
+
|
|
317
|
+
for (const { serviceName, serviceRoot, deps, devDeps } of serviceConfigs) {
|
|
318
|
+
console.log(pc.cyan(`\n📦 Installing dependencies for ${serviceName}...\n`));
|
|
245
319
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
320
|
+
try {
|
|
321
|
+
if (deps.length) {
|
|
322
|
+
execSync(`npm install ${deps.join(" ")}`, {
|
|
323
|
+
cwd: serviceRoot,
|
|
324
|
+
stdio: "inherit",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (devDeps.length) {
|
|
328
|
+
execSync(`npm install -D ${devDeps.join(" ")}`, {
|
|
329
|
+
cwd: serviceRoot,
|
|
330
|
+
stdio: "inherit",
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
execSync("npm install", { cwd: serviceRoot, stdio: "inherit" });
|
|
334
|
+
|
|
335
|
+
// Run format after successful install
|
|
336
|
+
console.log(pc.cyan("\n🎨 Formatting code...\n"));
|
|
337
|
+
try {
|
|
338
|
+
execSync("npm run format", { cwd: serviceRoot, stdio: "inherit" });
|
|
339
|
+
} catch (formatError) {
|
|
340
|
+
console.warn(
|
|
341
|
+
pc.yellow(
|
|
342
|
+
"⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n",
|
|
343
|
+
),
|
|
344
|
+
);
|
|
251
345
|
}
|
|
346
|
+
} catch (error) {
|
|
347
|
+
allInstallsSucceeded = false;
|
|
348
|
+
console.error(
|
|
349
|
+
pc.red(`\n❌ Failed to install dependencies for ${serviceName}`),
|
|
350
|
+
);
|
|
351
|
+
console.error(pc.dim(`\nYou can install them later by running:`));
|
|
352
|
+
console.error(pc.cyan(` cd services/${serviceName} && npm install\n`));
|
|
252
353
|
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Store for later use
|
|
357
|
+
config.allInstallsSucceeded = allInstallsSucceeded;
|
|
358
|
+
} else {
|
|
359
|
+
const result = await setupService(config, null, target, true);
|
|
360
|
+
config.installSucceeded = result.installSucceeded;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Generate README.md for monolith (microservices already done above)
|
|
364
|
+
if (!isInMicroserviceProject && config.projectType === "monolith") {
|
|
365
|
+
console.log(`\n${pc.cyan("📝 Generating README.md...")}\n`);
|
|
366
|
+
const readmeContent = generateReadme(config);
|
|
367
|
+
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
368
|
+
|
|
369
|
+
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
370
|
+
const gitignorePath = path.join(target, "gitignore");
|
|
371
|
+
const dotGitignorePath = path.join(target, ".gitignore");
|
|
372
|
+
if (fs.existsSync(gitignorePath)) {
|
|
373
|
+
fs.renameSync(gitignorePath, dotGitignorePath);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Generate .env from .env.example for monolith only
|
|
377
|
+
console.log(`${pc.cyan("📄 Setting up environment files...")}\n`);
|
|
378
|
+
try {
|
|
379
|
+
const rootEnvExamplePath = path.join(target, ".env.example");
|
|
380
|
+
const rootEnvPath = path.join(target, ".env");
|
|
381
|
+
if (fs.existsSync(rootEnvExamplePath) && !fs.existsSync(rootEnvPath)) {
|
|
382
|
+
fs.copyFileSync(rootEnvExamplePath, rootEnvPath);
|
|
258
383
|
}
|
|
384
|
+
} catch (err) {
|
|
385
|
+
// Non-fatal; proceed even if we fail to write env files
|
|
259
386
|
}
|
|
260
387
|
}
|
|
261
388
|
|
|
@@ -6,38 +6,63 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
6
6
|
|
|
7
7
|
export const generateDockerCompose = (target, allServices) => {
|
|
8
8
|
const dockerCompose = {
|
|
9
|
-
version: "3.8",
|
|
10
9
|
services: {},
|
|
11
10
|
};
|
|
12
11
|
|
|
12
|
+
// Build environment variables map for all services
|
|
13
|
+
const allServicePorts = allServices.map((service, index) => {
|
|
14
|
+
const isGateway = service === "gateway";
|
|
15
|
+
const port = isGateway ? 4000 : 4001 + allServices.filter((s, i) => s !== "gateway" && i < index).length;
|
|
16
|
+
const envVarName = `${service.toUpperCase().replace(/-/g, "_")}_PORT`;
|
|
17
|
+
return { service, port, envVarName };
|
|
18
|
+
});
|
|
19
|
+
|
|
13
20
|
for (const serviceName of allServices) {
|
|
14
21
|
// Gateway runs on 4000, other services start from 4001
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
22
|
+
const serviceInfo = allServicePorts.find(s => s.service === serviceName);
|
|
23
|
+
const port = serviceInfo.port;
|
|
24
|
+
const envVarName = serviceInfo.envVarName;
|
|
25
|
+
|
|
26
|
+
// Build environment variables array - include all service ports
|
|
27
|
+
const environmentVars = [
|
|
28
|
+
`NODE_ENV=\${NODE_ENV:-development}`,
|
|
29
|
+
...allServicePorts.map(s => `${s.envVarName}=\${${s.envVarName}:-${s.port}}`)
|
|
30
|
+
];
|
|
18
31
|
|
|
19
32
|
dockerCompose.services[serviceName] = {
|
|
20
|
-
build:
|
|
33
|
+
build: {
|
|
34
|
+
context: `./services/${serviceName}`,
|
|
35
|
+
dockerfile: "Dockerfile"
|
|
36
|
+
},
|
|
37
|
+
image: `${serviceName}:latest`,
|
|
38
|
+
container_name: serviceName,
|
|
21
39
|
ports: [
|
|
22
|
-
`\${${
|
|
23
|
-
|
|
24
|
-
|
|
40
|
+
`\${${envVarName}:-${port}}:\${${envVarName}:-${port}}`,
|
|
41
|
+
],
|
|
42
|
+
environment: environmentVars,
|
|
43
|
+
volumes: [
|
|
44
|
+
`./services/${serviceName}:/app`,
|
|
45
|
+
`./shared:/app/shared`,
|
|
46
|
+
`/app/node_modules`
|
|
25
47
|
],
|
|
26
|
-
environment: [`NODE_ENV=\${NODE_ENV:-development}`],
|
|
27
|
-
volumes: [`./services/${serviceName}:/app`, `/app/node_modules`],
|
|
28
48
|
};
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
fs.writeFileSync(
|
|
32
52
|
path.join(target, "docker-compose.yml"),
|
|
33
|
-
`
|
|
53
|
+
`services:\n` +
|
|
34
54
|
Object.entries(dockerCompose.services)
|
|
35
55
|
.map(
|
|
36
56
|
([name, config]) =>
|
|
37
57
|
` ${name}:\n` +
|
|
38
|
-
` build
|
|
58
|
+
` build:\n` +
|
|
59
|
+
` context: ${config.build.context}\n` +
|
|
60
|
+
` dockerfile: ${config.build.dockerfile}\n` +
|
|
61
|
+
` image: ${config.image}\n` +
|
|
62
|
+
` container_name: ${config.container_name}\n` +
|
|
39
63
|
` ports:\n - "${config.ports[0]}"\n` +
|
|
40
|
-
` environment:\n
|
|
64
|
+
` environment:\n` +
|
|
65
|
+
config.environment.map((e) => ` - ${e}`).join("\n") + "\n" +
|
|
41
66
|
` volumes:\n` +
|
|
42
67
|
config.volumes.map((v) => ` - ${v}`).join("\n")
|
|
43
68
|
)
|
|
@@ -87,3 +112,22 @@ export const copyDockerfile = (target, servicesToCreate) => {
|
|
|
87
112
|
}
|
|
88
113
|
}
|
|
89
114
|
};
|
|
115
|
+
|
|
116
|
+
export const copyDockerignore = (target, servicesToCreate) => {
|
|
117
|
+
const dockerignorePath = path.join(
|
|
118
|
+
__dirname,
|
|
119
|
+
"../../template/microservice/docker/.dockerignore"
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
for (const serviceName of servicesToCreate) {
|
|
123
|
+
const serviceDockerignore = path.join(
|
|
124
|
+
target,
|
|
125
|
+
"services",
|
|
126
|
+
serviceName,
|
|
127
|
+
".dockerignore"
|
|
128
|
+
);
|
|
129
|
+
if (!fs.existsSync(serviceDockerignore)) {
|
|
130
|
+
fs.copyFileSync(dockerignorePath, serviceDockerignore);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
package/bin/lib/prompts.js
CHANGED
|
@@ -86,12 +86,16 @@ export const getProjectConfig = async () => {
|
|
|
86
86
|
],
|
|
87
87
|
},
|
|
88
88
|
{
|
|
89
|
-
type: (prev, values) =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
type: (prev, values) => {
|
|
90
|
+
// Skip if in existing microservice project or CI mode
|
|
91
|
+
if (isInMicroserviceProject || isCI) return null;
|
|
92
|
+
|
|
93
|
+
// Get projectType from previous answers or CLI args
|
|
94
|
+
const projectType = prev || cliProjectType || values.projectType;
|
|
95
|
+
|
|
96
|
+
// Show prompt only if projectType is microservice
|
|
97
|
+
return projectType === "microservice" ? "select" : null;
|
|
98
|
+
},
|
|
95
99
|
name: "mode",
|
|
96
100
|
message: pc.cyan("Microservice setup"),
|
|
97
101
|
choices: [
|