@shriyanss/js-recon 1.3.1-alpha.3 → 1.3.1-beta.1

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.
Files changed (138) hide show
  1. package/.github/workflows/build-and-prettify.yaml +15 -1
  2. package/.github/workflows/publish-js-recon.yml +13 -1
  3. package/CHANGELOG.md +84 -0
  4. package/CLAUDE.md +104 -11
  5. package/Dockerfile +16 -2
  6. package/README.md +4 -0
  7. package/build/analyze/engine/astEngine.js +57 -29
  8. package/build/analyze/engine/astEngine.js.map +1 -1
  9. package/build/analyze/engine/index.js.map +1 -1
  10. package/build/analyze/helpers/engineHelpers/taintFlow.js +32 -0
  11. package/build/analyze/helpers/engineHelpers/taintFlow.js.map +1 -1
  12. package/build/analyze/helpers/initRules.js +9 -0
  13. package/build/analyze/helpers/initRules.js.map +1 -1
  14. package/build/analyze/helpers/schemas.js +5 -1
  15. package/build/analyze/helpers/schemas.js.map +1 -1
  16. package/build/analyze/index.js +7 -2
  17. package/build/analyze/index.js.map +1 -1
  18. package/build/endpoints/next_js/client_mappedJsonFile.js +12 -5
  19. package/build/endpoints/next_js/client_mappedJsonFile.js.map +1 -1
  20. package/build/fingerprint/index.js +123 -0
  21. package/build/fingerprint/index.js.map +1 -0
  22. package/build/globalConfig.js +1 -1
  23. package/build/globalConfig.js.map +1 -1
  24. package/build/index.js +51 -4
  25. package/build/index.js.map +1 -1
  26. package/build/lazyLoad/downloadQueue.js +9 -8
  27. package/build/lazyLoad/downloadQueue.js.map +1 -1
  28. package/build/lazyLoad/index.js +260 -163
  29. package/build/lazyLoad/index.js.map +1 -1
  30. package/build/lazyLoad/next_js/NextJsCrawler.js +58 -16
  31. package/build/lazyLoad/next_js/NextJsCrawler.js.map +1 -1
  32. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js +149 -139
  33. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js.map +1 -1
  34. package/build/lazyLoad/next_js/next_SubsequentRequests.js +25 -4
  35. package/build/lazyLoad/next_js/next_SubsequentRequests.js.map +1 -1
  36. package/build/lazyLoad/next_js/next_scriptTagsSubsequentRequests.js +13 -5
  37. package/build/lazyLoad/next_js/next_scriptTagsSubsequentRequests.js.map +1 -1
  38. package/build/lazyLoad/react/react_followImports.js +105 -0
  39. package/build/lazyLoad/react/react_followImports.js.map +1 -0
  40. package/build/lazyLoad/react/react_getScriptTags.js +28 -5
  41. package/build/lazyLoad/react/react_getScriptTags.js.map +1 -1
  42. package/build/lazyLoad/svelte/svelte_discoverPagesFromJs.js +162 -0
  43. package/build/lazyLoad/svelte/svelte_discoverPagesFromJs.js.map +1 -0
  44. package/build/lazyLoad/svelte/svelte_getFromPageSource.js +15 -0
  45. package/build/lazyLoad/svelte/svelte_getFromPageSource.js.map +1 -1
  46. package/build/lazyLoad/svelte/svelte_recursivePageCrawl.js +180 -0
  47. package/build/lazyLoad/svelte/svelte_recursivePageCrawl.js.map +1 -0
  48. package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js +15 -1
  49. package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js.map +1 -1
  50. package/build/lazyLoad/techDetect/checkReact.js +67 -36
  51. package/build/lazyLoad/techDetect/checkReact.js.map +1 -1
  52. package/build/lazyLoad/techDetect/checkSvelte.js +35 -35
  53. package/build/lazyLoad/techDetect/checkSvelte.js.map +1 -1
  54. package/build/lazyLoad/techDetect/index.js +10 -6
  55. package/build/lazyLoad/techDetect/index.js.map +1 -1
  56. package/build/lazyLoad/vue/vue_getClientSidePaths.js +6 -0
  57. package/build/lazyLoad/vue/vue_getClientSidePaths.js.map +1 -1
  58. package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js +6 -0
  59. package/build/lazyLoad/vue/vue_recursiveClientSidePathDownload.js.map +1 -1
  60. package/build/lazyLoad/vue/vue_stringJsFiles.js +6 -0
  61. package/build/lazyLoad/vue/vue_stringJsFiles.js.map +1 -1
  62. package/build/map/graphql/resolveGraphql.js +296 -0
  63. package/build/map/graphql/resolveGraphql.js.map +1 -0
  64. package/build/map/index.js +93 -1
  65. package/build/map/index.js.map +1 -1
  66. package/build/map/next_js/interactive_helpers/commandHandler.js +4 -0
  67. package/build/map/next_js/interactive_helpers/commandHandler.js.map +1 -1
  68. package/build/map/next_js/interactive_helpers/commandHelpers.js +28 -0
  69. package/build/map/next_js/interactive_helpers/commandHelpers.js.map +1 -1
  70. package/build/map/next_js/interactive_helpers/helpMenu.js +1 -1
  71. package/build/map/next_js/interactive_helpers/helpMenu.js.map +1 -1
  72. package/build/map/next_js/resolveServerActions.js +449 -0
  73. package/build/map/next_js/resolveServerActions.js.map +1 -0
  74. package/build/map/next_js/utils.js +1 -0
  75. package/build/map/next_js/utils.js.map +1 -1
  76. package/build/map/react_js/getReactConnections.js +298 -0
  77. package/build/map/react_js/getReactConnections.js.map +1 -0
  78. package/build/map/react_js/interactive.js +4 -0
  79. package/build/map/react_js/interactive.js.map +1 -0
  80. package/build/map/react_js/react_resolveFetch.js +6 -0
  81. package/build/map/react_js/react_resolveFetch.js.map +1 -0
  82. package/build/map/svelte_js/interactive.js +58 -0
  83. package/build/map/svelte_js/interactive.js.map +1 -0
  84. package/build/map/svelte_js/interactive_helpers/commandHandler.js +4 -0
  85. package/build/map/svelte_js/interactive_helpers/commandHandler.js.map +1 -0
  86. package/build/map/vue_js/bodyResolver.js +477 -0
  87. package/build/map/vue_js/bodyResolver.js.map +1 -0
  88. package/build/map/vue_js/crossFileResolver.js +438 -0
  89. package/build/map/vue_js/crossFileResolver.js.map +1 -0
  90. package/build/map/vue_js/getViteConnections.js +150 -129
  91. package/build/map/vue_js/getViteConnections.js.map +1 -1
  92. package/build/map/vue_js/taint_utils.js +621 -0
  93. package/build/map/vue_js/taint_utils.js.map +1 -0
  94. package/build/map/vue_js/vue_resolveFetch.js +79 -392
  95. package/build/map/vue_js/vue_resolveFetch.js.map +1 -1
  96. package/build/map/vue_js/vue_resolveHttpClient.js +733 -0
  97. package/build/map/vue_js/vue_resolveHttpClient.js.map +1 -0
  98. package/build/map/vue_js/vue_resolveXhr.js +279 -0
  99. package/build/map/vue_js/vue_resolveXhr.js.map +1 -0
  100. package/build/mcp/chatOneShot.js +101 -0
  101. package/build/mcp/chatOneShot.js.map +1 -0
  102. package/build/mcp/claudeCodeCreds.js +150 -0
  103. package/build/mcp/claudeCodeCreds.js.map +1 -0
  104. package/build/mcp/cli.js +140 -149
  105. package/build/mcp/cli.js.map +1 -1
  106. package/build/mcp/commands.js +80 -0
  107. package/build/mcp/commands.js.map +1 -1
  108. package/build/mcp/index.js +30 -9
  109. package/build/mcp/index.js.map +1 -1
  110. package/build/mcp/intent.js +204 -0
  111. package/build/mcp/intent.js.map +1 -0
  112. package/build/mcp/jobs.js +199 -0
  113. package/build/mcp/jobs.js.map +1 -0
  114. package/build/mcp/mcpServer.js +241 -0
  115. package/build/mcp/mcpServer.js.map +1 -0
  116. package/build/mcp/providers.js +18 -1
  117. package/build/mcp/providers.js.map +1 -1
  118. package/build/mcp/skills.js +115 -0
  119. package/build/mcp/skills.js.map +1 -0
  120. package/build/refactor/index.js +6 -0
  121. package/build/refactor/index.js.map +1 -1
  122. package/build/refactor/react/index.js +636 -0
  123. package/build/refactor/react/index.js.map +1 -0
  124. package/build/run/index.js +260 -60
  125. package/build/run/index.js.map +1 -1
  126. package/build/run/interruptHandler.js +93 -0
  127. package/build/run/interruptHandler.js.map +1 -0
  128. package/build/utility/globals.js +29 -0
  129. package/build/utility/globals.js.map +1 -1
  130. package/build/utility/makeReq.js +14 -2
  131. package/build/utility/makeReq.js.map +1 -1
  132. package/build/utility/openapiGenerator.js +46 -14
  133. package/build/utility/openapiGenerator.js.map +1 -1
  134. package/build/utility/postmanGenerator.js +62 -13
  135. package/build/utility/postmanGenerator.js.map +1 -1
  136. package/build/utility/progressLog.js +50 -0
  137. package/build/utility/progressLog.js.map +1 -0
  138. package/package.json +12 -9
