@vibecheckai/cli 3.2.2 → 3.2.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/bin/.generated +25 -25
- package/bin/dev/run-v2-torture.js +30 -30
- package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
- package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
- package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
- package/bin/runners/lib/analyzers.js +606 -325
- package/bin/runners/lib/auth-truth.js +193 -193
- package/bin/runners/lib/backup.js +62 -62
- package/bin/runners/lib/billing.js +107 -107
- package/bin/runners/lib/claims.js +118 -118
- package/bin/runners/lib/cli-ui.js +540 -540
- package/bin/runners/lib/contracts/auth-contract.js +202 -202
- package/bin/runners/lib/contracts/env-contract.js +181 -181
- package/bin/runners/lib/contracts/external-contract.js +206 -206
- package/bin/runners/lib/contracts/guard.js +168 -168
- package/bin/runners/lib/contracts/index.js +89 -89
- package/bin/runners/lib/contracts/plan-validator.js +311 -311
- package/bin/runners/lib/contracts/route-contract.js +199 -199
- package/bin/runners/lib/contracts.js +804 -804
- package/bin/runners/lib/detect.js +89 -89
- package/bin/runners/lib/doctor/autofix.js +254 -254
- package/bin/runners/lib/doctor/index.js +37 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
- package/bin/runners/lib/doctor/modules/index.js +46 -46
- package/bin/runners/lib/doctor/modules/network.js +250 -250
- package/bin/runners/lib/doctor/modules/project.js +312 -312
- package/bin/runners/lib/doctor/modules/runtime.js +224 -224
- package/bin/runners/lib/doctor/modules/security.js +348 -348
- package/bin/runners/lib/doctor/modules/system.js +213 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
- package/bin/runners/lib/doctor/reporter.js +262 -262
- package/bin/runners/lib/doctor/service.js +262 -262
- package/bin/runners/lib/doctor/types.js +113 -113
- package/bin/runners/lib/doctor/ui.js +263 -263
- package/bin/runners/lib/doctor-v2.js +608 -608
- package/bin/runners/lib/drift.js +425 -425
- package/bin/runners/lib/enforcement.js +72 -72
- package/bin/runners/lib/engines/accessibility-engine.js +190 -0
- package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
- package/bin/runners/lib/engines/ast-cache.js +99 -0
- package/bin/runners/lib/engines/code-quality-engine.js +255 -0
- package/bin/runners/lib/engines/console-logs-engine.js +115 -0
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
- package/bin/runners/lib/engines/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
- package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
- package/bin/runners/lib/engines/file-filter.js +131 -0
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
- package/bin/runners/lib/engines/mock-data-engine.js +272 -0
- package/bin/runners/lib/engines/parallel-processor.js +71 -0
- package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
- package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
- package/bin/runners/lib/engines/type-aware-engine.js +152 -0
- package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
- package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
- package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
- package/bin/runners/lib/enterprise-detect.js +603 -603
- package/bin/runners/lib/enterprise-init.js +942 -942
- package/bin/runners/lib/env-resolver.js +417 -417
- package/bin/runners/lib/env-template.js +66 -66
- package/bin/runners/lib/env.js +189 -189
- package/bin/runners/lib/extractors/client-calls.js +990 -990
- package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
- package/bin/runners/lib/extractors/fastify-routes.js +426 -426
- package/bin/runners/lib/extractors/index.js +363 -363
- package/bin/runners/lib/extractors/next-routes.js +524 -524
- package/bin/runners/lib/extractors/proof-graph.js +431 -431
- package/bin/runners/lib/extractors/route-matcher.js +451 -451
- package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
- package/bin/runners/lib/extractors/ui-bindings.js +547 -547
- package/bin/runners/lib/findings-schema.js +281 -281
- package/bin/runners/lib/firewall-prompt.js +50 -50
- package/bin/runners/lib/global-flags.js +213 -213
- package/bin/runners/lib/graph/graph-builder.js +265 -265
- package/bin/runners/lib/graph/html-renderer.js +413 -413
- package/bin/runners/lib/graph/index.js +32 -32
- package/bin/runners/lib/graph/runtime-collector.js +215 -215
- package/bin/runners/lib/graph/static-extractor.js +518 -518
- package/bin/runners/lib/html-report.js +650 -650
- package/bin/runners/lib/interactive-menu.js +1496 -1496
- package/bin/runners/lib/llm.js +75 -75
- package/bin/runners/lib/meter.js +61 -61
- package/bin/runners/lib/missions/evidence.js +126 -126
- package/bin/runners/lib/patch.js +40 -40
- package/bin/runners/lib/permissions/auth-model.js +213 -213
- package/bin/runners/lib/permissions/idor-prover.js +205 -205
- package/bin/runners/lib/permissions/index.js +45 -45
- package/bin/runners/lib/permissions/matrix-builder.js +198 -198
- package/bin/runners/lib/pkgjson.js +28 -28
- package/bin/runners/lib/policy.js +295 -295
- package/bin/runners/lib/preflight.js +142 -142
- package/bin/runners/lib/reality/correlation-detectors.js +359 -359
- package/bin/runners/lib/reality/index.js +318 -318
- package/bin/runners/lib/reality/request-hashing.js +416 -416
- package/bin/runners/lib/reality/request-mapper.js +453 -453
- package/bin/runners/lib/reality/safety-rails.js +463 -463
- package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
- package/bin/runners/lib/reality/toast-detector.js +393 -393
- package/bin/runners/lib/reality-findings.js +84 -84
- package/bin/runners/lib/receipts.js +179 -179
- package/bin/runners/lib/redact.js +29 -29
- package/bin/runners/lib/replay/capsule-manager.js +154 -154
- package/bin/runners/lib/replay/index.js +263 -263
- package/bin/runners/lib/replay/player.js +348 -348
- package/bin/runners/lib/replay/recorder.js +331 -331
- package/bin/runners/lib/report-output.js +187 -187
- package/bin/runners/lib/report.js +135 -135
- package/bin/runners/lib/route-detection.js +1140 -1140
- package/bin/runners/lib/sandbox/index.js +59 -59
- package/bin/runners/lib/sandbox/proof-chain.js +399 -399
- package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
- package/bin/runners/lib/sandbox/worktree.js +174 -174
- package/bin/runners/lib/scan-output.js +525 -190
- package/bin/runners/lib/schema-validator.js +350 -350
- package/bin/runners/lib/schemas/contracts.schema.json +160 -160
- package/bin/runners/lib/schemas/finding.schema.json +100 -100
- package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
- package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
- package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
- package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
- package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
- package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
- package/bin/runners/lib/schemas/validator.js +438 -438
- package/bin/runners/lib/score-history.js +282 -282
- package/bin/runners/lib/share-pack.js +239 -239
- package/bin/runners/lib/snippets.js +67 -67
- package/bin/runners/lib/status-output.js +253 -253
- package/bin/runners/lib/terminal-ui.js +351 -271
- package/bin/runners/lib/upsell.js +510 -510
- package/bin/runners/lib/usage.js +153 -153
- package/bin/runners/lib/validate-patch.js +156 -156
- package/bin/runners/lib/verdict-engine.js +628 -628
- package/bin/runners/reality/engine.js +917 -917
- package/bin/runners/reality/flows.js +122 -122
- package/bin/runners/reality/report.js +378 -378
- package/bin/runners/reality/session.js +193 -193
- package/bin/runners/runGuard.js +168 -168
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runProve.js +8 -0
- package/bin/runners/runReality.js +14 -0
- package/bin/runners/runScan.js +17 -1
- package/bin/runners/runTruth.js +15 -3
- package/mcp-server/tier-auth.js +4 -4
- package/mcp-server/tools/index.js +72 -72
- package/package.json +1 -1
|
@@ -1,453 +1,453 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Request Mapper v2
|
|
3
|
-
*
|
|
4
|
-
* Maps runtime requests to client calls with specificity scoring.
|
|
5
|
-
* Handles URL normalization, basePath, proxies, tRPC/GraphQL operations.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
"use strict";
|
|
9
|
-
|
|
10
|
-
const crypto = require("crypto");
|
|
11
|
-
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// IGNORE PATTERNS
|
|
14
|
-
// =============================================================================
|
|
15
|
-
|
|
16
|
-
const IGNORE_PATTERNS = {
|
|
17
|
-
// Asset extensions
|
|
18
|
-
assets: /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|map|webp|avif)(\?.*)?$/i,
|
|
19
|
-
|
|
20
|
-
// Dev/HMR patterns
|
|
21
|
-
hmr: /(_next\/static|__webpack_hmr|hot-update|\.hot-update\.|webpack|turbopack)/i,
|
|
22
|
-
|
|
23
|
-
// Analytics/tracking
|
|
24
|
-
analytics: /(analytics|gtag|ga\.js|gtm\.js|pixel|clarity|hotjar|segment|mixpanel|amplitude)/i,
|
|
25
|
-
|
|
26
|
-
// Third party
|
|
27
|
-
thirdParty: /^https?:\/\/(fonts\.googleapis|cdn\.|unpkg\.com|cdnjs\.|jsdelivr)/i,
|
|
28
|
-
|
|
29
|
-
// Browser internals
|
|
30
|
-
browser: /^(chrome-extension|moz-extension|safari-extension|about:|data:|blob:)/i,
|
|
31
|
-
|
|
32
|
-
// Favicon
|
|
33
|
-
favicon: /favicon\.ico/i,
|
|
34
|
-
|
|
35
|
-
// Source maps
|
|
36
|
-
sourceMaps: /\.map$/i,
|
|
37
|
-
|
|
38
|
-
// WebSocket
|
|
39
|
-
websocket: /^wss?:/i,
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Check if request should be ignored
|
|
44
|
-
*/
|
|
45
|
-
function shouldIgnoreRequest(url, method = "GET") {
|
|
46
|
-
if (!url) return true;
|
|
47
|
-
|
|
48
|
-
// Always include mutations
|
|
49
|
-
if (["POST", "PUT", "PATCH", "DELETE"].includes(method?.toUpperCase())) {
|
|
50
|
-
// But still ignore analytics POST
|
|
51
|
-
if (IGNORE_PATTERNS.analytics.test(url)) return true;
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const pattern of Object.values(IGNORE_PATTERNS)) {
|
|
56
|
-
if (pattern.test(url)) return true;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return false;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// =============================================================================
|
|
63
|
-
// URL NORMALIZATION
|
|
64
|
-
// =============================================================================
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Normalize URL for matching
|
|
68
|
-
*/
|
|
69
|
-
function normalizeRequestUrl(url, options = {}) {
|
|
70
|
-
const { basePath = "", stripHost = true, stripQuery = true } = options;
|
|
71
|
-
|
|
72
|
-
if (!url) return null;
|
|
73
|
-
|
|
74
|
-
let normalized = url;
|
|
75
|
-
|
|
76
|
-
// Strip host if absolute URL
|
|
77
|
-
if (stripHost && /^https?:\/\//.test(normalized)) {
|
|
78
|
-
try {
|
|
79
|
-
const parsed = new URL(normalized);
|
|
80
|
-
normalized = parsed.pathname + (stripQuery ? "" : parsed.search);
|
|
81
|
-
} catch {
|
|
82
|
-
// Invalid URL, try to extract path
|
|
83
|
-
normalized = normalized.replace(/^https?:\/\/[^/]+/, "");
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Strip query string
|
|
88
|
-
if (stripQuery) {
|
|
89
|
-
normalized = normalized.split("?")[0].split("#")[0];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Strip basePath prefix
|
|
93
|
-
if (basePath && normalized.startsWith(basePath)) {
|
|
94
|
-
normalized = normalized.slice(basePath.length) || "/";
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Ensure leading slash
|
|
98
|
-
if (!normalized.startsWith("/")) {
|
|
99
|
-
normalized = "/" + normalized;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Remove trailing slash except for root
|
|
103
|
-
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
104
|
-
normalized = normalized.slice(0, -1);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Normalize multiple slashes
|
|
108
|
-
normalized = normalized.replace(/\/+/g, "/");
|
|
109
|
-
|
|
110
|
-
return normalized;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Extract tRPC operation from URL
|
|
115
|
-
*/
|
|
116
|
-
function extractTrpcOperation(url) {
|
|
117
|
-
// /api/trpc/user.get?batch=1&input=...
|
|
118
|
-
const match = url.match(/\/trpc\/([^?]+)/);
|
|
119
|
-
if (!match) return null;
|
|
120
|
-
|
|
121
|
-
const procedure = match[1];
|
|
122
|
-
// Handle batch queries
|
|
123
|
-
const procedures = procedure.split(",").map(p => p.trim());
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
type: "trpc",
|
|
127
|
-
procedures,
|
|
128
|
-
isBatch: procedures.length > 1,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Extract GraphQL operation from request
|
|
134
|
-
*/
|
|
135
|
-
function extractGraphqlOperation(url, body) {
|
|
136
|
-
// Check if it's a GraphQL endpoint
|
|
137
|
-
if (!url.includes("graphql")) return null;
|
|
138
|
-
|
|
139
|
-
if (!body) return { type: "graphql", operationName: null };
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const parsed = typeof body === "string" ? JSON.parse(body) : body;
|
|
143
|
-
return {
|
|
144
|
-
type: "graphql",
|
|
145
|
-
operationName: parsed.operationName || null,
|
|
146
|
-
operationType: parsed.query?.trim().startsWith("mutation") ? "mutation" : "query",
|
|
147
|
-
};
|
|
148
|
-
} catch {
|
|
149
|
-
return { type: "graphql", operationName: null };
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// =============================================================================
|
|
154
|
-
// REQUEST → CLIENT CALL MATCHING
|
|
155
|
-
// =============================================================================
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Match a runtime request to client calls
|
|
159
|
-
*/
|
|
160
|
-
function matchRequestToClientCalls(request, clientCalls, options = {}) {
|
|
161
|
-
const { basePath = "", rewrites = [] } = options;
|
|
162
|
-
|
|
163
|
-
const normalizedPath = normalizeRequestUrl(request.url, { basePath });
|
|
164
|
-
if (!normalizedPath) return null;
|
|
165
|
-
|
|
166
|
-
const method = (request.method || "GET").toUpperCase();
|
|
167
|
-
|
|
168
|
-
// Check for tRPC
|
|
169
|
-
const trpcOp = extractTrpcOperation(request.url);
|
|
170
|
-
if (trpcOp) {
|
|
171
|
-
return matchTrpcRequest(trpcOp, clientCalls, request);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Check for GraphQL
|
|
175
|
-
const gqlOp = extractGraphqlOperation(request.url, request.body);
|
|
176
|
-
if (gqlOp) {
|
|
177
|
-
return matchGraphqlRequest(gqlOp, clientCalls, request);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Standard HTTP matching
|
|
181
|
-
const candidates = [];
|
|
182
|
-
|
|
183
|
-
for (const call of clientCalls) {
|
|
184
|
-
if (call.kind !== "http") continue;
|
|
185
|
-
|
|
186
|
-
const score = calculateMatchScore(normalizedPath, method, call, { basePath, rewrites });
|
|
187
|
-
if (score > 0) {
|
|
188
|
-
candidates.push({ call, score });
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Sort by score descending
|
|
193
|
-
candidates.sort((a, b) => b.score - a.score);
|
|
194
|
-
|
|
195
|
-
if (candidates.length === 0) {
|
|
196
|
-
return {
|
|
197
|
-
matched: false,
|
|
198
|
-
request,
|
|
199
|
-
normalizedPath,
|
|
200
|
-
method,
|
|
201
|
-
candidates: [],
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
matched: true,
|
|
207
|
-
request,
|
|
208
|
-
normalizedPath,
|
|
209
|
-
method,
|
|
210
|
-
bestMatch: candidates[0].call,
|
|
211
|
-
score: candidates[0].score,
|
|
212
|
-
candidates: candidates.slice(0, 3),
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Match tRPC request to client calls
|
|
218
|
-
*/
|
|
219
|
-
function matchTrpcRequest(trpcOp, clientCalls, request) {
|
|
220
|
-
const trpcCalls = clientCalls.filter(c => c.kind === "trpc");
|
|
221
|
-
|
|
222
|
-
for (const call of trpcCalls) {
|
|
223
|
-
const callProcedure = call.meta?.procedure;
|
|
224
|
-
if (!callProcedure) continue;
|
|
225
|
-
|
|
226
|
-
// Check if any procedure matches
|
|
227
|
-
for (const proc of trpcOp.procedures) {
|
|
228
|
-
if (proc === callProcedure || proc.endsWith(callProcedure)) {
|
|
229
|
-
return {
|
|
230
|
-
matched: true,
|
|
231
|
-
request,
|
|
232
|
-
matchType: "trpc",
|
|
233
|
-
procedure: proc,
|
|
234
|
-
bestMatch: call,
|
|
235
|
-
score: 1.0,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
matched: false,
|
|
243
|
-
request,
|
|
244
|
-
matchType: "trpc",
|
|
245
|
-
procedures: trpcOp.procedures,
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Match GraphQL request to client calls
|
|
251
|
-
*/
|
|
252
|
-
function matchGraphqlRequest(gqlOp, clientCalls, request) {
|
|
253
|
-
const gqlCalls = clientCalls.filter(c => c.kind === "graphql");
|
|
254
|
-
|
|
255
|
-
if (gqlOp.operationName) {
|
|
256
|
-
for (const call of gqlCalls) {
|
|
257
|
-
if (call.meta?.operationName === gqlOp.operationName) {
|
|
258
|
-
return {
|
|
259
|
-
matched: true,
|
|
260
|
-
request,
|
|
261
|
-
matchType: "graphql",
|
|
262
|
-
operationName: gqlOp.operationName,
|
|
263
|
-
bestMatch: call,
|
|
264
|
-
score: 1.0,
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Fallback: match any GraphQL call
|
|
271
|
-
if (gqlCalls.length > 0) {
|
|
272
|
-
return {
|
|
273
|
-
matched: true,
|
|
274
|
-
request,
|
|
275
|
-
matchType: "graphql",
|
|
276
|
-
operationName: gqlOp.operationName,
|
|
277
|
-
bestMatch: gqlCalls[0],
|
|
278
|
-
score: 0.5,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return {
|
|
283
|
-
matched: false,
|
|
284
|
-
request,
|
|
285
|
-
matchType: "graphql",
|
|
286
|
-
operationName: gqlOp.operationName,
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Calculate match score between request and client call
|
|
292
|
-
*/
|
|
293
|
-
function calculateMatchScore(requestPath, requestMethod, call, options = {}) {
|
|
294
|
-
const { basePath = "", rewrites = [] } = options;
|
|
295
|
-
|
|
296
|
-
let callPath = call.canonicalPath;
|
|
297
|
-
if (!callPath) return 0;
|
|
298
|
-
|
|
299
|
-
// Apply rewrites
|
|
300
|
-
for (const rewrite of rewrites) {
|
|
301
|
-
if (rewrite.source && rewrite.destination) {
|
|
302
|
-
const sourceRegex = new RegExp("^" + rewrite.source.replace(/:(\w+)/g, "([^/]+)") + "$");
|
|
303
|
-
if (sourceRegex.test(requestPath)) {
|
|
304
|
-
// Rewrite matches, adjust call path expectation
|
|
305
|
-
callPath = requestPath.replace(sourceRegex, rewrite.destination);
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
let score = 0;
|
|
312
|
-
|
|
313
|
-
// Exact path match
|
|
314
|
-
if (requestPath === callPath) {
|
|
315
|
-
score += 1.0;
|
|
316
|
-
}
|
|
317
|
-
// Parameterized match
|
|
318
|
-
else if (callPath.includes("{") || callPath.includes(":")) {
|
|
319
|
-
const pattern = "^" + callPath
|
|
320
|
-
.replace(/\{[^}]+\}/g, "[^/]+")
|
|
321
|
-
.replace(/:(\w+)/g, "[^/]+") + "$";
|
|
322
|
-
const regex = new RegExp(pattern);
|
|
323
|
-
if (regex.test(requestPath)) {
|
|
324
|
-
score += 0.9;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
// Prefix match (less confident)
|
|
328
|
-
else if (requestPath.startsWith(callPath) || callPath.startsWith(requestPath)) {
|
|
329
|
-
score += 0.5;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (score === 0) return 0;
|
|
333
|
-
|
|
334
|
-
// Method match bonus
|
|
335
|
-
const callMethod = (call.method || "UNKNOWN").toUpperCase();
|
|
336
|
-
if (callMethod === requestMethod) {
|
|
337
|
-
score += 0.1;
|
|
338
|
-
} else if (callMethod === "UNKNOWN" || callMethod === "*") {
|
|
339
|
-
// No penalty for unknown
|
|
340
|
-
} else {
|
|
341
|
-
score -= 0.2; // Method mismatch penalty
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Confidence bonus
|
|
345
|
-
if (call.confidence === "high") {
|
|
346
|
-
score += 0.05;
|
|
347
|
-
} else if (call.confidence === "low") {
|
|
348
|
-
score -= 0.05;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return Math.max(0, Math.min(1, score));
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// =============================================================================
|
|
355
|
-
// BATCH MAPPING
|
|
356
|
-
// =============================================================================
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Map all runtime requests to client calls
|
|
360
|
-
*/
|
|
361
|
-
function mapAllRequests(requests, clientCalls, options = {}) {
|
|
362
|
-
const { basePath = "", rewrites = [], includeIgnored = false } = options;
|
|
363
|
-
|
|
364
|
-
const results = {
|
|
365
|
-
mapped: [],
|
|
366
|
-
unmapped: [],
|
|
367
|
-
ignored: [],
|
|
368
|
-
stats: {
|
|
369
|
-
total: requests.length,
|
|
370
|
-
mapped: 0,
|
|
371
|
-
unmapped: 0,
|
|
372
|
-
ignored: 0,
|
|
373
|
-
byMatchType: {},
|
|
374
|
-
},
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
for (const request of requests) {
|
|
378
|
-
// Check if should ignore
|
|
379
|
-
if (shouldIgnoreRequest(request.url, request.method)) {
|
|
380
|
-
if (includeIgnored) {
|
|
381
|
-
results.ignored.push(request);
|
|
382
|
-
}
|
|
383
|
-
results.stats.ignored++;
|
|
384
|
-
continue;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
const match = matchRequestToClientCalls(request, clientCalls, { basePath, rewrites });
|
|
388
|
-
|
|
389
|
-
if (match.matched) {
|
|
390
|
-
results.mapped.push(match);
|
|
391
|
-
results.stats.mapped++;
|
|
392
|
-
|
|
393
|
-
const matchType = match.matchType || "http";
|
|
394
|
-
results.stats.byMatchType[matchType] = (results.stats.byMatchType[matchType] || 0) + 1;
|
|
395
|
-
} else {
|
|
396
|
-
results.unmapped.push(match);
|
|
397
|
-
results.stats.unmapped++;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
return results;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/**
|
|
405
|
-
* Group mapped requests by client call
|
|
406
|
-
*/
|
|
407
|
-
function groupRequestsByClientCall(mappedResults) {
|
|
408
|
-
const groups = new Map();
|
|
409
|
-
|
|
410
|
-
for (const match of mappedResults) {
|
|
411
|
-
if (!match.bestMatch) continue;
|
|
412
|
-
|
|
413
|
-
const callId = match.bestMatch.id;
|
|
414
|
-
if (!groups.has(callId)) {
|
|
415
|
-
groups.set(callId, {
|
|
416
|
-
clientCall: match.bestMatch,
|
|
417
|
-
requests: [],
|
|
418
|
-
statuses: [],
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const group = groups.get(callId);
|
|
423
|
-
group.requests.push(match.request);
|
|
424
|
-
if (match.request.status) {
|
|
425
|
-
group.statuses.push(match.request.status);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
return groups;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// =============================================================================
|
|
433
|
-
// EXPORTS
|
|
434
|
-
// =============================================================================
|
|
435
|
-
|
|
436
|
-
module.exports = {
|
|
437
|
-
// Ignore patterns
|
|
438
|
-
IGNORE_PATTERNS,
|
|
439
|
-
shouldIgnoreRequest,
|
|
440
|
-
|
|
441
|
-
// URL normalization
|
|
442
|
-
normalizeRequestUrl,
|
|
443
|
-
extractTrpcOperation,
|
|
444
|
-
extractGraphqlOperation,
|
|
445
|
-
|
|
446
|
-
// Matching
|
|
447
|
-
matchRequestToClientCalls,
|
|
448
|
-
calculateMatchScore,
|
|
449
|
-
|
|
450
|
-
// Batch operations
|
|
451
|
-
mapAllRequests,
|
|
452
|
-
groupRequestsByClientCall,
|
|
453
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Request Mapper v2
|
|
3
|
+
*
|
|
4
|
+
* Maps runtime requests to client calls with specificity scoring.
|
|
5
|
+
* Handles URL normalization, basePath, proxies, tRPC/GraphQL operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const crypto = require("crypto");
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// IGNORE PATTERNS
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
const IGNORE_PATTERNS = {
|
|
17
|
+
// Asset extensions
|
|
18
|
+
assets: /\.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?|ttf|eot|map|webp|avif)(\?.*)?$/i,
|
|
19
|
+
|
|
20
|
+
// Dev/HMR patterns
|
|
21
|
+
hmr: /(_next\/static|__webpack_hmr|hot-update|\.hot-update\.|webpack|turbopack)/i,
|
|
22
|
+
|
|
23
|
+
// Analytics/tracking
|
|
24
|
+
analytics: /(analytics|gtag|ga\.js|gtm\.js|pixel|clarity|hotjar|segment|mixpanel|amplitude)/i,
|
|
25
|
+
|
|
26
|
+
// Third party
|
|
27
|
+
thirdParty: /^https?:\/\/(fonts\.googleapis|cdn\.|unpkg\.com|cdnjs\.|jsdelivr)/i,
|
|
28
|
+
|
|
29
|
+
// Browser internals
|
|
30
|
+
browser: /^(chrome-extension|moz-extension|safari-extension|about:|data:|blob:)/i,
|
|
31
|
+
|
|
32
|
+
// Favicon
|
|
33
|
+
favicon: /favicon\.ico/i,
|
|
34
|
+
|
|
35
|
+
// Source maps
|
|
36
|
+
sourceMaps: /\.map$/i,
|
|
37
|
+
|
|
38
|
+
// WebSocket
|
|
39
|
+
websocket: /^wss?:/i,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if request should be ignored
|
|
44
|
+
*/
|
|
45
|
+
function shouldIgnoreRequest(url, method = "GET") {
|
|
46
|
+
if (!url) return true;
|
|
47
|
+
|
|
48
|
+
// Always include mutations
|
|
49
|
+
if (["POST", "PUT", "PATCH", "DELETE"].includes(method?.toUpperCase())) {
|
|
50
|
+
// But still ignore analytics POST
|
|
51
|
+
if (IGNORE_PATTERNS.analytics.test(url)) return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (const pattern of Object.values(IGNORE_PATTERNS)) {
|
|
56
|
+
if (pattern.test(url)) return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// URL NORMALIZATION
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Normalize URL for matching
|
|
68
|
+
*/
|
|
69
|
+
function normalizeRequestUrl(url, options = {}) {
|
|
70
|
+
const { basePath = "", stripHost = true, stripQuery = true } = options;
|
|
71
|
+
|
|
72
|
+
if (!url) return null;
|
|
73
|
+
|
|
74
|
+
let normalized = url;
|
|
75
|
+
|
|
76
|
+
// Strip host if absolute URL
|
|
77
|
+
if (stripHost && /^https?:\/\//.test(normalized)) {
|
|
78
|
+
try {
|
|
79
|
+
const parsed = new URL(normalized);
|
|
80
|
+
normalized = parsed.pathname + (stripQuery ? "" : parsed.search);
|
|
81
|
+
} catch {
|
|
82
|
+
// Invalid URL, try to extract path
|
|
83
|
+
normalized = normalized.replace(/^https?:\/\/[^/]+/, "");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Strip query string
|
|
88
|
+
if (stripQuery) {
|
|
89
|
+
normalized = normalized.split("?")[0].split("#")[0];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Strip basePath prefix
|
|
93
|
+
if (basePath && normalized.startsWith(basePath)) {
|
|
94
|
+
normalized = normalized.slice(basePath.length) || "/";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Ensure leading slash
|
|
98
|
+
if (!normalized.startsWith("/")) {
|
|
99
|
+
normalized = "/" + normalized;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Remove trailing slash except for root
|
|
103
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
104
|
+
normalized = normalized.slice(0, -1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Normalize multiple slashes
|
|
108
|
+
normalized = normalized.replace(/\/+/g, "/");
|
|
109
|
+
|
|
110
|
+
return normalized;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Extract tRPC operation from URL
|
|
115
|
+
*/
|
|
116
|
+
function extractTrpcOperation(url) {
|
|
117
|
+
// /api/trpc/user.get?batch=1&input=...
|
|
118
|
+
const match = url.match(/\/trpc\/([^?]+)/);
|
|
119
|
+
if (!match) return null;
|
|
120
|
+
|
|
121
|
+
const procedure = match[1];
|
|
122
|
+
// Handle batch queries
|
|
123
|
+
const procedures = procedure.split(",").map(p => p.trim());
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
type: "trpc",
|
|
127
|
+
procedures,
|
|
128
|
+
isBatch: procedures.length > 1,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extract GraphQL operation from request
|
|
134
|
+
*/
|
|
135
|
+
function extractGraphqlOperation(url, body) {
|
|
136
|
+
// Check if it's a GraphQL endpoint
|
|
137
|
+
if (!url.includes("graphql")) return null;
|
|
138
|
+
|
|
139
|
+
if (!body) return { type: "graphql", operationName: null };
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const parsed = typeof body === "string" ? JSON.parse(body) : body;
|
|
143
|
+
return {
|
|
144
|
+
type: "graphql",
|
|
145
|
+
operationName: parsed.operationName || null,
|
|
146
|
+
operationType: parsed.query?.trim().startsWith("mutation") ? "mutation" : "query",
|
|
147
|
+
};
|
|
148
|
+
} catch {
|
|
149
|
+
return { type: "graphql", operationName: null };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// =============================================================================
|
|
154
|
+
// REQUEST → CLIENT CALL MATCHING
|
|
155
|
+
// =============================================================================
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Match a runtime request to client calls
|
|
159
|
+
*/
|
|
160
|
+
function matchRequestToClientCalls(request, clientCalls, options = {}) {
|
|
161
|
+
const { basePath = "", rewrites = [] } = options;
|
|
162
|
+
|
|
163
|
+
const normalizedPath = normalizeRequestUrl(request.url, { basePath });
|
|
164
|
+
if (!normalizedPath) return null;
|
|
165
|
+
|
|
166
|
+
const method = (request.method || "GET").toUpperCase();
|
|
167
|
+
|
|
168
|
+
// Check for tRPC
|
|
169
|
+
const trpcOp = extractTrpcOperation(request.url);
|
|
170
|
+
if (trpcOp) {
|
|
171
|
+
return matchTrpcRequest(trpcOp, clientCalls, request);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check for GraphQL
|
|
175
|
+
const gqlOp = extractGraphqlOperation(request.url, request.body);
|
|
176
|
+
if (gqlOp) {
|
|
177
|
+
return matchGraphqlRequest(gqlOp, clientCalls, request);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Standard HTTP matching
|
|
181
|
+
const candidates = [];
|
|
182
|
+
|
|
183
|
+
for (const call of clientCalls) {
|
|
184
|
+
if (call.kind !== "http") continue;
|
|
185
|
+
|
|
186
|
+
const score = calculateMatchScore(normalizedPath, method, call, { basePath, rewrites });
|
|
187
|
+
if (score > 0) {
|
|
188
|
+
candidates.push({ call, score });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Sort by score descending
|
|
193
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
194
|
+
|
|
195
|
+
if (candidates.length === 0) {
|
|
196
|
+
return {
|
|
197
|
+
matched: false,
|
|
198
|
+
request,
|
|
199
|
+
normalizedPath,
|
|
200
|
+
method,
|
|
201
|
+
candidates: [],
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
matched: true,
|
|
207
|
+
request,
|
|
208
|
+
normalizedPath,
|
|
209
|
+
method,
|
|
210
|
+
bestMatch: candidates[0].call,
|
|
211
|
+
score: candidates[0].score,
|
|
212
|
+
candidates: candidates.slice(0, 3),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Match tRPC request to client calls
|
|
218
|
+
*/
|
|
219
|
+
function matchTrpcRequest(trpcOp, clientCalls, request) {
|
|
220
|
+
const trpcCalls = clientCalls.filter(c => c.kind === "trpc");
|
|
221
|
+
|
|
222
|
+
for (const call of trpcCalls) {
|
|
223
|
+
const callProcedure = call.meta?.procedure;
|
|
224
|
+
if (!callProcedure) continue;
|
|
225
|
+
|
|
226
|
+
// Check if any procedure matches
|
|
227
|
+
for (const proc of trpcOp.procedures) {
|
|
228
|
+
if (proc === callProcedure || proc.endsWith(callProcedure)) {
|
|
229
|
+
return {
|
|
230
|
+
matched: true,
|
|
231
|
+
request,
|
|
232
|
+
matchType: "trpc",
|
|
233
|
+
procedure: proc,
|
|
234
|
+
bestMatch: call,
|
|
235
|
+
score: 1.0,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
matched: false,
|
|
243
|
+
request,
|
|
244
|
+
matchType: "trpc",
|
|
245
|
+
procedures: trpcOp.procedures,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Match GraphQL request to client calls
|
|
251
|
+
*/
|
|
252
|
+
function matchGraphqlRequest(gqlOp, clientCalls, request) {
|
|
253
|
+
const gqlCalls = clientCalls.filter(c => c.kind === "graphql");
|
|
254
|
+
|
|
255
|
+
if (gqlOp.operationName) {
|
|
256
|
+
for (const call of gqlCalls) {
|
|
257
|
+
if (call.meta?.operationName === gqlOp.operationName) {
|
|
258
|
+
return {
|
|
259
|
+
matched: true,
|
|
260
|
+
request,
|
|
261
|
+
matchType: "graphql",
|
|
262
|
+
operationName: gqlOp.operationName,
|
|
263
|
+
bestMatch: call,
|
|
264
|
+
score: 1.0,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Fallback: match any GraphQL call
|
|
271
|
+
if (gqlCalls.length > 0) {
|
|
272
|
+
return {
|
|
273
|
+
matched: true,
|
|
274
|
+
request,
|
|
275
|
+
matchType: "graphql",
|
|
276
|
+
operationName: gqlOp.operationName,
|
|
277
|
+
bestMatch: gqlCalls[0],
|
|
278
|
+
score: 0.5,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
matched: false,
|
|
284
|
+
request,
|
|
285
|
+
matchType: "graphql",
|
|
286
|
+
operationName: gqlOp.operationName,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Calculate match score between request and client call
|
|
292
|
+
*/
|
|
293
|
+
function calculateMatchScore(requestPath, requestMethod, call, options = {}) {
|
|
294
|
+
const { basePath = "", rewrites = [] } = options;
|
|
295
|
+
|
|
296
|
+
let callPath = call.canonicalPath;
|
|
297
|
+
if (!callPath) return 0;
|
|
298
|
+
|
|
299
|
+
// Apply rewrites
|
|
300
|
+
for (const rewrite of rewrites) {
|
|
301
|
+
if (rewrite.source && rewrite.destination) {
|
|
302
|
+
const sourceRegex = new RegExp("^" + rewrite.source.replace(/:(\w+)/g, "([^/]+)") + "$");
|
|
303
|
+
if (sourceRegex.test(requestPath)) {
|
|
304
|
+
// Rewrite matches, adjust call path expectation
|
|
305
|
+
callPath = requestPath.replace(sourceRegex, rewrite.destination);
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let score = 0;
|
|
312
|
+
|
|
313
|
+
// Exact path match
|
|
314
|
+
if (requestPath === callPath) {
|
|
315
|
+
score += 1.0;
|
|
316
|
+
}
|
|
317
|
+
// Parameterized match
|
|
318
|
+
else if (callPath.includes("{") || callPath.includes(":")) {
|
|
319
|
+
const pattern = "^" + callPath
|
|
320
|
+
.replace(/\{[^}]+\}/g, "[^/]+")
|
|
321
|
+
.replace(/:(\w+)/g, "[^/]+") + "$";
|
|
322
|
+
const regex = new RegExp(pattern);
|
|
323
|
+
if (regex.test(requestPath)) {
|
|
324
|
+
score += 0.9;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
// Prefix match (less confident)
|
|
328
|
+
else if (requestPath.startsWith(callPath) || callPath.startsWith(requestPath)) {
|
|
329
|
+
score += 0.5;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (score === 0) return 0;
|
|
333
|
+
|
|
334
|
+
// Method match bonus
|
|
335
|
+
const callMethod = (call.method || "UNKNOWN").toUpperCase();
|
|
336
|
+
if (callMethod === requestMethod) {
|
|
337
|
+
score += 0.1;
|
|
338
|
+
} else if (callMethod === "UNKNOWN" || callMethod === "*") {
|
|
339
|
+
// No penalty for unknown
|
|
340
|
+
} else {
|
|
341
|
+
score -= 0.2; // Method mismatch penalty
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Confidence bonus
|
|
345
|
+
if (call.confidence === "high") {
|
|
346
|
+
score += 0.05;
|
|
347
|
+
} else if (call.confidence === "low") {
|
|
348
|
+
score -= 0.05;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return Math.max(0, Math.min(1, score));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// =============================================================================
|
|
355
|
+
// BATCH MAPPING
|
|
356
|
+
// =============================================================================
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Map all runtime requests to client calls
|
|
360
|
+
*/
|
|
361
|
+
function mapAllRequests(requests, clientCalls, options = {}) {
|
|
362
|
+
const { basePath = "", rewrites = [], includeIgnored = false } = options;
|
|
363
|
+
|
|
364
|
+
const results = {
|
|
365
|
+
mapped: [],
|
|
366
|
+
unmapped: [],
|
|
367
|
+
ignored: [],
|
|
368
|
+
stats: {
|
|
369
|
+
total: requests.length,
|
|
370
|
+
mapped: 0,
|
|
371
|
+
unmapped: 0,
|
|
372
|
+
ignored: 0,
|
|
373
|
+
byMatchType: {},
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
for (const request of requests) {
|
|
378
|
+
// Check if should ignore
|
|
379
|
+
if (shouldIgnoreRequest(request.url, request.method)) {
|
|
380
|
+
if (includeIgnored) {
|
|
381
|
+
results.ignored.push(request);
|
|
382
|
+
}
|
|
383
|
+
results.stats.ignored++;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const match = matchRequestToClientCalls(request, clientCalls, { basePath, rewrites });
|
|
388
|
+
|
|
389
|
+
if (match.matched) {
|
|
390
|
+
results.mapped.push(match);
|
|
391
|
+
results.stats.mapped++;
|
|
392
|
+
|
|
393
|
+
const matchType = match.matchType || "http";
|
|
394
|
+
results.stats.byMatchType[matchType] = (results.stats.byMatchType[matchType] || 0) + 1;
|
|
395
|
+
} else {
|
|
396
|
+
results.unmapped.push(match);
|
|
397
|
+
results.stats.unmapped++;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return results;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Group mapped requests by client call
|
|
406
|
+
*/
|
|
407
|
+
function groupRequestsByClientCall(mappedResults) {
|
|
408
|
+
const groups = new Map();
|
|
409
|
+
|
|
410
|
+
for (const match of mappedResults) {
|
|
411
|
+
if (!match.bestMatch) continue;
|
|
412
|
+
|
|
413
|
+
const callId = match.bestMatch.id;
|
|
414
|
+
if (!groups.has(callId)) {
|
|
415
|
+
groups.set(callId, {
|
|
416
|
+
clientCall: match.bestMatch,
|
|
417
|
+
requests: [],
|
|
418
|
+
statuses: [],
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const group = groups.get(callId);
|
|
423
|
+
group.requests.push(match.request);
|
|
424
|
+
if (match.request.status) {
|
|
425
|
+
group.statuses.push(match.request.status);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return groups;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// =============================================================================
|
|
433
|
+
// EXPORTS
|
|
434
|
+
// =============================================================================
|
|
435
|
+
|
|
436
|
+
module.exports = {
|
|
437
|
+
// Ignore patterns
|
|
438
|
+
IGNORE_PATTERNS,
|
|
439
|
+
shouldIgnoreRequest,
|
|
440
|
+
|
|
441
|
+
// URL normalization
|
|
442
|
+
normalizeRequestUrl,
|
|
443
|
+
extractTrpcOperation,
|
|
444
|
+
extractGraphqlOperation,
|
|
445
|
+
|
|
446
|
+
// Matching
|
|
447
|
+
matchRequestToClientCalls,
|
|
448
|
+
calculateMatchScore,
|
|
449
|
+
|
|
450
|
+
// Batch operations
|
|
451
|
+
mapAllRequests,
|
|
452
|
+
groupRequestsByClientCall,
|
|
453
|
+
};
|