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.
- package/package.json +1 -1
- package/src/generators/node.js +300 -241
package/package.json
CHANGED
package/src/generators/node.js
CHANGED
|
@@ -1,299 +1,358 @@
|
|
|
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 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(
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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]) => ({
|
|
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(
|
|
106
|
-
console.log(chalk.yellow(' -> Authentication requires a "User" model. Creating a default one.'))
|
|
107
|
-
modelsToGenerate.set(
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
128
|
-
packageJsonContent.dependencies.bcryptjs =
|
|
129
|
-
packageJsonContent.devDependencies[
|
|
130
|
-
packageJsonContent.devDependencies[
|
|
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[
|
|
134
|
-
if (!packageJsonContent.dependencies.chalk) packageJsonContent.dependencies.chalk =
|
|
135
|
-
packageJsonContent.scripts.seed =
|
|
136
|
-
packageJsonContent.scripts.destroy =
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
packageJsonContent.devDependencies.
|
|
141
|
-
packageJsonContent.devDependencies
|
|
142
|
-
packageJsonContent.devDependencies[
|
|
143
|
-
packageJsonContent.devDependencies[
|
|
144
|
-
packageJsonContent.
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
packageJsonContent.dependencies[
|
|
149
|
-
packageJsonContent.
|
|
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
|
-
|
|
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,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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) => {
|
|
161
|
-
|
|
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 ===
|
|
164
|
-
console.log(chalk.blue(
|
|
165
|
-
await fs.ensureDir(path.join(projectDir,
|
|
166
|
-
await renderAndWrite(
|
|
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
|
-
|
|
197
|
+
|
|
198
|
+
console.log(chalk.blue(" -> Generating controllers..."));
|
|
169
199
|
for (const [modelName] of modelsToGenerate.entries()) {
|
|
170
|
-
const templateFile = dbType ===
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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:
|
|
211
|
+
// --- Step 6: Auth ---
|
|
179
212
|
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
|
-
|
|
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:
|
|
234
|
+
// --- Step 7: Seeder ---
|
|
202
235
|
if (addSeeder) {
|
|
203
|
-
console.log(chalk.blue(
|
|
204
|
-
await fs.ensureDir(path.join(projectDir,
|
|
205
|
-
await renderAndWrite(
|
|
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:
|
|
209
|
-
if (extraFeatures.includes(
|
|
210
|
-
console.log(chalk.blue(
|
|
211
|
-
await renderAndWrite(getTemplatePath(
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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:
|
|
228
|
-
|
|
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(
|
|
235
|
-
path.join(destSrcDir,
|
|
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,
|
|
294
|
+
let serverFileContent = await fs.readFile(path.join(destSrcDir, "server.ts"), "utf-8");
|
|
240
295
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
let dbConnectionCode = ''; let swaggerInjector = ''; let authRoutesInjector = ''
|
|
296
|
+
let dbConnectionCode = "";
|
|
297
|
+
let swaggerInjector = "";
|
|
298
|
+
let authRoutesInjector = "";
|
|
245
299
|
|
|
246
|
-
if (dbType ===
|
|
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
|
-
|
|
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"
|
|
310
|
+
import { PrismaClient } from '@prisma/client';
|
|
311
|
+
export const prisma = new PrismaClient();
|
|
312
|
+
`;
|
|
256
313
|
}
|
|
257
|
-
|
|
258
|
-
|
|
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(
|
|
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
|
-
}
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (
|
|
291
|
-
|
|
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
|
-
|
|
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 };
|