@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.
Files changed (86) hide show
  1. package/README.md +39 -18
  2. package/bin/cli.js +205 -149
  3. package/bin/lib/microservice-config.js +57 -13
  4. package/bin/lib/prompts.js +42 -11
  5. package/bin/lib/readme-generator.js +75 -14
  6. package/bin/lib/service-setup.js +277 -123
  7. package/package.json +1 -1
  8. package/template/base/js/.eslintrc.json +13 -0
  9. package/template/base/js/.prettierrc +7 -0
  10. package/template/base/js/eslint.config.js +31 -0
  11. package/template/base/js/package.json +28 -0
  12. package/template/base/js/src/app.js +15 -0
  13. package/template/base/js/src/config/db.js +8 -0
  14. package/template/base/js/src/config/env.js +14 -0
  15. package/template/base/js/src/config/index.js +7 -0
  16. package/template/base/js/src/middlewares/index.js +9 -0
  17. package/template/base/js/src/middlewares/method-not-allowed.middleware.js +13 -0
  18. package/template/base/js/src/middlewares/not-found.middleware.js +10 -0
  19. package/template/base/js/src/middlewares/root.middleware.js +16 -0
  20. package/template/base/js/src/modules/index.js +8 -0
  21. package/template/base/js/src/modules/v1/health/health.controller.js +21 -0
  22. package/template/base/js/src/modules/v1/health/health.route.js +9 -0
  23. package/template/base/js/src/modules/v1/health/index.js +5 -0
  24. package/template/base/js/src/modules/v1/index.js +8 -0
  25. package/template/base/js/src/routes.js +16 -0
  26. package/template/base/js/src/server.js +18 -0
  27. package/template/base/js/src/utils/http-error.js +53 -0
  28. package/template/base/js/src/utils/index.js +22 -0
  29. package/template/base/js/src/utils/logger.js +67 -0
  30. package/template/base/ts/.env.example +5 -0
  31. package/template/base/ts/.husky/pre-commit +13 -0
  32. package/template/base/ts/.prettierignore +47 -0
  33. package/template/base/ts/gitignore +31 -0
  34. package/template/base/ts/src/app.ts +15 -0
  35. package/template/base/{src → ts/src}/config/env.ts +10 -10
  36. package/template/base/{src → ts/src}/middlewares/index.ts +3 -3
  37. package/template/base/{src → ts/src}/middlewares/method-not-allowed.middleware.ts +16 -17
  38. package/template/base/{src → ts/src}/middlewares/root.middleware.ts +2 -2
  39. package/template/base/{src → ts/src}/modules/v1/health/health.controller.ts +18 -18
  40. package/template/base/{src → ts/src}/modules/v1/health/health.route.ts +9 -9
  41. package/template/base/{src → ts/src}/modules/v1/health/index.ts +1 -1
  42. package/template/base/{src → ts/src}/modules/v1/index.ts +8 -8
  43. package/template/base/{src → ts/src}/routes.ts +15 -15
  44. package/template/base/{src → ts/src}/utils/http-error.ts +45 -47
  45. package/template/base/ts/src/utils/logger.ts +68 -0
  46. package/template/features/auth/argon2/inject.js +29 -4
  47. package/template/features/auth/base/inject.js +108 -26
  48. package/template/features/auth/bcrypt/inject.js +25 -5
  49. package/template/features/auth/models/user.model.js +24 -0
  50. package/template/features/auth/modules/auth.controller.js +21 -0
  51. package/template/features/auth/modules/auth.routes.js +10 -0
  52. package/template/features/auth/modules/auth.service.js +29 -0
  53. package/template/features/auth/modules/index.js +1 -0
  54. package/template/features/auth/utils/jwt.js +12 -0
  55. package/template/features/cors/inject.js +4 -1
  56. package/template/features/helmet/inject.js +4 -1
  57. package/template/features/morgan/inject.js +4 -1
  58. package/template/features/rate-limit/inject.js +4 -1
  59. package/template/gateway/js/app.js +42 -0
  60. package/template/gateway/js/inject.js +31 -0
  61. package/template/gateway/js/server.js +19 -0
  62. package/template/gateway/{app.ts → ts/app.ts} +19 -3
  63. package/template/gateway/ts/inject.js +31 -0
  64. package/template/gateway/{server.ts → ts/server.ts} +3 -3
  65. package/template/microservice/docker/.dockerignore +10 -0
  66. package/template/microservice/docker/Dockerfile +2 -1
  67. package/template/microservice/docker/docker-compose.yml +0 -1
  68. package/bin/lib/ts-to-js.js +0 -123
  69. package/template/base/src/app.ts +0 -8
  70. package/template/base/src/utils/logger.ts +0 -35
  71. package/template/gateway/inject.js +0 -27
  72. /package/template/base/{.env.example → js/.env.example} +0 -0
  73. /package/template/base/{.husky → js/.husky}/pre-commit +0 -0
  74. /package/template/base/{.prettierignore → js/.prettierignore} +0 -0
  75. /package/template/base/{gitignore → js/gitignore} +0 -0
  76. /package/template/base/{.eslintrc.json → ts/.eslintrc.json} +0 -0
  77. /package/template/base/{.prettierrc → ts/.prettierrc} +0 -0
  78. /package/template/base/{eslint.config.js → ts/eslint.config.js} +0 -0
  79. /package/template/base/{package.json → ts/package.json} +0 -0
  80. /package/template/base/{src → ts/src}/config/db.ts +0 -0
  81. /package/template/base/{src → ts/src}/config/index.ts +0 -0
  82. /package/template/base/{src → ts/src}/middlewares/not-found.middleware.ts +0 -0
  83. /package/template/base/{src → ts/src}/modules/index.ts +0 -0
  84. /package/template/base/{src → ts/src}/server.ts +0 -0
  85. /package/template/base/{src → ts/src}/utils/index.ts +0 -0
  86. /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/ # Database, environment configs
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
- ## TypeScript vs JavaScript
202
+ ## TypeScript vs JavaScript
198
203
 
