@molecule/api-mock-server 1.0.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/LICENSE +115 -0
- package/README.md +853 -0
- package/dist/browser-guard.d.ts +2 -0
- package/dist/browser-guard.d.ts.map +1 -0
- package/dist/browser-guard.js +19 -0
- package/dist/browser-guard.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +152 -0
- package/dist/cli.js.map +1 -0
- package/dist/fixtures/app-fixtures.d.ts +54 -0
- package/dist/fixtures/app-fixtures.d.ts.map +1 -0
- package/dist/fixtures/app-fixtures.js +601 -0
- package/dist/fixtures/app-fixtures.js.map +1 -0
- package/dist/fixtures/index.d.ts +9 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +9 -0
- package/dist/fixtures/index.js.map +1 -0
- package/dist/fixtures/seed.d.ts +74 -0
- package/dist/fixtures/seed.d.ts.map +1 -0
- package/dist/fixtures/seed.js +112 -0
- package/dist/fixtures/seed.js.map +1 -0
- package/dist/fixtures/semantic-generator.d.ts +20 -0
- package/dist/fixtures/semantic-generator.d.ts.map +1 -0
- package/dist/fixtures/semantic-generator.js +539 -0
- package/dist/fixtures/semantic-generator.js.map +1 -0
- package/dist/fixtures/zod-walker.d.ts +34 -0
- package/dist/fixtures/zod-walker.d.ts.map +1 -0
- package/dist/fixtures/zod-walker.js +183 -0
- package/dist/fixtures/zod-walker.js.map +1 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner/index.d.ts +6 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +6 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/scanner.d.ts +21 -0
- package/dist/scanner/scanner.d.ts.map +1 -0
- package/dist/scanner/scanner.js +463 -0
- package/dist/scanner/scanner.js.map +1 -0
- package/dist/server/index.d.ts +7 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +7 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +51 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +124 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/server.d.ts +29 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +314 -0
- package/dist/server/server.js.map +1 -0
- package/dist/states/index.d.ts +6 -0
- package/dist/states/index.d.ts.map +1 -0
- package/dist/states/index.js +6 -0
- package/dist/states/index.js.map +1 -0
- package/dist/states/states.d.ts +57 -0
- package/dist/states/states.d.ts.map +1 -0
- package/dist/states/states.js +89 -0
- package/dist/states/states.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static analysis scanner that reads handler files from mlcl templates
|
|
3
|
+
* and produces endpoint definitions. Uses regex-based AST parsing.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { basename, join } from 'node:path';
|
|
7
|
+
/* ------------------------------------------------------------------ */
|
|
8
|
+
/* Regex-based parsing */
|
|
9
|
+
/* ------------------------------------------------------------------ */
|
|
10
|
+
/**
|
|
11
|
+
* Pattern for router.use() calls in index.ts:
|
|
12
|
+
* router.use('/accounts', accounts)
|
|
13
|
+
*/
|
|
14
|
+
const ROUTER_USE_RE = /router\.use\(\s*['"]([^'"]+)['"]\s*,\s*(\w+)\s*\)/g;
|
|
15
|
+
/**
|
|
16
|
+
* Pattern for router.method() calls in handler files:
|
|
17
|
+
* router.get('/', async ...)
|
|
18
|
+
* router.post('/', validateBody(schema), async ...)
|
|
19
|
+
* router.put('/:id', validateBody(schema), async ...)
|
|
20
|
+
* router.patch('/:id', validateBody(schema), async ...)
|
|
21
|
+
* Uses a permissive capture for the middleware chain to handle nested parentheses.
|
|
22
|
+
* PATCH is included — 185 fleet template handler files use `router.patch`, and
|
|
23
|
+
* omitting it silently routed every partial-update call to the unmatched
|
|
24
|
+
* catch-all (`{}` + X-Mock-Unmatched) instead of the resource fixture.
|
|
25
|
+
*/
|
|
26
|
+
const ROUTER_METHOD_RE = /router\.(get|post|put|patch|delete)\(\s*['"]([^'"]+)['"]\s*,\s*([\s\S]*?)\basync\b/g;
|
|
27
|
+
/**
|
|
28
|
+
* Pattern for validateBody(schemaName) in middleware chain
|
|
29
|
+
*/
|
|
30
|
+
const VALIDATE_BODY_RE = /validateBody\(\s*(\w+)\s*\)/;
|
|
31
|
+
/**
|
|
32
|
+
* Pattern for getUserId(res) auth check
|
|
33
|
+
*/
|
|
34
|
+
const GET_USER_ID_RE = /getUserId\(res\)|res\.locals\.session\??\.\s*userId/;
|
|
35
|
+
/**
|
|
36
|
+
* Pattern for paginated responses: { data: ..., total: ..., page: ..., limit: ... }
|
|
37
|
+
*/
|
|
38
|
+
const PAGINATED_RE = /res\.json\(\s*\{\s*data\s*:/;
|
|
39
|
+
/**
|
|
40
|
+
* Pattern for z.object({ ... }) - capture the full object body
|
|
41
|
+
*/
|
|
42
|
+
const ZOD_OBJECT_RE = /const\s+(\w+)\s*=\s*z\.object\(\{([\s\S]*?)\}\)/g;
|
|
43
|
+
/**
|
|
44
|
+
* Pattern for individual field in z.object
|
|
45
|
+
*/
|
|
46
|
+
const ZOD_FIELD_RE = /(\w+)\s*:\s*z\.(\w+)\(([^)]*)\)([.\w()'"]*)/g;
|
|
47
|
+
/* ------------------------------------------------------------------ */
|
|
48
|
+
/* Response-shape extraction */
|
|
49
|
+
/* ------------------------------------------------------------------ */
|
|
50
|
+
/**
|
|
51
|
+
* Return the inner content of a balanced bracket pair starting at `openIdx`
|
|
52
|
+
* (which must point at `open`). Skips brackets inside strings/templates.
|
|
53
|
+
* Returns null if no matching close is found.
|
|
54
|
+
* @param str
|
|
55
|
+
* @param openIdx
|
|
56
|
+
* @param open
|
|
57
|
+
* @param close
|
|
58
|
+
*/
|
|
59
|
+
function matchBalanced(str, openIdx, open, close) {
|
|
60
|
+
let depth = 0;
|
|
61
|
+
let quote = null;
|
|
62
|
+
for (let i = openIdx; i < str.length; i++) {
|
|
63
|
+
const c = str[i];
|
|
64
|
+
if (quote) {
|
|
65
|
+
if (c === '\\') {
|
|
66
|
+
i++;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (c === quote)
|
|
70
|
+
quote = null;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
74
|
+
quote = c;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (c === open)
|
|
78
|
+
depth++;
|
|
79
|
+
else if (c === close) {
|
|
80
|
+
depth--;
|
|
81
|
+
if (depth === 0)
|
|
82
|
+
return str.slice(openIdx + 1, i);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse the top-level keys of an object-literal body (the text between
|
|
89
|
+
* `{` and `}`). Handles shorthand props, `key: value`, nested
|
|
90
|
+
* objects/arrays/calls, strings, and `...spread` (skipped).
|
|
91
|
+
* @param inner
|
|
92
|
+
*/
|
|
93
|
+
function parseTopLevelKeys(inner) {
|
|
94
|
+
const keys = [];
|
|
95
|
+
let depth = 0;
|
|
96
|
+
let quote = null;
|
|
97
|
+
let segStart = 0;
|
|
98
|
+
const segments = [];
|
|
99
|
+
for (let i = 0; i < inner.length; i++) {
|
|
100
|
+
const c = inner[i];
|
|
101
|
+
if (quote) {
|
|
102
|
+
if (c === '\\') {
|
|
103
|
+
i++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (c === quote)
|
|
107
|
+
quote = null;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
111
|
+
quote = c;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (c === '{' || c === '[' || c === '(')
|
|
115
|
+
depth++;
|
|
116
|
+
else if (c === '}' || c === ']' || c === ')')
|
|
117
|
+
depth--;
|
|
118
|
+
else if (c === ',' && depth === 0) {
|
|
119
|
+
segments.push(inner.slice(segStart, i));
|
|
120
|
+
segStart = i + 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
segments.push(inner.slice(segStart));
|
|
124
|
+
for (const seg of segments) {
|
|
125
|
+
const trimmed = seg.trim();
|
|
126
|
+
if (!trimmed || trimmed.startsWith('...'))
|
|
127
|
+
continue;
|
|
128
|
+
// `key: value` or shorthand `key` — the key is the leading identifier.
|
|
129
|
+
const m = trimmed.match(/^([A-Za-z_$][\w$]*)\s*(:|$)/);
|
|
130
|
+
if (m)
|
|
131
|
+
keys.push(m[1]);
|
|
132
|
+
}
|
|
133
|
+
return keys;
|
|
134
|
+
}
|
|
135
|
+
const PAGINATION_KEYS = new Set([
|
|
136
|
+
'data',
|
|
137
|
+
'total',
|
|
138
|
+
'page',
|
|
139
|
+
'limit',
|
|
140
|
+
'perPage',
|
|
141
|
+
'hasMore',
|
|
142
|
+
'nextCursor',
|
|
143
|
+
'count',
|
|
144
|
+
]);
|
|
145
|
+
/**
|
|
146
|
+
* Extract the success-response shape from a handler body slice by parsing
|
|
147
|
+
* its `res.json(...)` calls. Error responses (`res.json({ error })`) and
|
|
148
|
+
* `res.status(...).json(...)` chains are skipped so the success shape wins.
|
|
149
|
+
* @param body - Source slice covering a single route handler.
|
|
150
|
+
*/
|
|
151
|
+
function extractResponseShape(body) {
|
|
152
|
+
let best = [];
|
|
153
|
+
let isArrayLiteral = false;
|
|
154
|
+
let isPaginatedEnvelope = false;
|
|
155
|
+
let from = 0;
|
|
156
|
+
for (;;) {
|
|
157
|
+
const idx = body.indexOf('res.json(', from);
|
|
158
|
+
if (idx === -1)
|
|
159
|
+
break;
|
|
160
|
+
from = idx + 9;
|
|
161
|
+
// Skip `res.status(4xx|5xx)...json(...)` error responses.
|
|
162
|
+
const before = body.slice(Math.max(0, idx - 60), idx);
|
|
163
|
+
if (/\.status\([^)]*\)\s*\.?\s*$/.test(before))
|
|
164
|
+
continue;
|
|
165
|
+
let i = idx + 9;
|
|
166
|
+
while (i < body.length && /\s/.test(body[i]))
|
|
167
|
+
i++;
|
|
168
|
+
const first = body[i];
|
|
169
|
+
if (first === '[') {
|
|
170
|
+
isArrayLiteral = true;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (first !== '{')
|
|
174
|
+
continue; // res.json(variable) — shape not statically known
|
|
175
|
+
const inner = matchBalanced(body, i, '{', '}');
|
|
176
|
+
if (inner === null)
|
|
177
|
+
continue;
|
|
178
|
+
const keys = parseTopLevelKeys(inner);
|
|
179
|
+
if (keys.length === 0)
|
|
180
|
+
continue;
|
|
181
|
+
if (keys.length === 1 && keys[0] === 'error')
|
|
182
|
+
continue;
|
|
183
|
+
if (keys.includes('data') && keys.every((k) => PAGINATION_KEYS.has(k))) {
|
|
184
|
+
isPaginatedEnvelope = true;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (keys.length > best.length)
|
|
188
|
+
best = keys;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
isSingleObject: best.length > 0,
|
|
192
|
+
responseFields: best,
|
|
193
|
+
isArrayLiteral,
|
|
194
|
+
isPaginatedEnvelope,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Scan all handler files for a given app type and produce endpoint definitions.
|
|
199
|
+
* @param handlersPath - Path to the handlers directory
|
|
200
|
+
* @param appType - The app type name
|
|
201
|
+
* @returns The scan result with all discovered endpoints
|
|
202
|
+
*/
|
|
203
|
+
export function scanHandlers(handlersPath, appType) {
|
|
204
|
+
if (!existsSync(handlersPath)) {
|
|
205
|
+
return { appType, endpoints: [], resources: [] };
|
|
206
|
+
}
|
|
207
|
+
// Read index.ts to find route prefixes
|
|
208
|
+
const indexPath = join(handlersPath, 'index.ts');
|
|
209
|
+
const indexSource = existsSync(indexPath) ? readFileSync(indexPath, 'utf-8') : '';
|
|
210
|
+
// Map: handler variable name -> route prefix
|
|
211
|
+
const prefixMap = new Map();
|
|
212
|
+
let match;
|
|
213
|
+
ROUTER_USE_RE.lastIndex = 0;
|
|
214
|
+
while ((match = ROUTER_USE_RE.exec(indexSource)) !== null) {
|
|
215
|
+
const prefix = match[1];
|
|
216
|
+
const varName = match[2];
|
|
217
|
+
prefixMap.set(varName, prefix);
|
|
218
|
+
}
|
|
219
|
+
// Scan each handler file
|
|
220
|
+
const endpoints = [];
|
|
221
|
+
const resources = new Set();
|
|
222
|
+
const files = readdirSync(handlersPath).filter((f) => f.endsWith('.ts') && f !== 'index.ts');
|
|
223
|
+
for (const file of files) {
|
|
224
|
+
const filePath = join(handlersPath, file);
|
|
225
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
226
|
+
const handlerName = basename(file, '.ts');
|
|
227
|
+
// Find the prefix for this handler
|
|
228
|
+
let prefix = '';
|
|
229
|
+
for (const [varName, routePrefix] of prefixMap) {
|
|
230
|
+
if (varName === handlerName || varName === handlerName.replace(/-/g, '')) {
|
|
231
|
+
prefix = routePrefix;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// If no prefix found, derive from filename
|
|
236
|
+
if (!prefix) {
|
|
237
|
+
prefix = `/${handlerName}`;
|
|
238
|
+
}
|
|
239
|
+
resources.add(handlerName);
|
|
240
|
+
// Parse Zod schemas in this file
|
|
241
|
+
const schemas = parseZodSchemas(source);
|
|
242
|
+
// Find all router method calls
|
|
243
|
+
const hasAuth = GET_USER_ID_RE.test(source);
|
|
244
|
+
const isPaginated = PAGINATED_RE.test(source);
|
|
245
|
+
ROUTER_METHOD_RE.lastIndex = 0;
|
|
246
|
+
while ((match = ROUTER_METHOD_RE.exec(source)) !== null) {
|
|
247
|
+
const method = match[1].toUpperCase();
|
|
248
|
+
const routePath = match[2];
|
|
249
|
+
const middlewareChain = match[3] || '';
|
|
250
|
+
// Collapse any double slash from joining prefix + routePath — handlers
|
|
251
|
+
// mounted at `router.use('/', x)` keep full paths in their own routes.
|
|
252
|
+
const fullPath = `${prefix}${routePath === '/' ? '' : routePath}`.replace(/\/{2,}/g, '/');
|
|
253
|
+
// Check for validateBody in middleware
|
|
254
|
+
let bodySchema;
|
|
255
|
+
const validateMatch = VALIDATE_BODY_RE.exec(middlewareChain);
|
|
256
|
+
if (validateMatch) {
|
|
257
|
+
const schemaName = validateMatch[1];
|
|
258
|
+
bodySchema = schemas.get(schemaName);
|
|
259
|
+
}
|
|
260
|
+
// Slice this route's handler body — from this match to the next
|
|
261
|
+
// router.method() call (or EOF) — and extract its response shape.
|
|
262
|
+
const afterMatch = match.index + match[0].length;
|
|
263
|
+
const nextRel = source.slice(afterMatch).search(/router\.(get|post|put|patch|delete)\(/);
|
|
264
|
+
const handlerBody = source.slice(match.index, nextRel === -1 ? source.length : afterMatch + nextRel);
|
|
265
|
+
const shape = extractResponseShape(handlerBody);
|
|
266
|
+
// Determine response hints
|
|
267
|
+
const responseHints = inferResponseHints(method, fullPath, handlerName, (isPaginated && method === 'GET' && routePath === '/') || shape.isPaginatedEnvelope, source, shape);
|
|
268
|
+
endpoints.push({
|
|
269
|
+
method,
|
|
270
|
+
path: fullPath,
|
|
271
|
+
bodySchema,
|
|
272
|
+
requiresAuth: hasAuth,
|
|
273
|
+
responseHints,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
appType,
|
|
279
|
+
endpoints,
|
|
280
|
+
resources: [...resources],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Parse Zod schema definitions from source code.
|
|
285
|
+
* Returns a map of schema variable names to serialized definitions.
|
|
286
|
+
* @param source
|
|
287
|
+
*/
|
|
288
|
+
function parseZodSchemas(source) {
|
|
289
|
+
const schemas = new Map();
|
|
290
|
+
ZOD_OBJECT_RE.lastIndex = 0;
|
|
291
|
+
let match;
|
|
292
|
+
while ((match = ZOD_OBJECT_RE.exec(source)) !== null) {
|
|
293
|
+
const name = match[1];
|
|
294
|
+
const body = match[2];
|
|
295
|
+
const shape = parseZodObjectBody(body);
|
|
296
|
+
schemas.set(name, { type: 'ZodObject', shape });
|
|
297
|
+
}
|
|
298
|
+
return schemas;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Parse the body of a z.object({ ... }) call into field definitions.
|
|
302
|
+
* @param body
|
|
303
|
+
*/
|
|
304
|
+
function parseZodObjectBody(body) {
|
|
305
|
+
const shape = {};
|
|
306
|
+
ZOD_FIELD_RE.lastIndex = 0;
|
|
307
|
+
let match;
|
|
308
|
+
while ((match = ZOD_FIELD_RE.exec(body)) !== null) {
|
|
309
|
+
const fieldName = match[1];
|
|
310
|
+
const zodType = match[2];
|
|
311
|
+
const args = match[3];
|
|
312
|
+
const chain = match[4] || '';
|
|
313
|
+
shape[fieldName] = parseZodField(zodType, args, chain);
|
|
314
|
+
}
|
|
315
|
+
return shape;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Parse a single Zod field definition into a schema definition.
|
|
319
|
+
* @param zodType
|
|
320
|
+
* @param args
|
|
321
|
+
* @param chain
|
|
322
|
+
*/
|
|
323
|
+
function parseZodField(zodType, args, chain) {
|
|
324
|
+
const constraints = {};
|
|
325
|
+
// Check chain modifiers
|
|
326
|
+
if (chain.includes('.optional()')) {
|
|
327
|
+
const inner = parseZodField(zodType, args, chain.replace('.optional()', ''));
|
|
328
|
+
return { type: 'ZodOptional', innerType: inner };
|
|
329
|
+
}
|
|
330
|
+
if (chain.includes('.nullable()')) {
|
|
331
|
+
const inner = parseZodField(zodType, args, chain.replace('.nullable()', ''));
|
|
332
|
+
return { type: 'ZodNullable', innerType: inner };
|
|
333
|
+
}
|
|
334
|
+
const defaultMatch = chain.match(/\.default\(([^)]+)\)/);
|
|
335
|
+
if (defaultMatch) {
|
|
336
|
+
const inner = parseZodField(zodType, args, chain.replace(defaultMatch[0], ''));
|
|
337
|
+
let defaultValue = defaultMatch[1];
|
|
338
|
+
try {
|
|
339
|
+
defaultValue = JSON.parse(defaultMatch[1].replace(/'/g, '"'));
|
|
340
|
+
}
|
|
341
|
+
catch (_error) {
|
|
342
|
+
// JSON.parse failed (e.g. unquoted identifier like `undefined`) — keep the raw string as-is.
|
|
343
|
+
}
|
|
344
|
+
return { type: 'ZodDefault', innerType: inner, defaultValue };
|
|
345
|
+
}
|
|
346
|
+
// Parse constraints from chain
|
|
347
|
+
const minMatch = chain.match(/\.min\((\d+)\)/);
|
|
348
|
+
if (minMatch)
|
|
349
|
+
constraints.min = Number(minMatch[1]);
|
|
350
|
+
const maxMatch = chain.match(/\.max\((\d+)\)/);
|
|
351
|
+
if (maxMatch)
|
|
352
|
+
constraints.max = Number(maxMatch[1]);
|
|
353
|
+
if (chain.includes('.positive()'))
|
|
354
|
+
constraints.positive = true;
|
|
355
|
+
if (chain.includes('.int()'))
|
|
356
|
+
constraints.int = true;
|
|
357
|
+
switch (zodType) {
|
|
358
|
+
case 'string':
|
|
359
|
+
return { type: 'ZodString', constraints };
|
|
360
|
+
case 'number':
|
|
361
|
+
return { type: 'ZodNumber', constraints };
|
|
362
|
+
case 'boolean':
|
|
363
|
+
return { type: 'ZodBoolean' };
|
|
364
|
+
case 'enum': {
|
|
365
|
+
const enumMatch = args.match(/\[\s*([^\]]+)\s*\]/);
|
|
366
|
+
if (enumMatch) {
|
|
367
|
+
const enumValues = enumMatch[1]
|
|
368
|
+
.split(',')
|
|
369
|
+
.map((v) => v.trim().replace(/['"]/g, ''))
|
|
370
|
+
.filter(Boolean);
|
|
371
|
+
return { type: 'ZodEnum', enumValues };
|
|
372
|
+
}
|
|
373
|
+
return { type: 'ZodEnum', enumValues: [] };
|
|
374
|
+
}
|
|
375
|
+
case 'array': {
|
|
376
|
+
const elementMatch = args.match(/z\.(\w+)\(/);
|
|
377
|
+
const elementType = elementMatch
|
|
378
|
+
? { type: `Zod${elementMatch[1].charAt(0).toUpperCase() + elementMatch[1].slice(1)}` }
|
|
379
|
+
: { type: 'ZodUnknown' };
|
|
380
|
+
return { type: 'ZodArray', elementType };
|
|
381
|
+
}
|
|
382
|
+
case 'object':
|
|
383
|
+
return { type: 'ZodObject', shape: {} };
|
|
384
|
+
default:
|
|
385
|
+
return { type: 'ZodUnknown' };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Infer response hints for an endpoint based on method, path, and handler source.
|
|
390
|
+
* @param method
|
|
391
|
+
* @param path
|
|
392
|
+
* @param handlerName
|
|
393
|
+
* @param isPaginated
|
|
394
|
+
* @param source
|
|
395
|
+
*/
|
|
396
|
+
function inferResponseHints(method, path, handlerName, isPaginated, source, shape) {
|
|
397
|
+
// Recognize any `:<word>Id` (or `:<word>id`) param as a single-resource
|
|
398
|
+
// marker, not just :id/:itemId — e.g. :locationId, :botId, :userId. The
|
|
399
|
+
// previous narrow check classified those as list endpoints, so they
|
|
400
|
+
// returned `[]` and the calling page rendered empty.
|
|
401
|
+
const isSingle = /:\w*[Ii]d\b/.test(path);
|
|
402
|
+
const isReport = path.includes('/reports/') || path.includes('/storefront/');
|
|
403
|
+
// A handler whose success response is a bare object literal
|
|
404
|
+
// (`res.json({ a, b })`) is a single-object endpoint, not a list — even
|
|
405
|
+
// for a GET without an `:id` param (e.g. `/analytics/summary`,
|
|
406
|
+
// `/profile/me`). Without this, such endpoints get mis-classified as
|
|
407
|
+
// list endpoints and serve `[]` because no fixture file matches.
|
|
408
|
+
const isSingleObject = !!shape?.isSingleObject && method === 'GET' && !isSingle && !isReport && !isPaginated;
|
|
409
|
+
const isList = method === 'GET' && !isSingle && !isReport && !isSingleObject;
|
|
410
|
+
let resourceName = handlerName;
|
|
411
|
+
if (isReport) {
|
|
412
|
+
resourceName = 'reports';
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
isList,
|
|
416
|
+
isPaginated,
|
|
417
|
+
hasNestedResources: source.includes('Promise.all') || source.includes('findMany'),
|
|
418
|
+
resourceName,
|
|
419
|
+
isSingleObject,
|
|
420
|
+
responseFields: isSingleObject ? shape?.responseFields : undefined,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Resolve the handlers path for a given app type.
|
|
425
|
+
* Searches standard locations in the mlcl templates directory.
|
|
426
|
+
* @param appType - The app type name
|
|
427
|
+
* @param workspaceRoot - The workspace root directory
|
|
428
|
+
* @returns The resolved handlers path, or undefined if not found
|
|
429
|
+
*/
|
|
430
|
+
export function resolveHandlersPath(appType, workspaceRoot) {
|
|
431
|
+
const root = workspaceRoot ?? findWorkspaceRoot();
|
|
432
|
+
if (!root)
|
|
433
|
+
return undefined;
|
|
434
|
+
const candidates = [
|
|
435
|
+
join(root, 'mlcl', 'templates', 'apps', appType, 'api', 'src', 'handlers'),
|
|
436
|
+
join(root, 'mlcl', 'templates', 'apps', appType, 'api', 'handlers'),
|
|
437
|
+
join(root, 'mlcl', 'templates', appType, 'api', 'src', 'handlers'),
|
|
438
|
+
join(root, 'mlcl', 'templates', appType, 'api', 'handlers'),
|
|
439
|
+
];
|
|
440
|
+
for (const candidate of candidates) {
|
|
441
|
+
if (existsSync(candidate)) {
|
|
442
|
+
return candidate;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return undefined;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Attempt to find the workspace root by walking up from cwd.
|
|
449
|
+
*/
|
|
450
|
+
function findWorkspaceRoot() {
|
|
451
|
+
let dir = process.cwd();
|
|
452
|
+
for (let i = 0; i < 10; i++) {
|
|
453
|
+
if (existsSync(join(dir, 'mlcl')) && existsSync(join(dir, 'molecule'))) {
|
|
454
|
+
return dir;
|
|
455
|
+
}
|
|
456
|
+
const parent = join(dir, '..');
|
|
457
|
+
if (parent === dir)
|
|
458
|
+
break;
|
|
459
|
+
dir = parent;
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../../src/scanner/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State control middleware for the mock server.
|
|
3
|
+
* Allows per-request state overrides via query params or headers.
|
|
4
|
+
*
|
|
5
|
+
* Query params: ?_state=error&_delay=2000&_status=500
|
|
6
|
+
* Headers: X-Mock-State: error, X-Mock-Delay: 2000
|
|
7
|
+
*/
|
|
8
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
9
|
+
import type { ResponseState } from '../types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Express middleware that extracts state control signals from the request
|
|
12
|
+
* and attaches them to res.locals for the route handler to use.
|
|
13
|
+
* @param defaultState - The default state to use when no override is provided.
|
|
14
|
+
* Pass a function to have the default resolved per-request (a live getter) —
|
|
15
|
+
* required for `MockServer.setDefaultState()` to take effect after startup,
|
|
16
|
+
* since a plain object is captured once at middleware-creation time.
|
|
17
|
+
* @returns Express middleware function
|
|
18
|
+
*/
|
|
19
|
+
export declare function stateControlMiddleware(defaultState?: ResponseState | (() => ResponseState)): (req: Request, res: Response, next: NextFunction) => void;
|
|
20
|
+
/**
|
|
21
|
+
* CORS middleware that sets permissive CORS headers for development.
|
|
22
|
+
* @returns Express middleware function
|
|
23
|
+
*/
|
|
24
|
+
export declare function corsMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Request logging middleware for the mock server.
|
|
27
|
+
* @returns Express middleware function
|
|
28
|
+
*/
|
|
29
|
+
export declare function loggingMiddleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Maximum delay, in ms, that {@link applyDelay} will actually wait — a
|
|
32
|
+
* requested delay above this is clamped (and logged) rather than honored
|
|
33
|
+
* verbatim. Guards against a stray oversized `?_delay` / `X-Mock-Delay` /
|
|
34
|
+
* `defaultDelay` / `setState({ delay })` value (e.g. a units mistake
|
|
35
|
+
* applying `*1000` twice) hanging a request until the CLIENT gives up —
|
|
36
|
+
* which in an E2E harness presents as an inexplicable page timeout rather
|
|
37
|
+
* than an obvious mock misconfiguration.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MAX_MOCK_DELAY_MS = 60000;
|
|
40
|
+
/**
|
|
41
|
+
* Apply a delay if specified in the response state. The requested delay is
|
|
42
|
+
* capped at {@link MAX_MOCK_DELAY_MS} — a value above the cap is clamped and
|
|
43
|
+
* a warning is logged (via `console.warn`, immediately, before waiting —
|
|
44
|
+
* not after — so the clamp is visible in server logs right when the
|
|
45
|
+
* oversized delay is requested rather than a minute later).
|
|
46
|
+
* @param state - The response state that may contain a delay
|
|
47
|
+
* @returns A promise that resolves after the (possibly clamped) delay, or
|
|
48
|
+
* immediately if no delay was requested
|
|
49
|
+
*/
|
|
50
|
+
export declare function applyDelay(state: ResponseState): Promise<void>;
|
|
51
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAE9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAEhD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CACpC,YAAY,GAAE,aAAa,GAAG,CAAC,MAAM,aAAa,CAAwB,GACzE,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAkD3D;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAiB1F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI,CAO7F;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,QAAS,CAAA;AAEvC;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB9D"}
|