@shriyanss/js-recon 1.4.1-alpha.4 → 1.4.1-alpha.5

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 (189) hide show
  1. package/.github/workflows/build-and-prettify.yaml +187 -1
  2. package/.github/workflows/publish-js-recon.yml +0 -1
  3. package/.github/workflows/rules-smoke-test.yaml +79 -0
  4. package/CHANGELOG.md +40 -0
  5. package/CLAUDE.md +184 -8
  6. package/README.md +18 -17
  7. package/build/analyze/engine/csMastSEngine.js +129 -0
  8. package/build/analyze/engine/csMastSEngine.js.map +1 -0
  9. package/build/analyze/engine/index.js +15 -0
  10. package/build/analyze/engine/index.js.map +1 -1
  11. package/build/analyze/helpers/engineHelpers/findDirectAssignment.js +2 -1
  12. package/build/analyze/helpers/engineHelpers/findDirectAssignment.js.map +1 -1
  13. package/build/analyze/helpers/engineHelpers/findMemberExpressionAssignment.js +2 -1
  14. package/build/analyze/helpers/engineHelpers/findMemberExpressionAssignment.js.map +1 -1
  15. package/build/analyze/helpers/engineHelpers/resolveFunctionIdentifier.js +2 -1
  16. package/build/analyze/helpers/engineHelpers/resolveFunctionIdentifier.js.map +1 -1
  17. package/build/analyze/helpers/engineHelpers/taintFlow.js +11 -2
  18. package/build/analyze/helpers/engineHelpers/taintFlow.js.map +1 -1
  19. package/build/analyze/helpers/schemas.js +5 -1
  20. package/build/analyze/helpers/schemas.js.map +1 -1
  21. package/build/analyze/helpers/validate.js +3 -3
  22. package/build/analyze/helpers/validate.js.map +1 -1
  23. package/build/completion/index.js +432 -0
  24. package/build/completion/index.js.map +1 -0
  25. package/build/cs_mast/index.js +94 -3
  26. package/build/cs_mast/index.js.map +1 -1
  27. package/build/endpoints/next_js/client_jsFilesHref.js +2 -1
  28. package/build/endpoints/next_js/client_jsFilesHref.js.map +1 -1
  29. package/build/endpoints/next_js/client_jsonParse.js +2 -1
  30. package/build/endpoints/next_js/client_jsonParse.js.map +1 -1
  31. package/build/endpoints/next_js/client_mappedJsonFile.js +2 -1
  32. package/build/endpoints/next_js/client_mappedJsonFile.js.map +1 -1
  33. package/build/endpoints/next_js/client_subsequentRequests.js +2 -1
  34. package/build/endpoints/next_js/client_subsequentRequests.js.map +1 -1
  35. package/build/fingerprint/index.js +56 -33
  36. package/build/fingerprint/index.js.map +1 -1
  37. package/build/globalConfig.js +1 -1
  38. package/build/index.js +67 -6
  39. package/build/index.js.map +1 -1
  40. package/build/lazyLoad/angular/angular_getFromMainJs.js +2 -1
  41. package/build/lazyLoad/angular/angular_getFromMainJs.js.map +1 -1
  42. package/build/lazyLoad/downloadFilesUtil.js +6 -5
  43. package/build/lazyLoad/downloadFilesUtil.js.map +1 -1
  44. package/build/lazyLoad/downloadLoadedJsUtil.js +56 -7
  45. package/build/lazyLoad/downloadLoadedJsUtil.js.map +1 -1
  46. package/build/lazyLoad/downloadQueue.js +9 -8
  47. package/build/lazyLoad/downloadQueue.js.map +1 -1
  48. package/build/lazyLoad/index.js +28 -1
  49. package/build/lazyLoad/index.js.map +1 -1
  50. package/build/lazyLoad/next_js/next_GetLazyResourcesBuildManifestJs.js +36 -34
  51. package/build/lazyLoad/next_js/next_GetLazyResourcesBuildManifestJs.js.map +1 -1
  52. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js +63 -15
  53. package/build/lazyLoad/next_js/next_GetLazyResourcesWebpackJs.js.map +1 -1
  54. package/build/lazyLoad/next_js/next_parseLayoutJs.js +80 -68
  55. package/build/lazyLoad/next_js/next_parseLayoutJs.js.map +1 -1
  56. package/build/lazyLoad/next_js/next_promiseResolve.js +22 -16
  57. package/build/lazyLoad/next_js/next_promiseResolve.js.map +1 -1
  58. package/build/lazyLoad/nuxt_js/nuxt_astParse.js +49 -47
  59. package/build/lazyLoad/nuxt_js/nuxt_astParse.js.map +1 -1
  60. package/build/lazyLoad/nuxt_js/nuxt_getFromPageSource.js +3 -1
  61. package/build/lazyLoad/nuxt_js/nuxt_getFromPageSource.js.map +1 -1
  62. package/build/lazyLoad/nuxt_js/nuxt_stringAnalysisJSFiles.js +3 -2
  63. package/build/lazyLoad/nuxt_js/nuxt_stringAnalysisJSFiles.js.map +1 -1
  64. package/build/lazyLoad/react/react_followImports.js +12 -9
  65. package/build/lazyLoad/react/react_followImports.js.map +1 -1
  66. package/build/lazyLoad/react/react_webpackChunkPaths.js +175 -123
  67. package/build/lazyLoad/react/react_webpackChunkPaths.js.map +1 -1
  68. package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js +3 -2
  69. package/build/lazyLoad/svelte/svelte_stringAnalysisJSFiles.js.map +1 -1
  70. package/build/lazyLoad/techDetect/checkAngularJS.js +27 -6
  71. package/build/lazyLoad/techDetect/checkAngularJS.js.map +1 -1
  72. package/build/lazyLoad/techDetect/checkReact.js +8 -0
  73. package/build/lazyLoad/techDetect/checkReact.js.map +1 -1
  74. package/build/lazyLoad/techDetect/index.js +23 -2
  75. package/build/lazyLoad/techDetect/index.js.map +1 -1
  76. package/build/lazyLoad/vue/vue_RuntimeJs.js +2 -1
  77. package/build/lazyLoad/vue/vue_RuntimeJs.js.map +1 -1
  78. package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js +2 -1
  79. package/build/lazyLoad/vue/vue_SingleJsFileOnHome.js.map +1 -1
  80. package/build/lazyLoad/vue/vue_discoverJsFiles.js +22 -10
  81. package/build/lazyLoad/vue/vue_discoverJsFiles.js.map +1 -1
  82. package/build/lazyLoad/vue/vue_getClientSidePaths.js +2 -1
  83. package/build/lazyLoad/vue/vue_getClientSidePaths.js.map +1 -1
  84. package/build/lazyLoad/vue/vue_jsImports.js +2 -1
  85. package/build/lazyLoad/vue/vue_jsImports.js.map +1 -1
  86. package/build/lazyLoad/vue/vue_viteMapDeps.js +62 -54
  87. package/build/lazyLoad/vue/vue_viteMapDeps.js.map +1 -1
  88. package/build/map/angular_js/getAngularConnections.js +1 -1
  89. package/build/map/angular_js/getAngularConnections.js.map +1 -1
  90. package/build/map/graphql/resolveGraphql.js +2 -1
  91. package/build/map/graphql/resolveGraphql.js.map +1 -1
  92. package/build/map/next_js/getExports.js +2 -1
  93. package/build/map/next_js/getExports.js.map +1 -1
  94. package/build/map/next_js/getFetchInstances.js +2 -1
  95. package/build/map/next_js/getFetchInstances.js.map +1 -1
  96. package/build/map/next_js/getTurbopackConnections.js +2 -1
  97. package/build/map/next_js/getTurbopackConnections.js.map +1 -1
  98. package/build/map/next_js/getWebpackConnections.js +2 -1
  99. package/build/map/next_js/getWebpackConnections.js.map +1 -1
  100. package/build/map/next_js/resolveAxios.js +2 -1
  101. package/build/map/next_js/resolveAxios.js.map +1 -1
  102. package/build/map/next_js/resolveAxiosHelpers/directCallsWithoutAssignment.js +2 -1
  103. package/build/map/next_js/resolveAxiosHelpers/directCallsWithoutAssignment.js.map +1 -1
  104. package/build/map/next_js/resolveAxiosHelpers/findAxiosInstance.js +2 -1
  105. package/build/map/next_js/resolveAxiosHelpers/findAxiosInstance.js.map +1 -1
  106. package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js +2 -1
  107. package/build/map/next_js/resolveAxiosHelpers/findCrossChunkParams.js.map +1 -1
  108. package/build/map/next_js/resolveAxiosHelpers/handleAxiosCreate.js +2 -1
  109. package/build/map/next_js/resolveAxiosHelpers/handleAxiosCreate.js.map +1 -1
  110. package/build/map/next_js/resolveAxiosHelpers/handleZDotCreate.js +2 -1
  111. package/build/map/next_js/resolveAxiosHelpers/handleZDotCreate.js.map +1 -1
  112. package/build/map/next_js/resolveAxiosHelpers/interceptorHeaders.js +2 -1
  113. package/build/map/next_js/resolveAxiosHelpers/interceptorHeaders.js.map +1 -1
  114. package/build/map/next_js/resolveAxiosHelpers/processExportedEndpoints.js +2 -1
  115. package/build/map/next_js/resolveAxiosHelpers/processExportedEndpoints.js.map +1 -1
  116. package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js +2 -1
  117. package/build/map/next_js/resolveAxiosHelpers/traceAxiosInstanceExports.js.map +1 -1
  118. package/build/map/next_js/resolveAxiosHelpers/traceBody.js +2 -1
  119. package/build/map/next_js/resolveAxiosHelpers/traceBody.js.map +1 -1
  120. package/build/map/next_js/resolveFetch.js +2 -1
  121. package/build/map/next_js/resolveFetch.js.map +1 -1
  122. package/build/map/next_js/resolveNewRequest.js +2 -1
  123. package/build/map/next_js/resolveNewRequest.js.map +1 -1
  124. package/build/map/next_js/resolveServerActions.js +2 -1
  125. package/build/map/next_js/resolveServerActions.js.map +1 -1
  126. package/build/map/next_js/utils.js +2 -1
  127. package/build/map/next_js/utils.js.map +1 -1
  128. package/build/map/react_js/getReactConnections.js +5 -4
  129. package/build/map/react_js/getReactConnections.js.map +1 -1
  130. package/build/map/vue_js/crossFileResolver.js +3 -2
  131. package/build/map/vue_js/crossFileResolver.js.map +1 -1
  132. package/build/map/vue_js/getViteConnections.js +5 -4
  133. package/build/map/vue_js/getViteConnections.js.map +1 -1
  134. package/build/map/vue_js/taint_utils.js +2 -1
  135. package/build/map/vue_js/taint_utils.js.map +1 -1
  136. package/build/map/vue_js/vue_resolveFetch.js +3 -2
  137. package/build/map/vue_js/vue_resolveFetch.js.map +1 -1
  138. package/build/map/vue_js/vue_resolveHttpClient.js +3 -2
  139. package/build/map/vue_js/vue_resolveHttpClient.js.map +1 -1
  140. package/build/map/vue_js/vue_resolveXhr.js +3 -2
  141. package/build/map/vue_js/vue_resolveXhr.js.map +1 -1
  142. package/build/refactor/index.js +399 -60
  143. package/build/refactor/index.js.map +1 -1
  144. package/build/refactor/next/helpers.js +194 -13
  145. package/build/refactor/next/helpers.js.map +1 -1
  146. package/build/refactor/next/index.js +223 -36
  147. package/build/refactor/next/index.js.map +1 -1
  148. package/build/refactor/next/transform.js +905 -55
  149. package/build/refactor/next/transform.js.map +1 -1
  150. package/build/refactor/react/index.js +3 -3
  151. package/build/refactor/react/index.js.map +1 -1
  152. package/build/refactor/react/transform.js +2 -1
  153. package/build/refactor/react/transform.js.map +1 -1
  154. package/build/refactor/react/validator.js +2 -1
  155. package/build/refactor/react/validator.js.map +1 -1
  156. package/build/refactor/react-vite/index.js +39 -5
  157. package/build/refactor/react-vite/index.js.map +1 -1
  158. package/build/refactor/react-vite/vendor-analyze.js +3 -3
  159. package/build/refactor/react-vite/vendor-analyze.js.map +1 -1
  160. package/build/refactor/remote/config.js +17 -1
  161. package/build/refactor/remote/config.js.map +1 -1
  162. package/build/refactor/remote/hf-client.js +27 -2
  163. package/build/refactor/remote/hf-client.js.map +1 -1
  164. package/build/refactor/remote/version-detect.js +355 -0
  165. package/build/refactor/remote/version-detect.js.map +1 -0
  166. package/build/refactor/vue/index.js +176 -0
  167. package/build/refactor/vue/index.js.map +1 -0
  168. package/build/refactor/vue/vendor-analyze-vue.js +271 -0
  169. package/build/refactor/vue/vendor-analyze-vue.js.map +1 -0
  170. package/build/refactor/vue/vite.js +216 -0
  171. package/build/refactor/vue/vite.js.map +1 -0
  172. package/build/report/utility/genHtml.js +14 -3
  173. package/build/report/utility/genHtml.js.map +1 -1
  174. package/build/run/bundler-detect.js +140 -0
  175. package/build/run/bundler-detect.js.map +1 -0
  176. package/build/run/index.js +91 -5
  177. package/build/run/index.js.map +1 -1
  178. package/build/strings/index.js +6 -2
  179. package/build/strings/index.js.map +1 -1
  180. package/build/strings/trufflehog.js +71 -0
  181. package/build/strings/trufflehog.js.map +1 -0
  182. package/build/utility/makeReq.js +10 -9
  183. package/build/utility/makeReq.js.map +1 -1
  184. package/build/utility/postmanGenerator.js +2 -2
  185. package/build/utility/postmanGenerator.js.map +1 -1
  186. package/build/utility/puppeteerInstance.js +7 -1
  187. package/build/utility/puppeteerInstance.js.map +1 -1
  188. package/package.json +9 -5
  189. package/scripts/smoke-test.js +74 -0