@@ -0,0 +1,733 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import chalk from "chalk";
11
+ import parser from "@babel/parser";
12
+ import _traverse from "@babel/traverse";
13
+ import fs from "fs";
14
+ import path from "path";
15
+ import { resolveNodeValue, substituteVariablesInString } from "../next_js/utils.js";
16
+ import * as globals from "../../utility/globals.js";
17
+ import { inferEnclosingFn, substituteCallerPlaceholders, substituteCallerHeaders, makeGetCallers, enclosingFnChainHasBinding, } from "./taint_utils.js";
18
+ import { deepResolveValue, hasMarkers, deepSubstituteBodyValue } from "./bodyResolver.js";
19
+ import { substituteCrossFileMarkers, substituteCrossFileMarkersDeep } from "./crossFileResolver.js";
20
+ const stripAstNodes = (fn) => {
21
+ if (!fn)
22
+ return null;
23
+ const root = Object.assign(Object.assign({}, fn), { node: null });
24
+ let cur = root;
25
+ let src = fn.parent;
26
+ while (src) {
27
+ cur.parent = Object.assign(Object.assign({}, src), { node: null });
28
+ cur = cur.parent;
29
+ src = src.parent;
30
+ }
31
+ return root;
32
+ };
33
+ const traverse = _traverse.default;
34
+ const HTTP_VERBS = new Set(["get", "post", "put", "delete", "patch", "head", "options"]);
35
+ /**
36
+ * Heuristic: only treat a resolved string as a URL if, after stripping every
37
+ * `[…]` placeholder produced by resolveNodeValue, the remaining literal text
38
+ * still contains a `/` separator or an explicit scheme. This rejects calls on
39
+ * Map/Set/Headers/EventBus (`x.get("key")`, `bus.post(event)`) while keeping
40
+ * partial URLs like `[call:base()]<literal>/[param:e]/[param:t]`.
41
+ */
42
+ const looksLikeUrl = (s) => {
43
+ if (!s)
44
+ return false;
45
+ if (s.startsWith("http://") || s.startsWith("https://"))
46
+ return true;
47
+ const stripped = s.replace(/\[[^\]]*\]/g, "");
48
+ return stripped.includes("/");
49
+ };
50
+ /**
51
+ * Recovers `[unresolved: NAME]` placeholders by chasing later assignments to
52
+ * NAME within the enclosing function. Pattern: `let ue; …; ue = e + "/" + t;`
53
+ * — babel's binding has no `init`, so resolveNodeValue returns the unresolved
54
+ * marker. The assignment shows up under `binding.constantViolations`.
55
+ *
56
+ * Iterates until no further substitutions are made (an assignment may itself
57
+ * resolve to a string containing more unresolved markers).
58
+ */
59
+ /**
60
+ * Enumerates every concrete URL obtainable by substituting `[param:X]`
61
+ * placeholders with the resolved values from each caller of the enclosing
62
+ * function chain. Returns an empty array if no `[param:X]` markers are
63
+ * present (caller falls back to the single-result substituter).
64
+ *
65
+ * Walks up to 5 levels deep to chain across nested wrappers
66
+ * (callback → wrapper → exported method → external caller).
67
+ */
68
+ const expandParamPlaceholders = (url, fn, getCallers, depth = 0, deadlineMs = Date.now() + 10000, capacity = { remaining: 150 }, maxDepth = globals.getMaxRecursionDepth()) => {
69
+ var _a, _b, _c;
70
+ if (depth > maxDepth || typeof url !== "string")
71
+ return [];
72
+ if (Date.now() > deadlineMs || capacity.remaining <= 0)
73
+ return [url];
74
+ const all = url.match(/\[param:([A-Za-z_$][\w$]*)\]/g);
75
+ if (!all)
76
+ return [url];
77
+ // Pick the first placeholder; find the function in the chain that
78
+ // declares it. Then substitute *every* placeholder owned by that same
79
+ // function from a single caller's args, so multi-param URLs like
80
+ // `…/[param:e]/[param:t]` stay consistent across one caller.
81
+ const firstName = all[0].slice("[param:".length, -1);
82
+ let owner = fn;
83
+ while (owner && (!owner.bindingName || !((_a = owner.paramNames) !== null && _a !== void 0 ? _a : []).includes(firstName))) {
84
+ owner = (_b = owner.parent) !== null && _b !== void 0 ? _b : null;
85
+ }
86
+ if (!owner || !owner.bindingName)
87
+ return [url];
88
+ const ownerParams = (_c = owner.paramNames) !== null && _c !== void 0 ? _c : [];
89
+ const ownedNames = new Set();
90
+ for (const m of all) {
91
+ const n = m.slice("[param:".length, -1);
92
+ if (ownerParams.includes(n))
93
+ ownedNames.add(n);
94
+ }
95
+ const allCallers = getCallers(owner.bindingName, owner.file);
96
+ if (!allCallers || allCallers.length === 0)
97
+ return [url];
98
+ // Cap callers per level to bound work; deeper levels still recurse but the
99
+ // deadline/capacity guards above stop the explosion.
100
+ const callers = allCallers.slice(0, 25);
101
+ const out = new Set();
102
+ for (const caller of callers) {
103
+ if (Date.now() > deadlineMs || capacity.remaining <= 0)
104
+ break;
105
+ let nextUrl = url;
106
+ let hasArgs = false;
107
+ for (const name of ownedNames) {
108
+ const idx = ownerParams.indexOf(name);
109
+ const arg = caller.args[idx];
110
+ if (!arg)
111
+ continue;
112
+ hasArgs = true;
113
+ let resolved;
114
+ try {
115
+ resolved = resolveNodeValue(arg, caller.scope, "", "fetch", caller.fileContent);
116
+ }
117
+ catch (_d) {
118
+ continue;
119
+ }
120
+ if (resolved === null || resolved === undefined)
121
+ continue;
122
+ const resolvedStr = typeof resolved === "string" ? resolved : String(resolved);
123
+ nextUrl = nextUrl.split(`[param:${name}]`).join(resolvedStr);
124
+ }
125
+ if (!hasArgs)
126
+ continue;
127
+ if (!/\[param:/.test(nextUrl)) {
128
+ out.add(nextUrl);
129
+ capacity.remaining--;
130
+ }
131
+ else {
132
+ const subs = expandParamPlaceholders(nextUrl, caller.enclosingFn, getCallers, depth + 1, deadlineMs, capacity, maxDepth);
133
+ for (const s of subs) {
134
+ if (capacity.remaining <= 0)
135
+ break;
136
+ out.add(s);
137
+ capacity.remaining--;
138
+ }
139
+ }
140
+ }
141
+ return out.size === 0 ? [url] : Array.from(out);
142
+ };
143
+ const UNRESOLVED_IDENT_RE = /\[unresolved: ([A-Za-z_$][\w$]*)\]/g;
144
+ const resolveFromAssignments = (s, scope, fileContent) => {
145
+ if (typeof s !== "string" || !s)
146
+ return s;
147
+ let prev = null;
148
+ let out = s;
149
+ let guard = 0;
150
+ while (prev !== out && guard < 6) {
151
+ prev = out;
152
+ guard++;
153
+ out = out.replace(UNRESOLVED_IDENT_RE, (orig, name) => {
154
+ var _a, _b, _c;
155
+ try {
156
+ const binding = (_a = scope === null || scope === void 0 ? void 0 : scope.getBinding) === null || _a === void 0 ? void 0 : _a.call(scope, name);
157
+ if (!binding)
158
+ return orig;
159
+ for (const vp of (_b = binding.constantViolations) !== null && _b !== void 0 ? _b : []) {
160
+ const n = vp.node;
161
+ if ((n === null || n === void 0 ? void 0 : n.type) !== "AssignmentExpression" || n.operator !== "=")
162
+ continue;
163
+ if (((_c = n.left) === null || _c === void 0 ? void 0 : _c.type) !== "Identifier" || n.left.name !== name)
164
+ continue;
165
+ const r = resolveNodeValue(n.right, vp.scope, "", "fetch", fileContent);
166
+ if (typeof r === "string" && r.length > 0 && !r.startsWith("[unresolved"))
167
+ return r;
168
+ }
169
+ }
170
+ catch (_d) { }
171
+ return orig;
172
+ });
173
+ }
174
+ return out;
175
+ };
176
+ /**
177
+ * Generic HTTP-client call resolver for Vite/webpack bundles where the actual
178
+ * `new XMLHttpRequest()` lives inside a transport library (axios's xhrAdapter,
179
+ * Got, Ky, custom wrappers). At that depth the URL resolves only to
180
+ * `[member:re.url]` from a dispatcher config object, which is useless on its
181
+ * own.
182
+ *
183
+ * Instead we walk every `<obj>.<verb>(<url>, ...)` callsite where <verb> is a
184
+ * known HTTP method name. The URL is resolved through the existing
185
+ * resolveNodeValue + substituteVariablesInString machinery, and the
186
+ * `looksLikeUrl` heuristic filters out unrelated method calls (Map.get,
187
+ * Headers.delete, EventBus.post, …).
188
+ *
189
+ * A second-pass taint analysis walks back to each call's enclosing function
190
+ * callers to substitute [param:X] / [member:P.X] / [urlsearchparams:P.X]
191
+ * placeholders. This is the same chain `vue_resolveXhr` already uses.
192
+ */
193
+ const vue_resolveHttpClient = (directory_1, ...args_1) => __awaiter(void 0, [directory_1, ...args_1], void 0, function* (directory, frameworkName = "Vue.JS") {
194
+ console.log(chalk.cyan(`[i] Resolving ${frameworkName} HTTP client method calls`));
195
+ let files;
196
+ try {
197
+ files = fs.readdirSync(directory, { recursive: true, encoding: "utf8" });
198
+ }
199
+ catch (_a) {
200
+ console.log(chalk.red(`[!] Could not read directory: ${directory}`));
201
+ return;
202
+ }
203
+ files = files
204
+ .filter((f) => f.endsWith(".js") && !f.includes("___subsequent_requests"))
205
+ .filter((f) => !fs.lstatSync(path.join(directory, f)).isDirectory());
206
+ const allFilePaths = files.map((f) => path.join(directory, f));
207
+ const getCallers = makeGetCallers(allFilePaths);
208
+ const entries = [];
209
+ const verbPrefilter = /\.(?:get|post|put|delete|patch|head|options)\s*\(/;
210
+ const scanStartTs = Date.now();
211
+ console.log(chalk.cyan(`[i] Scanning ${files.length} ${frameworkName} JS file(s) for HTTP-client callsites`));
212
+ let lastScanPct = -1;
213
+ for (let _i = 0; _i < files.length; _i++) {
214
+ if (_i > 0 && _i % 50 === 0)
215
+ yield new Promise((r) => setImmediate(r));
216
+ const scanPct = files.length === 0 ? 100 : Math.floor(((_i + 1) * 100) / files.length);
217
+ if (scanPct !== lastScanPct && (scanPct % 10 === 0 || scanPct === 100)) {
218
+ const elapsed = ((Date.now() - scanStartTs) / 1000).toFixed(1);
219
+ console.log(chalk.gray(` [scan] ${scanPct}% (${_i + 1}/${files.length}) entries=${entries.length} elapsed=${elapsed}s`));
220
+ lastScanPct = scanPct;
221
+ }
222
+ const file = files[_i];
223
+ const filePath = path.join(directory, file);
224
+ let fileContent;
225
+ try {
226
+ fileContent = fs.readFileSync(filePath, "utf-8");
227
+ }
228
+ catch (_b) {
229
+ continue;
230
+ }
231
+ if (!verbPrefilter.test(fileContent))
232
+ continue;
233
+ let fileAst;
234
+ try {
235
+ fileAst = parser.parse(fileContent, {
236
+ sourceType: "unambiguous",
237
+ plugins: ["jsx", "typescript"],
238
+ errorRecovery: true,
239
+ });
240
+ }
241
+ catch (_c) {
242
+ continue;
243
+ }
244
+ traverse(fileAst, {
245
+ CallExpression(p) {
246
+ var _a, _b;
247
+ const callee = p.node.callee;
248
+ if (callee.type !== "MemberExpression" || callee.computed)
249
+ return;
250
+ const prop = callee.property;
251
+ if (prop.type !== "Identifier")
252
+ return;
253
+ const verb = prop.name;
254
+ if (!HTTP_VERBS.has(verb))
255
+ return;
256
+ const args = p.node.arguments;
257
+ if (args.length === 0)
258
+ return;
259
+ const obj = callee.object;
260
+ if (obj.type === "ThisExpression")
261
+ return;
262
+ if (obj.type === "StringLiteral" || obj.type === "TemplateLiteral")
263
+ return;
264
+ const rawUrl = resolveNodeValue(args[0], p.scope, "", "fetch", fileContent);
265
+ let url = typeof rawUrl === "string" ? rawUrl : "";
266
+ if (url.includes("[var ") || url.includes("[MemberExpression")) {
267
+ url = substituteVariablesInString(url, fileContent);
268
+ }
269
+ url = resolveFromAssignments(url, p.scope, fileContent);
270
+ if (!looksLikeUrl(url))
271
+ return;
272
+ let body = "";
273
+ let bodyValue = undefined;
274
+ const headers = {};
275
+ const hasBody = verb === "post" || verb === "put" || verb === "patch";
276
+ const configArgIndex = hasBody ? 2 : 1;
277
+ if (hasBody && args.length >= 2) {
278
+ bodyValue = deepResolveValue(args[1], p.scope, fileContent);
279
+ body =
280
+ bodyValue === null || bodyValue === undefined
281
+ ? ""
282
+ : typeof bodyValue === "object"
283
+ ? JSON.stringify(bodyValue)
284
+ : String(bodyValue);
285
+ }
286
+ const configArg = args[configArgIndex];
287
+ if (configArg && configArg.type === "ObjectExpression") {
288
+ for (const cp of configArg.properties) {
289
+ if (cp.type !== "ObjectProperty")
290
+ continue;
291
+ const k = cp.key.type === "Identifier"
292
+ ? cp.key.name
293
+ : cp.key.type === "StringLiteral"
294
+ ? cp.key.value
295
+ : null;
296
+ if (k !== "headers")
297
+ continue;
298
+ if (cp.value.type !== "ObjectExpression")
299
+ continue;
300
+ for (const hp of cp.value.properties) {
301
+ if (hp.type !== "ObjectProperty")
302
+ continue;
303
+ const hk = hp.key.type === "Identifier"
304
+ ? hp.key.name
305
+ : hp.key.type === "StringLiteral"
306
+ ? hp.key.value
307
+ : null;
308
+ if (!hk)
309
+ continue;
310
+ const rawV = resolveNodeValue(hp.value, p.scope, "", "fetch", fileContent);
311
+ headers[hk] =
312
+ typeof rawV === "string"
313
+ ? substituteVariablesInString(rawV, fileContent)
314
+ : String(rawV !== null && rawV !== void 0 ? rawV : "");
315
+ }
316
+ }
317
+ }
318
+ const line = (_b = (_a = p.node.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 0;
319
+ const enclosingFn = stripAstNodes(inferEnclosingFn(p, filePath));
320
+ entries.push({
321
+ file,
322
+ filePath,
323
+ fileLine: line,
324
+ url,
325
+ method: verb.toUpperCase(),
326
+ headers,
327
+ body,
328
+ bodyValue,
329
+ enclosingFn,
330
+ });
331
+ },
332
+ });
333
+ }
334
+ // Taint-analysis second pass — same machinery as vue_resolveXhr.
335
+ const MARKER_RE = /\[(urlsearchparams|member|param):/;
336
+ const headersHaveMarkers = (h) => {
337
+ for (const [k, v] of Object.entries(h)) {
338
+ if (k.startsWith("...") && v === "<spread>")
339
+ return true;
340
+ if (MARKER_RE.test(v))
341
+ return true;
342
+ }
343
+ return false;
344
+ };
345
+ // Apply caller-side substitution to every leaf string inside a structured
346
+ // body. Then JSON.stringify the result so it round-trips through the
347
+ // string-typed `body` field. Plain-string bodies fall back to the existing
348
+ // single-pass substituter.
349
+ const substituteBody = (entry, enclosingFn) => {
350
+ if (entry.bodyValue !== undefined && entry.bodyValue !== null && typeof entry.bodyValue === "object") {
351
+ const next = deepSubstituteBodyValue(entry.bodyValue, enclosingFn, getCallers);
352
+ return { body: JSON.stringify(next), bodyValue: next };
353
+ }
354
+ return {
355
+ body: substituteCallerPlaceholders(entry.body, enclosingFn, getCallers),
356
+ bodyValue: entry.bodyValue,
357
+ };
358
+ };
359
+ const collectParamNames = (s, set) => {
360
+ if (typeof s !== "string")
361
+ return;
362
+ const m = s.match(/\[param:([A-Za-z_$][\w$]*)\]/g);
363
+ if (!m)
364
+ return;
365
+ for (const x of m)
366
+ set.add(x.slice("[param:".length, -1));
367
+ };
368
+ const walkValueForParams = (v, set) => {
369
+ if (v === null || v === undefined)
370
+ return;
371
+ if (typeof v === "string")
372
+ collectParamNames(v, set);
373
+ else if (Array.isArray(v))
374
+ for (const x of v)
375
+ walkValueForParams(x, set);
376
+ else if (typeof v === "object")
377
+ for (const x of Object.values(v))
378
+ walkValueForParams(x, set);
379
+ };
380
+ const substituteParamsDeep = (value, subStr, subVal, owned) => {
381
+ if (value === null || value === undefined)
382
+ return value;
383
+ if (typeof value === "string") {
384
+ const exact = value.match(/^\[param:([A-Za-z_$][\w$]*)\]$/);
385
+ if (exact && owned.has(exact[1])) {
386
+ const v = subVal[exact[1]];
387
+ if (v !== undefined)
388
+ return v;
389
+ }
390
+ let out = value;
391
+ for (const [name, s] of Object.entries(subStr)) {
392
+ if (s === null || s === undefined)
393
+ continue;
394
+ out = out.split(`[param:${name}]`).join(String(s));
395
+ }
396
+ return out;
397
+ }
398
+ if (Array.isArray(value))
399
+ return value.map((v) => substituteParamsDeep(v, subStr, subVal, owned));
400
+ if (typeof value === "object") {
401
+ const o = {};
402
+ for (const [k, v] of Object.entries(value)) {
403
+ o[k] = substituteParamsDeep(v, subStr, subVal, owned);
404
+ }
405
+ return o;
406
+ }
407
+ return value;
408
+ };
409
+ // Hard bounds to prevent exponential blowup.
410
+ // depth=2 → up to (callers/level)² forwarding-wrapper chains, enabling
411
+ // resolution through registry-export wrappers like `ae.request → Me`.
412
+ const FANOUT_MAX_DEPTH = 2;
413
+ const FANOUT_MAX_CALLERS_PER_LEVEL = 12;
414
+ const FANOUT_MAX_EXPANSIONS_PER_ENTRY = 80;
415
+ const FANOUT_BUDGET_MS_PER_ENTRY = 1200;
416
+ // Lightweight arg resolver — avoids the deep recursive scope-walking that
417
+ // makes `resolveNodeValue`/`deepResolveValue` expensive (5+s per entry).
418
+ // Only handles the cheap patterns: literals, identifiers bound to literals,
419
+ // simple member chains where the base is a local ObjectExpression literal.
420
+ // Returns null for anything more complex; the outer loop falls back to
421
+ // leaving the [param:X] placeholder unresolved for that caller.
422
+ const lightResolveArg = (node, scope, depth = 0) => {
423
+ var _a, _b, _c, _d, _e, _f, _g, _h;
424
+ if (!node || depth > 3)
425
+ return { asString: null, asValue: null };
426
+ if (node.type === "StringLiteral")
427
+ return { asString: node.value, asValue: node.value };
428
+ if (node.type === "NumericLiteral")
429
+ return { asString: String(node.value), asValue: node.value };
430
+ if (node.type === "BooleanLiteral")
431
+ return { asString: String(node.value), asValue: node.value };
432
+ if (node.type === "NullLiteral")
433
+ return { asString: "null", asValue: null };
434
+ if (node.type === "TemplateLiteral" && node.expressions.length === 0 && node.quasis.length === 1) {
435
+ const v = (_b = (_a = node.quasis[0].value.cooked) !== null && _a !== void 0 ? _a : node.quasis[0].value.raw) !== null && _b !== void 0 ? _b : "";
436
+ return { asString: v, asValue: v };
437
+ }
438
+ if (node.type === "ObjectExpression") {
439
+ // Build a shallow plain-object representation for structured body subst.
440
+ const o = {};
441
+ for (const p of (_c = node.properties) !== null && _c !== void 0 ? _c : []) {
442
+ if (p.type !== "ObjectProperty")
443
+ continue;
444
+ const k = p.key.type === "Identifier" ? p.key.name : p.key.type === "StringLiteral" ? p.key.value : null;
445
+ if (!k)
446
+ continue;
447
+ o[k] = lightResolveArg(p.value, scope, depth + 1).asValue;
448
+ }
449
+ return { asString: null, asValue: o };
450
+ }
451
+ if (node.type === "ArrayExpression") {
452
+ const arr = [];
453
+ for (const e of (_d = node.elements) !== null && _d !== void 0 ? _d : []) {
454
+ arr.push(e ? lightResolveArg(e, scope, depth + 1).asValue : null);
455
+ }
456
+ return { asString: null, asValue: arr };
457
+ }
458
+ if (node.type === "Identifier") {
459
+ try {
460
+ const b = (_e = scope === null || scope === void 0 ? void 0 : scope.getBinding) === null || _e === void 0 ? void 0 : _e.call(scope, node.name);
461
+ const init = (_g = (_f = b === null || b === void 0 ? void 0 : b.path) === null || _f === void 0 ? void 0 : _f.node) === null || _g === void 0 ? void 0 : _g.init;
462
+ if (init) {
463
+ return lightResolveArg(init, (_h = b.scope) !== null && _h !== void 0 ? _h : scope, depth + 1);
464
+ }
465
+ }
466
+ catch (_j) { }
467
+ return { asString: null, asValue: null };
468
+ }
469
+ if (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier") {
470
+ const base = lightResolveArg(node.object, scope, depth + 1);
471
+ if (base.asValue && typeof base.asValue === "object" && node.property.name in base.asValue) {
472
+ const v = base.asValue[node.property.name];
473
+ return { asString: typeof v === "string" ? v : v != null ? String(v) : null, asValue: v };
474
+ }
475
+ }
476
+ return { asString: null, asValue: null };
477
+ };
478
+ const expandEntryAcrossCallers = (url, bodyValue, headers, fn, deadlineMs, capacity, depth = 0) => {
479
+ var _a, _b, _c;
480
+ if (depth > FANOUT_MAX_DEPTH || !fn)
481
+ return [{ url, bodyValue, headers }];
482
+ if (Date.now() > deadlineMs || capacity.remaining <= 0)
483
+ return [{ url, bodyValue, headers }];
484
+ const allNames = new Set();
485
+ collectParamNames(url, allNames);
486
+ walkValueForParams(bodyValue, allNames);
487
+ for (const v of Object.values(headers))
488
+ collectParamNames(v, allNames);
489
+ for (const k of Object.keys(headers))
490
+ collectParamNames(k, allNames);
491
+ if (allNames.size === 0)
492
+ return [{ url, bodyValue, headers }];
493
+ let owner = fn;
494
+ while (owner) {
495
+ const op = (_a = owner.paramNames) !== null && _a !== void 0 ? _a : [];
496
+ if (owner.bindingName && [...allNames].some((n) => op.includes(n)))
497
+ break;
498
+ owner = (_b = owner.parent) !== null && _b !== void 0 ? _b : null;
499
+ }
500
+ if (!owner || !owner.bindingName)
501
+ return [{ url, bodyValue, headers }];
502
+ const ownerParams = (_c = owner.paramNames) !== null && _c !== void 0 ? _c : [];
503
+ const owned = new Set([...allNames].filter((n) => ownerParams.includes(n)));
504
+ const allCallers = getCallers(owner.bindingName, owner.file);
505
+ if (!allCallers || allCallers.length === 0)
506
+ return [{ url, bodyValue, headers }];
507
+ const callers = allCallers.slice(0, FANOUT_MAX_CALLERS_PER_LEVEL);
508
+ // Split owned params by where they appear: only call deepResolveValue
509
+ // (which can walk deeply) when an owned param appears as a bare
510
+ // `[param:X]` LEAF in the structured body. URL/header/string-body
511
+ // substitution only needs resolveNodeValue (string).
512
+ const bodyDeepOwned = new Set();
513
+ const walkBodyForLeaves = (v) => {
514
+ if (v === null || v === undefined)
515
+ return;
516
+ if (typeof v === "string") {
517
+ const m = v.match(/^\[param:([A-Za-z_$][\w$]*)\]$/);
518
+ if (m && owned.has(m[1]))
519
+ bodyDeepOwned.add(m[1]);
520
+ }
521
+ else if (Array.isArray(v))
522
+ for (const x of v)
523
+ walkBodyForLeaves(x);
524
+ else if (typeof v === "object")
525
+ for (const x of Object.values(v))
526
+ walkBodyForLeaves(x);
527
+ };
528
+ walkBodyForLeaves(bodyValue);
529
+ const out = [];
530
+ for (const caller of callers) {
531
+ if (Date.now() > deadlineMs || capacity.remaining <= 0)
532
+ break;
533
+ const subStr = {};
534
+ const subVal = {};
535
+ let hasAny = false;
536
+ for (const name of owned) {
537
+ const idx = ownerParams.indexOf(name);
538
+ const arg = caller.args[idx];
539
+ if (!arg)
540
+ continue;
541
+ hasAny = true;
542
+ // String resolution: use full resolveNodeValue (handles template
543
+ // literals, concat, identifier chains). It's fast — only
544
+ // deepResolveValue is the perf hazard.
545
+ try {
546
+ const s = resolveNodeValue(arg, caller.scope, "", "fetch", caller.fileContent);
547
+ subStr[name] = typeof s === "string" ? s : null;
548
+ }
549
+ catch (_d) {
550
+ subStr[name] = null;
551
+ }
552
+ // Structured body value: only call deepResolveValue if THIS
553
+ // param appears as a bare `[param:X]` leaf in the body — and
554
+ // only within the deadline. Otherwise fall back to the string.
555
+ if (bodyDeepOwned.has(name) && Date.now() < deadlineMs) {
556
+ try {
557
+ subVal[name] = deepResolveValue(arg, caller.scope, caller.fileContent);
558
+ }
559
+ catch (_e) {
560
+ subVal[name] = subStr[name];
561
+ }
562
+ }
563
+ else {
564
+ subVal[name] = subStr[name];
565
+ }
566
+ }
567
+ if (!hasAny)
568
+ continue;
569
+ const urlSub = substituteParamsDeep(url, subStr, subVal, owned);
570
+ const bodyValSub = substituteParamsDeep(bodyValue, subStr, subVal, owned);
571
+ const headersSub = {};
572
+ for (const [k, v] of Object.entries(headers)) {
573
+ const nk = substituteParamsDeep(k, subStr, subVal, owned);
574
+ const nv = substituteParamsDeep(v, subStr, subVal, owned);
575
+ headersSub[typeof nk === "string" ? nk : k] = typeof nv === "string" ? nv : String(nv !== null && nv !== void 0 ? nv : "");
576
+ }
577
+ const stillHasParams = (typeof urlSub === "string" && /\[param:/.test(urlSub)) ||
578
+ hasMarkers(bodyValSub) ||
579
+ Object.values(headersSub).some((v) => typeof v === "string" && /\[param:/.test(v));
580
+ if (stillHasParams && caller.enclosingFn && depth + 1 <= FANOUT_MAX_DEPTH) {
581
+ const recursed = expandEntryAcrossCallers(typeof urlSub === "string" ? urlSub : url, bodyValSub, headersSub, caller.enclosingFn, deadlineMs, capacity, depth + 1);
582
+ for (const r of recursed) {
583
+ if (capacity.remaining <= 0)
584
+ break;
585
+ out.push(r);
586
+ capacity.remaining--;
587
+ }
588
+ }
589
+ else {
590
+ out.push({
591
+ url: typeof urlSub === "string" ? urlSub : url,
592
+ bodyValue: bodyValSub,
593
+ headers: headersSub,
594
+ });
595
+ capacity.remaining--;
596
+ }
597
+ }
598
+ return out.length === 0 ? [{ url, bodyValue, headers }] : out;
599
+ };
600
+ const expandedEntries = [];
601
+ const totalEntries = entries.length;
602
+ let processedCount = 0;
603
+ let lastProgressPct = -1;
604
+ const progressStartTs = Date.now();
605
+ console.log(chalk.cyan(`[i] Expanding ${totalEntries} ${frameworkName} HTTP-client callsite(s) across callers`));
606
+ for (const entry of entries) {
607
+ processedCount++;
608
+ const pct = totalEntries === 0 ? 100 : Math.floor((processedCount * 100) / totalEntries);
609
+ if (pct !== lastProgressPct && (pct % 10 === 0 || pct === 100)) {
610
+ const elapsed = ((Date.now() - progressStartTs) / 1000).toFixed(1);
611
+ console.log(chalk.gray(` [progress] ${pct}% (${processedCount}/${totalEntries}) elapsed=${elapsed}s`));
612
+ lastProgressPct = pct;
613
+ }
614
+ if (enclosingFnChainHasBinding(entry.enclosingFn) &&
615
+ (MARKER_RE.test(entry.url) ||
616
+ MARKER_RE.test(entry.body) ||
617
+ hasMarkers(entry.bodyValue) ||
618
+ headersHaveMarkers(entry.headers))) {
619
+ // URL fan-out: use the original `expandParamPlaceholders` (string
620
+ // resolveNodeValue per caller). This is the version that produced
621
+ // varied URLs like `<service>/<module>/<method>`.
622
+ //
623
+ // Scale per-entry budget and capacity with maxDepth so deeper
624
+ // recursion actually has room to produce more URLs. At depth=3
625
+ // (default) we get ~10s/150caps. At depth=10 we get ~80s/1200caps
626
+ // per entry.
627
+ const maxDepth = globals.getMaxRecursionDepth();
628
+ const depthFactor = maxDepth + 1;
629
+ // Linear deadline scaling — empirically gives ~5min total runtime
630
+ // at depth 8 once getCallers AST caching is in place.
631
+ const perEntryDeadlineMs = Date.now() + Math.max(2000, (8000 * depthFactor) / 4);
632
+ const perEntryCapacity = { remaining: Math.max(30, 50 * depthFactor) };
633
+ const entryStartTs = Date.now();
634
+ const urls = expandParamPlaceholders(entry.url, entry.enclosingFn, getCallers, 0, perEntryDeadlineMs, perEntryCapacity, maxDepth);
635
+ // Body+headers substitution: resolves [param:X] body leaves to the
636
+ // first matching caller's structured value (credentials object,
637
+ // etc.) via resolveParamToAnyValue.
638
+ const subBody = substituteBody(entry, entry.enclosingFn);
639
+ const headersSub = substituteCallerHeaders(entry.headers, entry.enclosingFn, getCallers);
640
+ const entryElapsed = Date.now() - entryStartTs;
641
+ if (entryElapsed > 200) {
642
+ console.log(chalk.gray(` [expand] entry ${processedCount}/${totalEntries} took ${entryElapsed}ms (${urls.length} urls)`));
643
+ }
644
+ const expansions = urls.length === 0
645
+ ? [
646
+ {
647
+ url: substituteCallerPlaceholders(entry.url, entry.enclosingFn, getCallers),
648
+ bodyValue: subBody.bodyValue,
649
+ headers: headersSub,
650
+ },
651
+ ]
652
+ : urls.map((u) => ({ url: u, bodyValue: subBody.bodyValue, headers: headersSub }));
653
+ for (const exp of expansions) {
654
+ // Cross-file pass (later) handles [member:X.Y] / [call:X.Y()].
655
+ const urlFinal = exp.url;
656
+ const headersFinal = exp.headers;
657
+ const bodyValueFinal = exp.bodyValue;
658
+ const bodyStr = bodyValueFinal === null || bodyValueFinal === undefined
659
+ ? ""
660
+ : typeof bodyValueFinal === "object"
661
+ ? JSON.stringify(bodyValueFinal)
662
+ : String(bodyValueFinal);
663
+ expandedEntries.push(Object.assign(Object.assign({}, entry), { url: urlFinal, body: bodyStr, bodyValue: bodyValueFinal, headers: headersFinal }));
664
+ }
665
+ }
666
+ else {
667
+ expandedEntries.push(entry);
668
+ }
669
+ }
670
+ entries.length = 0;
671
+ entries.push(...expandedEntries);
672
+ // Final pass: cross-file resolution for [member:X.Y] and [call:X.Y.Z()]
673
+ // markers that survived caller-chain substitution. These come from imports
674
+ // of other Vite chunks, which the in-file resolver can't follow.
675
+ console.log(chalk.cyan(`[i] Cross-file resolution pass for ${entries.length} entries`));
676
+ const crossStartTs = Date.now();
677
+ let lastCrossPct = -1;
678
+ for (let i = 0; i < entries.length; i++) {
679
+ const entry = entries[i];
680
+ const pct = entries.length === 0 ? 100 : Math.floor(((i + 1) * 100) / entries.length);
681
+ if (pct !== lastCrossPct && (pct % 20 === 0 || pct === 100)) {
682
+ const elapsed = ((Date.now() - crossStartTs) / 1000).toFixed(1);
683
+ console.log(chalk.gray(` [cross-file] ${pct}% (${i + 1}/${entries.length}) elapsed=${elapsed}s`));
684
+ lastCrossPct = pct;
685
+ }
686
+ try {
687
+ entry.url = substituteCrossFileMarkers(entry.url, entry.filePath, directory);
688
+ const headersResolved = {};
689
+ for (const [hk, hv] of Object.entries(entry.headers)) {
690
+ const nk = substituteCrossFileMarkers(hk, entry.filePath, directory);
691
+ const nv = substituteCrossFileMarkers(hv, entry.filePath, directory);
692
+ headersResolved[nk] = nv;
693
+ }
694
+ entry.headers = headersResolved;
695
+ if (entry.bodyValue !== undefined && entry.bodyValue !== null && typeof entry.bodyValue === "object") {
696
+ entry.bodyValue = substituteCrossFileMarkersDeep(entry.bodyValue, entry.filePath, directory);
697
+ entry.body = JSON.stringify(entry.bodyValue);
698
+ }
699
+ else if (typeof entry.body === "string" && entry.body) {
700
+ entry.body = substituteCrossFileMarkers(entry.body, entry.filePath, directory);
701
+ }
702
+ }
703
+ catch (_d) {
704
+ // Cross-file resolution is best-effort; never block emission on failure.
705
+ }
706
+ }
707
+ let emitted = 0;
708
+ for (const entry of entries) {
709
+ if (!looksLikeUrl(entry.url))
710
+ continue;
711
+ console.log(chalk.blue(`[+] Found ${entry.method} client call in "${entry.filePath}":${entry.fileLine}`));
712
+ console.log(chalk.green(` URL: ${entry.url}`));
713
+ if (Object.keys(entry.headers).length > 0) {
714
+ console.log(chalk.green(` Headers: ${JSON.stringify(entry.headers)}`));
715
+ }
716
+ if (entry.body)
717
+ console.log(chalk.green(` Body: ${entry.body}`));
718
+ globals.addOpenapiOutput({
719
+ url: entry.url,
720
+ method: entry.method,
721
+ path: entry.url,
722
+ headers: entry.headers,
723
+ body: entry.body,
724
+ chunkId: `${entry.file}:${entry.fileLine}`,
725
+ functionFile: entry.filePath,
726
+ functionFileLine: entry.fileLine,
727
+ });
728
+ emitted++;
729
+ }
730
+ console.log(chalk.green(`[✓] Emitted ${emitted} HTTP client call(s) across ${frameworkName} files`));
731
+ });
732
+ export default vue_resolveHttpClient;
733
+ //# sourceMappingURL=vue_resolveHttpClient.js.map