create-backlist 6.1.8 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-backlist",
3
- "version": "6.1.8",
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": {
@@ -7,11 +7,26 @@ 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
+ // 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
+
10
24
  function sanitizeEndpoints(endpoints) {
11
25
  if (!Array.isArray(endpoints)) return [];
12
26
 
13
27
  return endpoints.map((ep) => {
14
- const rawPath = String(ep.path || ep.route || "/");
28
+ // IMPORTANT: strip query string so it never leaks into names
29
+ const rawPath = stripQuery(ep.path || ep.route || "/");
15
30
 
16
31
  // remove empty, api, and version segments (v1/v2/v10...)
17
32
  const parts = rawPath
@@ -20,7 +35,7 @@ function sanitizeEndpoints(endpoints) {
20
35
  .filter((p) => p !== "api" && !/^v\d+$/i.test(p));
21
36
 
22
37
  const resource = parts[0] || "Default";
23
- const controllerName = `${resource.charAt(0).toUpperCase()}${resource.slice(1)}`;
38
+ const controllerName = safePascalName(resource);
24
39
 
25
40
  let functionName = "";
26
41
 
@@ -33,13 +48,15 @@ function sanitizeEndpoints(endpoints) {
33
48
  const singularName = resource.endsWith("s") ? resource.slice(0, -1) : resource;
34
49
  const pluralName = resource.endsWith("s") ? resource : `${resource}s`;
35
50
 
36
- const pascalSingular = `${singularName.charAt(0).toUpperCase()}${singularName.slice(1)}`;
37
- const pascalPlural = `${pluralName.charAt(0).toUpperCase()}${pluralName.slice(1)}`;
51
+ const pascalSingular = safePascalName(singularName);
52
+ const pascalPlural = safePascalName(pluralName);
38
53
 
39
54
  const method = String(ep.method || "GET").toUpperCase();
40
55
 
41
- // detect id param in route/path
42
- const hasId = rawPath.includes(":id") || /\/\d+/.test(rawPath) || rawPath.includes("{id}") || rawPath.includes("/:");
56
+ const hasId =
57
+ rawPath.includes(":") || // :id style
58
+ rawPath.includes("{") || // {id} style
59
+ /\/\d+/.test(rawPath); // numeric
43
60
 
44
61
  if (method === "GET") {
45
62
  functionName = hasId ? `get${pascalSingular}ById` : `getAll${pascalPlural}`;
@@ -54,7 +71,8 @@ function sanitizeEndpoints(endpoints) {
54
71
  }
55
72
  }
56
73
 
57
- return { ...ep, controllerName, functionName };
74
+ // return with clean path (query removed)
75
+ return { ...ep, path: rawPath, controllerName, functionName };
58
76
  });
59
77
  }
60
78
 
@@ -76,10 +94,11 @@ async function generateNodeProject(options) {
76
94
  console.log(chalk.blue(" -> Analyzing frontend for API endpoints..."));
77
95
  let endpoints = await analyzeFrontend(frontendSrcDir);
78
96
 
79
- if (endpoints.length > 0) {
97
+ if (Array.isArray(endpoints) && endpoints.length > 0) {
80
98
  console.log(chalk.green(` -> Found ${endpoints.length} endpoints.`));
81
99
  endpoints = sanitizeEndpoints(endpoints);
82
100
  } else {
101
+ endpoints = [];
83
102
  console.log(chalk.yellow(" -> No API endpoints found. A basic project will be created."));
84
103
  }
85
104
 
@@ -87,9 +106,12 @@ async function generateNodeProject(options) {
87
106
  const modelsToGenerate = new Map();
88
107
 
89
108
  endpoints.forEach((ep) => {
90
- if (!ep || !ep.controllerName) return;
109
+ if (!ep) return;
91
110
 
92
- if (ep.controllerName !== "Default" && ep.controllerName !== "Auth" && !modelsToGenerate.has(ep.controllerName)) {
111
+ const ctrl = safePascalName(ep.controllerName);
112
+ if (ctrl === "Default" || ctrl === "Auth") return;
113
+
114
+ if (!modelsToGenerate.has(ctrl)) {
93
115
  let fields = [];
94
116
  if (ep.schemaFields) {
95
117
  fields = Object.entries(ep.schemaFields).map(([key, type]) => ({
@@ -98,8 +120,7 @@ async function generateNodeProject(options) {
98
120
  isUnique: key === "email",
99
121
  }));
100
122
  }
101
-
102
- modelsToGenerate.set(ep.controllerName, { name: ep.controllerName, fields });
123
+ modelsToGenerate.set(ctrl, { name: ctrl, fields });
103
124
  }
104
125
  });
105
126
 
@@ -175,7 +196,7 @@ async function generateNodeProject(options) {
175
196
  await fs.ensureDir(path.join(destSrcDir, "models"));
176
197
 
177
198
  for (const [modelName, modelData] of modelsToGenerate.entries()) {
178
- const schema = modelData.fields.reduce((acc, field) => {
199
+ const schema = (modelData.fields || []).reduce((acc, field) => {
179
200
  acc[field.name] = field.type;
180
201
  return acc;
181
202
  }, {});
@@ -245,10 +266,11 @@ async function generateNodeProject(options) {
245
266
  // --- Step 8: Extras ---
246
267
  if (extraFeatures.includes("docker")) {
247
268
  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
- });
269
+ await renderAndWrite(
270
+ getTemplatePath("node-ts-express/partials/Dockerfile.ejs"),
271
+ path.join(projectDir, "Dockerfile"),
272
+ { dbType, port }
273
+ );
252
274
  await renderAndWrite(
253
275
  getTemplatePath("node-ts-express/partials/docker-compose.yml.ejs"),
254
276
  path.join(projectDir, "docker-compose.yml"),
@@ -274,7 +296,6 @@ async function generateNodeProject(options) {
274
296
  await fs.writeFile(path.join(projectDir, "jest.config.js"), jestConfig);
275
297
  await fs.ensureDir(path.join(projectDir, "src", "__tests__"));
276
298
 
277
- // IMPORTANT: pass endpoints for auto-tests
278
299
  await renderAndWrite(
279
300
  getTemplatePath("node-ts-express/partials/App.test.ts.ejs"),
280
301
  path.join(projectDir, "src", "__tests__", "api.test.ts"),
@@ -283,7 +304,7 @@ async function generateNodeProject(options) {
283
304
  }
284
305
 
285
306
  // --- Step 9: routes.ts + server inject ---
286
- const nonAuthEndpoints = endpoints.filter((ep) => ep.controllerName !== "Auth");
307
+ const nonAuthEndpoints = endpoints.filter((ep) => safePascalName(ep.controllerName) !== "Auth");
287
308
 
288
309
  await renderAndWrite(
289
310
  getTemplatePath("node-ts-express/partials/routes.ts.ejs"),