@@ -13,15 +13,19 @@ import path from "path";
13
13
  import { execSync } from "child_process";
14
14
  import prettier from "prettier";
15
15
  // Next.js
16
- import refactorNext from "./next/index.js";
16
+ import refactorNext, { refactorNextWebpack } from "./next/index.js";
17
17
  // React
18
18
  import refactorReact from "./react/index.js";
19
19
  // React (Vite)
20
20
  import refactorVite from "./react-vite/index.js";
21
+ // Vue
22
+ import refactorVueWebpack from "./vue/index.js";
23
+ import { refactorVueVite } from "./vue/vite.js";
21
24
  // Remote HuggingFace client + cache
22
- import { TECH_TO_BRANCH, getHfRawUrl, fetchCollisionsJson, listCollisionsFiles, validateRemoteBranch, getSampleSize, getTechnology, } from "./remote/hf-client.js";
23
- import { loadRefactorConfig, validateRefactorConfig } from "./remote/config.js";
25
+ import { TECH_TO_BRANCH, getHfRawUrl, fetchCollisionsJson, listCollisionsFiles, validateRemoteBranch, validateRemotePath, getSampleSize, getTechnology, } from "./remote/hf-client.js";
26
+ import { loadRefactorConfig, validateRefactorConfig, purgeDynamicVersionDetectionScatConfig, saveDynamicVersionDetectionScatConfig, } from "./remote/config.js";
24
27
  import { loadListCache, saveListCache, shouldRefreshListCache, loadCachedSignature, saveSignatureToCache, isSignatureCacheFresh, validateCaches, } from "./remote/cache.js";
