create-backlist 6.1.7 → 6.1.9

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 +322 -242
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "6.1.7",
3
+ "version": "6.1.9",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,299 +1,379 @@
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 stripQuery(p) {
11
+ return String(p || "").split("?")[0];
12
+ }
13
+
14
+ function safePascalName(name) {
15
+ // remove query + invalid filename chars, keep alphanumerics only
16
+ const cleaned = String(name || "Default")
17
+ .split("?")[0]
18
+ .replace(/[^a-zA-Z0-9]/g, "");
19
+
20
+ if (!cleaned) return "Default";
21
+ return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
22
+ }
23
+
24
+ function sanitizeEndpoints(endpoints) {
25
+ if (!Array.isArray(endpoints)) return [];
26
+
27
+ return endpoints.map((ep) => {
28
+ // IMPORTANT: strip query string so it never leaks into names
29
+ const rawPath = stripQuery(ep.path || ep.route || "/");
30
+
31
+ // remove empty, api, and version segments (v1/v2/v10...)
32
+ const parts = rawPath
33
+ .split("/")
34
+ .filter(Boolean)
35
+ .filter((p) => p !== "api" && !/^v\d+$/i.test(p));
36
+
37
+ const resource = parts[0] || "Default";
38
+ const controllerName = safePascalName(resource);
39
+
40
+ let functionName = "";
41
+
42
+ // AUTH naming
43
+ if (controllerName.toLowerCase() === "auth") {
44
+ if (rawPath.includes("login")) functionName = "loginUser";
45
+ else if (rawPath.includes("register")) functionName = "registerUser";
46
+ else functionName = "authAction";
47
+ } else {
48
+ const singularName = resource.endsWith("s") ? resource.slice(0, -1) : resource;
49
+ const pluralName = resource.endsWith("s") ? resource : `${resource}s`;
50
+
51
+ const pascalSingular = safePascalName(singularName);
52
+ const pascalPlural = safePascalName(pluralName);
53
+
54
+ const method = String(ep.method || "GET").toUpperCase();
55
+
56
+ const hasId =
57
+ rawPath.includes(":") || // :id style
58
+ rawPath.includes("{") || // {id} style
59
+ /\/\d+/.test(rawPath); // numeric
60
+
61
+ if (method === "GET") {
62
+ functionName = hasId ? `get${pascalSingular}ById` : `getAll${pascalPlural}`;
63
+ } else if (method === "POST") {
64
+ functionName = `create${pascalSingular}`;
65
+ } else if (method === "PUT" || method === "PATCH") {
66
+ functionName = `update${pascalSingular}ById`;
67
+ } else if (method === "DELETE") {
68
+ functionName = `delete${pascalSingular}ById`;
69
+ } else {
70
+ functionName = `${method.toLowerCase()}${pascalPlural}`;
71
+ }
72
+ }
73
+
74
+ // return with clean path (query removed)
75
+ return { ...ep, path: rawPath, controllerName, functionName };
76
+ });
77
+ }
78
+
79
+ async function generateNodeProject(options) {
80
+ const {
81
+ projectDir,
82
+ projectName,
83
+ frontendSrcDir,
84
+ dbType,
85
+ addAuth,
86
+ addSeeder,
87
+ extraFeatures = [],
88
+ } = options;
89
+
90
+ const port = 8000;
91
+
92
+ try {
93
+ // --- Step 1: Analyze Frontend ---
94
+ console.log(chalk.blue(" -> Analyzing frontend for API endpoints..."));
95
+ let endpoints = await analyzeFrontend(frontendSrcDir);
96
+
97
+ if (Array.isArray(endpoints) && endpoints.length > 0) {
98
+ console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
99
+ endpoints = sanitizeEndpoints(endpoints);
82
100
  } else {
83
- console.log(chalk.yellow(' -> No API endpoints found. A basic project will be created.'))
101
+ endpoints = [];
102
+ console.log(chalk.yellow(" -> No API endpoints found. A basic project will be created."));
84
103
  }
85
104
 
86
105
  // --- 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 = []
106
+ const modelsToGenerate = new Map();
107
+
108
+ endpoints.forEach((ep) => {
109
+ if (!ep) return;
110
+
111
+ const ctrl = safePascalName(ep.controllerName);
112
+ if (ctrl === "Default" || ctrl === "Auth") return;
113
+
114
+ if (!modelsToGenerate.has(ctrl)) {
115
+ let fields = [];
94
116
  if (ep.schemaFields) {
95
- fields = Object.entries(ep.schemaFields).map(([key, type]) => ({ name: key, type, isUnique: key === 'email' }))
117
+ fields = Object.entries(ep.schemaFields).map(([key, type]) => ({
118
+ name: key,
119
+ type,
120
+ isUnique: key === "email",
121
+ }));
96
122
  }
97
-
98
- modelsToGenerate.set(ep.controllerName, {
99
- name: ep.controllerName,
100
- fields
101
- })
123
+ modelsToGenerate.set(ctrl, { name: ctrl, fields });
102
124
  }
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' }] })
125
+ });
126
+
127
+ if (addAuth && !modelsToGenerate.has("User")) {
128
+ console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'));
129
+ modelsToGenerate.set("User", {
130
+ name: "User",
131
+ fields: [
132
+ { name: "name", type: "String" },
133
+ { name: "email", type: "String", isUnique: true },
134
+ { name: "password", type: "String" },
135
+ ],
136
+ });
108
137
  }
109
138
 
110
139
  // --- 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'}` }
140
+ console.log(chalk.blue(" -> Scaffolding Node.js project..."));
141
+ const destSrcDir = path.join(projectDir, "src");
142
+ await fs.ensureDir(destSrcDir);
143
+
144
+ await fs.copy(getTemplatePath("node-ts-express/base/server.ts"), path.join(destSrcDir, "server.ts"));
145
+ await fs.copy(getTemplatePath("node-ts-express/base/tsconfig.json"), path.join(projectDir, "tsconfig.json"));
146
+
147
+ // --- Step 4: package.json ---
148
+ const packageJsonContent = JSON.parse(
149
+ await ejs.renderFile(getTemplatePath("node-ts-express/partials/package.json.ejs"), { projectName })
150
+ );
151
+
152
+ if (dbType === "mongoose") packageJsonContent.dependencies.mongoose = "^7.6.3";
153
+ if (dbType === "prisma") {
154
+ packageJsonContent.dependencies["@prisma/client"] = "^5.6.0";
155
+ packageJsonContent.devDependencies.prisma = "^5.6.0";
156
+ packageJsonContent.prisma = { seed: `ts-node ${addSeeder ? "scripts/seeder.ts" : "prisma/seed.ts"}` };
125
157
  }
158
+
126
159
  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'
160
+ packageJsonContent.dependencies.jsonwebtoken = "^9.0.2";
161
+ packageJsonContent.dependencies.bcryptjs = "^2.4.3";
162
+ packageJsonContent.devDependencies["@types/jsonwebtoken"] = "^9.0.5";
163
+ packageJsonContent.devDependencies["@types/bcryptjs"] = "^2.4.6";
131
164
  }
165
+
132
166
  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'
167
+ packageJsonContent.devDependencies["@faker-js/faker"] = "^8.3.1";
168
+ if (!packageJsonContent.dependencies.chalk) packageJsonContent.dependencies.chalk = "^4.1.2";
169
+ packageJsonContent.scripts.seed = "ts-node scripts/seeder.ts";
170
+ packageJsonContent.scripts.destroy = "ts-node scripts/seeder.ts -d";
137
171
  }
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'
172
+
173
+ if (extraFeatures.includes("testing")) {
174
+ packageJsonContent.devDependencies.jest = "^29.7.0";
175
+ packageJsonContent.devDependencies.supertest = "^6.3.3";
176
+ packageJsonContent.devDependencies["@types/jest"] = "^29.5.10";
177
+ packageJsonContent.devDependencies["@types/supertest"] = "^2.0.16";
178
+ packageJsonContent.devDependencies["ts-jest"] = "^29.1.1";
179
+ packageJsonContent.scripts.test = "jest --detectOpenHandles --forceExit";
145
180
  }
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'
181
+
182
+ if (extraFeatures.includes("swagger")) {
183
+ packageJsonContent.dependencies["swagger-ui-express"] = "^5.0.0";
184
+ packageJsonContent.dependencies["swagger-jsdoc"] = "^6.2.8";
185
+ packageJsonContent.devDependencies["@types/swagger-ui-express"] = "^4.1.6";
150
186
  }
