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.
- package/package.json +1 -1
- package/src/generators/node.js +322 -242
package/package.json
CHANGED
package/src/generators/node.js
CHANGED
|
@@ -1,299 +1,379 @@
|
|
|
1
|
-
const chalk = require(
|
|
2
|
-
const { execa } = require(
|
|
3
|
-
const fs = require(
|
|
4
|
-
const path = require(
|
|
5
|
-
const ejs = require(
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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]) => ({
|
|
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(
|
|
106
|
-
console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'))
|
|
107
|
-
modelsToGenerate.set(
|
|
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(
|
|
112
|
-
const destSrcDir = path.join(projectDir,
|
|
113
|
-
await fs.ensureDir(destSrcDir)
|
|
114
|
-
|
|
115
|
-
await fs.copy(getTemplatePath(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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 =
|
|
128
|
-
packageJsonContent.dependencies.bcryptjs =
|
|
129
|
-
packageJsonContent.devDependencies[
|
|
130
|
-
packageJsonContent.devDependencies[
|
|
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[
|
|
134
|
-
if (!packageJsonContent.dependencies.chalk) packageJsonContent.dependencies.chalk =
|
|
135
|
-
packageJsonContent.scripts.seed =
|
|
136
|
-
packageJsonContent.scripts.destroy =
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
packageJsonContent.devDependencies.
|
|
141
|
-
packageJsonContent.devDependencies
|
|
142
|
-
packageJsonContent.devDependencies[
|
|
143
|
-
packageJsonContent.devDependencies[
|
|
144
|
-
packageJsonContent.
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
packageJsonContent.dependencies[
|
|
149
|
-
packageJsonContent.
|
|
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
|
-
|
|
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,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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) => {
|
|
161
|
-
|
|
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 ===
|
|
164
|
-
console.log(chalk.blue(
|
|
165
|
-
await fs.ensureDir(path.join(projectDir,
|
|
166
|
-
await renderAndWrite(
|
|
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
|
-
|
|
218
|
+
|
|
219
|
+
console.log(chalk.blue(" -> Generating controllers..."));
|
|
169
220
|
for (const [modelName] of modelsToGenerate.entries()) {
|
|
170
|
-
const templateFile = dbType ===
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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:
|
|
232
|
+
// --- Step 6: Auth ---
|
|
179
233
|
if (addAuth) {
|
|
180
|
-
console.log(chalk.blue(
|
|
181
|
-
await fs.ensureDir(path.join(destSrcDir,
|
|
182
|
-
await fs.ensureDir(path.join(destSrcDir,
|
|
183
|
-
|
|
184
|
-
await renderAndWrite(
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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:
|
|
255
|
+
// --- Step 7: Seeder ---
|
|
202
256
|
if (addSeeder) {
|
|
203
|
-
console.log(chalk.blue(
|
|
204
|
-
await fs.ensureDir(path.join(projectDir,
|
|
205
|
-
await renderAndWrite(
|
|
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:
|
|
209
|
-
if (extraFeatures.includes(
|
|
210
|
-
console.log(chalk.blue(
|
|
211
|
-
await renderAndWrite(
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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:
|
|
228
|
-
|
|
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(
|
|
235
|
-
path.join(destSrcDir,
|
|
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,
|
|
315
|
+
let serverFileContent = await fs.readFile(path.join(destSrcDir, "server.ts"), "utf-8");
|
|
240
316
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
let dbConnectionCode = ''; let swaggerInjector = ''; let authRoutesInjector = ''
|
|
317
|
+
let dbConnectionCode = "";
|
|
318
|
+
let swaggerInjector = "";
|
|
319
|
+
let authRoutesInjector = "";
|
|
245
320
|
|
|
246
|
-
if (dbType ===
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
258
|
-
|
|
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(
|
|
266
|
-
.replace(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
|
|
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
|
-
|
|
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 };
|