28
+ import { detectReactVersion, VERSION_TECH_TO_BUNDLER, listAvailableVersions, selectDynamicScatConfigs, validateStaticScatConfig, } from "./remote/version-detect.js";
25
29
  /**
26
30
  * Derives the assets directory from the URL embedded in any chunk's code comment.
27
31
  * Chunks have a header like `// File Source: http://localhost:3001/assets/foo.js`.
@@ -69,17 +73,18 @@ function findVendorChunkFiles(chunks, assetsDir) {
69
73
  const BASELINE_SCAT_DIR = {
70
74
  "react-webpack": "lit-decl-loop-cond",
71
75
  "react-vite": "lit-decl-loop-cond",
76
+ "next-webpack": "lit-decl-loop-cond",
72
77
  };
73
78
  // Canonical ordering of scat categories (matches ALL_SCAT_CATEGORIES in csmast.mjs).
74
79
  // Used when converting a user-supplied --scat list to a bucket directory name.
75
80
  const ALL_SCAT_CATEGORIES = ["lit", "id", "op", "decl", "loop", "cond", "name", "val", "op_name"];
76
81
  // Converts a scat list to the bucket directory name, preserving canonical category order.
77
- const scatToDir = (scat) => {
82
+ export const scatToDir = (scat) => {
78
83
  const scatSet = new Set(scat);
79
84
  return ALL_SCAT_CATEGORIES.filter((c) => scatSet.has(c)).join("-") || scat.join("-");
80
85
  };
81
86
  // Parses a collisions.json file and returns signatures whose count equals the maximum.
82
- const loadCollisionsFile = (filePath) => {
87
+ export const loadCollisionsFile = (filePath) => {
83
88
  const records = JSON.parse(fs.readFileSync(filePath, "utf8"));
84
89
  const maxCount = records.reduce((m, r) => (r.count > m ? r.count : m), 0);
85
90
  return new Set(records.filter((r) => r.count >= maxCount).map((r) => r.signature));
@@ -156,9 +161,12 @@ const buildLibSigs = (input, tech, scatOverride) => {
156
161
  };
157
162
  // Loads library signatures from the remote HuggingFace dataset.
158
163
  // Returns null if the branch is not configured for `tech` or if validation fails.
164
+ // When opts.remoteCollisions is provided it is used directly as the bucket path,
165
+ // bypassing the TECH_TO_BRANCH lookup and metadata-file validation.
166
+ // Exits with code 25 if opts.remoteCollisions is set but the path does not exist.
159
167
  const loadRemoteLibSigs = (tech, opts) => __awaiter(void 0, void 0, void 0, function* () {
160
- var _a, _b, _c;
161
- const branch = TECH_TO_BRANCH[tech];
168
+ var _a, _b, _c, _d;
169
+ const branch = (_a = opts.remoteCollisions) !== null && _a !== void 0 ? _a : TECH_TO_BRANCH[tech];
162
170
  if (!branch)
163
171
  return null;
164
172
  const scatDir = opts.scat ? scatToDir(opts.scat) : BASELINE_SCAT_DIR[tech];
@@ -175,33 +183,60 @@ const loadRemoteLibSigs = (tech, opts) => __awaiter(void 0, void 0, void 0, func
175
183
  for (const w of cacheWarnings) {
176
184
  console.log(chalk.yellow(`[!] Cache validation warning: ${w} (will refresh)`));
177
185
  }
178
- // Validate remote bucket prefix has required metadata files.
179
- console.log(chalk.cyan(`[i] Validating remote bucket prefix "${branch}"...`));
180
- const branchOk = yield validateRemoteBranch(branch);
181
- if (!branchOk) {
182
- console.log(chalk.red(`[!] Remote bucket prefix "${branch}" is missing required metadata (sample_size / technology). Skipping remote signatures.`));
183
- return null;
186
+ if (opts.remoteCollisions) {
187
+ // User-supplied path: validate it exists in the HF bucket.
188
+ console.log(chalk.cyan(`[i] Validating remote dataset path "${branch}"...`));
189
+ const pathExists = yield validateRemotePath(branch);
190
+ if (!pathExists) {
191
+ console.error(chalk.red(`[!] Remote dataset path "${branch}" not found in the shriyanss/cs-mast-s-dataset bucket.`));
192
+ process.exit(25);
193
+ }
184
194
  }
185
- // Verify the bucket prefix technology matches.
186
- const remoteTech = yield getTechnology(branch);
187
- if (remoteTech !== tech) {
188
- console.log(chalk.red(`[!] Remote bucket prefix "${branch}" is for technology "${remoteTech}", not "${tech}". Skipping remote signatures.`));
189
- return null;
195
+ else {
196
+ // Auto-detected branch: validate metadata files exist.
197
+ console.log(chalk.cyan(`[i] Validating remote bucket prefix "${branch}"...`));
198
+ const branchOk = yield validateRemoteBranch(branch);
199
+ if (!branchOk) {
200
+ console.log(chalk.red(`[!] Remote bucket prefix "${branch}" is missing required metadata (sample_size / technology). Skipping remote signatures.`));
201
+ return null;
202
+ }
203
+ // Verify the bucket prefix technology matches.
204
+ const remoteTech = yield getTechnology(branch);
205
+ if (remoteTech !== tech) {
206
+ console.log(chalk.red(`[!] Remote bucket prefix "${branch}" is for technology "${remoteTech}", not "${tech}". Skipping remote signatures.`));
207
+ return null;
208
+ }
209
+ }
210
+ // Try to load the sample size for quality filtering.
211
+ // Custom paths may not have a sample_size file; fall back to 0 (skip quality filter).
212
+ let sampleSize = 0;
213
+ if (opts.remoteCollisions) {
214
+ try {
215
+ sampleSize = yield getSampleSize(branch);
216
+ }
217
+ catch (_e) {
218
+ // Custom path may not have sample_size; quality filter will be skipped (sampleSize=0)
219
+ }
220
+ }
221
+ else {
222
+ sampleSize = yield getSampleSize(branch);
190
223
  }
191
- const sampleSize = yield getSampleSize(branch);
192
224
  // Build / refresh file list cache.
225
+ // Also refresh if the specific branch is not yet in the cache (e.g. first run
226
+ // with a new --remote-collisions path, or a new tech added after initial cache).
193
227
  let listCache = cacheWarnings.length > 0 ? null : loadListCache();
194
- if (shouldRefreshListCache(listCache, opts)) {
228
+ const branchMissingFromCache = !(listCache === null || listCache === void 0 ? void 0 : listCache.branches[branch]);
229
+ if (shouldRefreshListCache(listCache, opts) || branchMissingFromCache) {
195
230
  console.log(chalk.cyan(`[i] Refreshing remote file list cache for bucket prefix "${branch}"...`));
196
231
  const paths = yield listCollisionsFiles(branch, scatDir);
197
232
  const now = Date.now();
198
- const branches = (_a = listCache === null || listCache === void 0 ? void 0 : listCache.branches) !== null && _a !== void 0 ? _a : {};
233
+ const branches = (_b = listCache === null || listCache === void 0 ? void 0 : listCache.branches) !== null && _b !== void 0 ? _b : {};
199
234
  branches[branch] = paths;
200
235
  listCache = { generatedAt: now, branches };
201
236
  saveListCache(listCache);
202
237
  console.log(chalk.cyan(`[i] File list cache updated: ${paths.length} files found`));
203
238
  }
204
- const filePaths = (_b = listCache === null || listCache === void 0 ? void 0 : listCache.branches[branch]) !== null && _b !== void 0 ? _b : [];
239
+ const filePaths = (_c = listCache === null || listCache === void 0 ? void 0 : listCache.branches[branch]) !== null && _c !== void 0 ? _c : [];
205
240
  // Filter to only paths that match <something>/<scatDir>/collisions.json
206
241
  const matchingPaths = filePaths.filter((p) => p.endsWith(`/${scatDir}/collisions.json`));
207
242
  if (matchingPaths.length === 0) {
@@ -222,21 +257,34 @@ const loadRemoteLibSigs = (tech, opts) => __awaiter(void 0, void 0, void 0, func
222
257
  const url = getHfRawUrl(branch, relPath);
223
258
  records = yield fetchCollisionsJson(url);
224
259
  if (records === null) {
225
- // 404 or error — invalidate list cache unless skip-cache-checks.
226
- if (!opts.skipCacheChecks) {
260
+ // 404 or network error.
261
+ // For user-specified --remote-collisions paths the list is stable;
262
+ // just skip this file and continue without poisoning the list cache.
263
+ // For auto-detected branches, invalidate the list cache so a stale
264
+ // entry (e.g. a deleted file) is not tried again on the next run.
265
+ if (!opts.remoteCollisions && !opts.skipCacheChecks) {
227
266
  console.log(chalk.yellow(`[!] Could not fetch ${relPath} (404 or error) — refreshing list cache`));
228
267
  const freshPaths = yield listCollisionsFiles(branch, scatDir);
229
- const updatedBranches = (_c = listCache === null || listCache === void 0 ? void 0 : listCache.branches) !== null && _c !== void 0 ? _c : {};
268
+ const updatedBranches = (_d = listCache === null || listCache === void 0 ? void 0 : listCache.branches) !== null && _d !== void 0 ? _d : {};
230
269
  updatedBranches[branch] = freshPaths;
231
270
  saveListCache({ generatedAt: Date.now(), branches: updatedBranches });
232
271
  listCache = { generatedAt: Date.now(), branches: updatedBranches };
233
272
  }
273
+ else {
274
+ console.log(chalk.yellow(`[!] Could not fetch ${relPath} — skipping`));
275
+ }
234
276
  continue;
235
277
  }
236
278
  saveSignatureToCache(branch, relPath, records, config.maxCacheSizeMb);
237
279
  }
238
- // Apply signature quality filter: (count / sampleSize) * 100 >= threshold
239
- const filtered = records.filter((r) => sampleSize > 0 && (r.count / sampleSize) * 100 >= opts.signatureQuality);
280
+ // Apply signature quality filter: (count / sampleSize) * 100 >= threshold.
281
+ // When sampleSize is 0 (no sample_size file on the remote path), skip quality
282
+ // filtering and include all records.
283
+ const filtered = sampleSize === 0 ? records : records.filter((r) => (r.count / sampleSize) * 100 >= opts.signatureQuality);
284
+ // Skip feature dirs with no records — they indicate missing/empty dataset
285
+ // entries and would collapse the intersection to zero if included.
286
+ if (filtered.length === 0)
287
+ continue;
240
288
  // Intersect with previous sets.
241
289
  const sigSet = new Set(filtered.map((r) => r.signature));
242
290
  if (intersection === null) {
@@ -258,9 +306,12 @@ const loadRemoteLibSigs = (tech, opts) => __awaiter(void 0, void 0, void 0, func
258
306
  return { sigs: intersection, desc: `remote:${branch} (${loadedCount} files, quality>=${opts.signatureQuality}%)` };
259
307
  });
260
308
  const availableTechs = {
261
- next: "Next.js",
309
+ "next-turbopack": "Next.js (Turbopack)",
310
+ "next-webpack": "Next.js (webpack)",
262
311
  "react-webpack": "React (webpack)",
263
312
  "react-vite": "React (Vite)",
313
+ "vue-webpack": "Vue.js (webpack)",
314
+ "vue-vite": "Vue.js (Vite)",
264
315
  };
265
316
  /**
266
317
  * Scaffolds a minimal webpack project in `outputDir` (package.json,
@@ -271,7 +322,7 @@ const availableTechs = {
271
322
  * The entry file is the first written file that contains `createRoot(` — i.e.
272
323
  * the app entry point. If no such file is found, the first written file is used.
273
324
  */