151
- await fs.writeJson(path.join(projectDir, 'package.json'), packageJsonContent, { spaces: 2 })
152
187
 
153
- // --- Step 5: Generate DB-specific files & Controllers ---
188
+ await fs.writeJson(path.join(projectDir, "package.json"), packageJsonContent, { spaces: 2 });
189
+
190
+ // --- Step 5: DB + Controllers ---
154
191
  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'))
192
+ await fs.ensureDir(path.join(destSrcDir, "controllers"));
193
+
194
+ if (dbType === "mongoose") {
195
+ console.log(chalk.blue(" -> Generating Mongoose models and controllers..."));
196
+ await fs.ensureDir(path.join(destSrcDir, "models"));
197
+
159
198
  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 })
199
+ const schema = (modelData.fields || []).reduce((acc, field) => {
200
+ acc[field.name] = field.type;
201
+ return acc;
202
+ }, {});
203
+ await renderAndWrite(
204
+ getTemplatePath("node-ts-express/partials/Model.ts.ejs"),
205
+ path.join(destSrcDir, "models", `${modelName}.model.ts`),
206
+ { modelName, schema, projectName }
207
+ );
162
208
  }
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()) })
209
+ } else if (dbType === "prisma") {
210
+ console.log(chalk.blue(" -> Generating Prisma schema..."));
211
+ await fs.ensureDir(path.join(projectDir, "prisma"));
212
+ await renderAndWrite(
213
+ getTemplatePath("node-ts-express/partials/PrismaSchema.prisma.ejs"),
214
+ path.join(projectDir, "prisma", "schema.prisma"),
215
+ { modelsToGenerate: Array.from(modelsToGenerate.values()) }
216
+ );
167
217
  }
