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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "6.1.8",
3
+ "version": "6.2.0",
4
4
  "description": "An advanced, multi-language backend generator based on frontend analysis.",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -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 = String(ep.path || ep.route || "/");
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 = `${resource.charAt(0).toUpperCase()}${resource.slice(1)}`;
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 = `${singularName.charAt(0).toUpperCase()}${singularName.slice(1)}`;
37
- const pascalPlural = `${pluralName.charAt(0).toUpperCase()}${pluralName.slice(1)}`;
47
+ const pascalSingular = safePascalName(singularName);
48
+ const pascalPlural = safePascalName(pluralName);
38
49
 
39
50
  const method = String(ep.method || "GET").toUpperCase();
40
51
 
41
- // detect id param in route/path
42
- const hasId = rawPath.includes(":id") || /\/\d+/.test(rawPath) || rawPath.includes("{id}") || rawPath.includes("/:");
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 || !ep.controllerName) return;
104
+ if (!ep) return;
105
+ const ctrl = safePascalName(ep.controllerName);
106
+ if (ctrl === "Default" || ctrl === "Auth") return;
91
107
 
92
- if (ep.controllerName !== "Default" && ep.controllerName !== "Auth" && !modelsToGenerate.has(ep.controllerName)) {
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(getTemplatePath("node-ts-express/partials/Dockerfile.ejs"), path.join(projectDir, "Dockerfile"), {
249
- dbType,
250
- port,
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"),