@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 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,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);
@@ -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
- packageJson.name = serviceName || res.sanitizedName;
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(packageJson, null, 2) + "\n"
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.9",
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
- const environment =
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 (environment !== "development") return;
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 (environment !== "development") return;
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 (environment !== "development") return;
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 (environment !== "development") return;
30
+ if (getEnvironment() !== "development") return;
30
31
  console.error(...format(tag, args));
31
32
  },
32
33
  };