create-backlist 6.1.6 → 6.1.7
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/analyzer.js +410 -99
- package/src/generators/dotnet.js +1 -1
- package/src/generators/java.js +154 -97
- package/src/generators/node.js +213 -211
- package/src/templates/java-spring/partials/ApplicationSeeder.java.ejs +29 -7
- package/src/templates/java-spring/partials/AuthController.java.ejs +45 -14
- package/src/templates/java-spring/partials/Controller.java.ejs +25 -11
- package/src/templates/java-spring/partials/Dockerfile.ejs +25 -3
- package/src/templates/java-spring/partials/Entity.java.ejs +28 -3
- package/src/templates/java-spring/partials/JwtAuthFilter.java.ejs +41 -7
- package/src/templates/java-spring/partials/JwtService.java.ejs +47 -12
- package/src/templates/java-spring/partials/Repository.java.ejs +8 -1
- package/src/templates/java-spring/partials/Service.java.ejs +30 -6
- package/src/templates/java-spring/partials/User.java.ejs +26 -3
- package/src/templates/java-spring/partials/UserDetailsServiceImpl.java.ejs +10 -4
- package/src/templates/java-spring/partials/UserRepository.java.ejs +6 -0
- package/src/templates/java-spring/partials/docker-compose.yml.ejs +27 -5
- package/src/templates/node-ts-express/base/server.ts +63 -9
- package/src/templates/node-ts-express/base/tsconfig.json +19 -4
- package/src/templates/node-ts-express/partials/ApiDocs.ts.ejs +24 -9
- package/src/templates/node-ts-express/partials/App.test.ts.ejs +47 -27
- package/src/templates/node-ts-express/partials/Auth.controller.ts.ejs +68 -45
- package/src/templates/node-ts-express/partials/Auth.middleware.ts.ejs +45 -14
- package/src/templates/node-ts-express/partials/Auth.routes.ts.ejs +44 -5
- package/src/templates/node-ts-express/partials/Controller.ts.ejs +30 -16
- package/src/templates/node-ts-express/partials/Dockerfile.ejs +33 -11
- package/src/templates/node-ts-express/partials/Model.cs.ejs +38 -5
- package/src/templates/node-ts-express/partials/Model.ts.ejs +42 -12
- package/src/templates/node-ts-express/partials/PrismaController.ts.ejs +57 -23
- package/src/templates/node-ts-express/partials/PrismaSchema.prisma.ejs +33 -10
- package/src/templates/node-ts-express/partials/README.md.ejs +8 -10
- package/src/templates/node-ts-express/partials/Seeder.ts.ejs +99 -56
- package/src/templates/node-ts-express/partials/docker-compose.yml.ejs +30 -3
- package/src/templates/node-ts-express/partials/package.json.ejs +12 -7
- package/src/templates/node-ts-express/partials/routes.ts.ejs +31 -18
package/package.json
CHANGED
package/src/analyzer.js
CHANGED
|
@@ -1,125 +1,436 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
+
const fs = require("fs-extra");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { glob } = require("glob");
|
|
5
|
+
|
|
6
|
+
const parser = require("@babel/parser");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
|
|
9
|
+
const HTTP_METHODS = new Set(["get", "post", "put", "patch", "delete"]);
|
|
10
|
+
|
|
11
|
+
// -------------------------
|
|
12
|
+
// Utils
|
|
13
|
+
// -------------------------
|
|
14
|
+
function normalizeSlashes(p) {
|
|
15
|
+
return String(p || "").replace(/\\/g, "/");
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
function toTitleCase(str) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
if (!str) return "Default";
|
|
20
|
+
return String(str)
|
|
21
|
+
.replace(/[-_]+(\w)/g, (_, c) => c.toUpperCase())
|
|
22
|
+
.replace(/^\w/, (c) => c.toUpperCase())
|
|
23
|
+
.replace(/[^a-zA-Z0-9]/g, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Convert `/api/users/{id}` -> `/api/users/:id`
|
|
27
|
+
function normalizeRouteForBackend(urlValue) {
|
|
28
|
+
return String(urlValue || "").replace(/\{(\w+)\}/g, ":$1");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function extractApiPath(urlValue) {
|
|
32
|
+
// supports:
|
|
33
|
+
// - /api/...
|
|
34
|
+
// - http://localhost:5000/api/...
|
|
35
|
+
if (!urlValue) return null;
|
|
36
|
+
const idx = urlValue.indexOf("/api/");
|
|
37
|
+
if (idx === -1) return null;
|
|
38
|
+
return urlValue.slice(idx); // => /api/...
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractPathParams(route) {
|
|
42
|
+
const params = [];
|
|
43
|
+
const re = /[:{]([a-zA-Z0-9_]+)[}]/g;
|
|
44
|
+
let m;
|
|
45
|
+
while ((m = re.exec(route))) params.push(m[1]);
|
|
46
|
+
return Array.from(new Set(params));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extractQueryParamsFromUrl(urlValue) {
|
|
50
|
+
try {
|
|
51
|
+
const qIndex = urlValue.indexOf("?");
|
|
52
|
+
if (qIndex === -1) return [];
|
|
53
|
+
const qs = urlValue.slice(qIndex + 1);
|
|
54
|
+
return qs
|
|
55
|
+
.split("&")
|
|
56
|
+
.map((p) => p.split("=")[0])
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function deriveControllerNameFromUrl(urlValue) {
|
|
64
|
+
const apiPath = extractApiPath(urlValue) || urlValue;
|
|
65
|
+
const parts = String(apiPath).split("/").filter(Boolean); // ["api","v1","products"]
|
|
66
|
+
const apiIndex = parts.indexOf("api");
|
|
67
|
+
|
|
68
|
+
let seg = null;
|
|
69
|
+
if (apiIndex >= 0) {
|
|
70
|
+
seg = parts[apiIndex + 1] || null;
|
|
71
|
+
|
|
72
|
+
// skip version segment (v1, v2, v10...)
|
|
73
|
+
if (seg && /^v\d+$/i.test(seg)) {
|
|
74
|
+
seg = parts[apiIndex + 2] || seg;
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
seg = parts[0] || null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return toTitleCase(seg);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function deriveActionName(method, route) {
|
|
84
|
+
const cleaned = String(route).replace(/^\/api\//, "/").replace(/[/:{}-]/g, " ");
|
|
85
|
+
const last = cleaned.trim().split(/\s+/).filter(Boolean).pop() || "Action";
|
|
86
|
+
return `${String(method).toLowerCase()}${toTitleCase(last)}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// -------------------------
|
|
90
|
+
// URL extraction
|
|
91
|
+
// -------------------------
|
|
92
|
+
function getUrlValue(urlNode) {
|
|
93
|
+
if (!urlNode) return null;
|
|
94
|
+
|
|
95
|
+
if (urlNode.type === "StringLiteral") return urlNode.value;
|
|
96
|
+
|
|
97
|
+
if (urlNode.type === "TemplateLiteral") {
|
|
98
|
+
// `/api/users/${id}` -> `/api/users/{id}` or `{param1}`
|
|
99
|
+
const quasis = urlNode.quasis || [];
|
|
100
|
+
const exprs = urlNode.expressions || [];
|
|
101
|
+
let out = "";
|
|
102
|
+
for (let i = 0; i < quasis.length; i++) {
|
|
103
|
+
out += quasis[i].value.raw;
|
|
104
|
+
if (exprs[i]) {
|
|
105
|
+
if (exprs[i].type === "Identifier") out += `{${exprs[i].name}}`;
|
|
106
|
+
else out += `{param${i + 1}}`;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// -------------------------
|
|
116
|
+
// axios-like detection
|
|
117
|
+
// -------------------------
|
|
118
|
+
function detectAxiosLikeMethod(node) {
|
|
119
|
+
// axios.get(...) / api.get(...) / httpClient.post(...) etc
|
|
120
|
+
if (!node.callee || node.callee.type !== "MemberExpression") return null;
|
|
121
|
+
|
|
122
|
+
const prop = node.callee.property;
|
|
123
|
+
if (!prop || prop.type !== "Identifier") return null;
|
|
124
|
+
|
|
125
|
+
const name = prop.name.toLowerCase();
|
|
126
|
+
if (!HTTP_METHODS.has(name)) return null;
|
|
127
|
+
|
|
128
|
+
return name.toUpperCase();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// -------------------------
|
|
132
|
+
// Request body schema (simple + identifier tracing)
|
|
133
|
+
// -------------------------
|
|
134
|
+
function inferTypeFromNode(node) {
|
|
135
|
+
if (!node) return "String";
|
|
136
|
+
switch (node.type) {
|
|
137
|
+
case "StringLiteral":
|
|
138
|
+
return "String";
|
|
139
|
+
case "NumericLiteral":
|
|
140
|
+
return "Number";
|
|
141
|
+
case "BooleanLiteral":
|
|
142
|
+
return "Boolean";
|
|
143
|
+
case "NullLiteral":
|
|
144
|
+
return "String";
|
|
145
|
+
default:
|
|
146
|
+
return "String";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function extractObjectSchema(objExpr) {
|
|
151
|
+
const schemaFields = {};
|
|
152
|
+
if (!objExpr || objExpr.type !== "ObjectExpression") return null;
|
|
153
|
+
|
|
154
|
+
for (const prop of objExpr.properties) {
|
|
155
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
156
|
+
|
|
157
|
+
const key =
|
|
158
|
+
prop.key.type === "Identifier"
|
|
159
|
+
? prop.key.name
|
|
160
|
+
: prop.key.type === "StringLiteral"
|
|
161
|
+
? prop.key.value
|
|
162
|
+
: null;
|
|
163
|
+
|
|
164
|
+
if (!key) continue;
|
|
165
|
+
schemaFields[key] = inferTypeFromNode(prop.value);
|
|
166
|
+
}
|
|
167
|
+
return schemaFields;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function resolveIdentifierToInit(callPath, identifierName) {
|
|
171
|
+
try {
|
|
172
|
+
const binding = callPath.scope.getBinding(identifierName);
|
|
173
|
+
if (!binding) return null;
|
|
174
|
+
const declPath = binding.path;
|
|
175
|
+
if (!declPath || !declPath.node) return null;
|
|
176
|
+
|
|
177
|
+
if (declPath.node.type === "VariableDeclarator") return declPath.node.init || null;
|
|
178
|
+
return null;
|
|
179
|
+
} catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isJSONStringifyCall(node) {
|
|
185
|
+
// JSON.stringify(x)
|
|
186
|
+
return (
|
|
187
|
+
node &&
|
|
188
|
+
node.type === "CallExpression" &&
|
|
189
|
+
node.callee &&
|
|
190
|
+
node.callee.type === "MemberExpression" &&
|
|
191
|
+
node.callee.object &&
|
|
192
|
+
node.callee.object.type === "Identifier" &&
|
|
193
|
+
node.callee.object.name === "JSON" &&
|
|
194
|
+
node.callee.property &&
|
|
195
|
+
node.callee.property.type === "Identifier" &&
|
|
196
|
+
node.callee.property.name === "stringify"
|
|
197
|
+
);
|
|
17
198
|
}
|
|
18
199
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
200
|
+
// -------------------------
|
|
201
|
+
// DB insights: guess db + infer models + seeds
|
|
202
|
+
// -------------------------
|
|
203
|
+
function guessDbTypeFromRepo(rootDir) {
|
|
204
|
+
// Best-effort; if it's only frontend repo, usually null.
|
|
205
|
+
try {
|
|
206
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
207
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
208
|
+
|
|
209
|
+
const pkg = fs.readJsonSync(pkgPath);
|
|
210
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
211
|
+
|
|
212
|
+
if (deps.mongoose || deps.mongodb) return "mongodb-mongoose";
|
|
213
|
+
if (deps.prisma || deps["@prisma/client"]) return "sql-prisma";
|
|
214
|
+
if (deps.sequelize) return "sql-sequelize";
|
|
215
|
+
if (deps.typeorm) return "sql-typeorm";
|
|
216
|
+
|
|
217
|
+
return null;
|
|
218
|
+
} catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function inferModelsFromEndpoints(endpoints) {
|
|
224
|
+
const models = new Map();
|
|
225
|
+
|
|
226
|
+
for (const ep of endpoints) {
|
|
227
|
+
const modelName = ep.controllerName || "Default";
|
|
228
|
+
|
|
229
|
+
if (!models.has(modelName)) {
|
|
230
|
+
models.set(modelName, {
|
|
231
|
+
name: modelName,
|
|
232
|
+
fields: {}, // merged fields from bodies
|
|
233
|
+
sources: new Set(),
|
|
234
|
+
endpoints: [],
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const m = models.get(modelName);
|
|
239
|
+
m.endpoints.push({ method: ep.method, route: ep.route });
|
|
240
|
+
if (ep.sourceFile) m.sources.add(ep.sourceFile);
|
|
241
|
+
|
|
242
|
+
const fields = ep.schemaFields || (ep.requestBody && ep.requestBody.fields) || null;
|
|
243
|
+
if (fields) {
|
|
244
|
+
for (const [k, t] of Object.entries(fields)) {
|
|
245
|
+
if (!m.fields[k]) m.fields[k] = t || "String";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return Array.from(models.values()).map((m) => ({
|
|
251
|
+
name: m.name,
|
|
252
|
+
fields: m.fields,
|
|
253
|
+
sources: Array.from(m.sources),
|
|
254
|
+
endpoints: m.endpoints,
|
|
255
|
+
}));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function seedValueForType(t) {
|
|
259
|
+
if (t === "Number") return 1;
|
|
260
|
+
if (t === "Boolean") return true;
|
|
261
|
+
return "test"; // String default
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function generateSeedsFromModels(models, perModel = 3) {
|
|
265
|
+
return models.map((m) => {
|
|
266
|
+
const rows = [];
|
|
267
|
+
for (let i = 0; i < perModel; i++) {
|
|
268
|
+
const obj = {};
|
|
269
|
+
for (const [k, t] of Object.entries(m.fields || {})) {
|
|
270
|
+
obj[k] = seedValueForType(t);
|
|
271
|
+
}
|
|
272
|
+
rows.push(obj);
|
|
273
|
+
}
|
|
274
|
+
return { model: m.name, rows };
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// -------------------------
|
|
279
|
+
// MAIN frontend analyzer
|
|
280
|
+
// -------------------------
|
|
24
281
|
async function analyzeFrontend(srcPath) {
|
|
282
|
+
if (!srcPath) throw new Error("analyzeFrontend: srcPath is required");
|
|
25
283
|
if (!fs.existsSync(srcPath)) {
|
|
26
284
|
throw new Error(`The source directory '${srcPath}' does not exist.`);
|
|
27
285
|
}
|
|
28
286
|
|
|
29
|
-
const files = await glob(`${srcPath}/**/*.{js,ts,jsx,tsx}`, {
|
|
287
|
+
const files = await glob(`${normalizeSlashes(srcPath)}/**/*.{js,ts,jsx,tsx}`, {
|
|
288
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**", "**/coverage/**"],
|
|
289
|
+
});
|
|
290
|
+
|
|
30
291
|
const endpoints = new Map();
|
|
31
292
|
|
|
32
293
|
for (const file of files) {
|
|
33
|
-
|
|
294
|
+
let code;
|
|
34
295
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
296
|
+
code = await fs.readFile(file, "utf-8");
|
|
297
|
+
} catch {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let ast;
|
|
302
|
+
try {
|
|
303
|
+
ast = parser.parse(code, { sourceType: "module", plugins: ["jsx", "typescript"] });
|
|
304
|
+
} catch {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
traverse(ast, {
|
|
309
|
+
CallExpression(callPath) {
|
|
310
|
+
const node = callPath.node;
|
|
311
|
+
|
|
312
|
+
const isFetch = node.callee.type === "Identifier" && node.callee.name === "fetch";
|
|
313
|
+
const axiosMethod = detectAxiosLikeMethod(node);
|
|
314
|
+
|
|
315
|
+
if (!isFetch && !axiosMethod) return;
|
|
316
|
+
|
|
317
|
+
let urlValue = null;
|
|
318
|
+
let method = "GET";
|
|
319
|
+
let schemaFields = null;
|
|
320
|
+
|
|
321
|
+
// ---- fetch(url, options) ----
|
|
322
|
+
if (isFetch) {
|
|
323
|
+
urlValue = getUrlValue(node.arguments[0]);
|
|
324
|
+
const optionsNode = node.arguments[1];
|
|
325
|
+
|
|
326
|
+
if (optionsNode && optionsNode.type === "ObjectExpression") {
|
|
327
|
+
const methodProp = optionsNode.properties.find(
|
|
328
|
+
(p) =>
|
|
329
|
+
p.type === "ObjectProperty" &&
|
|
330
|
+
p.key.type === "Identifier" &&
|
|
331
|
+
p.key.name === "method"
|
|
332
|
+
);
|
|
333
|
+
if (methodProp && methodProp.value.type === "StringLiteral") {
|
|
65
334
|
method = methodProp.value.value.toUpperCase();
|
|
66
335
|
}
|
|
67
336
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
schemaFields[key] = 'Number';
|
|
89
|
-
} else if (valueNode.type === 'BooleanLiteral') {
|
|
90
|
-
schemaFields[key] = 'Boolean';
|
|
91
|
-
} else {
|
|
92
|
-
// Default to String if the type is complex or a variable
|
|
93
|
-
schemaFields[key] = 'String';
|
|
94
|
-
}
|
|
95
|
-
});
|
|
337
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
338
|
+
const bodyProp = optionsNode.properties.find(
|
|
339
|
+
(p) =>
|
|
340
|
+
p.type === "ObjectProperty" &&
|
|
341
|
+
p.key.type === "Identifier" &&
|
|
342
|
+
p.key.name === "body"
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
if (bodyProp) {
|
|
346
|
+
const v = bodyProp.value;
|
|
347
|
+
|
|
348
|
+
if (isJSONStringifyCall(v)) {
|
|
349
|
+
const arg0 = v.arguments[0];
|
|
350
|
+
|
|
351
|
+
if (arg0?.type === "ObjectExpression") {
|
|
352
|
+
schemaFields = extractObjectSchema(arg0);
|
|
353
|
+
} else if (arg0?.type === "Identifier") {
|
|
354
|
+
const init = resolveIdentifierToInit(callPath, arg0.name);
|
|
355
|
+
if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
|
|
356
|
+
}
|
|
96
357
|
}
|
|
97
358
|
}
|
|
98
359
|
}
|
|
99
360
|
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ---- axios-like client ----
|
|
364
|
+
if (axiosMethod) {
|
|
365
|
+
method = axiosMethod;
|
|
366
|
+
urlValue = getUrlValue(node.arguments[0]);
|
|
100
367
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
method,
|
|
110
|
-
controllerName,
|
|
111
|
-
schemaFields // This will be null for GET/DELETE, and an object for POST/PUT
|
|
112
|
-
});
|
|
368
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
369
|
+
const dataArg = node.arguments[1];
|
|
370
|
+
if (dataArg?.type === "ObjectExpression") {
|
|
371
|
+
schemaFields = extractObjectSchema(dataArg);
|
|
372
|
+
} else if (dataArg?.type === "Identifier") {
|
|
373
|
+
const init = resolveIdentifierToInit(callPath, dataArg.name);
|
|
374
|
+
if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
|
|
375
|
+
}
|
|
113
376
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// accept only URLs that contain /api/ anywhere
|
|
380
|
+
const apiPath = extractApiPath(urlValue);
|
|
381
|
+
if (!apiPath) return;
|
|
382
|
+
|
|
383
|
+
const route = normalizeRouteForBackend(apiPath.split("?")[0]);
|
|
384
|
+
const controllerName = deriveControllerNameFromUrl(apiPath);
|
|
385
|
+
const actionName = deriveActionName(method, route);
|
|
386
|
+
|
|
387
|
+
const key = `${method}:${route}`;
|
|
388
|
+
if (!endpoints.has(key)) {
|
|
389
|
+
endpoints.set(key, {
|
|
390
|
+
path: apiPath,
|
|
391
|
+
route,
|
|
392
|
+
method,
|
|
393
|
+
controllerName,
|
|
394
|
+
actionName,
|
|
395
|
+
pathParams: extractPathParams(route),
|
|
396
|
+
queryParams: extractQueryParamsFromUrl(apiPath),
|
|
397
|
+
schemaFields,
|
|
398
|
+
requestBody: schemaFields ? { fields: schemaFields } : null,
|
|
399
|
+
sourceFile: normalizeSlashes(file),
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
});
|
|
119
404
|
}
|
|
120
405
|
|
|
121
|
-
// Return all found endpoints as an array
|
|
122
406
|
return Array.from(endpoints.values());
|
|
123
407
|
}
|
|
124
408
|
|
|
125
|
-
|
|
409
|
+
// -------------------------
|
|
410
|
+
// Optional: full project analyze (endpoints + db insights)
|
|
411
|
+
// -------------------------
|
|
412
|
+
async function analyze(projectRoot = process.cwd()) {
|
|
413
|
+
const rootDir = path.resolve(projectRoot);
|
|
414
|
+
|
|
415
|
+
const frontendSrc = ["src", "app", "pages"]
|
|
416
|
+
.map((d) => path.join(rootDir, d))
|
|
417
|
+
.find((d) => fs.existsSync(d));
|
|
418
|
+
|
|
419
|
+
const endpoints = frontendSrc ? await analyzeFrontend(frontendSrc) : [];
|
|
420
|
+
|
|
421
|
+
const models = inferModelsFromEndpoints(endpoints);
|
|
422
|
+
const seeds = generateSeedsFromModels(models, 3);
|
|
423
|
+
const guessedDb = guessDbTypeFromRepo(rootDir);
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
rootDir: normalizeSlashes(rootDir),
|
|
427
|
+
endpoints,
|
|
428
|
+
dbInsights: {
|
|
429
|
+
guessedDb, // null | mongodb-mongoose | sql-prisma | ...
|
|
430
|
+
models, // inferred entities + fields
|
|
431
|
+
seeds, // dummy seed rows
|
|
432
|
+
},
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
module.exports = { analyzeFrontend, analyze };
|
package/src/generators/dotnet.js
CHANGED
|
@@ -77,7 +77,7 @@ async function generateDotnetProject(options) {
|
|
|
77
77
|
let usingStatements = 'using Microsoft.EntityFrameworkCore;\nusing '+projectName+'.Data;\n';
|
|
78
78
|
programCsContent = usingStatements + programCsContent;
|
|
79
79
|
|
|
80
|
-
let dbContextService =
|
|
80
|
+
let dbContextService = '// Configure the database context\nbuilder.Services.AddDbContext<ApplicationDbContext>(opt => opt.UseInMemoryDatabase("MyDb"));';
|
|
81
81
|
programCsContent = programCsContent.replace('builder.Services.AddControllers();', `builder.Services.AddControllers();\n\n${dbContextService}`);
|
|
82
82
|
|
|
83
83
|
// Enable CORS to allow frontend communication
|