168
- console.log(chalk.blue(' -> Generating controllers...'))
218
+
219
+ console.log(chalk.blue(" -> Generating controllers..."));
169
220
  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 })
221
+ const templateFile = dbType === "mongoose" ? "Controller.ts.ejs" : "PrismaController.ts.ejs";
222
+ if (modelName !== "Auth") {
223
+ await renderAndWrite(
224
+ getTemplatePath(`node-ts-express/partials/${templateFile}`),
225
+ path.join(destSrcDir, "controllers", `${modelName}.controller.ts`),
226
+ { modelName, projectName }
227
+ );
174
228
  }
175
229
  }
176
230
  }
177
231
 
178
- // --- Step 6: Generate Authentication Boilerplate ---
232
+ // --- Step 6: Auth ---
179
233
  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
- }
234
+ console.log(chalk.blue(" -> Generating authentication boilerplate..."));
235
+ await fs.ensureDir(path.join(destSrcDir, "routes"));
236
+ await fs.ensureDir(path.join(destSrcDir, "middleware"));
237
+
238
+ await renderAndWrite(
239
+ getTemplatePath("node-ts-express/partials/Auth.controller.ts.ejs"),
240
+ path.join(destSrcDir, "controllers", "Auth.controller.ts"),
241
+ { dbType, projectName }
242
+ );
243
+ await renderAndWrite(
244
+ getTemplatePath("node-ts-express/partials/Auth.routes.ts.ejs"),
245
+ path.join(destSrcDir, "routes", "Auth.routes.ts"),
246
+ { projectName }
247
+ );
248
+ await renderAndWrite(
249
+ getTemplatePath("node-ts-express/partials/Auth.middleware.ts.ejs"),
250
+ path.join(destSrcDir, "middleware", "Auth.middleware.ts"),
251
+ { projectName }
252
+ );
199
253
  }
