@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/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
|
@@ -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,105 +61,25 @@ if (!isInMicroserviceProject && config.projectType === "microservice") {
|
|
|
57
61
|
}
|
|
58
62
|
fs.cpSync(base, target, { recursive: true });
|
|
59
63
|
|
|
60
|
-
//
|
|
61
|
-
if (config.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} else if (isInMicroserviceProject) {
|
|
67
|
-
console.log(`\n${pc.cyan("🏗️ Adding service:")} ${pc.bold(servicesToCreate[0])}...\n`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Helper function to transform TypeScript project to JavaScript
|
|
71
|
-
function transformToJavaScript(projectRoot) {
|
|
72
|
-
// Recursively find and transform all .ts files
|
|
73
|
-
function transformDirectory(dir) {
|
|
74
|
-
const items = fs.readdirSync(dir);
|
|
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
|
+
}
|
|
75
70
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (item !== 'node_modules' && item !== 'dist') {
|
|
83
|
-
transformDirectory(fullPath);
|
|
84
|
-
}
|
|
85
|
-
} else if (item.endsWith('.ts') && !item.endsWith('.d.ts')) {
|
|
86
|
-
// Transform TypeScript file to JavaScript
|
|
87
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
88
|
-
const jsContent = stripTypeScript(content);
|
|
89
|
-
const jsPath = fullPath.replace(/\.ts$/, '.js');
|
|
90
|
-
|
|
91
|
-
fs.writeFileSync(jsPath, jsContent);
|
|
92
|
-
fs.unlinkSync(fullPath); // Remove original .ts file
|
|
93
|
-
}
|
|
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);
|
|
94
77
|
}
|
|
95
78
|
}
|
|
96
79
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
|
|
101
|
-
if (fs.existsSync(tsconfigPath)) {
|
|
102
|
-
fs.unlinkSync(tsconfigPath);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Remove .eslintrc.json and replace with JS version
|
|
106
|
-
const eslintrcPath = path.join(projectRoot, '.eslintrc.json');
|
|
107
|
-
if (fs.existsSync(eslintrcPath)) {
|
|
108
|
-
fs.unlinkSync(eslintrcPath);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Update package.json
|
|
112
|
-
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
113
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
114
|
-
|
|
115
|
-
// Update scripts
|
|
116
|
-
packageJson.scripts = {
|
|
117
|
-
...packageJson.scripts,
|
|
118
|
-
...getJavaScriptScripts()
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// Update type
|
|
122
|
-
packageJson.type = "module";
|
|
123
|
-
|
|
124
|
-
// Remove main field
|
|
125
|
-
delete packageJson.main;
|
|
126
|
-
|
|
127
|
-
// Update dependencies
|
|
128
|
-
const { dependencies, devDependencies } = getJavaScriptDependencies(
|
|
129
|
-
packageJson.dependencies,
|
|
130
|
-
packageJson.devDependencies
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
packageJson.dependencies = dependencies;
|
|
134
|
-
packageJson.devDependencies = devDependencies;
|
|
135
|
-
|
|
136
|
-
// Remove _moduleAliases (not needed for ES modules with import maps)
|
|
137
|
-
delete packageJson._moduleAliases;
|
|
138
|
-
|
|
139
|
-
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
|
|
140
|
-
|
|
141
|
-
// Create simple .eslintrc.json for JavaScript
|
|
142
|
-
const eslintConfig = {
|
|
143
|
-
env: {
|
|
144
|
-
node: true,
|
|
145
|
-
es2021: true
|
|
146
|
-
},
|
|
147
|
-
extends: ["eslint:recommended", "prettier"],
|
|
148
|
-
parserOptions: {
|
|
149
|
-
ecmaVersion: "latest",
|
|
150
|
-
sourceType: "module"
|
|
151
|
-
},
|
|
152
|
-
rules: {}
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
fs.writeFileSync(
|
|
156
|
-
path.join(projectRoot, '.eslintrc.json'),
|
|
157
|
-
JSON.stringify(eslintConfig, null, 2) + '\n'
|
|
158
|
-
);
|
|
80
|
+
// No TypeScript-to-JavaScript conversion — templates include language-specific variants
|
|
81
|
+
} else if (isInMicroserviceProject) {
|
|
82
|
+
console.log(`\n${pc.cyan("🏗️ Adding service:")} ${pc.bold(servicesToCreate[0])}...\n`);
|
|
159
83
|
}
|
|
160
84
|
|
|
161
85
|
// Process services
|
|
@@ -176,11 +100,53 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
176
100
|
fs.cpSync(baseConfigDir, sharedConfigDir, { recursive: true });
|
|
177
101
|
fs.cpSync(baseUtilsDir, sharedUtilsDir, { recursive: true });
|
|
178
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
|
+
|
|
179
145
|
// Create shared package.json
|
|
180
146
|
const sharedPackageJson = {
|
|
181
147
|
name: "@shared/common",
|
|
182
148
|
version: "1.0.0",
|
|
183
|
-
type: "
|
|
149
|
+
type: "commonjs",
|
|
184
150
|
exports: {
|
|
185
151
|
"./config/*": "./config/*",
|
|
186
152
|
"./utils/*": "./utils/*",
|
|
@@ -204,6 +170,12 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
204
170
|
console.log(`\n🔨 Setting up ${serviceName}...`);
|
|
205
171
|
fs.cpSync(base, serviceRoot, { recursive: true });
|
|
206
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
|
+
|
|
207
179
|
// Remove config and utils from service (they'll use shared) - except gateway handles it differently
|
|
208
180
|
if (serviceName !== "gateway") {
|
|
209
181
|
const serviceConfigDir = path.join(serviceRoot, "src", "config");
|
|
@@ -223,10 +195,10 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
223
195
|
.filter((f) => fs.statSync(path.join(servicesDir, f)).isDirectory())
|
|
224
196
|
: servicesToCreate;
|
|
225
197
|
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
+
|
|
230
202
|
for (const serviceName of servicesToCreate) {
|
|
231
203
|
const serviceRoot = path.join(target, "services", serviceName);
|
|
232
204
|
const shouldIncludeAuth = isInMicroserviceProject
|
|
@@ -237,19 +209,22 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
237
209
|
serviceName,
|
|
238
210
|
serviceRoot,
|
|
239
211
|
shouldIncludeAuth,
|
|
240
|
-
allServices
|
|
212
|
+
allServices,
|
|
213
|
+
true // Skip install for now
|
|
241
214
|
);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
215
|
+
serviceConfigs.push({
|
|
216
|
+
serviceName,
|
|
217
|
+
serviceRoot,
|
|
218
|
+
deps: result.deps,
|
|
219
|
+
devDeps: result.devDeps
|
|
220
|
+
});
|
|
245
221
|
}
|
|
246
222
|
|
|
247
|
-
//
|
|
248
|
-
config.allInstallsSucceeded = allInstallsSucceeded;
|
|
249
|
-
|
|
223
|
+
// Step 2: Generate docker-compose/pm2 config and root files
|
|
250
224
|
if (mode === "docker") {
|
|
251
225
|
generateDockerCompose(target, allServices);
|
|
252
226
|
copyDockerfile(target, servicesToCreate);
|
|
227
|
+
copyDockerignore(target, servicesToCreate);
|
|
253
228
|
} else {
|
|
254
229
|
generatePm2Config(target, allServices);
|
|
255
230
|
}
|
|
@@ -273,24 +248,14 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
273
248
|
JSON.stringify(rootPackageJson, null, 2) + "\n"
|
|
274
249
|
);
|
|
275
250
|
}
|
|
276
|
-
} else {
|
|
277
|
-
const result = await setupService(config, null, target, true);
|
|
278
|
-
config.installSucceeded = result.installSucceeded;
|
|
279
|
-
}
|
|
280
251
|
|
|
281
|
-
// Generate README
|
|
282
|
-
if (!isInMicroserviceProject) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
288
|
-
if (config.projectType === "microservice") {
|
|
289
|
-
const servicesDir = path.join(target, "services");
|
|
290
|
-
const allServices = fs.readdirSync(servicesDir).filter((f) =>
|
|
291
|
-
fs.statSync(path.join(servicesDir, f)).isDirectory()
|
|
292
|
-
);
|
|
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);
|
|
293
257
|
|
|
258
|
+
// Rename gitignore to .gitignore (npm doesn't publish .gitignore files)
|
|
294
259
|
for (const service of allServices) {
|
|
295
260
|
const gitignorePath = path.join(servicesDir, service, "gitignore");
|
|
296
261
|
const dotGitignorePath = path.join(servicesDir, service, ".gitignore");
|
|
@@ -298,35 +263,126 @@ if (!isInMicroserviceProject) {
|
|
|
298
263
|
fs.renameSync(gitignorePath, dotGitignorePath);
|
|
299
264
|
}
|
|
300
265
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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"
|
|
315
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`));
|
|
316
319
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
try {
|
|
321
|
+
if (deps.length) {
|
|
322
|
+
execSync(`npm install ${deps.join(" ")}`, {
|
|
323
|
+
cwd: serviceRoot,
|
|
324
|
+
stdio: "inherit",
|
|
325
|
+
});
|
|
322
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
|
+
);
|
|
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`));
|
|
323
353
|
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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);
|
|
329
383
|
}
|
|
384
|
+
} catch (err) {
|
|
385
|
+
// Non-fatal; proceed even if we fail to write env files
|
|
330
386
|
}
|
|
331
387
|
}
|
|
332
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
|
+
};
|