create-backlist 6.1.7 → 6.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/generators/node.js +300 -241
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "6.1.7",
3
+ "version": "6.1.8",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,299 +1,358 @@
1
- const chalk = require('chalk')
2
- const { execa } = require('execa')
3
- const fs = require('fs-extra')
4
- const path = require('path')
5
- const ejs = require('ejs')
6
- const { analyzeFrontend } = require('../analyzer')
7
- const { renderAndWrite, getTemplatePath } = require('./template')
8
-
9
- async function generateNodeProject (options) {
10
- // v5.0: Destructure all new options
11
- const { projectDir, projectName, frontendSrcDir, dbType, addAuth, addSeeder, extraFeatures = [] } = options
12
- const port = 8000
13
-
14
- // --- Step 1: Analyze Frontend ---
15
- console.log(chalk.blue(' -> Analyzing frontend for API endpoints...'))
16
-
17
- // NOTE: 'let' use kala api endpoints wenas karana nisa
18
- let endpoints = await analyzeFrontend(frontendSrcDir)
19
-
20
- if (endpoints.length > 0) {
21
- console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`))
22
-
23
- // ============================================================
24
- // 🔥 FIX START: Sanitizing Endpoints Logic
25
- // ============================================================
26
- endpoints = endpoints.map(ep => {
27
- // 1. Path eka sudda kirima (/api/v1/users -> ['users'])
28
- // 'api', 'v1', histhan ain karanawa
29
- const parts = ep.path.split('/').filter(part => part !== '' && part !== 'api' && part !== 'v1')
30
-
31
- // Resource eka hoyaganeema (e.g., 'users')
32
- const resource = parts[0] || 'Default'
33
-
34
- // 2. Controller Name eka hadeema (CamelCase: 'users' -> 'Users')
35
- // Special Case: resource eka 'auth' nam Controller eka 'Auth'
36
- // 'V1' kiyana eka ain wenne methanin
37
- const controllerName = `${resource.charAt(0).toUpperCase()}${resource.slice(1)}`
38
-
39
- // 3. Function Names hariyatama map kirima
40
- let functionName = ''
41
-
42
- // --- AUTH LOGIC ---
43
- if (controllerName.toLowerCase() === 'auth') {
44
- if (ep.path.includes('login')) functionName = 'loginUser'
45
- else if (ep.path.includes('register')) functionName = 'registerUser'
46
- else functionName = 'authAction' // fallback
47
- }
48
- // --- GENERAL RESOURCES LOGIC ---
49
- else {
50
- // Singular/Plural logic to avoid 'Userss'
51
- const singularName = resource.endsWith('s') ? resource.slice(0, -1) : resource
52
- const pluralName = resource.endsWith('s') ? resource : `${resource}s`
53
-
54
- const pascalSingular = `${singularName.charAt(0).toUpperCase()}${singularName.slice(1)}`
55
- const pascalPlural = `${pluralName.charAt(0).toUpperCase()}${pluralName.slice(1)}`
56
-
57
- if (ep.method === 'GET') {
58
- if (ep.path.includes(':id')) functionName = `get${pascalSingular}ById`
59
- else functionName = `getAll${pascalPlural}` // Fixes 'getAllUserss'
60
- } else if (ep.method === 'POST') {
61
- functionName = `create${pascalSingular}`
62
- } else if (ep.method === 'PUT') {
63
- functionName = `update${pascalSingular}ById`
64
- } else if (ep.method === 'DELETE') {
65
- functionName = `delete${pascalSingular}ById`
66
- } else {
67
- functionName = `${ep.method.toLowerCase()}${pascalPlural}`
68
- }
69
- }
1
+ const chalk = require("chalk");
2
+ const { execa } = require("execa");
3
+ const fs = require("fs-extra");
4
+ const path = require("path");
5
+ const ejs = require("ejs");
70
6
 
71
- // Update the endpoint object
72
- // meka ejs file ekedi <%= ep.functionName %> kiyala use karanna puluwan
73
- return {
74
- ...ep,
75
- controllerName,
76
- functionName
77
- }
78
- })
79
- // ============================================================
80
- // 🔥 FIX END
81
- // ============================================================
7
+ const { analyzeFrontend } = require("../analyzer");
8
+ const { renderAndWrite, getTemplatePath } = require("./template");
9
+
10
+ function sanitizeEndpoints(endpoints) {
11
+ if (!Array.isArray(endpoints)) return [];
12
+
13
+ return endpoints.map((ep) => {
14
+ const rawPath = String(ep.path || ep.route || "/");
15
+
16
+ // remove empty, api, and version segments (v1/v2/v10...)
17
+ const parts = rawPath
18
+ .split("/")
19
+ .filter(Boolean)
20
+ .filter((p) => p !== "api" && !/^v\d+$/i.test(p));
21
+
22
+ const resource = parts[0] || "Default";
23
+ const controllerName = `${resource.charAt(0).toUpperCase()}${resource.slice(1)}`;
24
+
25
+ let functionName = "";
26
+
27
+ // AUTH naming
28
+ if (controllerName.toLowerCase() === "auth") {
29
+ if (rawPath.includes("login")) functionName = "loginUser";
30
+ else if (rawPath.includes("register")) functionName = "registerUser";
31
+ else functionName = "authAction";
32
+ } else {
33
+ const singularName = resource.endsWith("s") ? resource.slice(0, -1) : resource;
34
+ const pluralName = resource.endsWith("s") ? resource : `${resource}s`;
35
+
36
+ const pascalSingular = `${singularName.charAt(0).toUpperCase()}${singularName.slice(1)}`;
37
+ const pascalPlural = `${pluralName.charAt(0).toUpperCase()}${pluralName.slice(1)}`;
38
+
39
+ const method = String(ep.method || "GET").toUpperCase();
40
+
41
+ // detect id param in route/path
42
+ const hasId = rawPath.includes(":id") || /\/\d+/.test(rawPath) || rawPath.includes("{id}") || rawPath.includes("/:");
43
+
44
+ if (method === "GET") {
45
+ functionName = hasId ? `get${pascalSingular}ById` : `getAll${pascalPlural}`;
46
+ } else if (method === "POST") {
47
+ functionName = `create${pascalSingular}`;
48
+ } else if (method === "PUT" || method === "PATCH") {
49
+ functionName = `update${pascalSingular}ById`;
50
+ } else if (method === "DELETE") {
51
+ functionName = `delete${pascalSingular}ById`;
52
+ } else {
53
+ functionName = `${method.toLowerCase()}${pascalPlural}`;
54
+ }
55
+ }
56
+
57
+ return { ...ep, controllerName, functionName };
58
+ });
59
+ }
60
+
61
+ async function generateNodeProject(options) {
62
+ const {
63
+ projectDir,
64
+ projectName,
65
+ frontendSrcDir,
66
+ dbType,
67
+ addAuth,
68
+ addSeeder,
69
+ extraFeatures = [],
70
+ } = options;
71
+
72
+ const port = 8000;
73
+
74
+ try {
75
+ // --- Step 1: Analyze Frontend ---
76
+ console.log(chalk.blue(" -> Analyzing frontend for API endpoints..."));
77
+ let endpoints = await analyzeFrontend(frontendSrcDir);
78
+
79
+ if (endpoints.length > 0) {
80
+ console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
81
+ endpoints = sanitizeEndpoints(endpoints);
82
82
  } else {
83
- console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'))
83
+ console.log(chalk.yellow(" -> No API endpoints found. A basic project will be created."));
84
84
  }
85
85
 
86
86
  // --- Step 2: Identify Models to Generate ---
87
- const modelsToGenerate = new Map()
88
- endpoints.forEach(ep => {
89
- // 🔥 FIX: 'ep.schemaFields' තිබ්බත් නැතත් Controller එක හදන්න ඕන.
90
- // නැත්නම් Routes Import එකේදි Error එනවා.
91
- if (ep.controllerName !== 'Default' && ep.controllerName !== 'Auth' && !modelsToGenerate.has(ep.controllerName)) {
92
- // Schema Fields නැත්නම් හිස් Array එකක් ගන්න
93
- let fields = []
87
+ const modelsToGenerate = new Map();
88
+
89
+ endpoints.forEach((ep) => {
90
+ if (!ep || !ep.controllerName) return;
91
+
92
+ if (ep.controllerName !== "Default" && ep.controllerName !== "Auth" && !modelsToGenerate.has(ep.controllerName)) {
93
+ let fields = [];
94
94
  if (ep.schemaFields) {
95
- fields = Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type, isUnique: key === 'email' }))
95
+ fields = Object.entries(ep.schemaFields).map(([key, type]) => ({
96
+ name: key,
97
+ type,
98
+ isUnique: key === "email",
99
+ }));
96
100
  }
97
101
 
98
- modelsToGenerate.set(ep.controllerName, {
99
- name: ep.controllerName,
100
- fields
101
- })
102
+ modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields });
102
103
  }
103
- })
104
-
105
- if (addAuth && !modelsToGenerate.has('User')) {
106
- console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'))
107
- modelsToGenerate.set('User', { name: 'User', fields: [{ name: 'name', type: 'String' }, { name: 'email', type: 'String', isUnique: true }, { name: 'password', type: 'String' }] })
104
+ });
105
+
106
+ if (addAuth && !modelsToGenerate.has("User")) {
107
+ console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
108
+ modelsToGenerate.set("User", {
109
+ name: "User",
110
+ fields: [
111
+ { name: "name", type: "String" },
112
+ { name: "email", type: "String", isUnique: true },
113
+ { name: "password", type: "String" },
114
+ ],
115
+ });
108
116
  }
109
117
 
110
118
  // --- Step 3: Base Scaffolding ---
111
- console.log(chalk.blue(' -> Scaffolding Node.js project...'))
112
- const destSrcDir = path.join(projectDir, 'src')
113
- await fs.ensureDir(destSrcDir)
114
- await fs.copy(getTemplatePath('node-ts-express/base/server.ts'), path.join(destSrcDir, 'server.ts'))
115
- await fs.copy(getTemplatePath('node-ts-express/base/tsconfig.json'), path.join(projectDir, 'tsconfig.json'))
116
-
117
- // --- Step 4: Prepare and Write package.json ---
118
- const packageJsonContent = JSON.parse(await ejs.renderFile(getTemplatePath('node-ts-express/partials/package.json.ejs'), { projectName }))
119
-
120
- if (dbType === 'mongoose') packageJsonContent.dependencies.mongoose = '^7.6.3'
121
- if (dbType === 'prisma') {
122
- packageJsonContent.dependencies['@prisma/client'] = '^5.6.0'
123
- packageJsonContent.devDependencies.prisma = '^5.6.0'
124
- packageJsonContent.prisma = { seed: `ts-node ${addSeeder ? 'scripts/seeder.ts' : 'prisma/seed.ts'}` }
119
+ console.log(chalk.blue(" -> Scaffolding Node.js project..."));
120
+ const destSrcDir = path.join(projectDir, "src");
121
+ await fs.ensureDir(destSrcDir);
122
+
123
+ await fs.copy(getTemplatePath("node-ts-express/base/server.ts"), path.join(destSrcDir, "server.ts"));
124
+ await fs.copy(getTemplatePath("node-ts-express/base/tsconfig.json"), path.join(projectDir, "tsconfig.json"));
125
+
126
+ // --- Step 4: package.json ---
127
+ const packageJsonContent = JSON.parse(
128
+ await ejs.renderFile(getTemplatePath("node-ts-express/partials/package.json.ejs"), { projectName })
129
+ );
130
+
131
+ if (dbType === "mongoose") packageJsonContent.dependencies.mongoose = "^7.6.3";
132
+ if (dbType === "prisma") {
133
+ packageJsonContent.dependencies["@prisma/client"] = "^5.6.0";
134
+ packageJsonContent.devDependencies.prisma = "^5.6.0";
135
+ packageJsonContent.prisma = { seed: `ts-node ${addSeeder ? "scripts/seeder.ts" : "prisma/seed.ts"}` };
125
136
  }
137
+
126
138
  if (addAuth) {
127
- packageJsonContent.dependencies.jsonwebtoken = '^9.0.2'
128
- packageJsonContent.dependencies.bcryptjs = '^2.4.3'
129
- packageJsonContent.devDependencies['@types/jsonwebtoken'] = '^9.0.5'
130
- packageJsonContent.devDependencies['@types/bcryptjs'] = '^2.4.6'
139
+ packageJsonContent.dependencies.jsonwebtoken = "^9.0.2";
140
+ packageJsonContent.dependencies.bcryptjs = "^2.4.3";
141
+ packageJsonContent.devDependencies["@types/jsonwebtoken"] = "^9.0.5";
142
+ packageJsonContent.devDependencies["@types/bcryptjs"] = "^2.4.6";
131
143
  }
144
+
132
145
  if (addSeeder) {
133
- packageJsonContent.devDependencies['@faker-js/faker'] = '^8.3.1'
134
- if (!packageJsonContent.dependencies.chalk) packageJsonContent.dependencies.chalk = '^4.1.2'
135
- packageJsonContent.scripts.seed = 'ts-node scripts/seeder.ts'
136
- packageJsonContent.scripts.destroy = 'ts-node scripts/seeder.ts -d'
146
+ packageJsonContent.devDependencies["@faker-js/faker"] = "^8.3.1";
147
+ if (!packageJsonContent.dependencies.chalk) packageJsonContent.dependencies.chalk = "^4.1.2";
148
+ packageJsonContent.scripts.seed = "ts-node scripts/seeder.ts";
149
+ packageJsonContent.scripts.destroy = "ts-node scripts/seeder.ts -d";
137
150
  }
138
- if (extraFeatures.includes('testing')) {
139
- packageJsonContent.devDependencies.jest = '^29.7.0'
140
- packageJsonContent.devDependencies.supertest = '^6.3.3'
141
- packageJsonContent.devDependencies['@types/jest'] = '^29.5.10'
142
- packageJsonContent.devDependencies['@types/supertest'] = '^2.0.16'
143
- packageJsonContent.devDependencies['ts-jest'] = '^29.1.1'
144
- packageJsonContent.scripts.test = 'jest --detectOpenHandles --forceExit'
151
+
152
+ if (extraFeatures.includes("testing")) {
153
+ packageJsonContent.devDependencies.jest = "^29.7.0";
154
+ packageJsonContent.devDependencies.supertest = "^6.3.3";
155
+ packageJsonContent.devDependencies["@types/jest"] = "^29.5.10";
156
+ packageJsonContent.devDependencies["@types/supertest"] = "^2.0.16";
157
+ packageJsonContent.devDependencies["ts-jest"] = "^29.1.1";
158
+ packageJsonContent.scripts.test = "jest --detectOpenHandles --forceExit";
145
159
  }
146
- if (extraFeatures.includes('swagger')) {
147
- packageJsonContent.dependencies['swagger-ui-express'] = '^5.0.0'
148
- packageJsonContent.dependencies['swagger-jsdoc'] = '^6.2.8'
149
- packageJsonContent.devDependencies['@types/swagger-ui-express'] = '^4.1.6'
160
+
161
+ if (extraFeatures.includes("swagger")) {
162
+ packageJsonContent.dependencies["swagger-ui-express"] = "^5.0.0";
163
+ packageJsonContent.dependencies["swagger-jsdoc"] = "^6.2.8";
164
+ packageJsonContent.devDependencies["@types/swagger-ui-express"] = "^4.1.6";
150
165
  }
151
- await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 })
152
166
 
153
- // --- Step 5: Generate DB-specific files & Controllers ---
167
+ await fs.writeJson(path.join(projectDir, "package.json"), packageJsonContent, { spaces: 2 });
168
+
169
+ // --- Step 5: DB + Controllers ---
154
170
  if (modelsToGenerate.size > 0) {
155
- await fs.ensureDir(path.join(destSrcDir, 'controllers'))
156
- if (dbType === 'mongoose') {
157
- console.log(chalk.blue(' -> Generating Mongoose models and controllers...'))
158
- await fs.ensureDir(path.join(destSrcDir, 'models'))
171
+ await fs.ensureDir(path.join(destSrcDir, "controllers"));
172
+
173
+ if (dbType === "mongoose") {
174
+ console.log(chalk.blue(" -> Generating Mongoose models and controllers..."));
175
+ await fs.ensureDir(path.join(destSrcDir, "models"));
176
+
159
177
  for (const [modelName, modelData] of modelsToGenerate.entries()) {
160
- const schema = modelData.fields.reduce((acc, field) => { acc[field.name] = field.type; return acc }, {})
161
- await renderAndWrite(getTemplatePath('node-ts-express/partials/Model.ts.ejs'), path.join(destSrcDir, 'models', `${modelName}.model.ts`), { modelName, schema, projectName })
178
+ const schema = modelData.fields.reduce((acc, field) => {
179
+ acc[field.name] = field.type;
180
+ return acc;
181
+ }, {});
182
+ await renderAndWrite(
183
+ getTemplatePath("node-ts-express/partials/Model.ts.ejs"),
184
+ path.join(destSrcDir, "models", `${modelName}.model.ts`),
185
+ { modelName, schema, projectName }
186
+ );
162
187
  }
163
- } else if (dbType === 'prisma') {
164
- console.log(chalk.blue(' -> Generating Prisma schema...'))
165
- await fs.ensureDir(path.join(projectDir, 'prisma'))
166
- await renderAndWrite(getTemplatePath('node-ts-express/partials/PrismaSchema.prisma.ejs'), path.join(projectDir, 'prisma', 'schema.prisma'), { modelsToGenerate: Array.from(modelsToGenerate.values()) })
188
+ } else if (dbType === "prisma") {
189
+ console.log(chalk.blue(" -> Generating Prisma schema..."));
190
+ await fs.ensureDir(path.join(projectDir, "prisma"));
191
+ await renderAndWrite(
192
+ getTemplatePath("node-ts-express/partials/PrismaSchema.prisma.ejs"),
193
+ path.join(projectDir, "prisma", "schema.prisma"),
194
+ { modelsToGenerate: Array.from(modelsToGenerate.values()) }
195
+ );
167
196
  }
168
- console.log(chalk.blue(' -> Generating controllers...'))
197
+
198
+ console.log(chalk.blue(" -> Generating controllers..."));
169
199
  for (const [modelName] of modelsToGenerate.entries()) {
170
- const templateFile = dbType === 'mongoose' ? 'Controller.ts.ejs' : 'PrismaController.ts.ejs'
171
- // Controller hadaddi Auth eka skip karanawa (mokada eka yatin wenama hadanawa)
172
- if (modelName !== 'Auth') {
173
- await renderAndWrite(getTemplatePath(`node-ts-express/partials/${templateFile}`), path.join(destSrcDir, 'controllers', `${modelName}.controller.ts`), { modelName, projectName })
200
+ const templateFile = dbType === "mongoose" ? "Controller.ts.ejs" : "PrismaController.ts.ejs";
201
+ if (modelName !== "Auth") {
202
+ await renderAndWrite(
203
+ getTemplatePath(`node-ts-express/partials/${templateFile}`),
204
+ path.join(destSrcDir, "controllers", `${modelName}.controller.ts`),
205
+ { modelName, projectName }
206
+ );
174
207
  }
175
208
  }
176
209
  }
177
210
 
178
- // --- Step 6: Generate Authentication Boilerplate ---
211
+ // --- Step 6: Auth ---
179
212
  if (addAuth) {
180
- console.log(chalk.blue(' -> Generating authentication boilerplate...'))
181
- await fs.ensureDir(path.join(destSrcDir, 'routes'))
182
- await fs.ensureDir(path.join(destSrcDir, 'middleware'))
183
- await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.controller.ts.ejs'), path.join(destSrcDir, 'controllers', 'Auth.controller.ts'), { dbType, projectName })
184
- await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.routes.ts.ejs'), path.join(destSrcDir, 'routes', 'Auth.routes.ts'), { projectName })
185
- await renderAndWrite(getTemplatePath('node-ts-express/partials/Auth.middleware.ts.ejs'), path.join(destSrcDir, 'middleware', 'Auth.middleware.ts'), { projectName })
186
-
187
- if (dbType === 'mongoose') {
188
- const userModelPath = path.join(destSrcDir, 'models', 'User.model.ts')
189
- if (await fs.pathExists(userModelPath)) {
190
- let userModelContent = await fs.readFile(userModelPath, 'utf-8')
191
- if (!userModelContent.includes('bcryptjs')) {
192
- userModelContent = userModelContent.replace("import mongoose, { Schema, Document } from 'mongoose';", "import mongoose, { Schema, Document } from 'mongoose';\nimport bcrypt from 'bcryptjs';")
193
- const preSaveHook = "\n// Hash password before saving\nUserSchema.pre('save', async function(next) {\n if (!this.isModified('password')) { return next(); }\n const salt = await bcrypt.genSalt(10);\n this.password = await bcrypt.hash(this.password, salt);\n next();\n});\n"
194
- userModelContent = userModelContent.replace('// Create and export the Model', `${preSaveHook}\n// Create and export the Model`)
195
- await fs.writeFile(userModelPath, userModelContent)
196
- }
197
- }
198
- }
213
+ console.log(chalk.blue(" -> Generating authentication boilerplate..."));
214
+ await fs.ensureDir(path.join(destSrcDir, "routes"));
215
+ await fs.ensureDir(path.join(destSrcDir, "middleware"));
216
+
217
+ await renderAndWrite(
218
+ getTemplatePath("node-ts-express/partials/Auth.controller.ts.ejs"),
219
+ path.join(destSrcDir, "controllers", "Auth.controller.ts"),
220
+ { dbType, projectName }
221
+ );
222
+ await renderAndWrite(
223
+ getTemplatePath("node-ts-express/partials/Auth.routes.ts.ejs"),
224
+ path.join(destSrcDir, "routes", "Auth.routes.ts"),
225
+ { projectName }
226
+ );
227
+ await renderAndWrite(
228
+ getTemplatePath("node-ts-express/partials/Auth.middleware.ts.ejs"),
229
+ path.join(destSrcDir, "middleware", "Auth.middleware.ts"),
230
+ { projectName }
231
+ );
199
232
  }
