@ifecodes/backend-template 1.0.9 → 1.1.0
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 +54 -24
- package/bin/lib/service-setup.js +45 -13
- package/bin/lib/ts-to-js.js +123 -0
- package/package.json +6 -3
- package/template/base/package.json +7 -0
- package/template/base/src/utils/logger.ts +7 -6
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,16 +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
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: isInMicroserviceProject || hasCliArgs || isCI ? null : "text",
|
|
63
|
+
name: "description",
|
|
64
|
+
message: pc.cyan("Project description") + pc.dim(" (optional)"),
|
|
65
|
+
initial: "",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: isInMicroserviceProject || hasCliArgs || isCI ? null : "text",
|
|
69
|
+
name: "author",
|
|
70
|
+
message: pc.cyan("Author") + pc.dim(" (optional)"),
|
|
71
|
+
initial: "",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: isInMicroserviceProject || hasCliArgs || isCI ? null : "text",
|
|
75
|
+
name: "keywords",
|
|
76
|
+
message: pc.cyan("Keywords") + pc.dim(" (comma-separated, optional)"),
|
|
77
|
+
initial: "",
|
|
78
|
+
},
|
|
50
79
|
{
|
|
51
80
|
type: isInMicroserviceProject || (hasCliArgs && cliProjectType) || isCI ? null : "select",
|
|
52
81
|
name: "projectType",
|
|
53
|
-
message: "Project type",
|
|
82
|
+
message: pc.cyan("Project type"),
|
|
54
83
|
choices: [
|
|
55
|
-
{ title: "Monolith API", value: "monolith" },
|
|
56
|
-
{ title: "Microservice", value: "microservice" },
|
|
84
|
+
{ title: pc.blue("Monolith API"), value: "monolith" },
|
|
85
|
+
{ title: pc.magenta("Microservice"), value: "microservice" },
|
|
57
86
|
],
|
|
58
87
|
},
|
|
59
88
|
{
|
|
@@ -64,46 +93,46 @@ export const getProjectConfig = async () => {
|
|
|
64
93
|
? "select"
|
|
65
94
|
: null,
|
|
66
95
|
name: "mode",
|
|
67
|
-
message: "Microservice setup",
|
|
96
|
+
message: pc.cyan("Microservice setup"),
|
|
68
97
|
choices: [
|
|
69
|
-
{ title: "With Docker", value: "docker" },
|
|
70
|
-
{ title: "Without Docker", value: "nodocker" },
|
|
98
|
+
{ title: pc.blue("With Docker 🐳"), value: "docker" },
|
|
99
|
+
{ title: pc.yellow("Without Docker (PM2)"), value: "nodocker" },
|
|
71
100
|
],
|
|
72
101
|
},
|
|
73
102
|
{
|
|
74
103
|
type: isInMicroserviceProject ? "text" : isCI ? null : "multiselect",
|
|
75
104
|
name: isInMicroserviceProject ? "serviceName" : "features",
|
|
76
105
|
message: isInMicroserviceProject
|
|
77
|
-
? "New service name (e.g., user-service, order-service)"
|
|
78
|
-
: "Select features",
|
|
106
|
+
? pc.cyan("New service name") + pc.dim(" (e.g., user-service, order-service)")
|
|
107
|
+
: pc.cyan("Select features"),
|
|
79
108
|
choices: isInMicroserviceProject
|
|
80
109
|
? undefined
|
|
81
110
|
: [
|
|
82
|
-
{ title: "CORS", value: "cors" },
|
|
83
|
-
{ title: "Rate Limiter", value: "rate-limit" },
|
|
84
|
-
{ title: "Helmet", value: "helmet" },
|
|
85
|
-
{ 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" },
|
|
86
115
|
],
|
|
87
116
|
},
|
|
88
117
|
{
|
|
89
118
|
type: isCI ? null : "toggle",
|
|
90
119
|
name: "auth",
|
|
91
120
|
message: isInMicroserviceProject
|
|
92
|
-
? "Include authentication in this service?"
|
|
93
|
-
: "Include authentication system?",
|
|
121
|
+
? pc.cyan("Include authentication in this service?")
|
|
122
|
+
: pc.cyan("Include authentication system?"),
|
|
94
123
|
initial: true,
|
|
95
|
-
active: "yes",
|
|
96
|
-
inactive: "no",
|
|
124
|
+
active: pc.green("yes"),
|
|
125
|
+
inactive: pc.red("no"),
|
|
97
126
|
},
|
|
98
127
|
{
|
|
99
128
|
type: isInMicroserviceProject && !isCI ? "multiselect" : null,
|
|
100
129
|
name: "features",
|
|
101
|
-
message: "Select features for this service",
|
|
130
|
+
message: pc.cyan("Select features for this service"),
|
|
102
131
|
choices: [
|
|
103
|
-
{ title: "Rate Limiter", value: "rate-limit" },
|
|
104
|
-
{ title: "Helmet", value: "helmet" },
|
|
105
|
-
{ title: "Morgan (HTTP logger)", value: "morgan" },
|
|
106
|
-
{ 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" },
|
|
107
136
|
],
|
|
108
137
|
},
|
|
109
138
|
]);
|
|
@@ -113,6 +142,7 @@ export const getProjectConfig = async () => {
|
|
|
113
142
|
res.features = res.features || [];
|
|
114
143
|
res.auth = res.auth ?? false;
|
|
115
144
|
res.mode = res.mode || "docker"; // Default to docker in CI
|
|
145
|
+
res.language = res.language || "typescript"; // Default to TypeScript in CI
|
|
116
146
|
}
|
|
117
147
|
|
|
118
148
|
// Merge CLI args with prompted responses
|
|
@@ -139,8 +169,8 @@ export const getProjectConfig = async () => {
|
|
|
139
169
|
? "docker"
|
|
140
170
|
: "nodocker";
|
|
141
171
|
|
|
142
|
-
console.log(`\n📁 Detected existing microservice project: ${sanitizedName}`);
|
|
143
|
-
console.log(`Mode: ${mode}\n`);
|
|
172
|
+
console.log(pc.cyan(`\n📁 Detected existing microservice project: ${sanitizedName}`));
|
|
173
|
+
console.log(pc.dim(`Mode: ${mode}\n`));
|
|
144
174
|
} else {
|
|
145
175
|
sanitizedName = res.name.replace(/\s+/g, "-");
|
|
146
176
|
target = path.resolve(process.cwd(), sanitizedName);
|
package/bin/lib/service-setup.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
|
|
|
@@ -301,15 +302,46 @@ await connectDB();`
|
|
|
301
302
|
// Update package.json
|
|
302
303
|
const packageJsonPath = path.join(serviceRoot, "package.json");
|
|
303
304
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
304
|
-
|
|
305
|
+
|
|
306
|
+
// Create new package.json with name at the top
|
|
307
|
+
const orderedPackageJson = {
|
|
308
|
+
name: serviceName || res.sanitizedName,
|
|
309
|
+
version: packageJson.version,
|
|
310
|
+
description: res.description || packageJson.description,
|
|
311
|
+
...packageJson,
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Remove duplicate keys that were moved to the top
|
|
315
|
+
delete orderedPackageJson.name;
|
|
316
|
+
delete orderedPackageJson.version;
|
|
317
|
+
delete orderedPackageJson.description;
|
|
318
|
+
|
|
319
|
+
// Re-add them at the top in correct order
|
|
320
|
+
const finalPackageJson = {
|
|
321
|
+
name: serviceName || res.sanitizedName,
|
|
322
|
+
version: packageJson.version,
|
|
323
|
+
description: res.description || packageJson.description,
|
|
324
|
+
...orderedPackageJson,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Add author if provided
|
|
328
|
+
if (res.author) {
|
|
329
|
+
finalPackageJson.author = res.author;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Add keywords if provided
|
|
333
|
+
if (res.keywords && res.keywords.trim()) {
|
|
334
|
+
finalPackageJson.keywords = res.keywords.split(',').map(k => k.trim()).filter(Boolean);
|
|
335
|
+
}
|
|
336
|
+
|
|
305
337
|
fs.writeFileSync(
|
|
306
338
|
packageJsonPath,
|
|
307
|
-
JSON.stringify(
|
|
339
|
+
JSON.stringify(finalPackageJson, null, 2) + "\n"
|
|
308
340
|
);
|
|
309
341
|
|
|
310
342
|
// Install dependencies
|
|
311
343
|
console.log(
|
|
312
|
-
`\n📦 Installing dependencies for ${serviceName || "project"}...\n`
|
|
344
|
+
pc.cyan(`\n📦 Installing dependencies for ${serviceName || "project"}...\n`)
|
|
313
345
|
);
|
|
314
346
|
|
|
315
347
|
let installSucceeded = false;
|
|
@@ -331,23 +363,23 @@ await connectDB();`
|
|
|
331
363
|
installSucceeded = true;
|
|
332
364
|
|
|
333
365
|
// Run format after successful install
|
|
334
|
-
console.log("\n🎨 Formatting code...\n");
|
|
366
|
+
console.log(pc.cyan("\n🎨 Formatting code...\n"));
|
|
335
367
|
try {
|
|
336
368
|
execSync("npm run format", { cwd: serviceRoot, stdio: "inherit" });
|
|
337
369
|
} catch (formatError) {
|
|
338
|
-
console.warn("⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n");
|
|
370
|
+
console.warn(pc.yellow("⚠️ Warning: Code formatting failed. You can run it manually later with: npm run format\n"));
|
|
339
371
|
}
|
|
340
372
|
} catch (error) {
|
|
341
|
-
console.error("\n⚠️ Warning: Some dependencies failed to install.");
|
|
342
|
-
console.error("This is usually due to native modules (like argon2) requiring build tools.\n");
|
|
343
|
-
console.error("💡 Solutions:");
|
|
344
|
-
console.error(" 1. Install build tools: npm install --global windows-build-tools");
|
|
345
|
-
console.error(" 2. Or switch to bcrypt (works better on Windows)");
|
|
346
|
-
console.error(" 3. Or manually install later: cd " + (serviceName || res.sanitizedName) + " && npm install");
|
|
347
|
-
console.error(" 4. Then run: npm run format\n");
|
|
373
|
+
console.error(pc.yellow("\n⚠️ Warning: Some dependencies failed to install."));
|
|
374
|
+
console.error(pc.yellow("This is usually due to native modules (like argon2) requiring build tools.\n"));
|
|
375
|
+
console.error(pc.cyan("💡 Solutions:"));
|
|
376
|
+
console.error(pc.dim(" 1. Install build tools: npm install --global windows-build-tools"));
|
|
377
|
+
console.error(pc.dim(" 2. Or switch to bcrypt (works better on Windows)"));
|
|
378
|
+
console.error(pc.dim(" 3. Or manually install later: cd " + (serviceName || res.sanitizedName) + " && npm install"));
|
|
379
|
+
console.error(pc.dim(" 4. Then run: npm run format\n"));
|
|
348
380
|
|
|
349
381
|
// Don't exit - let the project be created anyway
|
|
350
|
-
console.log("⏭️ Continuing with project creation...\n");
|
|
382
|
+
console.log(pc.cyan("⏭️ Continuing with project creation...\n"));
|
|
351
383
|
}
|
|
352
384
|
|
|
353
385
|
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.0
|
|
4
|
-
"description": "Production-ready Express + TypeScript backend generator with optional features and microservice support",
|
|
3
|
+
"version": "1.1.0",
|
|
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": {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"description": "",
|
|
4
|
+
"main": "index.js",
|
|
2
5
|
"scripts": {
|
|
3
6
|
"dev": "ts-node-dev --respawn --transpile-only -r tsconfig-paths/register src/server.ts",
|
|
4
7
|
"start": "node dist/server.js",
|
|
@@ -8,6 +11,10 @@
|
|
|
8
11
|
"check-format": "prettier --check \"src/**/*.{ts,json}\"",
|
|
9
12
|
"prepare": "husky"
|
|
10
13
|
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "",
|
|
16
|
+
"license": "ISC",
|
|
17
|
+
"type": "commonjs",
|
|
11
18
|
"dependencies": {
|
|
12
19
|
"dotenv": "^16.3.1",
|
|
13
20
|
"express": "^5.2.1",
|
|
@@ -4,29 +4,30 @@ function format(tag: string, args: unknown[]) {
|
|
|
4
4
|
return [`%c[${tag}]`, "color:#6366f1;font-weight:600", ...args];
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
ENV.NODE_ENV === "development" || ENV.NODE_ENV === "staging"
|
|
7
|
+
function getEnvironment() {
|
|
8
|
+
return ENV.NODE_ENV === "development" || ENV.NODE_ENV === "staging"
|
|
9
9
|
? "development"
|
|
10
10
|
: ENV.NODE_ENV;
|
|
11
|
+
}
|
|
11
12
|
|
|
12
13
|
const logger = {
|
|
13
14
|
log(tag: string, ...args: unknown[]) {
|
|
14
|
-
if (
|
|
15
|
+
if (getEnvironment() !== "development") return;
|
|
15
16
|
console.log(...format(tag, args));
|
|
16
17
|
},
|
|
17
18
|
|
|
18
19
|
info(tag: string, ...args: unknown[]) {
|
|
19
|
-
if (
|
|
20
|
+
if (getEnvironment() !== "development") return;
|
|
20
21
|
console.info(...format(tag, args));
|
|
21
22
|
},
|
|
22
23
|
|
|
23
24
|
warn(tag: string, ...args: unknown[]) {
|
|
24
|
-
if (
|
|
25
|
+
if (getEnvironment() !== "development") return;
|
|
25
26
|
console.warn(...format(tag, args));
|
|
26
27
|
},
|
|
27
28
|
|
|
28
29
|
error(tag: string, ...args: unknown[]) {
|
|
29
|
-
if (
|
|
30
|
+
if (getEnvironment() !== "development") return;
|
|
30
31
|
console.error(...format(tag, args));
|
|
31
32
|
},
|
|
32
33
|
};
|