274
- function runBuildCheck(outputDir, writtenFiles) {
325
+ function runBuildCheck(outputDir, writtenFiles, detectedVersion) {
275
326
  var _a;
276
327
  if (writtenFiles.length === 0)
277
328
  return;
@@ -286,16 +337,23 @@ function runBuildCheck(outputDir, writtenFiles) {
286
337
  })) !== null && _a !== void 0 ? _a : writtenFiles[0];
287
338
  const entryRelative = `./${path.basename(entryFile)}`;
288
339
  console.log(chalk.cyan(`[i] Setting up webpack build check in ${outputDir}/ (entry: ${entryRelative})`));
340
+ // Use detected React version when available, fall back to React 18.
341
+ const reactVersion = detectedVersion ? `^${detectedVersion.reactNpm}` : "^18.3.1";
342
+ const reactDomVersion = (detectedVersion === null || detectedVersion === void 0 ? void 0 : detectedVersion.reactDomNpm)
343
+ ? `^${detectedVersion.reactDomNpm}`
344
+ : detectedVersion
345
+ ? undefined // pre-0.14: no react-dom
346
+ : "^18.3.1";
347
+ if (detectedVersion) {
348
+ console.log(chalk.cyan(`[i] Using detected React version ${detectedVersion.versionKey} in package.json (react@${reactVersion})`));
349
+ }
350
+ const dependencies = Object.assign(Object.assign({ react: reactVersion }, (reactDomVersion ? { "react-dom": reactDomVersion } : {})), { "react-router-dom": "^6.27.0" });
289
351
  // package.json — no "type": "module" so webpack.config.js can use CommonJS require