200
254
 
201
- // --- Step 7: Generate Seeder Script ---
255
+ // --- Step 7: Seeder ---
202
256
  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 })
257
+ console.log(chalk.blue(" -> Generating database seeder script..."));
258
+ await fs.ensureDir(path.join(projectDir, "scripts"));
259
+ await renderAndWrite(
260
+ getTemplatePath("node-ts-express/partials/Seeder.ts.ejs"),
261
+ path.join(projectDir, "scripts", "seeder.ts"),
262
+ { projectName, models: Array.from(modelsToGenerate.values()) }
263
+ );
206
264
  }
207
265
 
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 })
266
+ // --- Step 8: Extras ---
267
+ if (extraFeatures.includes("docker")) {
268
+ console.log(chalk.blue(" -> Generating Docker files..."));
269
+ await renderAndWrite(
270
+ getTemplatePath("node-ts-express/partials/Dockerfile.ejs"),
271
+ path.join(projectDir, "Dockerfile"),
272
+ { dbType, port }
273
+ );
274
+ await renderAndWrite(
275
+ getTemplatePath("node-ts-express/partials/docker-compose.yml.ejs"),
276
+ path.join(projectDir, "docker-compose.yml"),
277
+ { projectName, dbType, port, addAuth, extraFeatures }
278
+ );
213
279
  }
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 })
280
+
281
+ if (extraFeatures.includes("swagger")) {
282
+ console.log(chalk.blue(" -> Generating API documentation setup..."));
283
+ await fs.ensureDir(path.join(destSrcDir, "utils"));
284
+ await renderAndWrite(
285
+ getTemplatePath("node-ts-express/partials/ApiDocs.ts.ejs"),
286
+ path.join(destSrcDir, "utils", "swagger.ts"),
287
+ { projectName, port, addAuth }
288
+ );
218
289
  }
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 })
290
+
291
+ if (extraFeatures.includes("testing")) {
292
+ console.log(chalk.blue(" -> Generating testing boilerplate..."));
293
+ const jestConfig =
294
+ "/** @type {import('ts-jest').JestConfigWithTsJest} */\nmodule.exports = {\n preset: 'ts-jest',\n testEnvironment: 'node',\n verbose: true,\n};";
295
+
296
+ await fs.writeFile(path.join(projectDir, "jest.config.js"), jestConfig);
297
+ await fs.ensureDir(path.join(projectDir, "src", "__tests__"));
298
+
299
+ await renderAndWrite(
300
+ getTemplatePath("node-ts-express/partials/App.test.ts.ejs"),
301
+ path.join(projectDir, "src", "__tests__", "api.test.ts"),
302
+ { addAuth, endpoints }
303
+ );
225
304
  }
226
305
 
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')
306
+ // --- Step 9: routes.ts + server inject ---
307
+ const nonAuthEndpoints = endpoints.filter((ep) => safePascalName(ep.controllerName) !== "Auth");
231
308
 
232
- // IMPORTANT: Pass 'nonAuthEndpoints' instead of 'endpoints'
233
309
  await renderAndWrite(
234
- getTemplatePath('node-ts-express/partials/routes.ts.ejs'),
235
- path.join(destSrcDir, 'routes.ts'),
310
+ getTemplatePath("node-ts-express/partials/routes.ts.ejs"),
311
+ path.join(destSrcDir, "routes.ts"),
236
312
  { endpoints: nonAuthEndpoints, addAuth, dbType }
237
- )
313
+ );
238
314
 
