@ifecodes/backend-template 1.0.10 → 1.1.1
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 +62 -6
- package/bin/cli.js +125 -25
- package/bin/lib/prompts.js +45 -27
- package/bin/lib/service-setup.js +40 -23
- package/bin/lib/ts-to-js.js +123 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
# 🚀 Backend Template Generator
|
|
2
2
|
|
|
3
|
-
A powerful CLI tool to generate production-ready Node.js
|
|
3
|
+
A powerful CLI tool to generate production-ready Node.js backend applications with Express.js. Supports both **TypeScript** and **JavaScript**, with monolith and microservice architectures, and optional features like authentication, CORS, rate limiting, and more.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- 🎯 **TypeScript & JavaScript Support** - Choose your preferred language
|
|
10
|
+
- 🏗️ **Dual Architecture** - Monolith or Microservice
|
|
11
|
+
- 🐳 **Docker Ready** - Containerized microservices
|
|
12
|
+
- ⚡ **PM2 Support** - Process management for production
|
|
13
|
+
- 🔐 **JWT Authentication** - Built-in auth with MongoDB
|
|
14
|
+
- 🛡️ **Security First** - CORS, Helmet, Rate Limiting
|
|
15
|
+
- 📝 **Professional Logging** - Morgan + Winston
|
|
16
|
+
- 🎨 **Colored CLI** - Beautiful Vite-like terminal output
|
|
17
|
+
- 📋 **Project Metadata** - Description, author, and keywords support
|
|
4
18
|
|
|
5
19
|
---
|
|
6
20
|
|
|
@@ -35,21 +49,36 @@ npx @ifecodes/backend-template my-project micro
|
|
|
35
49
|
|
|
36
50
|
When you run the CLI, you'll be prompted to choose:
|
|
37
51
|
|
|
38
|
-
### 1. **
|
|
52
|
+
### 1. **Language**
|
|
53
|
+
|
|
54
|
+
- **TypeScript** (default) - Full type safety and modern tooling
|
|
55
|
+
- **JavaScript** - Transpiled from TypeScript for simplicity
|
|
56
|
+
|
|
57
|
+
### 2. **Project Metadata**
|
|
58
|
+
|
|
59
|
+
- **Description** - Project description for package.json
|
|
60
|
+
- **Author** - Your name or organization
|
|
61
|
+
- **Keywords** - Comma-separated keywords for discoverability
|
|
62
|
+
|
|
63
|
+
### 3. **Project Type**
|
|
64
|
+
|
|
39
65
|
- **Monolith API** - Traditional single-server architecture
|
|
40
66
|
- **Microservice** - Distributed services with API Gateway
|
|
41
67
|
|
|
42
|
-
###
|
|
68
|
+
### 4. **Deployment Mode** (Microservices only)
|
|
69
|
+
|
|
43
70
|
- **Docker** - Container-based deployment with docker-compose
|
|
44
71
|
- **PM2** - Process manager for Node.js applications
|
|
45
72
|
|
|
46
|
-
###
|
|
73
|
+
### 5. **Optional Features**
|
|
74
|
+
|
|
47
75
|
- ✅ **CORS** - Cross-Origin Resource Sharing
|
|
48
76
|
- ✅ **Helmet** - Security headers middleware
|
|
49
77
|
- ✅ **Rate Limiting** - API request throttling
|
|
50
78
|
- ✅ **Morgan** - HTTP request logger
|
|
51
79
|
|
|
52
|
-
###
|
|
80
|
+
### 6. **Authentication**
|
|
81
|
+
|
|
53
82
|
- ✅ **JWT Authentication** with MongoDB
|
|
54
83
|
- Choose between **bcrypt** (recommended for Windows) or **argon2** for password hashing
|
|
55
84
|
|
|
@@ -154,7 +183,7 @@ pm2 stop all
|
|
|
154
183
|
## 🛠 Tech Stack
|
|
155
184
|
|
|
156
185
|
- **Runtime**: Node.js (v18+)
|
|
157
|
-
- **Language**: TypeScript
|
|
186
|
+
- **Language**: TypeScript or JavaScript
|
|
158
187
|
- **Framework**: Express.js
|
|
159
188
|
- **Database**: MongoDB (with Mongoose, if auth enabled)
|
|
160
189
|
- **Authentication**: JWT + bcrypt/argon2
|
|
@@ -165,20 +194,46 @@ pm2 stop all
|
|
|
165
194
|
|
|
166
195
|
---
|
|
167
196
|
|
|
197
|
+
## � TypeScript vs JavaScript
|
|
198
|
+
|
|
199
|
+
This CLI generates **TypeScript** projects by default but fully supports **JavaScript** through intelligent transformation:
|
|
200
|
+
|
|
201
|
+
### TypeScript (Default)
|
|
202
|
+
|
|
203
|
+
- Full type safety and IntelliSense
|
|
204
|
+
- Modern ECMAScript features
|
|
205
|
+
- Compile-time error checking
|
|
206
|
+
- Better tooling and refactoring support
|
|
207
|
+
|
|
208
|
+
### JavaScript
|
|
209
|
+
|
|
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.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
168
219
|
## 🌟 Features
|
|
169
220
|
|
|
170
221
|
### ✅ Smart Defaults
|
|
222
|
+
|
|
171
223
|
- Auto-generates README with project-specific instructions
|
|
172
224
|
- Creates `.env` from `.env.example` with default values
|
|
173
225
|
- Configures TypeScript paths for clean imports (`@/config`, `@/utils`)
|
|
226
|
+
- Project metadata (description, author, keywords) in package.json
|
|
174
227
|
|
|
175
228
|
### ✅ Microservice Architecture
|
|
229
|
+
|
|
176
230
|
- **API Gateway** on port 4000 (single entry point)
|
|
177
231
|
- **Service Discovery** - Automatically routes to correct service
|
|
178
232
|
- **Shared Folder** - Common utilities across all services
|
|
179
233
|
- **Health Checks** - Built-in monitoring endpoints
|
|
180
234
|
|
|
181
235
|
### ✅ Developer Experience
|
|
236
|
+
|
|
182
237
|
- **Hot Reload** - Development server with nodemon
|
|
183
238
|
- **ESLint** - Code quality enforcement
|
|
184
239
|
- **Git Hooks** - Pre-commit linting with Husky
|
|
@@ -246,6 +301,7 @@ npx @ifecodes/backend-template
|
|
|
246
301
|
```
|
|
247
302
|
|
|
248
303
|
The CLI will:
|
|
304
|
+
|
|
249
305
|
- Create the new service
|
|
250
306
|
- Update `docker-compose.yml` or `pm2.config.js`
|
|
251
307
|
- Configure routing in the API Gateway
|
package/bin/cli.js
CHANGED
|
@@ -3,9 +3,11 @@ import fs from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
+
import pc from "picocolors";
|
|
6
7
|
import { getProjectConfig } from "./lib/prompts.js";
|
|
7
8
|
import { setupService } from "./lib/service-setup.js";
|
|
8
9
|
import { generateReadme } from "./lib/readme-generator.js";
|
|
10
|
+
import { stripTypeScript, getJavaScriptScripts, getJavaScriptDependencies } from "./lib/ts-to-js.js";
|
|
9
11
|
import {
|
|
10
12
|
generateDockerCompose,
|
|
11
13
|
generatePm2Config,
|
|
@@ -42,20 +44,118 @@ if (isInMicroserviceProject) {
|
|
|
42
44
|
// Validate and prepare project
|
|
43
45
|
if (!isInMicroserviceProject && config.projectType === "microservice") {
|
|
44
46
|
if (isExistingProject) {
|
|
45
|
-
console.error(`\n❌ Error: Project
|
|
47
|
+
console.error(`\n${pc.red("❌ Error:")} Project ${pc.bold(sanitizedName)} already exists!`);
|
|
46
48
|
process.exit(1);
|
|
47
49
|
}
|
|
48
50
|
console.log(
|
|
49
|
-
`\n🏗️ Creating microservices: ${servicesToCreate.join(", ")}...\n`
|
|
51
|
+
`\n${pc.cyan("🏗️ Creating microservices:")} ${pc.bold(servicesToCreate.join(", "))}...\n`
|
|
50
52
|
);
|
|
51
53
|
} else if (!isInMicroserviceProject && config.projectType === "monolith") {
|
|
52
54
|
if (isExistingProject) {
|
|
53
|
-
console.error(`\n❌ Error: Project
|
|
55
|
+
console.error(`\n${pc.red("❌ Error:")} Project ${pc.bold(sanitizedName)} already exists!`);
|
|
54
56
|
process.exit(1);
|
|
55
57
|
}
|
|
56
58
|
fs.cpSync(base, target, { recursive: true });
|
|
59
|
+
|
|
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
|
+
}
|
|
57
66
|
} else if (isInMicroserviceProject) {
|
|
58
|
-
console.log(`\n🏗️ Adding service: ${servicesToCreate[0]}...\n`);
|
|
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);
|
|
75
|
+
|
|
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
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
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
|
+
);
|
|
59
159
|
}
|
|
60
160
|
|
|
61
161
|
// Process services
|
|
@@ -64,7 +164,7 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
64
164
|
if (!isInMicroserviceProject) {
|
|
65
165
|
const sharedDir = path.join(target, "shared");
|
|
66
166
|
if (!fs.existsSync(sharedDir)) {
|
|
67
|
-
console.log(`\n📦 Creating shared folder for config and utils
|
|
167
|
+
console.log(`\n${pc.cyan("📦 Creating shared folder for config and utils...")}`);
|
|
68
168
|
fs.mkdirSync(sharedDir, { recursive: true });
|
|
69
169
|
|
|
70
170
|
// Copy config and utils from base template
|
|
@@ -180,7 +280,7 @@ if (isInMicroserviceProject || config.projectType === "microservice") {
|
|
|
180
280
|
|
|
181
281
|
// Generate README.md
|
|
182
282
|
if (!isInMicroserviceProject) {
|
|
183
|
-
console.log("
|
|
283
|
+
console.log(`\n${pc.cyan("📝 Generating README.md...")}\n`);
|
|
184
284
|
const readmeContent = generateReadme(config);
|
|
185
285
|
fs.writeFileSync(path.join(target, "README.md"), readmeContent);
|
|
186
286
|
|
|
@@ -207,7 +307,7 @@ if (!isInMicroserviceProject) {
|
|
|
207
307
|
}
|
|
208
308
|
|
|
209
309
|
// Generate .env from .env.example for each service or root
|
|
210
|
-
console.log("📄 Setting up environment files
|
|
310
|
+
console.log(`${pc.cyan("📄 Setting up environment files...")}\n`);
|
|
211
311
|
if (config.projectType === "microservice") {
|
|
212
312
|
const servicesDir = path.join(target, "services");
|
|
213
313
|
const allServices = fs.readdirSync(servicesDir).filter((f) =>
|
|
@@ -251,14 +351,14 @@ if (!isInMicroserviceProject) {
|
|
|
251
351
|
} else if (config.projectType === "monolith") {
|
|
252
352
|
// Only setup Husky if installation succeeded
|
|
253
353
|
if (config.installSucceeded) {
|
|
254
|
-
console.log("
|
|
354
|
+
console.log(`\n${pc.cyan("🔧 Setting up Husky...")}\n`);
|
|
255
355
|
try {
|
|
256
356
|
execSync("npm run prepare", { cwd: target, stdio: "inherit" });
|
|
257
357
|
} catch (error) {
|
|
258
|
-
console.log("
|
|
358
|
+
console.log(`\n${pc.yellow("⚠️ Husky setup failed")} ${pc.dim("(run 'npm run prepare' manually after fixing dependencies)")}\n`);
|
|
259
359
|
}
|
|
260
360
|
} else {
|
|
261
|
-
console.log("
|
|
361
|
+
console.log(`\n${pc.yellow("⚠️ Husky setup skipped")} ${pc.dim("(run 'npm install && npm run prepare' to set up git hooks)")}\n`);
|
|
262
362
|
}
|
|
263
363
|
}
|
|
264
364
|
}
|
|
@@ -272,27 +372,27 @@ const allServices = fs.existsSync(servicesDir)
|
|
|
272
372
|
: servicesToCreate;
|
|
273
373
|
|
|
274
374
|
if (isInMicroserviceProject) {
|
|
275
|
-
console.log(`\n✅ Service
|
|
276
|
-
console.log(`\n📦 All services: ${allServices.join(", ")}`);
|
|
277
|
-
console.log(`\n💡 Next steps
|
|
375
|
+
console.log(`\n${pc.green("✅ Service")} ${pc.bold(servicesToCreate[0])} ${pc.green("added successfully!")}`);
|
|
376
|
+
console.log(`\n${pc.cyan("📦 All services:")} ${allServices.join(", ")}`);
|
|
377
|
+
console.log(`\n${pc.blue("💡 Next steps:")}`);
|
|
278
378
|
console.log(
|
|
279
379
|
mode === "docker"
|
|
280
|
-
? ` 1. Start services: docker-compose up`
|
|
281
|
-
: ` 1. Start services: pm2 start pm2.config.js`
|
|
380
|
+
? ` ${pc.dim("1.")} Start services: ${pc.bold("docker-compose up")}`
|
|
381
|
+
: ` ${pc.dim("1.")} Start services: ${pc.bold("pm2 start pm2.config.js")}`
|
|
282
382
|
);
|
|
283
383
|
} else if (config.projectType === "microservice") {
|
|
284
|
-
console.log("
|
|
285
|
-
console.log(`\n📦 Created services: ${servicesToCreate.join(", ")}`);
|
|
286
|
-
console.log(`\n💡 Next steps
|
|
287
|
-
console.log(` 1. cd ${sanitizedName}`);
|
|
384
|
+
console.log(`\n${pc.green("✅ Backend created successfully!")}`);
|
|
385
|
+
console.log(`\n${pc.cyan("📦 Created services:")} ${servicesToCreate.join(", ")}`);
|
|
386
|
+
console.log(`\n${pc.blue("💡 Next steps:")}`);
|
|
387
|
+
console.log(` ${pc.dim("1.")} cd ${pc.bold(sanitizedName)}`);
|
|
288
388
|
console.log(
|
|
289
389
|
mode === "docker"
|
|
290
|
-
? ` 2. Start services: docker-compose up`
|
|
291
|
-
: ` 2. Start services: pm2 start pm2.config.js`
|
|
390
|
+
? ` ${pc.dim("2.")} Start services: ${pc.bold("docker-compose up")}`
|
|
391
|
+
: ` ${pc.dim("2.")} Start services: ${pc.bold("pm2 start pm2.config.js")}`
|
|
292
392
|
);
|
|
293
393
|
} else {
|
|
294
|
-
console.log("
|
|
295
|
-
console.log(`\n💡 Next steps
|
|
296
|
-
console.log(` 1. cd ${sanitizedName}`);
|
|
297
|
-
console.log(` 2. npm run dev`);
|
|
394
|
+
console.log(`\n${pc.green("✅ Backend created successfully!")}`);
|
|
395
|
+
console.log(`\n${pc.blue("💡 Next steps:")}`);
|
|
396
|
+
console.log(` ${pc.dim("1.")} cd ${pc.bold(sanitizedName)}`);
|
|
397
|
+
console.log(` ${pc.dim("2.")} npm run dev`);
|
|
298
398
|
}
|
package/bin/lib/prompts.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import prompts from "prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
2
3
|
import fs from "fs";
|
|
3
4
|
import path from "path";
|
|
4
5
|
|
|
@@ -44,34 +45,44 @@ export const getProjectConfig = async () => {
|
|
|
44
45
|
{
|
|
45
46
|
type: isInMicroserviceProject || hasCliArgs || isCI ? null : "text",
|
|
46
47
|
name: "name",
|
|
47
|
-
message: "Project name",
|
|
48
|
+
message: pc.cyan("Project name"),
|
|
48
49
|
initial: "my-backend",
|
|
49
50
|
},
|
|
51
|
+
{
|
|
52
|
+
type: isInMicroserviceProject || isCI ? null : "select",
|
|
53
|
+
name: "language",
|
|
54
|
+
message: pc.cyan("Select language"),
|
|
55
|
+
choices: [
|
|
56
|
+
{ title: pc.green("TypeScript"), value: "typescript" },
|
|
57
|
+
{ title: pc.yellow("JavaScript"), value: "javascript" },
|
|
58
|
+
],
|
|
59
|
+
initial: 0,
|
|
60
|
+
},
|
|
50
61
|
{
|
|
51
62
|
type: isInMicroserviceProject || hasCliArgs || isCI ? null : "text",
|
|
52
63
|
name: "description",
|
|
53
|
-
message: "Project description (optional)",
|
|
64
|
+
message: pc.cyan("Project description") + pc.dim(" (optional)"),
|
|
54
65
|
initial: "",
|
|
55
66
|
},
|
|
56
67
|
{
|
|
57
68
|
type: isInMicroserviceProject || hasCliArgs || isCI ? null : "text",
|
|
58
69
|
name: "author",
|
|
59
|
-
message: "Author (optional)",
|
|
70
|
+
message: pc.cyan("Author") + pc.dim(" (optional)"),
|
|
60
71
|
initial: "",
|
|
61
72
|
},
|
|
62
73
|
{
|
|
63
74
|
type: isInMicroserviceProject || hasCliArgs || isCI ? null : "text",
|
|
64
75
|
name: "keywords",
|
|
65
|
-
message: "Keywords (comma-separated, optional)",
|
|
76
|
+
message: pc.cyan("Keywords") + pc.dim(" (comma-separated, optional)"),
|
|
66
77
|
initial: "",
|
|
67
78
|
},
|
|
68
79
|
{
|
|
69
80
|
type: isInMicroserviceProject || (hasCliArgs && cliProjectType) || isCI ? null : "select",
|
|
70
81
|
name: "projectType",
|
|
71
|
-
message: "Project type",
|
|
82
|
+
message: pc.cyan("Project type"),
|
|
72
83
|
choices: [
|
|
73
|
-
{ title: "Monolith API", value: "monolith" },
|
|
74
|
-
{ title: "Microservice", value: "microservice" },
|
|
84
|
+
{ title: pc.blue("Monolith API"), value: "monolith" },
|
|
85
|
+
{ title: pc.magenta("Microservice"), value: "microservice" },
|
|
75
86
|
],
|
|
76
87
|
},
|
|
77
88
|
{
|
|
@@ -82,55 +93,62 @@ export const getProjectConfig = async () => {
|
|
|
82
93
|
? "select"
|
|
83
94
|
: null,
|
|
84
95
|
name: "mode",
|
|
85
|
-
message: "Microservice setup",
|
|
96
|
+
message: pc.cyan("Microservice setup"),
|
|
86
97
|
choices: [
|
|
87
|
-
{ title: "With Docker", value: "docker" },
|
|
88
|
-
{ title: "Without Docker", value: "nodocker" },
|
|
98
|
+
{ title: pc.blue("With Docker 🐳"), value: "docker" },
|
|
99
|
+
{ title: pc.yellow("Without Docker (PM2)"), value: "nodocker" },
|
|
89
100
|
],
|
|
90
101
|
},
|
|
91
102
|
{
|
|
92
103
|
type: isInMicroserviceProject ? "text" : isCI ? null : "multiselect",
|
|
93
104
|
name: isInMicroserviceProject ? "serviceName" : "features",
|
|
94
105
|
message: isInMicroserviceProject
|
|
95
|
-
? "New service name (e.g., user-service, order-service)"
|
|
96
|
-
: "Select features",
|
|
106
|
+
? pc.cyan("New service name") + pc.dim(" (e.g., user-service, order-service)")
|
|
107
|
+
: pc.cyan("Select features"),
|
|
97
108
|
choices: isInMicroserviceProject
|
|
98
109
|
? undefined
|
|
99
110
|
: [
|
|
100
|
-
{ title: "CORS", value: "cors" },
|
|
101
|
-
{ title: "Rate Limiter", value: "rate-limit" },
|
|
102
|
-
{ title: "Helmet", value: "helmet" },
|
|
103
|
-
{ title: "Morgan (HTTP logger)", value: "morgan" },
|
|
111
|
+
{ title: pc.blue("CORS"), value: "cors" },
|
|
112
|
+
{ title: pc.yellow("Rate Limiter"), value: "rate-limit" },
|
|
113
|
+
{ title: pc.green("Helmet"), value: "helmet" },
|
|
114
|
+
{ title: pc.magenta("Morgan (HTTP logger)"), value: "morgan" },
|
|
104
115
|
],
|
|
105
116
|
},
|
|
106
117
|
{
|
|
107
118
|
type: isCI ? null : "toggle",
|
|
108
119
|
name: "auth",
|
|
109
120
|
message: isInMicroserviceProject
|
|
110
|
-
? "Include authentication in this service?"
|
|
111
|
-
: "Include authentication system?",
|
|
121
|
+
? pc.cyan("Include authentication in this service?")
|
|
122
|
+
: pc.cyan("Include authentication system?"),
|
|
112
123
|
initial: true,
|
|
113
|
-
active: "yes",
|
|
114
|
-
inactive: "no",
|
|
124
|
+
active: pc.green("yes"),
|
|
125
|
+
inactive: pc.red("no"),
|
|
115
126
|
},
|
|
116
127
|
{
|
|
117
128
|
type: isInMicroserviceProject && !isCI ? "multiselect" : null,
|
|
118
129
|
name: "features",
|
|
119
|
-
message: "Select features for this service",
|
|
130
|
+
message: pc.cyan("Select features for this service"),
|
|
120
131
|
choices: [
|
|
121
|
-
{ title: "Rate Limiter", value: "rate-limit" },
|
|
122
|
-
{ title: "Helmet", value: "helmet" },
|
|
123
|
-
{ title: "Morgan (HTTP logger)", value: "morgan" },
|
|
124
|
-
{ title: "CORS", value: "cors" },
|
|
132
|
+
{ title: pc.yellow("Rate Limiter"), value: "rate-limit" },
|
|
133
|
+
{ title: pc.green("Helmet"), value: "helmet" },
|
|
134
|
+
{ title: pc.magenta("Morgan (HTTP logger)"), value: "morgan" },
|
|
135
|
+
{ title: pc.blue("CORS"), value: "cors" },
|
|
125
136
|
],
|
|
126
137
|
},
|
|
127
138
|
]);
|
|
128
139
|
|
|
140
|
+
// Handle cancelled prompts (user pressed Ctrl+C or closed the prompt)
|
|
141
|
+
if (!res || !res.name) {
|
|
142
|
+
console.log(pc.yellow("\n❌ Operation cancelled by user."));
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
129
146
|
// Set defaults for CI/non-interactive mode
|
|
130
147
|
if (isCI) {
|
|
131
148
|
res.features = res.features || [];
|
|
132
149
|
res.auth = res.auth ?? false;
|
|
133
150
|
res.mode = res.mode || "docker"; // Default to docker in CI
|
|
151
|
+
res.language = res.language || "typescript"; // Default to TypeScript in CI
|
|
134
152
|
}
|
|
135
153
|
|
|
136
154
|
// Merge CLI args with prompted responses
|
|
@@ -157,8 +175,8 @@ export const getProjectConfig = async () => {
|
|
|
157
175
|
? "docker"
|
|
158
176
|
: "nodocker";
|
|
159
177
|
|
|
160
|
-
console.log(`\n📁 Detected existing microservice project: ${sanitizedName}`);
|
|
161
|
-
console.log(`Mode: ${mode}\n`);
|
|
178
|
+
console.log(pc.cyan(`\n📁 Detected existing microservice project: ${sanitizedName}`));
|
|
179
|
+
console.log(pc.dim(`Mode: ${mode}\n`));
|
|
162
180
|
} else {
|
|
163
181
|
sanitizedName = res.name.replace(/\s+/g, "-");
|
|
164
182
|
target = path.resolve(process.cwd(), sanitizedName);
|
package/bin/lib/service-setup.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import prompts from "prompts";
|
|
4
|
+
import pc from "picocolors";
|
|
4
5
|
import { execSync } from "child_process";
|
|
5
6
|
import { fileURLToPath } from "url";
|
|
6
7
|
|
|
8
|
+
// Helper function to get the correct file extension (.ts or .js)
|
|
9
|
+
function getFileExtension(dir) {
|
|
10
|
+
// Check if .ts files exist, otherwise use .js
|
|
11
|
+
const sampleFiles = ['src/app.ts', 'src/server.ts', 'src/routes.ts'];
|
|
12
|
+
for (const file of sampleFiles) {
|
|
13
|
+
const tsPath = path.join(dir, file);
|
|
14
|
+
if (fs.existsSync(tsPath)) return 'ts';
|
|
15
|
+
const jsPath = path.join(dir, file.replace('.ts', '.js'));
|
|
16
|
+
if (fs.existsSync(jsPath)) return 'js';
|
|
17
|
+
}
|
|
18
|
+
return 'ts'; // default to ts
|
|
19
|
+
}
|
|
20
|
+
|
|
7
21
|
export const setupService = async (
|
|
8
22
|
res,
|
|
9
23
|
serviceName,
|
|
@@ -17,6 +31,9 @@ export const setupService = async (
|
|
|
17
31
|
let devDeps = [];
|
|
18
32
|
let v1Imports = [];
|
|
19
33
|
let v1Routes = [];
|
|
34
|
+
|
|
35
|
+
// Detect file extension (ts or js)
|
|
36
|
+
const ext = getFileExtension(serviceRoot);
|
|
20
37
|
|
|
21
38
|
// Special handling for gateway service
|
|
22
39
|
if (serviceName === "gateway") {
|
|
@@ -24,8 +41,8 @@ export const setupService = async (
|
|
|
24
41
|
deps.push(...gatewayModule.gatewayDeps);
|
|
25
42
|
|
|
26
43
|
// Copy gateway-specific files
|
|
27
|
-
const gatewayAppPath = path.join(serviceRoot,
|
|
28
|
-
const gatewayServerPath = path.join(serviceRoot,
|
|
44
|
+
const gatewayAppPath = path.join(serviceRoot, `src/app.${ext}`);
|
|
45
|
+
const gatewayServerPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
29
46
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
30
47
|
|
|
31
48
|
const gatewayAppContent = fs.readFileSync(
|
|
@@ -45,7 +62,7 @@ export const setupService = async (
|
|
|
45
62
|
fs.writeFileSync(gatewayServerPath, gatewayServerContent);
|
|
46
63
|
|
|
47
64
|
// Remove unnecessary files for gateway
|
|
48
|
-
const routesPath = path.join(serviceRoot,
|
|
65
|
+
const routesPath = path.join(serviceRoot, `src/routes.${ext}`);
|
|
49
66
|
const modulesPath = path.join(serviceRoot, "src/modules");
|
|
50
67
|
const middlewaresPath = path.join(serviceRoot, "src/middlewares");
|
|
51
68
|
|
|
@@ -124,15 +141,15 @@ export const setupService = async (
|
|
|
124
141
|
v1Routes.push(baseAuth.middleware);
|
|
125
142
|
}
|
|
126
143
|
|
|
127
|
-
// Update app
|
|
128
|
-
const appPath = path.join(serviceRoot,
|
|
144
|
+
// Update app file
|
|
145
|
+
const appPath = path.join(serviceRoot, `src/app.${ext}`);
|
|
129
146
|
let content = fs.readFileSync(appPath, "utf8");
|
|
130
147
|
content = content.replace("/*__IMPORTS__*/", imports.join("\n"));
|
|
131
148
|
content = content.replace("/*__MIDDLEWARE__*/", middlewares.join("\n"));
|
|
132
149
|
fs.writeFileSync(appPath, content);
|
|
133
150
|
|
|
134
151
|
// Update root endpoint middleware with project info
|
|
135
|
-
const rootMiddlewarePath = path.join(serviceRoot,
|
|
152
|
+
const rootMiddlewarePath = path.join(serviceRoot, `src/middlewares/root.middleware.${ext}`);
|
|
136
153
|
if (fs.existsSync(rootMiddlewarePath)) {
|
|
137
154
|
let rootContent = fs.readFileSync(rootMiddlewarePath, "utf8");
|
|
138
155
|
rootContent = rootContent.replace("/*__PROJECT_NAME__*/", serviceName || res.sanitizedName);
|
|
@@ -151,9 +168,9 @@ export const setupService = async (
|
|
|
151
168
|
fs.writeFileSync(rootMiddlewarePath, rootContent);
|
|
152
169
|
}
|
|
153
170
|
|
|
154
|
-
// Update v1 index
|
|
171
|
+
// Update v1 index file if needed
|
|
155
172
|
if (v1Imports.length || v1Routes.length) {
|
|
156
|
-
const v1IndexPath = path.join(serviceRoot,
|
|
173
|
+
const v1IndexPath = path.join(serviceRoot, `src/modules/v1/index.${ext}`);
|
|
157
174
|
let v1Content = fs.readFileSync(v1IndexPath, "utf8");
|
|
158
175
|
|
|
159
176
|
const lastImportIndex = v1Content.lastIndexOf("import");
|
|
@@ -174,8 +191,8 @@ export const setupService = async (
|
|
|
174
191
|
fs.writeFileSync(v1IndexPath, v1Content);
|
|
175
192
|
}
|
|
176
193
|
|
|
177
|
-
// Update env
|
|
178
|
-
const envPath = path.join(serviceRoot,
|
|
194
|
+
// Update env file to conditionally include ALLOWED_ORIGIN and MONGO_URI
|
|
195
|
+
const envPath = path.join(serviceRoot, `src/config/env.${ext}`);
|
|
179
196
|
if (fs.existsSync(envPath)) {
|
|
180
197
|
let envContent = fs.readFileSync(envPath, "utf8");
|
|
181
198
|
|
|
@@ -219,8 +236,8 @@ export const setupService = async (
|
|
|
219
236
|
fs.writeFileSync(envPath, envContent);
|
|
220
237
|
}
|
|
221
238
|
|
|
222
|
-
// Update server
|
|
223
|
-
const serverPath = path.join(serviceRoot,
|
|
239
|
+
// Update server file to connect to DB if auth is enabled
|
|
240
|
+
const serverPath = path.join(serviceRoot, `src/server.${ext}`);
|
|
224
241
|
if (fs.existsSync(serverPath)) {
|
|
225
242
|
let serverContent = fs.readFileSync(serverPath, "utf8");
|
|
226
243
|
|
|
@@ -340,7 +357,7 @@ await connectDB();`
|
|
|
340
357
|
|
|
341
358
|
// Install dependencies
|
|
342
359
|
console.log(
|
|
343
|
-
`\n📦 Installing dependencies for ${serviceName || "project"}...\n`
|
|
360
|
+
pc.cyan(`\n📦 Installing dependencies for ${serviceName || "project"}...\n`)
|
|
344
361
|
);
|
|
345
362
|
|
|
346
363
|
let installSucceeded = false;
|
|
@@ -362,23 +379,23 @@ await connectDB();`
|
|
|
362
379
|
installSucceeded = true;
|
|
363
380
|
|
|
364
381
|
// Run format after successful install
|
|
365
|
-
console.log("\n🎨 Formatting code...\n");
|
|
382
|
+
console.log(pc.cyan("\n🎨 Formatting code...\n"));
|
|
366
383
|
try {
|
|
367
384
|
execSync("npm run format", { cwd: serviceRoot, stdio: "inherit" });
|
|
368
385
|
} catch (formatError) {
|
|
369
|
-
console.warn("⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n");
|
|
386
|
+
console.warn(pc.yellow("⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n"));
|
|
370
387
|
}
|
|
371
388
|
} catch (error) {
|
|
372
|
-
console.error("\n⚠️ Warning: Some dependencies failed to install.");
|
|
373
|
-
console.error("This is usually due to native modules (like argon2) requiring build tools.\n");
|
|
374
|
-
console.error("💡 Solutions:");
|
|
375
|
-
console.error(" 1. Install build tools: npm install --global windows-build-tools");
|
|
376
|
-
console.error(" 2. Or switch to bcrypt (works better on Windows)");
|
|
377
|
-
console.error(" 3. Or manually install later: cd " + (serviceName || res.sanitizedName) + " && npm install");
|
|
378
|
-
console.error(" 4. Then run: npm run format\n");
|
|
389
|
+
console.error(pc.yellow("\n⚠️ Warning: Some dependencies failed to install."));
|
|
390
|
+
console.error(pc.yellow("This is usually due to native modules (like argon2) requiring build tools.\n"));
|
|
391
|
+
console.error(pc.cyan("💡 Solutions:"));
|
|
392
|
+
console.error(pc.dim(" 1. Install build tools: npm install --global windows-build-tools"));
|
|
393
|
+
console.error(pc.dim(" 2. Or switch to bcrypt (works better on Windows)"));
|
|
394
|
+
console.error(pc.dim(" 3. Or manually install later: cd " + (serviceName || res.sanitizedName) + " && npm install"));
|
|
395
|
+
console.error(pc.dim(" 4. Then run: npm run format\n"));
|
|
379
396
|
|
|
380
397
|
// Don't exit - let the project be created anyway
|
|
381
|
-
console.log("⏭️ Continuing with project creation...\n");
|
|
398
|
+
console.log(pc.cyan("⏭️ Continuing with project creation...\n"));
|
|
382
399
|
}
|
|
383
400
|
|
|
384
401
|
return { deps, installSucceeded };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transform TypeScript files to JavaScript
|
|
3
|
+
* Removes type annotations and converts file extensions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Strip TypeScript type annotations from code
|
|
8
|
+
* @param {string} content - TypeScript file content
|
|
9
|
+
* @returns {string} - JavaScript content
|
|
10
|
+
*/
|
|
11
|
+
export function stripTypeScript(content) {
|
|
12
|
+
let jsContent = content;
|
|
13
|
+
|
|
14
|
+
// Remove type imports
|
|
15
|
+
jsContent = jsContent.replace(/^import\s+type\s+.*?;?\s*$/gm, '');
|
|
16
|
+
|
|
17
|
+
// Remove type-only imports from regular imports
|
|
18
|
+
jsContent = jsContent.replace(/,\s*type\s+\{[^}]+\}/g, '');
|
|
19
|
+
jsContent = jsContent.replace(/import\s+\{[^}]*type\s+[^}]*\}/g, (match) => {
|
|
20
|
+
return match.replace(/,?\s*type\s+\w+/g, '').replace(/\{\s*,/, '{').replace(/,\s*\}/, '}');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Remove interface declarations
|
|
24
|
+
jsContent = jsContent.replace(/^export\s+interface\s+\w+\s*\{[^}]*\}\s*$/gm, '');
|
|
25
|
+
jsContent = jsContent.replace(/^interface\s+\w+\s*\{[^}]*\}\s*$/gm, '');
|
|
26
|
+
|
|
27
|
+
// Remove type aliases
|
|
28
|
+
jsContent = jsContent.replace(/^export\s+type\s+\w+\s*=\s*[^;]+;\s*$/gm, '');
|
|
29
|
+
jsContent = jsContent.replace(/^type\s+\w+\s*=\s*[^;]+;\s*$/gm, '');
|
|
30
|
+
|
|
31
|
+
// Remove parameter types: (param: Type) => (param)
|
|
32
|
+
jsContent = jsContent.replace(/\(\s*(\w+)\s*:\s*[^,)]+/g, '($1');
|
|
33
|
+
jsContent = jsContent.replace(/,\s*(\w+)\s*:\s*[^,)]+/g, ', $1');
|
|
34
|
+
|
|
35
|
+
// Remove return types: ): Type => ): void => ()
|
|
36
|
+
jsContent = jsContent.replace(/\)\s*:\s*[^{=>\n]+\s*=>/g, ') =>');
|
|
37
|
+
jsContent = jsContent.replace(/\)\s*:\s*[^{=>\n]+\s*\{/g, ') {');
|
|
38
|
+
|
|
39
|
+
// Remove variable type annotations: const x: Type = ... => const x = ...
|
|
40
|
+
jsContent = jsContent.replace(/:\s*[^=\n]+(?=\s*=)/g, '');
|
|
41
|
+
|
|
42
|
+
// Remove function return types
|
|
43
|
+
jsContent = jsContent.replace(/function\s+(\w+)\s*\([^)]*\)\s*:\s*[^{]+\{/g, 'function $1($2) {');
|
|
44
|
+
|
|
45
|
+
// Remove as type assertions
|
|
46
|
+
jsContent = jsContent.replace(/\s+as\s+\w+/g, '');
|
|
47
|
+
|
|
48
|
+
// Remove angle bracket type assertions: <Type>value => value
|
|
49
|
+
jsContent = jsContent.replace(/<\w+>/g, '');
|
|
50
|
+
|
|
51
|
+
// Remove ! non-null assertions
|
|
52
|
+
jsContent = jsContent.replace(/(\w+)!/g, '$1');
|
|
53
|
+
|
|
54
|
+
// Remove ? optional chaining that's TypeScript specific
|
|
55
|
+
// Keep ?. and ?? as they're valid JS
|
|
56
|
+
|
|
57
|
+
// Remove generic type parameters
|
|
58
|
+
jsContent = jsContent.replace(/<[^>]+>/g, '');
|
|
59
|
+
|
|
60
|
+
// Remove enum declarations (convert to object)
|
|
61
|
+
jsContent = jsContent.replace(/enum\s+(\w+)\s*\{([^}]+)\}/g, (match, name, body) => {
|
|
62
|
+
const entries = body.split(',').map(e => e.trim()).filter(Boolean);
|
|
63
|
+
const obj = entries.map(e => {
|
|
64
|
+
const [key, value] = e.split('=').map(s => s.trim());
|
|
65
|
+
return value ? ` ${key}: ${value}` : ` ${key}: "${key}"`;
|
|
66
|
+
}).join(',\n');
|
|
67
|
+
return `const ${name} = {\n${obj}\n}`;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Remove multiple consecutive blank lines
|
|
71
|
+
jsContent = jsContent.replace(/\n\s*\n\s*\n/g, '\n\n');
|
|
72
|
+
|
|
73
|
+
return jsContent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get JavaScript package.json scripts
|
|
78
|
+
* @returns {Object} - Scripts for JavaScript project
|
|
79
|
+
*/
|
|
80
|
+
export function getJavaScriptScripts() {
|
|
81
|
+
return {
|
|
82
|
+
dev: "nodemon --exec node src/server.js",
|
|
83
|
+
start: "node src/server.js",
|
|
84
|
+
lint: 'eslint "src/**/*.js"',
|
|
85
|
+
format: 'prettier --write "src/**/*.{js,json}"',
|
|
86
|
+
"check-format": 'prettier --check "src/**/*.{js,json}"',
|
|
87
|
+
prepare: "husky"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get JavaScript dependencies (remove TS-specific ones)
|
|
93
|
+
* @param {Object} originalDeps - Original TypeScript dependencies
|
|
94
|
+
* @param {Object} originalDevDeps - Original TypeScript devDependencies
|
|
95
|
+
* @returns {Object} - { dependencies, devDependencies }
|
|
96
|
+
*/
|
|
97
|
+
export function getJavaScriptDependencies(originalDeps, originalDevDeps) {
|
|
98
|
+
// Remove TypeScript-specific packages
|
|
99
|
+
const tsPackages = [
|
|
100
|
+
'typescript',
|
|
101
|
+
'ts-node-dev',
|
|
102
|
+
'tsconfig-paths',
|
|
103
|
+
'@typescript-eslint/eslint-plugin',
|
|
104
|
+
'@typescript-eslint/parser'
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
const typePackages = Object.keys(originalDevDeps).filter(pkg => pkg.startsWith('@types/'));
|
|
108
|
+
|
|
109
|
+
const devDeps = { ...originalDevDeps };
|
|
110
|
+
|
|
111
|
+
// Remove TS packages
|
|
112
|
+
[...tsPackages, ...typePackages].forEach(pkg => {
|
|
113
|
+
delete devDeps[pkg];
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Add nodemon for JS development
|
|
117
|
+
devDeps.nodemon = '^3.0.2';
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
dependencies: { ...originalDeps },
|
|
121
|
+
devDependencies: devDeps
|
|
122
|
+
};
|
|
123
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ifecodes/backend-template",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Production-ready Express + TypeScript backend generator with optional features and microservice support",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "Production-ready Express + TypeScript/JavaScript backend generator with optional features and microservice support",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ifecodes-template": "bin/cli.js"
|
|
7
7
|
},
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"template",
|
|
30
30
|
"boilerplate",
|
|
31
31
|
"typescript",
|
|
32
|
+
"javascript",
|
|
32
33
|
"backend",
|
|
33
34
|
"express",
|
|
34
35
|
"microservice",
|
|
@@ -39,11 +40,13 @@
|
|
|
39
40
|
"docker",
|
|
40
41
|
"pm2",
|
|
41
42
|
"jwt",
|
|
42
|
-
"authentication"
|
|
43
|
+
"authentication",
|
|
44
|
+
"colored-cli"
|
|
43
45
|
],
|
|
44
46
|
"license": "MIT",
|
|
45
47
|
"author": "Aladetan Fortune Ifeloju (IfeCodes) <ifecodes01@gmail.com>",
|
|
46
48
|
"dependencies": {
|
|
49
|
+
"picocolors": "^1.1.1",
|
|
47
50
|
"prompts": "^2.4.2"
|
|
48
51
|
},
|
|
49
52
|
"engines": {
|