199
- This CLI generates **TypeScript** projects by default but fully supports **JavaScript** through intelligent transformation:
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
- - Automatically transpiled from TypeScript templates
211
- - Type annotations removed for cleaner code
212
- - Dependencies adjusted (no @types packages)
213
- - Same functionality, simpler syntax
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 / - API information
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 - Health service
261
- POST /auth/register - Auth service (if enabled)
262
- POST /auth/login - Auth service (if enabled)
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
- ### Basic Setup
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
- import { stripTypeScript, getJavaScriptScripts, getJavaScriptDependencies } from "./lib/ts-to-js.js";
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 base = path.join(__dirname, "../template/base");
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
- // Transform to JavaScript if selected
61
- if (config.language === "javascript") {
62
- // console.log("\n🔄 Converting TypeScript to JavaScript...\n");
63
- console.log(`\n${pc.cyan("⚙️ Setting up JavaScript project...")}\n`);
64
- transformToJavaScript(target);
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
- for (const item of items) {
77
- const fullPath = path.join(dir, item);
78
- const stat = fs.statSync(fullPath);
79
-
80
- if (stat.isDirectory()) {
81
- // Skip node_modules and dist
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
- transformDirectory(path.join(projectRoot, 'src'));
98
-
99
- // Remove TypeScript config file
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: "module",
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
- // Track if all installs succeeded for Husky setup
227
- let allInstallsSucceeded = true;
228
-
229
- // Now setup each service with knowledge of all services
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
- if (!result.installSucceeded) {
243
- allInstallsSucceeded = false;
244
- }
215
+ serviceConfigs.push({
216
+ serviceName,
217
+ serviceRoot,
218
+ deps: result.deps,
219
+ devDeps: result.devDeps
220
+ });
245
221
  }
246
222
 
247
- // Store for later use
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.md
282
- if (!isInMicroserviceProject) {
283
- console.log(`\n${pc.cyan("📝 Generating README.md...")}\n`);
284
- const readmeContent = generateReadme(config);
285
- fs.writeFileSync(path.join(target, "README.md"), readmeContent);
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
- } else {
302
- const gitignorePath = path.join(target, "gitignore");
303
- const dotGitignorePath = path.join(target, ".gitignore");
304
- if (fs.existsSync(gitignorePath)) {
305
- fs.renameSync(gitignorePath, dotGitignorePath);
306
- }
307
- }
308
-
309
- // Generate .env from .env.example for each service or root
310
- console.log(`${pc.cyan("📄 Setting up environment files...")}\n`);
311
- if (config.projectType === "microservice") {
312
- const servicesDir = path.join(target, "services");
313
- const allServices = fs.readdirSync(servicesDir).filter((f) =>
314
- fs.statSync(path.join(servicesDir, f)).isDirectory()
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
- for (const service of allServices) {
318
- const envExamplePath = path.join(servicesDir, service, ".env.example");
319
- const envPath = path.join(servicesDir, service, ".env");
320
- if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
321
- fs.copyFileSync(envExamplePath, envPath);
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
- } else {
325
- const envExamplePath = path.join(target, ".env.example");
326
- const envPath = path.join(target, ".env");
327
- if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
328
- fs.copyFileSync(envExamplePath, envPath);
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 isGateway = serviceName === "gateway";
16
- const serviceIndex = allServices.indexOf(serviceName);
17
- const port = isGateway ? 4000 : 4001 + allServices.filter((s, i) => s !== "gateway" && i < serviceIndex).length;
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: `./services/${serviceName}`,
33
+ build: {
34
+ context: `./services/${serviceName}`,
35
+ dockerfile: "Dockerfile"
36
+ },
37
+ image: `${serviceName}:latest`,
38
+ container_name: serviceName,
21
39
  ports: [
22
- `\${${serviceName
23
- .toUpperCase()
24
- .replace(/-/g, "_")}_PORT:-${port}}:${isGateway ? 4000 : 4000}`,
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
- `version: "${dockerCompose.version}"\nservices:\n` +
53
+ `services:\n` +
34
54
  Object.entries(dockerCompose.services)
35
55
  .map(
36
56
  ([name, config]) =>
37
57
  ` ${name}:\n` +
38
- ` build: ${config.build}\n` +
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 - ${config.environment[0]}\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
+ };