239
- let serverFileContent = await fs.readFile(path.join(destSrcDir, 'server.ts'), 'utf-8')
315
+ let serverFileContent = await fs.readFile(path.join(destSrcDir, "server.ts"), "utf-8");
240
316
 
241
- // =========================================================================
242
- // 👇 මේ ටික තමයි උඹේ Code එකෙන් Missing වෙලා තිබ්බේ. මේක නැතුව DB Connect වෙන්නේ නෑ.
243
- // =========================================================================
244
- let dbConnectionCode = ''; let swaggerInjector = ''; let authRoutesInjector = ''
317
+ let dbConnectionCode = "";
318
+ let swaggerInjector = "";
319
+ let authRoutesInjector = "";
245
320
 
246
- if (dbType === 'mongoose') {
321
+ if (dbType === "mongoose") {
322
+ dbConnectionCode = `
323
+ import mongoose from 'mongoose';
324
+ const MONGO_URI = process.env.MONGO_URI || 'mongodb://127.0.0.1:27017/${projectName}';
325
+ mongoose.connect(MONGO_URI)
326
+ .then(() => console.log('MongoDB Connected...'))
327
+ .catch(err => console.error(err));
328
+ `;
329
+ } else if (dbType === "prisma") {
247
330
  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"
331
+ import { PrismaClient } from '@prisma/client';
332
+ export const prisma = new PrismaClient();
333
+ `;
256
334
  }
257
- if (extraFeatures.includes('swagger')) {
258
- swaggerInjector = "\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n"
335
+
336
+ if (extraFeatures.includes("swagger")) {
337
+ swaggerInjector = "\nimport { setupSwagger } from './utils/swagger';\nsetupSwagger(app);\n";
259
338
  }
339
+
260
340
  if (addAuth) {
261
- authRoutesInjector = "import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n"
341
+ authRoutesInjector = "import authRoutes from './routes/Auth.routes';\napp.use('/api/auth', authRoutes);\n\n";
262
342
  }
263
343
 
264
344
  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
- }
345
+ .replace("dotenv.config();", `dotenv.config();${dbConnectionCode}`)
346
+ .replace(
347
+ "// INJECT:ROUTES",
348
+ `${authRoutesInjector}import apiRoutes from './routes';
349
+ app.use('/api', apiRoutes);`
350
+ );
281
351
 
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}`
352
+ serverFileContent = serverFileContent.replace(/(app\.listen\()/, `${swaggerInjector}\n$1`);
353
+
354
+ await fs.writeFile(path.join(destSrcDir, "server.ts"), serverFileContent);
355
+
356
+ // --- Step 10: Install deps ---
357
+ console.log(chalk.magenta(" -> Installing dependencies... This may take a moment."));
358
+ await execa("npm", ["install"], { cwd: projectDir });
359
+
360
+ if (dbType === "prisma") {
361
+ console.log(chalk.blue(" -> Running `prisma generate`..."));
362
+ await execa("npx", ["prisma", "generate"], { cwd: projectDir });
292
363
  }
293
- await fs.writeFile(path.join(projectDir, '.env.example'), envContent)
364
+
365
+ // --- Step 11: .env.example ---
366
+ let envContent = `PORT=${port}\n`;
367
+ if (dbType === "mongoose") envContent += `MONGO_URI=mongodb://127.0.0.1:27017/${projectName}\n`;
368
+ if (dbType === "prisma") envContent += `DATABASE_URL="postgresql://user:password@localhost:5432/${projectName}?schema=public"\n`;
369
+ if (addAuth) envContent += "JWT_SECRET=your_super_secret_jwt_key_12345\nJWT_EXPIRES_IN=5h\n";
370
+
371
+ await fs.writeFile(path.join(projectDir, ".env.example"), envContent);
372
+
373
+ console.log(chalk.green(" -> Node backend generation complete."));
294
374
  } catch (error) {
295
- throw error
375
+ throw error;
296
376
  }
297
377
  }
298
378
 
299
- module.exports = { generateNodeProject }
379
+ module.exports = { generateNodeProject };