200
233
 
201
- // --- Step 7: Generate Seeder Script ---
234
+ // --- Step 7: Seeder ---
202
235
  if (addSeeder) {
203
- console.log(chalk.blue(' -> Generating database seeder script...'))
204
- await fs.ensureDir(path.join(projectDir, 'scripts'))
205
- await renderAndWrite(getTemplatePath('node-ts-express/partials/Seeder.ts.ejs'), path.join(projectDir, 'scripts', 'seeder.ts'), { projectName })
236
+ console.log(chalk.blue(" -> Generating database seeder script..."));
237
+ await fs.ensureDir(path.join(projectDir, "scripts"));
238
+ await renderAndWrite(
239
+ getTemplatePath("node-ts-express/partials/Seeder.ts.ejs"),
240
+ path.join(projectDir, "scripts", "seeder.ts"),
241
+ { projectName, models: Array.from(modelsToGenerate.values()) }
242
+ );
206
243
  }
207
244
 
208
- // --- Step 8: Generate Extra Features ---
209
- if (extraFeatures.includes('docker')) {
210
- console.log(chalk.blue(' -> Generating Docker files...'))
211
- await renderAndWrite(getTemplatePath('node-ts-express/partials/Dockerfile.ejs'), path.join(projectDir, 'Dockerfile'), { dbType, port })
212
- await renderAndWrite(getTemplatePath('node-ts-express/partials/docker-compose.yml.ejs'), path.join(projectDir, 'docker-compose.yml'), { projectName, dbType, port })
245
+ // --- Step 8: Extras ---
246
+ if (extraFeatures.includes("docker")) {
247
+ console.log(chalk.blue(" -> Generating Docker files..."));
248
+ await renderAndWrite(getTemplatePath("node-ts-express/partials/Dockerfile.ejs"), path.join(projectDir, "Dockerfile"), {
249
+ dbType,
250
+ port,
251
+ });
252
+ await renderAndWrite(
253
+ getTemplatePath("node-ts-express/partials/docker-compose.yml.ejs"),
254
+ path.join(projectDir, "docker-compose.yml"),
255
+ { projectName, dbType, port, addAuth, extraFeatures }
256
+ );
213
257
  }
214
- if (extraFeatures.includes('swagger')) {
215
- console.log(chalk.blue(' -> Generating API documentation setup...'))
216
- await fs.ensureDir(path.join(destSrcDir, 'utils'))
217
- await renderAndWrite(getTemplatePath('node-ts-express/partials/ApiDocs.ts.ejs'), path.join(destSrcDir, 'utils', 'swagger.ts'), { projectName, port })
258
+
259
+ if (extraFeatures.includes("swagger")) {
260
+ console.log(chalk.blue(" -> Generating API documentation setup..."));
261
+ await fs.ensureDir(path.join(destSrcDir, "utils"));
262
+ await renderAndWrite(
263
+ getTemplatePath("node-ts-express/partials/ApiDocs.ts.ejs"),
264
+ path.join(destSrcDir, "utils", "swagger.ts"),
265
+ { projectName, port, addAuth }
266
+ );
218
267
  }
219
- if (extraFeatures.includes('testing')) {
220
- console.log(chalk.blue(' -> Generating testing boilerplate...'))
221
- const jestConfig = "/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n verbose: true,\n};"
222
- await fs.writeFile(path.join(projectDir, 'jest.config.js'), jestConfig)
223
- await fs.ensureDir(path.join(projectDir, 'src', '__tests__'))
224
- await renderAndWrite(getTemplatePath('node-ts-express/partials/App.test.ts.ejs'), path.join(projectDir, 'src', '__tests__', 'api.test.ts'), { addAuth })
268
+
269
+ if (extraFeatures.includes("testing")) {
270
+ console.log(chalk.blue(" -> Generating testing boilerplate..."));
271
+ const jestConfig =
272
+ "/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n verbose: true,\n};";
273
+
274
+ await fs.writeFile(path.join(projectDir, "jest.config.js"), jestConfig);
275
+ await fs.ensureDir(path.join(projectDir, "src", "__tests__"));
276
+
277
+ // IMPORTANT: pass endpoints for auto-tests
278
+ await renderAndWrite(
279
+ getTemplatePath("node-ts-express/partials/App.test.ts.ejs"),
280
+ path.join(projectDir, "src", "__tests__", "api.test.ts"),
281
+ { addAuth, endpoints }
282
+ );
225
283
  }
226
284
 
227
- // --- Step 9: Generate Main Route File & Inject Logic into Server ---
228
- // 🔥 FIX: Auth Endpoints ටික routes.ts එකට යවන්න එපා.
229
- // මොකද ඒවා Auth.routes.ts එකෙන් වෙනම හැන්ඩ්ල් වෙනවා.
230
- const nonAuthEndpoints = endpoints.filter(ep => ep.controllerName !== 'Auth')
285
+ // --- Step 9: routes.ts + server inject ---
286
+ const nonAuthEndpoints = endpoints.filter((ep) => ep.controllerName !== "Auth");
231
287
 
232
- // IMPORTANT: Pass 'nonAuthEndpoints' instead of 'endpoints'
233
288
  await renderAndWrite(
234
- getTemplatePath('node-ts-express/partials/routes.ts.ejs'),
235
- path.join(destSrcDir, 'routes.ts'),
289
+ getTemplatePath("node-ts-express/partials/routes.ts.ejs"),
290
+ path.join(destSrcDir, "routes.ts"),
236
291
  { endpoints: nonAuthEndpoints, addAuth, dbType }
237
- )
292
+ );
238
293
 
239
- let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8')
294
+ let serverFileContent = await fs.readFile(path.join(destSrcDir, "server.ts"), "utf-8");
240
295
 
241
- // =========================================================================
242
- // 👇 මේ ටික තමයි උඹේ Code එකෙන් Missing වෙලා තිබ්බේ. මේක නැතුව DB Connect වෙන්නේ නෑ.
243
- // =========================================================================
244
- let dbConnectionCode = ''; let swaggerInjector = ''; let authRoutesInjector = ''
296
+ let dbConnectionCode = "";
297
+ let swaggerInjector = "";
298
+ let authRoutesInjector = "";
245
299
 
246
- if (dbType === 'mongoose') {
300
+ if (dbType === "mongoose") {
301
+ dbConnectionCode = `
302
+ import mongoose from 'mongoose';
303
+ const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';
304
+ mongoose.connect(MONGO_URI)
305
+ .then(() => console.log('MongoDB Connected...'))
306
+ .catch(err => console.error(err));
307
+ `;
308
+ } else if (dbType === "prisma") {
247
309
  dbConnectionCode = `
248
- // --- Database Connection ---
249
- import mongoose from 'mongoose';
250
- const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';
251
- mongoose.connect(MONGO_URI).then(() => console.log('MongoDB Connected...')).catch(err => console.error(err));
252
- // -------------------------
253
- `
254
- } else if (dbType === 'prisma') {
255
- dbConnectionCode = "\nimport { PrismaClient } from '@prisma/client';\nexport const prisma = new PrismaClient();\n"
310
+ import { PrismaClient } from '@prisma/client';
311
+ export const prisma = new PrismaClient();
312
+ `;
256
313
  }
257
- if (extraFeatures.includes('swagger')) {
258
- swaggerInjector = "\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n"
314
+
315
+ if (extraFeatures.includes("swagger")) {
316
+ swaggerInjector = "\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n";
259
317
  }
318
+
260
319
  if (addAuth) {
261
- authRoutesInjector = "import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n"
320
+ authRoutesInjector = "import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n";
262
321
  }
263
322
 
264
323
  serverFileContent = serverFileContent
265
- .replace('dotenv.config();', `dotenv.config();${dbConnectionCode}`)
266
- .replace('// INJECT:ROUTES', `${authRoutesInjector}import apiRoutes from './routes';
267
- app.use('/api', apiRoutes);`)
268
-
269
- const listenRegex = /(app\.listen\()/
270
- serverFileContent = serverFileContent.replace(listenRegex, `${swaggerInjector}\n$1`)
271
- await fs.writeFile(path.join(destSrcDir, 'server.ts'), serverFileContent)
272
- // =========================================================================
273
-
274
- // --- Step 10: Install Dependencies & Run Post-install Scripts ---
275
- console.log(chalk.magenta(' -> Installing dependencies... This may take a moment.'))
276
- await execa('npm', ['install'], { cwd: projectDir })
277
- if (dbType === 'prisma') {
278
- console.log(chalk.blue(' -> Running `prisma generate`...'))
279
- await execa('npx', ['prisma', 'generate'], { cwd: projectDir })
280
- }
324
+ .replace("dotenv.config();", `dotenv.config();${dbConnectionCode}`)
325
+ .replace(
326
+ "// INJECT:ROUTES",
327
+ `${authRoutesInjector}import apiRoutes from './routes';
328
+ app.use('/api', apiRoutes);`
329
+ );
281
330
 
282
- // --- Step 11: Generate Final Files (.env.example) ---
283
- let envContent = `PORT=${port}\n`
284
- if (dbType === 'mongoose') {
285
- envContent += `MONGO_URI=mongodb://root:example@db:27017/${projectName}?authSource=admin\n`
286
- } else if (dbType === 'prisma') {
287
- envContent += `DATABASE_URL="postgresql://user:password@db:5432/${projectName}?schema=public"\n`
288
- }
289
- if (addAuth) envContent += 'JWT_SECRET=your_super_secret_jwt_key_12345\n'
290
- if (extraFeatures.includes('docker')) {
291
- envContent += `\n# Docker-compose credentials (used in docker-compose.yml)\nDB_USER=user\nDB_PASSWORD=password\nDB_NAME=${projectName}`
331
+ serverFileContent = serverFileContent.replace(/(app\.listen\()/, `${swaggerInjector}\n$1`);
332
+
333
+ await fs.writeFile(path.join(destSrcDir, "server.ts"), serverFileContent);
334
+
335
+ // --- Step 10: Install deps ---
336
+ console.log(chalk.magenta(" -> Installing dependencies... This may take a moment."));
337
+ await execa("npm", ["install"], { cwd: projectDir });
338
+
339
+ if (dbType === "prisma") {
340
+ console.log(chalk.blue(" -> Running `prisma generate`..."));
341
+ await execa("npx", ["prisma", "generate"], { cwd: projectDir });
292
342
  }
293
- await fs.writeFile(path.join(projectDir, '.env.example'), envContent)
343
+
344
+ // --- Step 11: .env.example ---
345
+ let envContent = `PORT=${port}\n`;
346
+ if (dbType === "mongoose") envContent += `MONGO_URI=mongodb://127.0.0.1:27017/${projectName}\n`;
347
+ if (dbType === "prisma") envContent += `DATABASE_URL="postgresql://user:password@localhost:5432/${projectName}?schema=public"\n`;
348
+ if (addAuth) envContent += "JWT_SECRET=your_super_secret_jwt_key_12345\nJWT_EXPIRES_IN=5h\n";
349
+
350
+ await fs.writeFile(path.join(projectDir, ".env.example"), envContent);
351
+
352
+ console.log(chalk.green(" -> Node backend generation complete."));
294
353
  } catch (error) {
295
- throw error
354
+ throw error;
296
355
  }
297
356
  }
298
357
 
299
- module.exports = { generateNodeProject }
358
+ module.exports = { generateNodeProject };