290
352
  const pkg = {
291
353
  name: "refactored-app",
292
354
  version: "1.0.0",
293
355
  scripts: { build: "webpack" },
294
- dependencies: {
295
- react: "^18.3.1",
296
- "react-dom": "^18.3.1",
297
- "react-router-dom": "^6.27.0",
298
- },
356
+ dependencies,
299
357
  devDependencies: {
300
358
  "@babel/core": "^7.25.0",
301
359
  "@babel/preset-env": "^7.25.0",
@@ -365,13 +423,17 @@ module.exports = {
365
423
  *
366
424
  * The entry file is the first written file that contains `createRoot(`.
367
425
  */
368
- function runViteBuildCheck(outputDir, writtenFiles) {
426
+ function runViteBuildCheck(outputDir, writtenFiles, detectedVersion) {
369
427
  var _a;
370
428
  if (writtenFiles.length === 0)
371
429
  return;
430
+ // Deduplicate — callers should not pass duplicates, but guard here too so a
431
+ // duplicate does not cause the renameSync below to throw ENOENT on the
432
+ // second occurrence of the same path.
433
+ const uniqueFiles = [...new Set(writtenFiles)];
372
434
  // Rename .js files to .jsx — Vite's import analysis can't parse JSX in .js files,
373
435
  // and refactored Vite chunks always contain JSX. The original source files were .jsx.
374
- const jsxFiles = writtenFiles.map((f) => {
436
+ const jsxFiles = uniqueFiles.map((f) => {
375
437
  if (!f.endsWith(".js"))
376
438
  return f;
377
439
  const jsxPath = f.replace(/\.js$/, ".jsx");
@@ -400,16 +462,23 @@ function runViteBuildCheck(outputDir, writtenFiles) {
400
462
  })) !== null && _a !== void 0 ? _a : jsxFiles[0];
401
463
  const entryRelative = `./${path.basename(entryFile)}`;
402
464
  console.log(chalk.cyan(`[i] Setting up Vite build check in ${outputDir}/ (entry: ${entryRelative})`));
465
+ // Use detected React version when available, fall back to React 18.
466
+ const reactVersion = detectedVersion ? `^${detectedVersion.reactNpm}` : "^18.3.1";
467
+ const reactDomVersion = (detectedVersion === null || detectedVersion === void 0 ? void 0 : detectedVersion.reactDomNpm)
468
+ ? `^${detectedVersion.reactDomNpm}`
469
+ : detectedVersion
470
+ ? undefined
471
+ : "^18.3.1";
472
+ if (detectedVersion) {
473
+ console.log(chalk.cyan(`[i] Using detected React version ${detectedVersion.versionKey} in package.json (react@${reactVersion})`));
474
+ }
475
+ const dependencies = Object.assign(Object.assign({ react: reactVersion }, (reactDomVersion ? { "react-dom": reactDomVersion } : {})), { "react-router-dom": "^6.27.0" });
403
476
  const pkg = {
404
477
  name: "refactored-app",
405
478
  version: "1.0.0",
406
479
  type: "module",
407
480
  scripts: { build: "vite build" },
408
- dependencies: {
409
- react: "^18.3.1",
410
- "react-dom": "^18.3.1",
411
- "react-router-dom": "^6.27.0",
412
- },
481
+ dependencies,
413
482
  devDependencies: {
414
483
  "@vitejs/plugin-react": "^4.3.1",
415
484
  vite: "^5.4.0",
@@ -451,6 +520,83 @@ export default defineConfig({
451
520
  console.log(chalk.red("[!] Vite build check failed — review output above"));
452
521
  }
453
522
  }
523
+ /**
524
+ * Resolves the scat dir list to use for version detection based on the user's options.
525
+ * Returns an empty array when version detection should be skipped (e.g., invalid static config).
526
+ *
527
+ * Dynamic mode (default):
528
+ * - Reads cached dynamic scat configs from ~/.js-recon/refactor/config.json.
529
+ * - If --detect-version-dynamic-conf-purge is set, clears the cache first.
530
+ * - If no cached configs exist, runs selectDynamicScatConfigs() and saves the result.
531
+ *
532
+ * Static mode (user passes comma-separated categories):
533
+ * - Validates that reliable_signatures.json is non-empty for all known versions.
534
+ * - Exits with code 26 if the config is invalid or empty for any version.
535
+ */
536
+ function resolveVersionDetectionScatDirs(bundler, opts) {
537
+ return __awaiter(this, void 0, void 0, function* () {
538
+ var _a, _b;
539
+ const detectConfig = (_a = opts.detectVersionConfig) !== null && _a !== void 0 ? _a : "dynamic";
540
+ if (detectConfig === "dynamic") {
541
+ if (opts.detectVersionDynamicConfPurge) {
542
+ console.log(chalk.cyan("[i] Version detection: purging cached dynamic scat config..."));
543
+ purgeDynamicVersionDetectionScatConfig();
544
+ }
545
+ const cfg = loadRefactorConfig();
546
+ if (cfg.dynamicVersionDetectionScatConfig && cfg.dynamicVersionDetectionScatConfig.length > 0) {
547
+ console.log(chalk.cyan(`[i] Version detection: using cached dynamic scat config (${cfg.dynamicVersionDetectionScatConfig.join(", ")})`));
548
+ return cfg.dynamicVersionDetectionScatConfig;
549
+ }
550
+ // No cached config — discover versions and run dynamic selection.
551
+ let versions;
552
+ try {
553
+ versions = yield listAvailableVersions(bundler);
554
+ }
555
+ catch (e) {
556
+ console.log(chalk.yellow(`[!] Version detection: could not list versions (${String(e)})`));
557
+ return [];
558
+ }
559
+ if (versions.length === 0) {
560
+ console.log(chalk.yellow(`[!] Version detection: no version data found for ${bundler}`));
561
+ return [];
562
+ }
563
+ const threshold = Math.max(1, (_b = opts.detectVersionDynamicThreshold) !== null && _b !== void 0 ? _b : 3);
564
+ const selected = yield selectDynamicScatConfigs(bundler, versions, threshold);
565
+ if (selected.length > 0) {
566
+ saveDynamicVersionDetectionScatConfig(selected);
567
+ }
568
+ else {
569
+ console.log(chalk.yellow(`[!] Version detection: dynamic selection found no valid scat configs`));
570
+ }
571
+ return selected;
572
+ }
573
+ // Static config: parse comma-separated scat categories and convert to bucket dir name.
574
+ const categories = detectConfig
575
+ .split(",")
576
+ .map((s) => s.trim())
577
+ .filter(Boolean);
578
+ const scatDir = scatToDir(categories);
579
+ if (!scatDir) {
580
+ console.log(chalk.red(`[!] --detect-version-config "${detectConfig}" produced an empty scat dir; check category names`));
581
+ process.exit(26);
582
+ }
583
+ // Validate by checking that reliable_signatures.json is non-empty for all versions.
584
+ let versions;
585
+ try {
586
+ versions = yield listAvailableVersions(bundler);
587
+ }
588
+ catch (e) {
589
+ console.log(chalk.yellow(`[!] Version detection: could not list versions to validate static config (${String(e)})`));
590
+ return [];
591
+ }
592
+ const valid = yield validateStaticScatConfig(bundler, versions, scatDir);
593
+ if (!valid) {
594
+ console.log(chalk.red(`[!] --detect-version-config "${detectConfig}" (scat dir: "${scatDir}") has empty reliable_signatures.json for one or more versions. Cannot use for version detection.`));
595
+ process.exit(26);
596
+ }
597
+ return [scatDir];
598
+ });
599
+ }
454
600
  /**
455
601
  * Refactors JavaScript code chunks based on technology-specific patterns.
456
602
  *
@@ -465,7 +611,7 @@ export default defineConfig({
465
611
  * @returns Promise that resolves when refactoring is complete
466
612
  */
467
613
  const refactor = (mappedJson, outputDir, tech, list, collisionsFile, remoteOpts) => __awaiter(void 0, void 0, void 0, function* () {
468
- var _a, _b, _c, _d;
614
+ var _a, _b, _c, _d, _e;
469
615
  console.log(chalk.cyan("[i] Loading refactor module..."));
470
616
  if (list) {
471
617
  console.log(chalk.cyan("[i] Listing available technologies"));
@@ -517,12 +663,15 @@ const refactor = (mappedJson, outputDir, tech, list, collisionsFile, remoteOpts)
517
663
  libSigs = result.sigs;
518
664
  console.log(chalk.cyan(`[i] Loaded ${libSigs.size} library signatures from ${result.desc}`));
519
665
  }
520
- else if (!(remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.noRemote) && TECH_TO_BRANCH[tech]) {
521
- // Default: load from remote HuggingFace dataset.
666
+ else if ((remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.remoteCollisions) || (!(remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.noRemote) && TECH_TO_BRANCH[tech])) {
667
+ // Load from remote HuggingFace dataset.
668
+ // --remote-collisions uses the explicit path; otherwise falls back to TECH_TO_BRANCH.
522
669
  const opts = {
523
670
  signatureQuality: (_a = remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.signatureQuality) !== null && _a !== void 0 ? _a : 100,
524
671
  refreshCache: (_b = remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.refreshCache) !== null && _b !== void 0 ? _b : false,
525
672
  skipCacheChecks: (_c = remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.skipCacheChecks) !== null && _c !== void 0 ? _c : false,
673
+ remoteCollisions: remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.remoteCollisions,
674
+ scat: remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.scat,
526
675
  };
527
676
  const result = yield loadRemoteLibSigs(tech, opts);
528
677
  if (result) {
@@ -533,7 +682,7 @@ const refactor = (mappedJson, outputDir, tech, list, collisionsFile, remoteOpts)
533
682
  }
534
683
  }
535
684
  // iterate through the chunks
536
- if (tech === "next") {
685
+ if (tech === "next-turbopack") {
537
686
  for (const [, value] of Object.entries(chunks)) {
538
687
  const moduleFiles = yield refactorNext(value);
539
688
  for (const [moduleId, rawCode] of Object.entries(moduleFiles)) {
@@ -542,8 +691,42 @@ const refactor = (mappedJson, outputDir, tech, list, collisionsFile, remoteOpts)
542
691
  singleQuote: true,
543
692
  trailingComma: "none",
544
693
  });
545
- fs.writeFileSync(`${outputDir}/${moduleId}.js`, formatted);
546
- console.log(chalk.green(`[✓] Module ${moduleId} written to ${outputDir}/${moduleId}.js`));
694
+ // Skip writing empty files
695
+ if (formatted.trim().length === 0) {
696
+ console.log(chalk.gray(`[~] Module ${moduleId} is empty after stripping — skipping`));
697
+ continue;
698
+ }
699
+ const filePath = `${outputDir}/${moduleId}.js`;
700
+ fs.writeFileSync(filePath, formatted);
701
+ console.log(chalk.green(`[✓] Module ${moduleId} written to ${filePath}`));
702
+ }
703
+ }
704
+ }
705
+ else if (tech === "next-webpack") {
706
+ if (remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.scat) {
707
+ console.log(chalk.cyan(`[i] Using custom scat config: ${remoteOpts.scat.join(",")}`));
708
+ }
709
+ for (const [, value] of Object.entries(chunks)) {
710
+ const moduleFiles = yield refactorNextWebpack(value, libSigs, remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.scat);
711
+ for (const [moduleId, rawCode] of Object.entries(moduleFiles)) {
712
+ let formatted;
713
+ try {
714
+ formatted = yield prettier.format(rawCode, {
715
+ parser: "babel",
716
+ singleQuote: true,
717
+ trailingComma: "none",
718
+ });
719
+ }
720
+ catch (_f) {
721
+ formatted = rawCode;
722
+ }
723
+ if (formatted.trim().length === 0) {
724
+ console.log(chalk.gray(`[~] Module ${moduleId} is empty after stripping — skipping`));
725
+ continue;
726
+ }
727
+ const filePath = `${outputDir}/${moduleId}.js`;
728
+ fs.writeFileSync(filePath, formatted);
729
+ console.log(chalk.green(`[✓] Module ${moduleId} written to ${filePath}`));
547
730
  }
548
731
  }
549
732
  }
@@ -618,43 +801,199 @@ const refactor = (mappedJson, outputDir, tech, list, collisionsFile, remoteOpts)
618
801
  console.log(chalk.green(`[✓] Module ${moduleId} written to ${outputDir}/${moduleId}.js`));
619
802
  }
620
803
  }
804
+ // Version detection — run before build check so the detected version can be
805
+ // written into package.json by runBuildCheck.
806
+ let detectedVersion;
807
+ if ((remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.detectVersion) && VERSION_TECH_TO_BUNDLER[tech]) {
808
+ const bundler = VERSION_TECH_TO_BUNDLER[tech];
809
+ const vScatDirs = yield resolveVersionDetectionScatDirs(bundler, remoteOpts);
810
+ if (vScatDirs.length > 0) {
811
+ // Include vendor chunk file contents (e.g. vendor-react-dom.*.js) that hold
812
+ // React library code but are not present in mapped.json. Without these, the
813
+ // signature set contains only app-level code and misses version-specific React
814
+ // library signatures.
815
+ const vendorCodes = [];
816
+ const vDetectAssetsDir = findAssetsDir(chunks, mappedJson);
817
+ if (vDetectAssetsDir) {
818
+ for (const vendorFile of findVendorChunkFiles(chunks, vDetectAssetsDir)) {
819
+ try {
820
+ vendorCodes.push(fs.readFileSync(vendorFile, "utf8"));
821
+ }
822
+ catch (_g) {
823
+ // non-fatal
824
+ }
825
+ }
826
+ }
827
+ const vResult = yield detectReactVersion(chunks, tech, vScatDirs, vendorCodes.length > 0 ? vendorCodes : undefined);
828
+ if (vResult) {
829
+ console.log(chalk.green(`[✓] Detected React version: ${vResult.versionKey} (${vResult.matchCount} signature matches)`));
830
+ detectedVersion = vResult;
831
+ }
832
+ else {
833
+ console.log(chalk.yellow(`[!] Version detection: no matching version found`));
834
+ }
835
+ }
836
+ }
621
837
  // Build check — scaffold a minimal webpack project and verify the refactored
622
838
  // code compiles.
623
- runBuildCheck(outputDir, writtenFiles);
839
+ runBuildCheck(outputDir, writtenFiles, detectedVersion);
624
840
  }
625
841
  else if (tech === "react-vite") {
626
- const viteFiles = yield refactorVite(chunks, libSigs);
627
- const writtenFiles = [];
842
+ // Pre-scan vendor chunks from the assets directory that are not in mapped.json.
843
+ // Vendor chunks (vendor-react-*.js) are referenced by app chunks but are typically
844
+ // absent from mapped.json. Without them, vendorExportMaps is empty and vendor
845
+ // import statements are never rewritten — leaving unresolvable references in output.
846
+ const vitAssetsDir = findAssetsDir(chunks, mappedJson);
847
+ if (vitAssetsDir) {
848
+ const missingFiles = findVendorChunkFiles(chunks, vitAssetsDir);
849
+ for (const vendorFile of missingFiles) {
850
+ const basename = path.basename(vendorFile);
851
+ if (!/vendor[-_]react/i.test(basename) && !/rolldown[-_]runtime/i.test(basename))
852
+ continue;
853
+ console.log(chalk.cyan(`[i] Including missing vendor chunk: ${basename}`));
854
+ const key = basename.replace(/\.js$/, "");
855
+ chunks[key] = {
856
+ id: basename,
857
+ description: "",
858
+ loadedOn: [],
859
+ containsFetch: false,
860
+ isAxiosLibrary: false,
861
+ exports: [],
862
+ callStack: [],
863
+ code: fs.readFileSync(vendorFile, "utf8"),
864
+ imports: [],
865
+ file: `assets/${basename}`,
866
+ };
867
+ }
868
+ }
869
+ const viteFiles = yield refactorVite(chunks, libSigs, remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.scat);
870
+ // Group chunk outputs by their output file path before writing.
871
+ // Multiple mapped.json sub-chunks can originate from the same Vite source
872
+ // file (one per top-level function detected by `map`). Writing each
873
+ // sub-chunk independently would overwrite previous writes so only the last
874
+ // sub-chunk would survive. Grouping and concatenating avoids the overwrite:
875
+ // all sub-chunks for the same file are merged into one formatted output.
876
+ const fileGroups = new Map();
628
877
  for (const [chunkKey, rawCode] of Object.entries(viteFiles)) {
878
+ // Use the original chunk basename so dynamic imports (which reference
879
+ // the original Vite filenames) resolve correctly in the build check.
880
+ const chunkInfo = chunks[chunkKey];
881
+ const originalFile = chunkInfo ? ((_d = chunkInfo.file) !== null && _d !== void 0 ? _d : chunkInfo.id) : null;
882
+ const outputBasename = originalFile
883
+ ? path.basename(originalFile)
884
+ : chunkKey.replace(/[/\\]/g, "_").replace(/\.js$/, "") + ".js";
885
+ const filePath = `${outputDir}/${outputBasename}`;
886
+ if (!fileGroups.has(filePath)) {
887
+ fileGroups.set(filePath, { codes: [], keys: [] });
888
+ }
889
+ fileGroups.get(filePath).codes.push(rawCode);
890
+ fileGroups.get(filePath).keys.push(chunkKey);
891
+ }
892
+ const writtenFiles = [];
893
+ for (const [filePath, { codes, keys }] of fileGroups) {
894
+ // Put import-bearing code parts first so ESM import statements end up
895
+ // at the top of the concatenated file. Helper sub-chunks typically have
896
+ // no imports; the component sub-chunk that has peer imports (e.g.
897
+ // from ./shared-*.js) must come first or the combined file is invalid.
898
+ const importBearing = codes.filter((c) => /^import\s/m.test(c));
899
+ const rest = codes.filter((c) => !/^import\s/m.test(c));
900
+ const combined = [...importBearing, ...rest].join("\n\n");
629
901
  let formatted;
630
902
  try {
631
- formatted = yield prettier.format(rawCode, {
903
+ formatted = yield prettier.format(combined, {
632
904
  parser: "babel",
633
905
  singleQuote: true,
634
906
  trailingComma: "none",
635
907
  });
636
908
  }
637
- catch (_e) {
638
- formatted = rawCode;
909
+ catch (_h) {
910
+ formatted = combined;
639
911
  }
640
912
  if (formatted.trim().length === 0) {
641
- console.log(chalk.gray(`[~] Chunk ${chunkKey} is empty after refactoring — skipping`));
913
+ console.log(chalk.gray(`[~] Chunk ${keys.join("+")} is empty after refactoring — skipping`));
642
914
  continue;
643
915
  }
644
- // Use the original chunk basename so dynamic imports (which reference
645
- // the original Vite filenames) resolve correctly in the build check.
916
+ fs.writeFileSync(filePath, formatted);
917
+ // Push exactly once per output file prevents duplicate entries in
918
+ // writtenFiles that would cause the rename loop in runViteBuildCheck
919
+ // to attempt renaming the same path twice (ENOENT on the second try).
920
+ writtenFiles.push(filePath);
921
+ for (const key of keys) {
922
+ console.log(chalk.green(`[✓] Chunk ${key} written to ${filePath}`));
923
+ }
924
+ }
925
+ // Version detection — run before build check so the detected version can be
926
+ // written into package.json by runViteBuildCheck.
927
+ let viteDetectedVersion;
928
+ if ((remoteOpts === null || remoteOpts === void 0 ? void 0 : remoteOpts.detectVersion) && VERSION_TECH_TO_BUNDLER[tech]) {
929
+ const bundler = VERSION_TECH_TO_BUNDLER[tech];
930
+ const vScatDirs = yield resolveVersionDetectionScatDirs(bundler, remoteOpts);
931
+ if (vScatDirs.length > 0) {
932
+ const vResult = yield detectReactVersion(chunks, tech, vScatDirs);
933
+ if (vResult) {
934
+ console.log(chalk.green(`[✓] Detected React version: ${vResult.versionKey} (${vResult.matchCount} signature matches)`));
935
+ viteDetectedVersion = vResult;
936
+ }
937
+ else {
938
+ console.log(chalk.yellow(`[!] Version detection: no matching version found`));
939
+ }
940
+ }
941
+ }
942
+ // Build check with Vite scaffold
943
+ runViteBuildCheck(outputDir, writtenFiles, viteDetectedVersion);
944
+ }
945
+ else if (tech === "vue-webpack") {
946
+ for (const [, value] of Object.entries(chunks)) {
947
+ const moduleFiles = yield refactorVueWebpack(value);
948
+ for (const [moduleId, rawCode] of Object.entries(moduleFiles)) {
949
+ let formatted;
950
+ try {
951
+ formatted = yield prettier.format(rawCode, {
952
+ parser: "babel",
953
+ singleQuote: true,
954
+ trailingComma: "none",
955
+ });
956
+ }
957
+ catch (_j) {
958
+ formatted = rawCode;
959
+ }
960
+ if (formatted.trim().length === 0) {
961
+ console.log(chalk.gray(`[~] Module ${moduleId} is empty after stripping — skipping`));
962
+ continue;
963
+ }
964
+ const filePath = `${outputDir}/${moduleId}.js`;
965
+ fs.writeFileSync(filePath, formatted);
966
+ console.log(chalk.green(`[✓] Module ${moduleId} written to ${filePath}`));
967
+ }
968
+ }
969
+ }
970
+ else if (tech === "vue-vite") {
971
+ const viteFiles = yield refactorVueVite(chunks);
972
+ for (const [chunkKey, rawCode] of Object.entries(viteFiles)) {
646
973
  const chunkInfo = chunks[chunkKey];
647
- const originalFile = chunkInfo ? ((_d = chunkInfo.file) !== null && _d !== void 0 ? _d : chunkInfo.id) : null;
974
+ const originalFile = chunkInfo ? ((_e = chunkInfo.file) !== null && _e !== void 0 ? _e : chunkInfo.id) : null;
648
975
  const outputBasename = originalFile
649
976
  ? path.basename(originalFile)
650
977
  : chunkKey.replace(/[/\\]/g, "_").replace(/\.js$/, "") + ".js";
651
978
  const filePath = `${outputDir}/${outputBasename}`;
979
+ let formatted;
980
+ try {
981
+ formatted = yield prettier.format(rawCode, {
982
+ parser: "babel",
983
+ singleQuote: true,
984
+ trailingComma: "none",
985
+ });
986
+ }
987
+ catch (_k) {
988
+ formatted = rawCode;
989
+ }
990
+ if (formatted.trim().length === 0) {
991
+ console.log(chalk.gray(`[~] Chunk ${chunkKey} is empty after refactoring — skipping`));
992
+ continue;
993
+ }
652
994
  fs.writeFileSync(filePath, formatted);
653
- writtenFiles.push(filePath);
654
995
  console.log(chalk.green(`[✓] Chunk ${chunkKey} written to ${filePath}`));
655
996
  }
656
- // Build check with Vite scaffold
657
- runViteBuildCheck(outputDir, writtenFiles);
658
997
  }
659
998
  console.log(chalk.green("[✓] Refactoring complete."));
660
999
  });