create-backlist 6.0.2 → 6.0.4
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
package/src/analyzer.js
CHANGED
|
@@ -1,68 +1,149 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
+
// Small utils
|
|
13
|
+
// -------------------------
|
|
14
|
+
function normalizeSlashes(p) {
|
|
15
|
+
return String(p || "").replace(/\\/g, "/");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function readJSONSafe(p) {
|
|
19
|
+
try {
|
|
20
|
+
return fs.readJsonSync(p);
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// -------------------------
|
|
27
|
+
// AUTH detection (for addAuth)
|
|
28
|
+
// -------------------------
|
|
29
|
+
function findAuthUsageInRepo(rootDir) {
|
|
30
|
+
// 1) package.json quick check
|
|
31
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
32
|
+
const pkg = readJSONSafe(pkgPath) || {};
|
|
33
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
34
|
+
|
|
35
|
+
const authDeps = [
|
|
36
|
+
"next-auth",
|
|
37
|
+
"@auth/core",
|
|
38
|
+
"@clerk/nextjs",
|
|
39
|
+
"@supabase/auth-helpers-nextjs",
|
|
40
|
+
"@supabase/supabase-js",
|
|
41
|
+
"firebase",
|
|
42
|
+
"lucia",
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
if (authDeps.some((d) => deps[d])) return true;
|
|
46
|
+
|
|
47
|
+
// 2) Source scan for common auth identifiers
|
|
48
|
+
const scanDirs = ["src", "app", "pages", "components", "lib", "utils"]
|
|
49
|
+
.map((d) => path.join(rootDir, d))
|
|
50
|
+
.filter((d) => fs.existsSync(d));
|
|
51
|
+
|
|
52
|
+
const patterns = [
|
|
53
|
+
"next-auth",
|
|
54
|
+
"getServerSession",
|
|
55
|
+
"useSession",
|
|
56
|
+
"SessionProvider",
|
|
57
|
+
"@clerk/nextjs",
|
|
58
|
+
"ClerkProvider",
|
|
59
|
+
"auth()",
|
|
60
|
+
"currentUser",
|
|
61
|
+
"createServerClient",
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
for (const dir of scanDirs) {
|
|
65
|
+
const files = glob.sync(`${normalizeSlashes(dir)}/**/*.{js,ts,jsx,tsx}`, {
|
|
66
|
+
ignore: [
|
|
67
|
+
"**/node_modules/**",
|
|
68
|
+
"**/dist/**",
|
|
69
|
+
"**/build/**",
|
|
70
|
+
"**/.next/**",
|
|
71
|
+
"**/coverage/**",
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
for (const f of files) {
|
|
76
|
+
try {
|
|
77
|
+
const content = fs.readFileSync(f, "utf8");
|
|
78
|
+
if (patterns.some((p) => content.includes(p))) return true;
|
|
79
|
+
} catch {
|
|
80
|
+
// ignore read errors
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// -------------------------
|
|
89
|
+
// Frontend API call analyzer (your code)
|
|
90
|
+
// -------------------------
|
|
9
91
|
function toTitleCase(str) {
|
|
10
|
-
if (!str) return
|
|
92
|
+
if (!str) return "Default";
|
|
11
93
|
return String(str)
|
|
12
|
-
.replace(/[-_]+(\w)/g, (_, c) => c.toUpperCase())
|
|
13
|
-
.replace(/^\w/, c => c.toUpperCase())
|
|
14
|
-
.replace(/[^a-zA-Z0-9]/g,
|
|
94
|
+
.replace(/[-_]+(\w)/g, (_, c) => c.toUpperCase())
|
|
95
|
+
.replace(/^\w/, (c) => c.toUpperCase())
|
|
96
|
+
.replace(/[^a-zA-Z0-9]/g, "");
|
|
15
97
|
}
|
|
16
98
|
|
|
17
99
|
function normalizeRouteForBackend(urlValue) {
|
|
18
|
-
|
|
19
|
-
return urlValue.replace(/\{(\w+)\}/g, ':$1');
|
|
100
|
+
return urlValue.replace(/\{(\w+)\}/g, ":$1");
|
|
20
101
|
}
|
|
21
102
|
|
|
22
103
|
function inferTypeFromNode(node) {
|
|
23
|
-
if (!node) return
|
|
104
|
+
if (!node) return "String";
|
|
24
105
|
switch (node.type) {
|
|
25
|
-
case
|
|
26
|
-
|
|
27
|
-
case
|
|
28
|
-
|
|
29
|
-
|
|
106
|
+
case "StringLiteral":
|
|
107
|
+
return "String";
|
|
108
|
+
case "NumericLiteral":
|
|
109
|
+
return "Number";
|
|
110
|
+
case "BooleanLiteral":
|
|
111
|
+
return "Boolean";
|
|
112
|
+
case "NullLiteral":
|
|
113
|
+
return "String";
|
|
114
|
+
default:
|
|
115
|
+
return "String";
|
|
30
116
|
}
|
|
31
117
|
}
|
|
32
118
|
|
|
33
119
|
function extractObjectSchema(objExpr) {
|
|
34
120
|
const schemaFields = {};
|
|
35
|
-
if (!objExpr || objExpr.type !==
|
|
121
|
+
if (!objExpr || objExpr.type !== "ObjectExpression") return null;
|
|
36
122
|
|
|
37
123
|
for (const prop of objExpr.properties) {
|
|
38
|
-
if (prop.type !==
|
|
124
|
+
if (prop.type !== "ObjectProperty") continue;
|
|
39
125
|
|
|
40
126
|
const key =
|
|
41
|
-
prop.key.type ===
|
|
42
|
-
|
|
43
|
-
|
|
127
|
+
prop.key.type === "Identifier"
|
|
128
|
+
? prop.key.name
|
|
129
|
+
: prop.key.type === "StringLiteral"
|
|
130
|
+
? prop.key.value
|
|
131
|
+
: null;
|
|
44
132
|
|
|
45
133
|
if (!key) continue;
|
|
46
|
-
|
|
47
134
|
schemaFields[key] = inferTypeFromNode(prop.value);
|
|
48
135
|
}
|
|
49
136
|
return schemaFields;
|
|
50
137
|
}
|
|
51
138
|
|
|
52
|
-
|
|
53
|
-
* Try to resolve Identifier -> its init value if it's const payload = {...}
|
|
54
|
-
*/
|
|
55
|
-
function resolveIdentifierToInit(path, identifierName) {
|
|
139
|
+
function resolveIdentifierToInit(pathObj, identifierName) {
|
|
56
140
|
try {
|
|
57
|
-
const binding =
|
|
141
|
+
const binding = pathObj.scope.getBinding(identifierName);
|
|
58
142
|
if (!binding) return null;
|
|
59
|
-
const declPath = binding.path;
|
|
143
|
+
const declPath = binding.path;
|
|
60
144
|
if (!declPath || !declPath.node) return null;
|
|
61
145
|
|
|
62
|
-
|
|
63
|
-
if (declPath.node.type === 'VariableDeclarator') {
|
|
64
|
-
return declPath.node.init || null;
|
|
65
|
-
}
|
|
146
|
+
if (declPath.node.type === "VariableDeclarator") return declPath.node.init || null;
|
|
66
147
|
return null;
|
|
67
148
|
} catch {
|
|
68
149
|
return null;
|
|
@@ -72,18 +153,17 @@ function resolveIdentifierToInit(path, identifierName) {
|
|
|
72
153
|
function getUrlValue(urlNode) {
|
|
73
154
|
if (!urlNode) return null;
|
|
74
155
|
|
|
75
|
-
if (urlNode.type ===
|
|
156
|
+
if (urlNode.type === "StringLiteral") return urlNode.value;
|
|
76
157
|
|
|
77
|
-
if (urlNode.type ===
|
|
78
|
-
// `/api/users/${id}` -> `/api/users/{id}`
|
|
158
|
+
if (urlNode.type === "TemplateLiteral") {
|
|
79
159
|
const quasis = urlNode.quasis || [];
|
|
80
160
|
const exprs = urlNode.expressions || [];
|
|
81
|
-
let out =
|
|
161
|
+
let out = "";
|
|
82
162
|
for (let i = 0; i < quasis.length; i++) {
|
|
83
163
|
out += quasis[i].value.raw;
|
|
84
164
|
if (exprs[i]) {
|
|
85
|
-
if (exprs[i].type ===
|
|
86
|
-
else out += `{param}`;
|
|
165
|
+
if (exprs[i].type === "Identifier") out += `{${exprs[i].name}}`;
|
|
166
|
+
else out += `{param${i + 1}}`;
|
|
87
167
|
}
|
|
88
168
|
}
|
|
89
169
|
return out;
|
|
@@ -92,58 +172,95 @@ function getUrlValue(urlNode) {
|
|
|
92
172
|
return null;
|
|
93
173
|
}
|
|
94
174
|
|
|
175
|
+
function extractApiPath(urlValue) {
|
|
176
|
+
if (!urlValue) return null;
|
|
177
|
+
const idx = urlValue.indexOf("/api/");
|
|
178
|
+
if (idx === -1) return null;
|
|
179
|
+
return urlValue.slice(idx);
|
|
180
|
+
}
|
|
181
|
+
|
|
95
182
|
function deriveControllerNameFromUrl(urlValue) {
|
|
96
|
-
|
|
97
|
-
const parts =
|
|
98
|
-
const apiIndex = parts.indexOf(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
183
|
+
const apiPath = extractApiPath(urlValue) || urlValue;
|
|
184
|
+
const parts = String(apiPath).split("/").filter(Boolean);
|
|
185
|
+
const apiIndex = parts.indexOf("api");
|
|
186
|
+
|
|
187
|
+
let seg = null;
|
|
188
|
+
|
|
189
|
+
if (apiIndex >= 0) {
|
|
190
|
+
seg = parts[apiIndex + 1] || null;
|
|
191
|
+
if (seg && /^v\d+$/i.test(seg)) {
|
|
192
|
+
seg = parts[apiIndex + 2] || seg;
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
seg = parts[0] || null;
|
|
196
|
+
}
|
|
102
197
|
|
|
103
198
|
return toTitleCase(seg);
|
|
104
199
|
}
|
|
105
200
|
|
|
106
201
|
function deriveActionName(method, route) {
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
return `${method.toLowerCase()}${toTitleCase(last)}`;
|
|
202
|
+
const cleaned = String(route).replace(/^\/api\//, "/").replace(/[/:{}-]/g, " ");
|
|
203
|
+
const last = cleaned.trim().split(/\s+/).filter(Boolean).pop() || "Action";
|
|
204
|
+
return `${String(method).toLowerCase()}${toTitleCase(last)}`;
|
|
111
205
|
}
|
|
112
206
|
|
|
113
207
|
function extractPathParams(route) {
|
|
114
208
|
const params = [];
|
|
115
|
-
const re = /[:{]([a-zA-Z0-9_]+)[}]/g;
|
|
209
|
+
const re = /[:{]([a-zA-Z0-9_]+)[}]/g;
|
|
116
210
|
let m;
|
|
117
211
|
while ((m = re.exec(route))) params.push(m[1]);
|
|
118
212
|
return Array.from(new Set(params));
|
|
119
213
|
}
|
|
120
214
|
|
|
121
215
|
function extractQueryParamsFromUrl(urlValue) {
|
|
122
|
-
// if url has ?a=b&c=d as string literal
|
|
123
216
|
try {
|
|
124
|
-
const qIndex = urlValue.indexOf(
|
|
217
|
+
const qIndex = urlValue.indexOf("?");
|
|
125
218
|
if (qIndex === -1) return [];
|
|
126
219
|
const qs = urlValue.slice(qIndex + 1);
|
|
127
|
-
return qs
|
|
220
|
+
return qs
|
|
221
|
+
.split("&")
|
|
222
|
+
.map((p) => p.split("=")[0])
|
|
223
|
+
.filter(Boolean);
|
|
128
224
|
} catch {
|
|
129
225
|
return [];
|
|
130
226
|
}
|
|
131
227
|
}
|
|
132
228
|
|
|
229
|
+
function detectAxiosLikeMethod(node) {
|
|
230
|
+
if (!node.callee || node.callee.type !== "MemberExpression") return null;
|
|
231
|
+
|
|
232
|
+
const prop = node.callee.property;
|
|
233
|
+
if (!prop || prop.type !== "Identifier") return null;
|
|
234
|
+
|
|
235
|
+
const name = prop.name.toLowerCase();
|
|
236
|
+
if (!HTTP_METHODS.has(name)) return null;
|
|
237
|
+
|
|
238
|
+
return name.toUpperCase();
|
|
239
|
+
}
|
|
240
|
+
|
|
133
241
|
async function analyzeFrontend(srcPath) {
|
|
242
|
+
if (!srcPath) throw new Error("analyzeFrontend: srcPath is required");
|
|
134
243
|
if (!fs.existsSync(srcPath)) {
|
|
135
244
|
throw new Error(`The source directory '${srcPath}' does not exist.`);
|
|
136
245
|
}
|
|
137
246
|
|
|
138
|
-
const files = await glob(`${srcPath}/**/*.{js,ts,jsx,tsx}`, {
|
|
247
|
+
const files = await glob(`${normalizeSlashes(srcPath)}/**/*.{js,ts,jsx,tsx}`, {
|
|
248
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.next/**", "**/coverage/**"],
|
|
249
|
+
});
|
|
250
|
+
|
|
139
251
|
const endpoints = new Map();
|
|
140
252
|
|
|
141
253
|
for (const file of files) {
|
|
142
|
-
|
|
254
|
+
let code;
|
|
255
|
+
try {
|
|
256
|
+
code = await fs.readFile(file, "utf-8");
|
|
257
|
+
} catch {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
143
260
|
|
|
144
261
|
let ast;
|
|
145
262
|
try {
|
|
146
|
-
ast = parser.parse(code, { sourceType:
|
|
263
|
+
ast = parser.parse(code, { sourceType: "module", plugins: ["jsx", "typescript"] });
|
|
147
264
|
} catch {
|
|
148
265
|
continue;
|
|
149
266
|
}
|
|
@@ -152,88 +269,98 @@ async function analyzeFrontend(srcPath) {
|
|
|
152
269
|
CallExpression(callPath) {
|
|
153
270
|
const node = callPath.node;
|
|
154
271
|
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
// --- Detect axios.<method>(url, data?, config?) ---
|
|
159
|
-
const isAxiosMethod =
|
|
160
|
-
node.callee.type === 'MemberExpression' &&
|
|
161
|
-
node.callee.object.type === 'Identifier' &&
|
|
162
|
-
node.callee.object.name === 'axios' &&
|
|
163
|
-
node.callee.property.type === 'Identifier';
|
|
272
|
+
const isFetch = node.callee.type === "Identifier" && node.callee.name === "fetch";
|
|
273
|
+
const axiosMethod = detectAxiosLikeMethod(node);
|
|
164
274
|
|
|
165
|
-
if (!isFetch && !
|
|
275
|
+
if (!isFetch && !axiosMethod) return;
|
|
166
276
|
|
|
167
277
|
let urlValue = null;
|
|
168
|
-
let method =
|
|
278
|
+
let method = "GET";
|
|
169
279
|
let schemaFields = null;
|
|
170
280
|
|
|
171
281
|
if (isFetch) {
|
|
172
282
|
urlValue = getUrlValue(node.arguments[0]);
|
|
173
283
|
const optionsNode = node.arguments[1];
|
|
174
284
|
|
|
175
|
-
if (optionsNode && optionsNode.type ===
|
|
176
|
-
const methodProp = optionsNode.properties.find(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (v.type === 'CallExpression' && v.callee.type === 'MemberExpression' &&
|
|
186
|
-
v.callee.object.type === 'Identifier' && v.callee.object.name === 'JSON' &&
|
|
187
|
-
v.callee.property.type === 'Identifier' && v.callee.property.name === 'stringify'
|
|
188
|
-
) {
|
|
189
|
-
const arg0 = v.arguments[0];
|
|
285
|
+
if (optionsNode && optionsNode.type === "ObjectExpression") {
|
|
286
|
+
const methodProp = optionsNode.properties.find(
|
|
287
|
+
(p) =>
|
|
288
|
+
p.type === "ObjectProperty" &&
|
|
289
|
+
p.key.type === "Identifier" &&
|
|
290
|
+
p.key.name === "method"
|
|
291
|
+
);
|
|
292
|
+
if (methodProp && methodProp.value.type === "StringLiteral") {
|
|
293
|
+
method = methodProp.value.value.toUpperCase();
|
|
294
|
+
}
|
|
190
295
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
296
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
297
|
+
const bodyProp = optionsNode.properties.find(
|
|
298
|
+
(p) =>
|
|
299
|
+
p.type === "ObjectProperty" &&
|
|
300
|
+
p.key.type === "Identifier" &&
|
|
301
|
+
p.key.name === "body"
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
if (bodyProp) {
|
|
305
|
+
const v = bodyProp.value;
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
v.type === "CallExpression" &&
|
|
309
|
+
v.callee.type === "MemberExpression" &&
|
|
310
|
+
v.callee.object.type === "Identifier" &&
|
|
311
|
+
v.callee.object.name === "JSON" &&
|
|
312
|
+
v.callee.property.type === "Identifier" &&
|
|
313
|
+
v.callee.property.name === "stringify"
|
|
314
|
+
) {
|
|
315
|
+
const arg0 = v.arguments[0];
|
|
316
|
+
|
|
317
|
+
if (arg0?.type === "ObjectExpression") {
|
|
318
|
+
schemaFields = extractObjectSchema(arg0);
|
|
319
|
+
} else if (arg0?.type === "Identifier") {
|
|
320
|
+
const init = resolveIdentifierToInit(callPath, arg0.name);
|
|
321
|
+
if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
|
|
322
|
+
}
|
|
196
323
|
}
|
|
197
324
|
}
|
|
198
325
|
}
|
|
199
326
|
}
|
|
200
327
|
}
|
|
201
328
|
|
|
202
|
-
if (
|
|
203
|
-
method =
|
|
329
|
+
if (axiosMethod) {
|
|
330
|
+
method = axiosMethod;
|
|
204
331
|
urlValue = getUrlValue(node.arguments[0]);
|
|
205
332
|
|
|
206
|
-
|
|
207
|
-
if (['POST', 'PUT', 'PATCH'].includes(method)) {
|
|
333
|
+
if (["POST", "PUT", "PATCH"].includes(method)) {
|
|
208
334
|
const dataArg = node.arguments[1];
|
|
209
|
-
if (dataArg?.type ===
|
|
335
|
+
if (dataArg?.type === "ObjectExpression") {
|
|
210
336
|
schemaFields = extractObjectSchema(dataArg);
|
|
211
|
-
} else if (dataArg?.type ===
|
|
337
|
+
} else if (dataArg?.type === "Identifier") {
|
|
212
338
|
const init = resolveIdentifierToInit(callPath, dataArg.name);
|
|
213
|
-
if (init?.type ===
|
|
339
|
+
if (init?.type === "ObjectExpression") schemaFields = extractObjectSchema(init);
|
|
214
340
|
}
|
|
215
341
|
}
|
|
216
342
|
}
|
|
217
343
|
|
|
218
|
-
|
|
344
|
+
const apiPath = extractApiPath(urlValue);
|
|
345
|
+
if (!apiPath) return;
|
|
219
346
|
|
|
220
|
-
const route = normalizeRouteForBackend(
|
|
221
|
-
const controllerName = deriveControllerNameFromUrl(
|
|
347
|
+
const route = normalizeRouteForBackend(apiPath.split("?")[0]);
|
|
348
|
+
const controllerName = deriveControllerNameFromUrl(apiPath);
|
|
222
349
|
const actionName = deriveActionName(method, route);
|
|
223
350
|
|
|
224
351
|
const key = `${method}:${route}`;
|
|
225
352
|
if (!endpoints.has(key)) {
|
|
226
353
|
endpoints.set(key, {
|
|
227
|
-
path:
|
|
228
|
-
route,
|
|
354
|
+
path: apiPath,
|
|
355
|
+
route,
|
|
229
356
|
method,
|
|
230
357
|
controllerName,
|
|
231
358
|
actionName,
|
|
232
359
|
pathParams: extractPathParams(route),
|
|
233
|
-
queryParams: extractQueryParamsFromUrl(
|
|
234
|
-
schemaFields,
|
|
360
|
+
queryParams: extractQueryParamsFromUrl(apiPath),
|
|
361
|
+
schemaFields,
|
|
235
362
|
requestBody: schemaFields ? { fields: schemaFields } : null,
|
|
236
|
-
sourceFile: file
|
|
363
|
+
sourceFile: normalizeSlashes(file),
|
|
237
364
|
});
|
|
238
365
|
}
|
|
239
366
|
},
|
|
@@ -243,4 +370,21 @@ async function analyzeFrontend(srcPath) {
|
|
|
243
370
|
return Array.from(endpoints.values());
|
|
244
371
|
}
|
|
245
372
|
|
|
246
|
-
|
|
373
|
+
// -------------------------
|
|
374
|
+
// Main analyze() exported for CLI
|
|
375
|
+
// -------------------------
|
|
376
|
+
function analyze(projectRoot = process.cwd()) {
|
|
377
|
+
const rootDir = path.resolve(projectRoot);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
rootDir: normalizeSlashes(rootDir),
|
|
381
|
+
hasAuth: findAuthUsageInRepo(rootDir),
|
|
382
|
+
// If CLI expects addAuth directly, keep both:
|
|
383
|
+
addAuth: findAuthUsageInRepo(rootDir),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
module.exports = {
|
|
388
|
+
analyze,
|
|
389
|
+
analyzeFrontend,
|
|
390
|
+
};
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import request from 'supertest';
|
|
3
3
|
import express from 'express';
|
|
4
4
|
|
|
5
|
-
import apiRoutes from '../routes';
|
|
5
|
+
import apiRoutes from '../routes';
|
|
6
|
+
|
|
6
7
|
<% if (addAuth) { -%>
|
|
7
8
|
import authRoutes from '../routes/Auth.routes';
|
|
8
9
|
<% } -%>
|
|
@@ -13,6 +14,7 @@ app.use(express.json());
|
|
|
13
14
|
<% if (addAuth) { -%>
|
|
14
15
|
app.use('/api/auth', authRoutes);
|
|
15
16
|
<% } -%>
|
|
17
|
+
|
|
16
18
|
app.use('/api', apiRoutes);
|
|
17
19
|
|
|
18
20
|
describe('API Endpoints (Generated)', () => {
|
|
@@ -22,14 +24,35 @@ describe('API Endpoints (Generated)', () => {
|
|
|
22
24
|
|
|
23
25
|
<% endpoints
|
|
24
26
|
.filter(ep => ep && ep.route && ep.method)
|
|
25
|
-
.forEach(ep => {
|
|
26
|
-
const method = ep.method.toLowerCase();
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
.forEach(ep => {
|
|
28
|
+
const method = String(ep.method).toLowerCase();
|
|
29
|
+
|
|
30
|
+
// ep.route might be "/api/..." or "/..."
|
|
31
|
+
const url = (String(ep.route).startsWith('/api/')
|
|
32
|
+
? String(ep.route).replace(/^\/api/, '')
|
|
33
|
+
: String(ep.route)
|
|
34
|
+
)
|
|
35
|
+
// replace ":id" style params with dummy value
|
|
36
|
+
.replace(/:\w+/g, '1');
|
|
29
37
|
-%>
|
|
30
38
|
it('<%= ep.method %> <%= url %> should respond', async () => {
|
|
31
|
-
const
|
|
32
|
-
|
|
39
|
+
const req = request(app).<%= method %>('<%= '/api' + url %>');
|
|
40
|
+
|
|
41
|
+
<% if (['post','put','patch'].includes(method) && ep.schemaFields) { -%>
|
|
42
|
+
req.send(
|
|
43
|
+
<%- JSON.stringify(
|
|
44
|
+
Object.fromEntries(
|
|
45
|
+
Object.entries(ep.schemaFields).map(([k, t]) => [
|
|
46
|
+
k,
|
|
47
|
+
t === 'Number' ? 1 : (t === 'Boolean' ? true : 'test')
|
|
48
|
+
])
|
|
49
|
+
)
|
|
50
|
+
) %>
|
|
51
|
+
);
|
|
52
|
+
<% } -%>
|
|
53
|
+
|
|
54
|
+
const res = await req;
|
|
55
|
+
|
|
33
56
|
// We only assert "not 404" because generated handlers may be TODO stubs,
|
|
34
57
|
// and auth/validation may affect exact status codes.
|
|
35
58
|
expect(res.statusCode).not.toBe(404);
|