@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.
Files changed (85) hide show
  1. package/README.md +39 -18
  2. package/bin/cli.js +207 -80
  3. package/bin/lib/microservice-config.js +57 -13
  4. package/bin/lib/prompts.js +10 -6
  5. package/bin/lib/readme-generator.js +75 -14
  6. package/bin/lib/service-setup.js +272 -126
  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 -45
  45. package/template/base/{src → ts/src}/utils/logger.ts +23 -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 -342
  69. package/template/base/src/app.ts +0 -8
  70. package/template/gateway/inject.js +0 -27
  71. /package/template/base/{.env.example → js/.env.example} +0 -0
  72. /package/template/base/{.husky → js/.husky}/pre-commit +0 -0
  73. /package/template/base/{.prettierignore → js/.prettierignore} +0 -0
  74. /package/template/base/{gitignore → js/gitignore} +0 -0
  75. /package/template/base/{.eslintrc.json → ts/.eslintrc.json} +0 -0
  76. /package/template/base/{.prettierrc → ts/.prettierrc} +0 -0
  77. /package/template/base/{eslint.config.js → ts/eslint.config.js} +0 -0
  78. /package/template/base/{package.json → ts/package.json} +0 -0
  79. /package/template/base/{src → ts/src}/config/db.ts +0 -0
  80. /package/template/base/{src → ts/src}/config/index.ts +0 -0
  81. /package/template/base/{src → ts/src}/middlewares/not-found.middleware.ts +0 -0
  82. /package/template/base/{src → ts/src}/modules/index.ts +0 -0
  83. /package/template/base/{src → ts/src}/server.ts +0 -0
  84. /package/template/base/{src → ts/src}/utils/index.ts +0 -0
  85. /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
@@ -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
- import { stripTypeScript, getJavaScriptScripts, getJavaScriptDependencies, transformToJavaScript, transformDirectory } 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,12 +61,23 @@ 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);
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: "module",
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
- // Track if all installs succeeded for Husky setup
136
- let allInstallsSucceeded = true;
137
-
138
- // 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
+
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
- if (!result.installSucceeded) {
152
- allInstallsSucceeded = false;
153
- }
154
- }
155
-
156
- // Store for later use
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.md
211
- if (!isInMicroserviceProject) {
212
- console.log(`\n${pc.cyan("📝 Generating README.md...")}\n`);
213
- const readmeContent = generateReadme(config);
214
- fs.writeFileSync(path.join(target, "README.md"), readmeContent);
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
- } else {
231
- const gitignorePath = path.join(target, "gitignore");
232
- const dotGitignorePath = path.join(target, ".gitignore");
233
- if (fs.existsSync(gitignorePath)) {
234
- fs.renameSync(gitignorePath, dotGitignorePath);
235
- }
236
- }
237
-
238
- // Generate .env from .env.example for each service or root
239
- console.log(`${pc.cyan("📄 Setting up environment files...")}\n`);
240
- if (config.projectType === "microservice") {
241
- const servicesDir = path.join(target, "services");
242
- const allServices = fs.readdirSync(servicesDir).filter((f) =>
243
- 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"
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
- for (const service of allServices) {
247
- const envExamplePath = path.join(servicesDir, service, ".env.example");
248
- const envPath = path.join(servicesDir, service, ".env");
249
- if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
250
- fs.copyFileSync(envExamplePath, envPath);
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
- } else {
254
- const envExamplePath = path.join(target, ".env.example");
255
- const envPath = path.join(target, ".env");
256
- if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
257
- 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);
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 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
+ };
@@ -86,12 +86,16 @@ export const getProjectConfig = async () => {
86
86
  ],
87
87
  },
88
88
  {
89
- type: (prev, values) =>
90
- isInMicroserviceProject || isCI
91
- ? null
92
- : prev === "microservice"
93
- ? "select"
94
- : null,
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: [