@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 CHANGED
@@ -1,6 +1,20 @@
1
1
  # 🚀 Backend Template Generator
2
2
 
3
- A powerful CLI tool to generate production-ready Node.js + TypeScript backend applications with Express.js. Supports both monolith and microservice architectures with optional features like authentication, CORS, rate limiting, and more.
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. **Project Type**
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
- ### 2. **Deployment Mode** (Microservices only)
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
- ### 3. **Optional Features**
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
- ### 4. **Authentication**
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 '${sanitizedName}' already exists!`);
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 '${sanitizedName}' already exists!`);
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("\n📝 Generating README.md...\n");
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...\n");
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("\n🔧 Setting up Husky...\n");
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("\n⚠️ Husky setup failed (run 'npm run prepare' manually after fixing dependencies)\n");
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("\n⚠️ Husky setup skipped (run 'npm install && npm run prepare' to set up git hooks)\n");
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 '${servicesToCreate[0]}' added successfully!`);
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("\n✅ Backend created successfully!");
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("\n✅ Backend created successfully!");
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
  }
@@ -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);
@@ -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, "src/app.ts");
28
- const gatewayServerPath = path.join(serviceRoot, "src/server.ts");
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, "src/routes.ts");
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.ts
128
- const appPath = path.join(serviceRoot, "src/app.ts");
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, "src/middlewares/root.middleware.ts");
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.ts if needed
171
+ // Update v1 index file if needed
155
172
  if (v1Imports.length || v1Routes.length) {
156
- const v1IndexPath = path.join(serviceRoot, "src/modules/v1/index.ts");
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.ts to conditionally include ALLOWED_ORIGIN and MONGO_URI
178
- const envPath = path.join(serviceRoot, "src/config/env.ts");
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.ts to connect to DB if auth is enabled
223
- const serverPath = path.join(serviceRoot, "src/server.ts");
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.0.10",
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": {