create-backlist 6.1.8 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/generators/node.js +39 -23
package/package.json
CHANGED
package/src/generators/node.js
CHANGED
|
@@ -7,24 +7,35 @@ const ejs = require("ejs");
|
|
|
7
7
|
const { analyzeFrontend } = require("../analyzer");
|
|
8
8
|
const { renderAndWrite, getTemplatePath } = require("./template");
|
|
9
9
|
|
|
10
|
+
function stripQuery(p) {
|
|
11
|
+
return String(p || "").split("?")[0];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function safePascalName(name) {
|
|
15
|
+
const cleaned = String(name || "Default")
|
|
16
|
+
.split("?")[0]
|
|
17
|
+
.replace(/[^a-zA-Z0-9]/g, "");
|
|
18
|
+
|
|
19
|
+
if (!cleaned) return "Default";
|
|
20
|
+
return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
function sanitizeEndpoints(endpoints) {
|
|
11
24
|
if (!Array.isArray(endpoints)) return [];
|
|
12
25
|
|
|
13
26
|
return endpoints.map((ep) => {
|
|
14
|
-
const rawPath =
|
|
27
|
+
const rawPath = stripQuery(ep.path || ep.route || "/");
|
|
15
28
|
|
|
16
|
-
// remove empty, api, and version segments (v1/v2/v10...)
|
|
17
29
|
const parts = rawPath
|
|
18
30
|
.split("/")
|
|
19
31
|
.filter(Boolean)
|
|
20
32
|
.filter((p) => p !== "api" && !/^v\d+$/i.test(p));
|
|
21
33
|
|
|
22
34
|
const resource = parts[0] || "Default";
|
|
23
|
-
const controllerName =
|
|
35
|
+
const controllerName = safePascalName(resource);
|
|
24
36
|
|
|
25
37
|
let functionName = "";
|
|
26
38
|
|
|
27
|
-
// AUTH naming
|
|
28
39
|
if (controllerName.toLowerCase() === "auth") {
|
|
29
40
|
if (rawPath.includes("login")) functionName = "loginUser";
|
|
30
41
|
else if (rawPath.includes("register")) functionName = "registerUser";
|
|
@@ -33,13 +44,15 @@ function sanitizeEndpoints(endpoints) {
|
|
|
33
44
|
const singularName = resource.endsWith("s") ? resource.slice(0, -1) : resource;
|
|
34
45
|
const pluralName = resource.endsWith("s") ? resource : `${resource}s`;
|
|
35
46
|
|
|
36
|
-
const pascalSingular =
|
|
37
|
-
const pascalPlural =
|
|
47
|
+
const pascalSingular = safePascalName(singularName);
|
|
48
|
+
const pascalPlural = safePascalName(pluralName);
|
|
38
49
|
|
|
39
50
|
const method = String(ep.method || "GET").toUpperCase();
|
|
40
51
|
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
const hasId =
|
|
53
|
+
rawPath.includes(":") ||
|
|
54
|
+
rawPath.includes("{") ||
|
|
55
|
+
/\/\d+/.test(rawPath);
|
|
43
56
|
|
|
44
57
|
if (method === "GET") {
|
|
45
58
|
functionName = hasId ? `get${pascalSingular}ById` : `getAll${pascalPlural}`;
|
|
@@ -54,7 +67,7 @@ function sanitizeEndpoints(endpoints) {
|
|
|
54
67
|
}
|
|
55
68
|
}
|
|
56
69
|
|
|
57
|
-
return { ...ep, controllerName, functionName };
|
|
70
|
+
return { ...ep, path: rawPath, controllerName, functionName };
|
|
58
71
|
});
|
|
59
72
|
}
|
|
60
73
|
|
|
@@ -76,10 +89,11 @@ async function generateNodeProject(options) {
|
|
|
76
89
|
console.log(chalk.blue(" -> Analyzing frontend for API endpoints..."));
|
|
77
90
|
let endpoints = await analyzeFrontend(frontendSrcDir);
|
|
78
91
|
|
|
79
|
-
if (endpoints.length > 0) {
|
|
92
|
+
if (Array.isArray(endpoints) && endpoints.length > 0) {
|
|
80
93
|
console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
|
|
81
94
|
endpoints = sanitizeEndpoints(endpoints);
|
|
82
95
|
} else {
|
|
96
|
+
endpoints = [];
|
|
83
97
|
console.log(chalk.yellow(" -> No API endpoints found. A basic project will be created."));
|
|
84
98
|
}
|
|
85
99
|
|
|
@@ -87,9 +101,11 @@ async function generateNodeProject(options) {
|
|
|
87
101
|
const modelsToGenerate = new Map();
|
|
88
102
|
|
|
89
103
|
endpoints.forEach((ep) => {
|
|
90
|
-
if (!ep
|
|
104
|
+
if (!ep) return;
|
|
105
|
+
const ctrl = safePascalName(ep.controllerName);
|
|
106
|
+
if (ctrl === "Default" || ctrl === "Auth") return;
|
|
91
107
|
|
|
92
|
-
if (
|
|
108
|
+
if (!modelsToGenerate.has(ctrl)) {
|
|
93
109
|
let fields = [];
|
|
94
110
|
if (ep.schemaFields) {
|
|
95
111
|
fields = Object.entries(ep.schemaFields).map(([key, type]) => ({
|
|
@@ -98,8 +114,7 @@ async function generateNodeProject(options) {
|
|
|
98
114
|
isUnique: key === "email",
|
|
99
115
|
}));
|
|
100
116
|
}
|
|
101
|
-
|
|
102
|
-
modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields });
|
|
117
|
+
modelsToGenerate.set(ctrl, { name: ctrl, fields });
|
|
103
118
|
}
|
|
104
119
|
});
|
|
105
120
|
|
|
@@ -175,7 +190,7 @@ async function generateNodeProject(options) {
|
|
|
175
190
|
await fs.ensureDir(path.join(destSrcDir, "models"));
|
|
176
191
|
|
|
177
192
|
for (const [modelName, modelData] of modelsToGenerate.entries()) {
|
|
178
|
-
const schema = modelData.fields.reduce((acc, field) => {
|
|
193
|
+
const schema = (modelData.fields || []).reduce((acc, field) => {
|
|
179
194
|
acc[field.name] = field.type;
|
|
180
195
|
return acc;
|
|
181
196
|
}, {});
|
|
@@ -242,13 +257,14 @@ async function generateNodeProject(options) {
|
|
|
242
257
|
);
|
|
243
258
|
}
|
|
244
259
|
|
|
245
|
-
// --- Step 8: Extras ---
|
|
260
|
+
// --- Step 8: Extras (FIXED) ---
|
|
246
261
|
if (extraFeatures.includes("docker")) {
|
|
247
262
|
console.log(chalk.blue(" -> Generating Docker files..."));
|
|
248
|
-
await renderAndWrite(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
263
|
+
await renderAndWrite(
|
|
264
|
+
getTemplatePath("node-ts-express/partials/Dockerfile.ejs"),
|
|
265
|
+
path.join(projectDir, "Dockerfile"),
|
|
266
|
+
{ dbType, port }
|
|
267
|
+
);
|
|
252
268
|
await renderAndWrite(
|
|
253
269
|
getTemplatePath("node-ts-express/partials/docker-compose.yml.ejs"),
|
|
254
270
|
path.join(projectDir, "docker-compose.yml"),
|
|
@@ -259,10 +275,11 @@ async function generateNodeProject(options) {
|
|
|
259
275
|
if (extraFeatures.includes("swagger")) {
|
|
260
276
|
console.log(chalk.blue(" -> Generating API documentation setup..."));
|
|
261
277
|
await fs.ensureDir(path.join(destSrcDir, "utils"));
|
|
278
|
+
// FIX: Added 'paths' to the EJS data object
|
|
262
279
|
await renderAndWrite(
|
|
263
280
|
getTemplatePath("node-ts-express/partials/ApiDocs.ts.ejs"),
|
|
264
281
|
path.join(destSrcDir, "utils", "swagger.ts"),
|
|
265
|
-
{ projectName, port, addAuth }
|
|
282
|
+
{ projectName, port, addAuth, paths: endpoints }
|
|
266
283
|
);
|
|
267
284
|
}
|
|
268
285
|
|
|
@@ -274,7 +291,6 @@ async function generateNodeProject(options) {
|
|
|
274
291
|
await fs.writeFile(path.join(projectDir, "jest.config.js"), jestConfig);
|
|
275
292
|
await fs.ensureDir(path.join(projectDir, "src", "__tests__"));
|
|
276
293
|
|
|
277
|
-
// IMPORTANT: pass endpoints for auto-tests
|
|
278
294
|
await renderAndWrite(
|
|
279
295
|
getTemplatePath("node-ts-express/partials/App.test.ts.ejs"),
|
|
280
296
|
path.join(projectDir, "src", "__tests__", "api.test.ts"),
|
|
@@ -283,7 +299,7 @@ async function generateNodeProject(options) {
|
|
|
283
299
|
}
|
|
284
300
|
|
|
285
301
|
// --- Step 9: routes.ts + server inject ---
|
|
286
|
-
const nonAuthEndpoints = endpoints.filter((ep) => ep.controllerName !== "Auth");
|
|
302
|
+
const nonAuthEndpoints = endpoints.filter((ep) => safePascalName(ep.controllerName) !== "Auth");
|
|
287
303
|
|
|
288
304
|
await renderAndWrite(
|
|
289
305
|
getTemplatePath("node-ts-express/partials/routes.ts.ejs"),
|