@hyperfrontend/project-scope 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/ARCHITECTURE.md +370 -0
  2. package/CHANGELOG.md +10 -0
  3. package/FUNDING.md +141 -0
  4. package/LICENSE.md +21 -0
  5. package/README.md +242 -0
  6. package/SECURITY.md +82 -0
  7. package/analyze.d.ts +33 -0
  8. package/analyze.d.ts.map +1 -0
  9. package/cli/commands/analyze.d.ts +20 -0
  10. package/cli/commands/analyze.d.ts.map +1 -0
  11. package/cli/commands/config.d.ts +20 -0
  12. package/cli/commands/config.d.ts.map +1 -0
  13. package/cli/commands/deps.d.ts +18 -0
  14. package/cli/commands/deps.d.ts.map +1 -0
  15. package/cli/commands/tree.d.ts +24 -0
  16. package/cli/commands/tree.d.ts.map +1 -0
  17. package/cli/index.cjs.js +6639 -0
  18. package/cli/index.cjs.js.map +1 -0
  19. package/cli/index.d.ts +7 -0
  20. package/cli/index.d.ts.map +1 -0
  21. package/cli/index.esm.js +6629 -0
  22. package/cli/index.esm.js.map +1 -0
  23. package/cli/run.d.ts +25 -0
  24. package/cli/run.d.ts.map +1 -0
  25. package/cli/types.d.ts +55 -0
  26. package/cli/types.d.ts.map +1 -0
  27. package/core/cache.d.ts +157 -0
  28. package/core/cache.d.ts.map +1 -0
  29. package/core/encoding/convert.d.ts +32 -0
  30. package/core/encoding/convert.d.ts.map +1 -0
  31. package/core/encoding/detect.d.ts +86 -0
  32. package/core/encoding/detect.d.ts.map +1 -0
  33. package/core/encoding/index.cjs.js +751 -0
  34. package/core/encoding/index.cjs.js.map +1 -0
  35. package/core/encoding/index.d.ts +3 -0
  36. package/core/encoding/index.d.ts.map +1 -0
  37. package/core/encoding/index.esm.js +736 -0
  38. package/core/encoding/index.esm.js.map +1 -0
  39. package/core/errors/structured-errors.d.ts +64 -0
  40. package/core/errors/structured-errors.d.ts.map +1 -0
  41. package/core/fs/directory.d.ts +88 -0
  42. package/core/fs/directory.d.ts.map +1 -0
  43. package/core/fs/index.cjs.js +1079 -0
  44. package/core/fs/index.cjs.js.map +1 -0
  45. package/core/fs/index.d.ts +6 -0
  46. package/core/fs/index.d.ts.map +1 -0
  47. package/core/fs/index.esm.js +1056 -0
  48. package/core/fs/index.esm.js.map +1 -0
  49. package/core/fs/read.d.ts +86 -0
  50. package/core/fs/read.d.ts.map +1 -0
  51. package/core/fs/stat.d.ts +58 -0
  52. package/core/fs/stat.d.ts.map +1 -0
  53. package/core/fs/traversal.d.ts +26 -0
  54. package/core/fs/traversal.d.ts.map +1 -0
  55. package/core/fs/write.d.ts +75 -0
  56. package/core/fs/write.d.ts.map +1 -0
  57. package/core/index.cjs.js +2376 -0
  58. package/core/index.cjs.js.map +1 -0
  59. package/core/index.d.ts +9 -0
  60. package/core/index.d.ts.map +1 -0
  61. package/core/index.esm.js +2286 -0
  62. package/core/index.esm.js.map +1 -0
  63. package/core/logger.d.ts +111 -0
  64. package/core/logger.d.ts.map +1 -0
  65. package/core/path/index.cjs.js +254 -0
  66. package/core/path/index.cjs.js.map +1 -0
  67. package/core/path/index.d.ts +5 -0
  68. package/core/path/index.d.ts.map +1 -0
  69. package/core/path/index.esm.js +233 -0
  70. package/core/path/index.esm.js.map +1 -0
  71. package/core/path/join.d.ts +17 -0
  72. package/core/path/join.d.ts.map +1 -0
  73. package/core/path/normalize.d.ts +37 -0
  74. package/core/path/normalize.d.ts.map +1 -0
  75. package/core/path/resolve.d.ts +52 -0
  76. package/core/path/resolve.d.ts.map +1 -0
  77. package/core/path/segments.d.ts +59 -0
  78. package/core/path/segments.d.ts.map +1 -0
  79. package/core/patterns/glob.d.ts +46 -0
  80. package/core/patterns/glob.d.ts.map +1 -0
  81. package/core/platform/detect.d.ts +66 -0
  82. package/core/platform/detect.d.ts.map +1 -0
  83. package/core/platform/index.cjs.js +241 -0
  84. package/core/platform/index.cjs.js.map +1 -0
  85. package/core/platform/index.d.ts +3 -0
  86. package/core/platform/index.d.ts.map +1 -0
  87. package/core/platform/index.esm.js +226 -0
  88. package/core/platform/index.esm.js.map +1 -0
  89. package/core/platform/line-endings.d.ts +48 -0
  90. package/core/platform/line-endings.d.ts.map +1 -0
  91. package/heuristics/dependencies/analyze.d.ts +77 -0
  92. package/heuristics/dependencies/analyze.d.ts.map +1 -0
  93. package/heuristics/dependencies/index.cjs.js +1126 -0
  94. package/heuristics/dependencies/index.cjs.js.map +1 -0
  95. package/heuristics/dependencies/index.d.ts +2 -0
  96. package/heuristics/dependencies/index.d.ts.map +1 -0
  97. package/heuristics/dependencies/index.esm.js +1122 -0
  98. package/heuristics/dependencies/index.esm.js.map +1 -0
  99. package/heuristics/entry-points/discover.d.ts +86 -0
  100. package/heuristics/entry-points/discover.d.ts.map +1 -0
  101. package/heuristics/entry-points/index.cjs.js +1581 -0
  102. package/heuristics/entry-points/index.cjs.js.map +1 -0
  103. package/heuristics/entry-points/index.d.ts +2 -0
  104. package/heuristics/entry-points/index.d.ts.map +1 -0
  105. package/heuristics/entry-points/index.esm.js +1577 -0
  106. package/heuristics/entry-points/index.esm.js.map +1 -0
  107. package/heuristics/framework/identify.d.ts +84 -0
  108. package/heuristics/framework/identify.d.ts.map +1 -0
  109. package/heuristics/framework/index.cjs.js +3618 -0
  110. package/heuristics/framework/index.cjs.js.map +1 -0
  111. package/heuristics/framework/index.d.ts +2 -0
  112. package/heuristics/framework/index.d.ts.map +1 -0
  113. package/heuristics/framework/index.esm.js +3614 -0
  114. package/heuristics/framework/index.esm.js.map +1 -0
  115. package/heuristics/index.cjs.js +4833 -0
  116. package/heuristics/index.cjs.js.map +1 -0
  117. package/heuristics/index.d.ts +5 -0
  118. package/heuristics/index.d.ts.map +1 -0
  119. package/heuristics/index.esm.js +4822 -0
  120. package/heuristics/index.esm.js.map +1 -0
  121. package/heuristics/project-type/detect.d.ts +61 -0
  122. package/heuristics/project-type/detect.d.ts.map +1 -0
  123. package/heuristics/project-type/index.cjs.js +3633 -0
  124. package/heuristics/project-type/index.cjs.js.map +1 -0
  125. package/heuristics/project-type/index.d.ts +2 -0
  126. package/heuristics/project-type/index.d.ts.map +1 -0
  127. package/heuristics/project-type/index.esm.js +3631 -0
  128. package/heuristics/project-type/index.esm.js.map +1 -0
  129. package/index.cjs.js +10255 -0
  130. package/index.cjs.js.map +1 -0
  131. package/index.d.ts +10 -0
  132. package/index.d.ts.map +1 -0
  133. package/index.esm.js +10006 -0
  134. package/index.esm.js.map +1 -0
  135. package/models/index.cjs.js +3 -0
  136. package/models/index.cjs.js.map +1 -0
  137. package/models/index.d.ts +176 -0
  138. package/models/index.d.ts.map +1 -0
  139. package/models/index.esm.js +2 -0
  140. package/models/index.esm.js.map +1 -0
  141. package/nx/detect.d.ts +105 -0
  142. package/nx/detect.d.ts.map +1 -0
  143. package/nx/devkit-loader.d.ts +62 -0
  144. package/nx/devkit-loader.d.ts.map +1 -0
  145. package/nx/index.cjs.js +1302 -0
  146. package/nx/index.cjs.js.map +1 -0
  147. package/nx/index.d.ts +4 -0
  148. package/nx/index.d.ts.map +1 -0
  149. package/nx/index.esm.js +1286 -0
  150. package/nx/index.esm.js.map +1 -0
  151. package/nx/project-config.d.ts +109 -0
  152. package/nx/project-config.d.ts.map +1 -0
  153. package/package.json +218 -0
  154. package/project/config/detect.d.ts +77 -0
  155. package/project/config/detect.d.ts.map +1 -0
  156. package/project/config/index.cjs.js +1982 -0
  157. package/project/config/index.cjs.js.map +1 -0
  158. package/project/config/index.d.ts +4 -0
  159. package/project/config/index.d.ts.map +1 -0
  160. package/project/config/index.esm.js +1971 -0
  161. package/project/config/index.esm.js.map +1 -0
  162. package/project/config/parse.d.ts +53 -0
  163. package/project/config/parse.d.ts.map +1 -0
  164. package/project/config/patterns.d.ts +31 -0
  165. package/project/config/patterns.d.ts.map +1 -0
  166. package/project/index.cjs.js +2585 -0
  167. package/project/index.cjs.js.map +1 -0
  168. package/project/index.d.ts +5 -0
  169. package/project/index.d.ts.map +1 -0
  170. package/project/index.esm.js +2549 -0
  171. package/project/index.esm.js.map +1 -0
  172. package/project/package/dependencies.d.ts +101 -0
  173. package/project/package/dependencies.d.ts.map +1 -0
  174. package/project/package/index.cjs.js +972 -0
  175. package/project/package/index.cjs.js.map +1 -0
  176. package/project/package/index.d.ts +3 -0
  177. package/project/package/index.d.ts.map +1 -0
  178. package/project/package/index.esm.js +957 -0
  179. package/project/package/index.esm.js.map +1 -0
  180. package/project/package/read.d.ts +66 -0
  181. package/project/package/read.d.ts.map +1 -0
  182. package/project/root/detect.d.ts +65 -0
  183. package/project/root/detect.d.ts.map +1 -0
  184. package/project/root/index.cjs.js +860 -0
  185. package/project/root/index.cjs.js.map +1 -0
  186. package/project/root/index.d.ts +2 -0
  187. package/project/root/index.d.ts.map +1 -0
  188. package/project/root/index.esm.js +853 -0
  189. package/project/root/index.esm.js.map +1 -0
  190. package/project/traversal/index.cjs.js +1179 -0
  191. package/project/traversal/index.cjs.js.map +1 -0
  192. package/project/traversal/index.d.ts +3 -0
  193. package/project/traversal/index.d.ts.map +1 -0
  194. package/project/traversal/index.esm.js +1173 -0
  195. package/project/traversal/index.esm.js.map +1 -0
  196. package/project/traversal/search.d.ts +59 -0
  197. package/project/traversal/search.d.ts.map +1 -0
  198. package/project/traversal/walk.d.ts +63 -0
  199. package/project/traversal/walk.d.ts.map +1 -0
  200. package/tech/backend/detect-all.d.ts +13 -0
  201. package/tech/backend/detect-all.d.ts.map +1 -0
  202. package/tech/backend/express.d.ts +11 -0
  203. package/tech/backend/express.d.ts.map +1 -0
  204. package/tech/backend/fastify.d.ts +11 -0
  205. package/tech/backend/fastify.d.ts.map +1 -0
  206. package/tech/backend/hono.d.ts +11 -0
  207. package/tech/backend/hono.d.ts.map +1 -0
  208. package/tech/backend/index.cjs.js +939 -0
  209. package/tech/backend/index.cjs.js.map +1 -0
  210. package/tech/backend/index.d.ts +8 -0
  211. package/tech/backend/index.d.ts.map +1 -0
  212. package/tech/backend/index.esm.js +931 -0
  213. package/tech/backend/index.esm.js.map +1 -0
  214. package/tech/backend/koa.d.ts +11 -0
  215. package/tech/backend/koa.d.ts.map +1 -0
  216. package/tech/backend/nestjs.d.ts +11 -0
  217. package/tech/backend/nestjs.d.ts.map +1 -0
  218. package/tech/backend/types.d.ts +31 -0
  219. package/tech/backend/types.d.ts.map +1 -0
  220. package/tech/build/babel.d.ts +13 -0
  221. package/tech/build/babel.d.ts.map +1 -0
  222. package/tech/build/detect-all.d.ts +13 -0
  223. package/tech/build/detect-all.d.ts.map +1 -0
  224. package/tech/build/esbuild.d.ts +11 -0
  225. package/tech/build/esbuild.d.ts.map +1 -0
  226. package/tech/build/index.cjs.js +1118 -0
  227. package/tech/build/index.cjs.js.map +1 -0
  228. package/tech/build/index.d.ts +10 -0
  229. package/tech/build/index.d.ts.map +1 -0
  230. package/tech/build/index.esm.js +1102 -0
  231. package/tech/build/index.esm.js.map +1 -0
  232. package/tech/build/parcel.d.ts +13 -0
  233. package/tech/build/parcel.d.ts.map +1 -0
  234. package/tech/build/rollup.d.ts +13 -0
  235. package/tech/build/rollup.d.ts.map +1 -0
  236. package/tech/build/swc.d.ts +13 -0
  237. package/tech/build/swc.d.ts.map +1 -0
  238. package/tech/build/types.d.ts +31 -0
  239. package/tech/build/types.d.ts.map +1 -0
  240. package/tech/build/vite.d.ts +13 -0
  241. package/tech/build/vite.d.ts.map +1 -0
  242. package/tech/build/webpack.d.ts +13 -0
  243. package/tech/build/webpack.d.ts.map +1 -0
  244. package/tech/frontend/angular.d.ts +11 -0
  245. package/tech/frontend/angular.d.ts.map +1 -0
  246. package/tech/frontend/astro.d.ts +11 -0
  247. package/tech/frontend/astro.d.ts.map +1 -0
  248. package/tech/frontend/detect-all.d.ts +13 -0
  249. package/tech/frontend/detect-all.d.ts.map +1 -0
  250. package/tech/frontend/gatsby.d.ts +11 -0
  251. package/tech/frontend/gatsby.d.ts.map +1 -0
  252. package/tech/frontend/index.cjs.js +1310 -0
  253. package/tech/frontend/index.cjs.js.map +1 -0
  254. package/tech/frontend/index.d.ts +15 -0
  255. package/tech/frontend/index.d.ts.map +1 -0
  256. package/tech/frontend/index.esm.js +1295 -0
  257. package/tech/frontend/index.esm.js.map +1 -0
  258. package/tech/frontend/nextjs.d.ts +11 -0
  259. package/tech/frontend/nextjs.d.ts.map +1 -0
  260. package/tech/frontend/nuxt.d.ts +11 -0
  261. package/tech/frontend/nuxt.d.ts.map +1 -0
  262. package/tech/frontend/qwik.d.ts +11 -0
  263. package/tech/frontend/qwik.d.ts.map +1 -0
  264. package/tech/frontend/react.d.ts +11 -0
  265. package/tech/frontend/react.d.ts.map +1 -0
  266. package/tech/frontend/remix.d.ts +11 -0
  267. package/tech/frontend/remix.d.ts.map +1 -0
  268. package/tech/frontend/solid.d.ts +11 -0
  269. package/tech/frontend/solid.d.ts.map +1 -0
  270. package/tech/frontend/svelte.d.ts +11 -0
  271. package/tech/frontend/svelte.d.ts.map +1 -0
  272. package/tech/frontend/sveltekit.d.ts +11 -0
  273. package/tech/frontend/sveltekit.d.ts.map +1 -0
  274. package/tech/frontend/types.d.ts +35 -0
  275. package/tech/frontend/types.d.ts.map +1 -0
  276. package/tech/frontend/vue.d.ts +11 -0
  277. package/tech/frontend/vue.d.ts.map +1 -0
  278. package/tech/index.cjs.js +3684 -0
  279. package/tech/index.cjs.js.map +1 -0
  280. package/tech/index.d.ts +96 -0
  281. package/tech/index.d.ts.map +1 -0
  282. package/tech/index.esm.js +3603 -0
  283. package/tech/index.esm.js.map +1 -0
  284. package/tech/legacy/angularjs.d.ts +12 -0
  285. package/tech/legacy/angularjs.d.ts.map +1 -0
  286. package/tech/legacy/backbone.d.ts +11 -0
  287. package/tech/legacy/backbone.d.ts.map +1 -0
  288. package/tech/legacy/detect-all.d.ts +13 -0
  289. package/tech/legacy/detect-all.d.ts.map +1 -0
  290. package/tech/legacy/ember.d.ts +11 -0
  291. package/tech/legacy/ember.d.ts.map +1 -0
  292. package/tech/legacy/index.cjs.js +903 -0
  293. package/tech/legacy/index.cjs.js.map +1 -0
  294. package/tech/legacy/index.d.ts +7 -0
  295. package/tech/legacy/index.d.ts.map +1 -0
  296. package/tech/legacy/index.esm.js +896 -0
  297. package/tech/legacy/index.esm.js.map +1 -0
  298. package/tech/legacy/jquery.d.ts +11 -0
  299. package/tech/legacy/jquery.d.ts.map +1 -0
  300. package/tech/legacy/types.d.ts +33 -0
  301. package/tech/legacy/types.d.ts.map +1 -0
  302. package/tech/linting/biome.d.ts +11 -0
  303. package/tech/linting/biome.d.ts.map +1 -0
  304. package/tech/linting/detect-all.d.ts +13 -0
  305. package/tech/linting/detect-all.d.ts.map +1 -0
  306. package/tech/linting/eslint.d.ts +13 -0
  307. package/tech/linting/eslint.d.ts.map +1 -0
  308. package/tech/linting/index.cjs.js +992 -0
  309. package/tech/linting/index.cjs.js.map +1 -0
  310. package/tech/linting/index.d.ts +7 -0
  311. package/tech/linting/index.d.ts.map +1 -0
  312. package/tech/linting/index.esm.js +982 -0
  313. package/tech/linting/index.esm.js.map +1 -0
  314. package/tech/linting/prettier.d.ts +13 -0
  315. package/tech/linting/prettier.d.ts.map +1 -0
  316. package/tech/linting/stylelint.d.ts +13 -0
  317. package/tech/linting/stylelint.d.ts.map +1 -0
  318. package/tech/linting/types.d.ts +31 -0
  319. package/tech/linting/types.d.ts.map +1 -0
  320. package/tech/monorepo/detect-all.d.ts +13 -0
  321. package/tech/monorepo/detect-all.d.ts.map +1 -0
  322. package/tech/monorepo/index.cjs.js +1021 -0
  323. package/tech/monorepo/index.cjs.js.map +1 -0
  324. package/tech/monorepo/index.d.ts +10 -0
  325. package/tech/monorepo/index.d.ts.map +1 -0
  326. package/tech/monorepo/index.esm.js +1011 -0
  327. package/tech/monorepo/index.esm.js.map +1 -0
  328. package/tech/monorepo/lerna.d.ts +11 -0
  329. package/tech/monorepo/lerna.d.ts.map +1 -0
  330. package/tech/monorepo/npm-workspaces.d.ts +11 -0
  331. package/tech/monorepo/npm-workspaces.d.ts.map +1 -0
  332. package/tech/monorepo/nx.d.ts +11 -0
  333. package/tech/monorepo/nx.d.ts.map +1 -0
  334. package/tech/monorepo/pnpm-workspaces.d.ts +9 -0
  335. package/tech/monorepo/pnpm-workspaces.d.ts.map +1 -0
  336. package/tech/monorepo/rush.d.ts +11 -0
  337. package/tech/monorepo/rush.d.ts.map +1 -0
  338. package/tech/monorepo/turborepo.d.ts +11 -0
  339. package/tech/monorepo/turborepo.d.ts.map +1 -0
  340. package/tech/monorepo/types.d.ts +39 -0
  341. package/tech/monorepo/types.d.ts.map +1 -0
  342. package/tech/monorepo/yarn-workspaces.d.ts +11 -0
  343. package/tech/monorepo/yarn-workspaces.d.ts.map +1 -0
  344. package/tech/shared-utils/detector-helpers.d.ts +52 -0
  345. package/tech/shared-utils/detector-helpers.d.ts.map +1 -0
  346. package/tech/shared-utils/types.d.ts +41 -0
  347. package/tech/shared-utils/types.d.ts.map +1 -0
  348. package/tech/testing/cypress.d.ts +13 -0
  349. package/tech/testing/cypress.d.ts.map +1 -0
  350. package/tech/testing/detect-all.d.ts +13 -0
  351. package/tech/testing/detect-all.d.ts.map +1 -0
  352. package/tech/testing/index.cjs.js +1031 -0
  353. package/tech/testing/index.cjs.js.map +1 -0
  354. package/tech/testing/index.d.ts +8 -0
  355. package/tech/testing/index.d.ts.map +1 -0
  356. package/tech/testing/index.esm.js +1018 -0
  357. package/tech/testing/index.esm.js.map +1 -0
  358. package/tech/testing/jest.d.ts +13 -0
  359. package/tech/testing/jest.d.ts.map +1 -0
  360. package/tech/testing/mocha.d.ts +13 -0
  361. package/tech/testing/mocha.d.ts.map +1 -0
  362. package/tech/testing/playwright.d.ts +13 -0
  363. package/tech/testing/playwright.d.ts.map +1 -0
  364. package/tech/testing/types.d.ts +35 -0
  365. package/tech/testing/types.d.ts.map +1 -0
  366. package/tech/testing/vitest.d.ts +13 -0
  367. package/tech/testing/vitest.d.ts.map +1 -0
  368. package/tech/types/detectors.d.ts +67 -0
  369. package/tech/types/detectors.d.ts.map +1 -0
  370. package/tech/types/index.cjs.js +1045 -0
  371. package/tech/types/index.cjs.js.map +1 -0
  372. package/tech/types/index.d.ts +2 -0
  373. package/tech/types/index.d.ts.map +1 -0
  374. package/tech/types/index.esm.js +1039 -0
  375. package/tech/types/index.esm.js.map +1 -0
  376. package/vfs/commit.d.ts +32 -0
  377. package/vfs/commit.d.ts.map +1 -0
  378. package/vfs/diff.d.ts +73 -0
  379. package/vfs/diff.d.ts.map +1 -0
  380. package/vfs/factory.d.ts +37 -0
  381. package/vfs/factory.d.ts.map +1 -0
  382. package/vfs/fs-tree.d.ts +13 -0
  383. package/vfs/fs-tree.d.ts.map +1 -0
  384. package/vfs/index.cjs.js +1600 -0
  385. package/vfs/index.cjs.js.map +1 -0
  386. package/vfs/index.d.ts +6 -0
  387. package/vfs/index.d.ts.map +1 -0
  388. package/vfs/index.esm.js +1590 -0
  389. package/vfs/index.esm.js.map +1 -0
  390. package/vfs/types.d.ts +178 -0
  391. package/vfs/types.d.ts.map +1 -0
@@ -0,0 +1,3603 @@
1
+ import { join as join$1 } from 'node:path';
2
+ import { existsSync, readFileSync, statSync, lstatSync, readdirSync } from 'node:fs';
3
+
4
+ /**
5
+ * Safe copies of Map built-in via factory function.
6
+ *
7
+ * Since constructors cannot be safely captured via Object.assign, this module
8
+ * provides a factory function that uses Reflect.construct internally.
9
+ *
10
+ * These references are captured at module initialization time to protect against
11
+ * prototype pollution attacks. Import only what you need for tree-shaking.
12
+ *
13
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/map
14
+ */
15
+ // Capture references at module initialization time
16
+ const _Map = globalThis.Map;
17
+ const _Reflect$2 = globalThis.Reflect;
18
+ /**
19
+ * (Safe copy) Creates a new Map using the captured Map constructor.
20
+ * Use this instead of `new Map()`.
21
+ *
22
+ * @param iterable - Optional iterable of key-value pairs.
23
+ * @returns A new Map instance.
24
+ */
25
+ const createMap = (iterable) => _Reflect$2.construct(_Map, iterable ? [iterable] : []);
26
+
27
+ /**
28
+ * Safe copies of Object built-in methods.
29
+ *
30
+ * These references are captured at module initialization time to protect against
31
+ * prototype pollution attacks. Import only what you need for tree-shaking.
32
+ *
33
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/object
34
+ */
35
+ // Capture references at module initialization time
36
+ const _Object = globalThis.Object;
37
+ /**
38
+ * (Safe copy) Prevents modification of existing property attributes and values,
39
+ * and prevents the addition of new properties.
40
+ */
41
+ const freeze = _Object.freeze;
42
+ /**
43
+ * (Safe copy) Returns the names of the enumerable string properties and methods of an object.
44
+ */
45
+ const keys = _Object.keys;
46
+ /**
47
+ * (Safe copy) Returns an array of key/values of the enumerable own properties of an object.
48
+ */
49
+ const entries = _Object.entries;
50
+ /**
51
+ * (Safe copy) Returns an array of values of the enumerable own properties of an object.
52
+ */
53
+ const values = _Object.values;
54
+ /**
55
+ * (Safe copy) Adds one or more properties to an object, and/or modifies attributes of existing properties.
56
+ */
57
+ const defineProperties = _Object.defineProperties;
58
+
59
+ /**
60
+ * Safe copies of Set built-in via factory function.
61
+ *
62
+ * Since constructors cannot be safely captured via Object.assign, this module
63
+ * provides a factory function that uses Reflect.construct internally.
64
+ *
65
+ * These references are captured at module initialization time to protect against
66
+ * prototype pollution attacks. Import only what you need for tree-shaking.
67
+ *
68
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/set
69
+ */
70
+ // Capture references at module initialization time
71
+ const _Set = globalThis.Set;
72
+ const _Reflect$1 = globalThis.Reflect;
73
+ /**
74
+ * (Safe copy) Creates a new Set using the captured Set constructor.
75
+ * Use this instead of `new Set()`.
76
+ *
77
+ * @param iterable - Optional iterable of values.
78
+ * @returns A new Set instance.
79
+ */
80
+ const createSet = (iterable) => _Reflect$1.construct(_Set, iterable ? [iterable] : []);
81
+
82
+ /**
83
+ * Global registry of all caches for bulk operations.
84
+ */
85
+ const cacheRegistry = createSet();
86
+ /**
87
+ * Create a cache with optional TTL and size limits.
88
+ *
89
+ * The cache provides a simple key-value store with:
90
+ * - Optional TTL (time-to-live) for automatic expiration
91
+ * - Optional maxSize for limiting cache size with FIFO eviction
92
+ * - Lazy expiration (entries are checked on access)
93
+ *
94
+ * @param options - Cache configuration options
95
+ * @returns Cache instance
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * // Basic cache
100
+ * const cache = createCache<string, number>()
101
+ * cache.set('answer', 42)
102
+ * cache.get('answer') // 42
103
+ *
104
+ * // Cache with TTL (expires after 60 seconds)
105
+ * const ttlCache = createCache<string, object>({ ttl: 60000 })
106
+ *
107
+ * // Cache with max size (evicts oldest when full)
108
+ * const lruCache = createCache<string, object>({ maxSize: 100 })
109
+ *
110
+ * // Combined options
111
+ * const configCache = createCache<string, object>({
112
+ * ttl: 30000,
113
+ * maxSize: 50
114
+ * })
115
+ * ```
116
+ */
117
+ function createCache(options) {
118
+ const { ttl, maxSize } = options ?? {};
119
+ const store = createMap();
120
+ // Track insertion order for FIFO eviction
121
+ const insertionOrder = [];
122
+ /**
123
+ * Check if an entry is expired.
124
+ *
125
+ * @param entry - Cache entry to check
126
+ * @returns True if entry is expired
127
+ */
128
+ function isExpired(entry) {
129
+ if (ttl === undefined)
130
+ return false;
131
+ // eslint-disable-next-line workspace/no-unsafe-builtin-methods -- Date.now() is needed for Jest fake timers compatibility
132
+ return Date.now() - entry.timestamp > ttl;
133
+ }
134
+ /**
135
+ * Evict oldest entries to make room for new ones.
136
+ */
137
+ function evictIfNeeded() {
138
+ if (maxSize === undefined)
139
+ return;
140
+ while (store.size >= maxSize && insertionOrder.length > 0) {
141
+ const oldestKey = insertionOrder.shift();
142
+ if (oldestKey !== undefined) {
143
+ store.delete(oldestKey);
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * Remove key from insertion order tracking.
149
+ *
150
+ * @param key - Key to remove from order tracking
151
+ */
152
+ function removeFromOrder(key) {
153
+ const index = insertionOrder.indexOf(key);
154
+ if (index !== -1) {
155
+ insertionOrder.splice(index, 1);
156
+ }
157
+ }
158
+ const cache = {
159
+ get(key) {
160
+ const entry = store.get(key);
161
+ if (!entry)
162
+ return undefined;
163
+ if (isExpired(entry)) {
164
+ store.delete(key);
165
+ removeFromOrder(key);
166
+ return undefined;
167
+ }
168
+ return entry.value;
169
+ },
170
+ set(key, value) {
171
+ // If key exists, remove from order first
172
+ if (store.has(key)) {
173
+ removeFromOrder(key);
174
+ }
175
+ else {
176
+ // Evict if needed before adding new entry
177
+ evictIfNeeded();
178
+ }
179
+ // eslint-disable-next-line workspace/no-unsafe-builtin-methods -- Date.now() is needed for Jest fake timers compatibility
180
+ store.set(key, { value, timestamp: Date.now() });
181
+ insertionOrder.push(key);
182
+ },
183
+ has(key) {
184
+ const entry = store.get(key);
185
+ if (!entry)
186
+ return false;
187
+ if (isExpired(entry)) {
188
+ store.delete(key);
189
+ removeFromOrder(key);
190
+ return false;
191
+ }
192
+ return true;
193
+ },
194
+ delete(key) {
195
+ removeFromOrder(key);
196
+ return store.delete(key);
197
+ },
198
+ clear() {
199
+ store.clear();
200
+ insertionOrder.length = 0;
201
+ },
202
+ size() {
203
+ return store.size;
204
+ },
205
+ keys() {
206
+ return [...insertionOrder];
207
+ },
208
+ };
209
+ // Register cache for global operations
210
+ cacheRegistry.add(cache);
211
+ return freeze(cache);
212
+ }
213
+
214
+ /**
215
+ * Safe copies of Array built-in static methods.
216
+ *
217
+ * These references are captured at module initialization time to protect against
218
+ * prototype pollution attacks. Import only what you need for tree-shaking.
219
+ *
220
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/array
221
+ */
222
+ // Capture references at module initialization time
223
+ const _Array = globalThis.Array;
224
+ /**
225
+ * (Safe copy) Determines whether the passed value is an Array.
226
+ */
227
+ const isArray = _Array.isArray;
228
+
229
+ /**
230
+ * Safe copies of Console built-in methods.
231
+ *
232
+ * These references are captured at module initialization time to protect against
233
+ * prototype pollution attacks. Import only what you need for tree-shaking.
234
+ *
235
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/console
236
+ */
237
+ // Capture references at module initialization time
238
+ const _console = globalThis.console;
239
+ /**
240
+ * (Safe copy) Outputs a message to the console.
241
+ */
242
+ const log = _console.log.bind(_console);
243
+ /**
244
+ * (Safe copy) Outputs a warning message to the console.
245
+ */
246
+ const warn = _console.warn.bind(_console);
247
+ /**
248
+ * (Safe copy) Outputs an error message to the console.
249
+ */
250
+ const error = _console.error.bind(_console);
251
+ /**
252
+ * (Safe copy) Outputs an informational message to the console.
253
+ */
254
+ const info = _console.info.bind(_console);
255
+ /**
256
+ * (Safe copy) Outputs a debug message to the console.
257
+ */
258
+ const debug = _console.debug.bind(_console);
259
+ /**
260
+ * (Safe copy) Outputs a stack trace to the console.
261
+ */
262
+ _console.trace.bind(_console);
263
+ /**
264
+ * (Safe copy) Displays an interactive listing of the properties of a specified object.
265
+ */
266
+ _console.dir.bind(_console);
267
+ /**
268
+ * (Safe copy) Displays tabular data as a table.
269
+ */
270
+ _console.table.bind(_console);
271
+ /**
272
+ * (Safe copy) Writes an error message to the console if the assertion is false.
273
+ */
274
+ _console.assert.bind(_console);
275
+ /**
276
+ * (Safe copy) Clears the console.
277
+ */
278
+ _console.clear.bind(_console);
279
+ /**
280
+ * (Safe copy) Logs the number of times that this particular call to count() has been called.
281
+ */
282
+ _console.count.bind(_console);
283
+ /**
284
+ * (Safe copy) Resets the counter used with console.count().
285
+ */
286
+ _console.countReset.bind(_console);
287
+ /**
288
+ * (Safe copy) Creates a new inline group in the console.
289
+ */
290
+ _console.group.bind(_console);
291
+ /**
292
+ * (Safe copy) Creates a new inline group in the console that is initially collapsed.
293
+ */
294
+ _console.groupCollapsed.bind(_console);
295
+ /**
296
+ * (Safe copy) Exits the current inline group.
297
+ */
298
+ _console.groupEnd.bind(_console);
299
+ /**
300
+ * (Safe copy) Starts a timer with a name specified as an input parameter.
301
+ */
302
+ _console.time.bind(_console);
303
+ /**
304
+ * (Safe copy) Stops a timer that was previously started.
305
+ */
306
+ _console.timeEnd.bind(_console);
307
+ /**
308
+ * (Safe copy) Logs the current value of a timer that was previously started.
309
+ */
310
+ _console.timeLog.bind(_console);
311
+
312
+ /**
313
+ * Safe copies of JSON built-in methods.
314
+ *
315
+ * These references are captured at module initialization time to protect against
316
+ * prototype pollution attacks. Import only what you need for tree-shaking.
317
+ *
318
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/json
319
+ */
320
+ // Capture references at module initialization time
321
+ const _JSON = globalThis.JSON;
322
+ /**
323
+ * (Safe copy) Converts a JavaScript Object Notation (JSON) string into an object.
324
+ */
325
+ const parse = _JSON.parse;
326
+ /**
327
+ * (Safe copy) Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
328
+ */
329
+ const stringify = _JSON.stringify;
330
+
331
+ const registeredClasses = [];
332
+
333
+ /**
334
+ * Returns the data type of the target.
335
+ * Uses native `typeof` operator, however, makes distinction between `null`, `array`, and `object`.
336
+ * Also, when classes are registered via `registerClass`, it checks if objects are instance of any known registered class.
337
+ *
338
+ * @param target - The target to get the data type of.
339
+ * @returns The data type of the target.
340
+ */
341
+ const getType = (target) => {
342
+ if (target === null)
343
+ return 'null';
344
+ const nativeDataType = typeof target;
345
+ if (nativeDataType === 'object') {
346
+ if (isArray(target))
347
+ return 'array';
348
+ for (const registeredClass of registeredClasses) {
349
+ if (target instanceof registeredClass)
350
+ return registeredClass.name;
351
+ }
352
+ }
353
+ return nativeDataType;
354
+ };
355
+
356
+ /**
357
+ * Safe copies of Error built-ins via factory functions.
358
+ *
359
+ * Since constructors cannot be safely captured via Object.assign, this module
360
+ * provides factory functions that use Reflect.construct internally.
361
+ *
362
+ * These references are captured at module initialization time to protect against
363
+ * prototype pollution attacks. Import only what you need for tree-shaking.
364
+ *
365
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/error
366
+ */
367
+ // Capture references at module initialization time
368
+ const _Error = globalThis.Error;
369
+ const _Reflect = globalThis.Reflect;
370
+ /**
371
+ * (Safe copy) Creates a new Error using the captured Error constructor.
372
+ * Use this instead of `new Error()`.
373
+ *
374
+ * @param message - Optional error message.
375
+ * @param options - Optional error options.
376
+ * @returns A new Error instance.
377
+ */
378
+ const createError = (message, options) => _Reflect.construct(_Error, [message, options]);
379
+
380
+ /**
381
+ * Safe copies of Math built-in methods.
382
+ *
383
+ * These references are captured at module initialization time to protect against
384
+ * prototype pollution attacks. Import only what you need for tree-shaking.
385
+ *
386
+ * @module @hyperfrontend/immutable-api-utils/built-in-copy/math
387
+ */
388
+ // Capture references at module initialization time
389
+ const _Math = globalThis.Math;
390
+ /**
391
+ * (Safe copy) Returns the smaller of zero or more numbers.
392
+ */
393
+ const min = _Math.min;
394
+
395
+ /* eslint-disable @typescript-eslint/no-explicit-any */
396
+ /**
397
+ * Creates a wrapper function that only executes the wrapped function if the condition function returns true.
398
+ *
399
+ * @param func - The function to be conditionally executed.
400
+ * @param conditionFunc - A function that returns a boolean, determining if `func` should be executed.
401
+ * @returns A wrapped version of `func` that executes conditionally.
402
+ */
403
+ function createConditionalExecutionFunction(func, conditionFunc) {
404
+ return function (...args) {
405
+ if (conditionFunc()) {
406
+ return func(...args);
407
+ }
408
+ };
409
+ }
410
+
411
+ /* eslint-disable @typescript-eslint/no-explicit-any */
412
+ /**
413
+ * Creates a wrapper function that silently ignores any errors thrown by the wrapped void function.
414
+ * This function is specifically for wrapping functions that do not return a value (void functions).
415
+ * Exceptions are swallowed without any logging or handling.
416
+ *
417
+ * @param func - The void function to be wrapped.
418
+ * @returns A wrapped version of the input function that ignores errors.
419
+ */
420
+ function createErrorIgnoringFunction(func) {
421
+ return function (...args) {
422
+ try {
423
+ func(...args);
424
+ }
425
+ catch {
426
+ // Deliberately swallowing/ignoring the exception
427
+ }
428
+ };
429
+ }
430
+
431
+ /* eslint-disable @typescript-eslint/no-unused-vars */
432
+ /**
433
+ * A no-operation function (noop) that does nothing regardless of the arguments passed.
434
+ * It is designed to be as permissive as possible in its typing without using the `Function` keyword.
435
+ *
436
+ * @param args - Any arguments passed to the function (ignored)
437
+ */
438
+ const noop = (...args) => {
439
+ // Intentionally does nothing
440
+ };
441
+
442
+ const logLevels = ['none', 'error', 'warn', 'log', 'info', 'debug'];
443
+ const priority = {
444
+ error: 4,
445
+ warn: 3,
446
+ log: 2,
447
+ info: 1,
448
+ debug: 0,
449
+ };
450
+ /**
451
+ * Validates whether a given string is a valid log level.
452
+ *
453
+ * @param level - The log level to validate
454
+ * @returns True if the level is valid, false otherwise
455
+ */
456
+ function isValidLogLevel(level) {
457
+ return logLevels.includes(level);
458
+ }
459
+ /**
460
+ * Creates a log level configuration manager for controlling logging behavior.
461
+ * Provides methods to get, set, and evaluate log levels based on priority.
462
+ *
463
+ * @param level - The initial log level (defaults to 'error')
464
+ * @returns A configuration object with log level management methods
465
+ * @throws {Error} When the provided level is not a valid log level
466
+ */
467
+ function createLogLevelConfig(level = 'error') {
468
+ if (!isValidLogLevel(level)) {
469
+ throw createError('Cannot create log level configuration with a valid default log level');
470
+ }
471
+ const state = { level };
472
+ const getLogLevel = () => state.level;
473
+ const setLogLevel = (level) => {
474
+ if (!isValidLogLevel(level)) {
475
+ throw createError(`Cannot set value '${level}' level. Expected levels are ${logLevels}.`);
476
+ }
477
+ state.level = level;
478
+ };
479
+ const shouldLog = (level) => {
480
+ if (state.level === 'none' || level === 'none' || !isValidLogLevel(level)) {
481
+ return false;
482
+ }
483
+ return priority[level] >= priority[state.level];
484
+ };
485
+ return freeze({
486
+ getLogLevel,
487
+ setLogLevel,
488
+ shouldLog,
489
+ });
490
+ }
491
+
492
+ /**
493
+ * Creates a logger instance with configurable log level filtering.
494
+ * Each log function is wrapped to respect the current log level setting.
495
+ *
496
+ * @param error - Function to handle error-level logs (required)
497
+ * @param warn - Function to handle warning-level logs (optional, defaults to noop)
498
+ * @param log - Function to handle standard logs (optional, defaults to noop)
499
+ * @param info - Function to handle info-level logs (optional, defaults to noop)
500
+ * @param debug - Function to handle debug-level logs (optional, defaults to noop)
501
+ * @returns A frozen logger object with log methods and level control
502
+ * @throws {ErrorLevelFn} When any provided log function is invalid
503
+ */
504
+ function createLogger(error, warn = noop, log = noop, info = noop, debug = noop) {
505
+ if (notValidLogFn(error)) {
506
+ throw createError(notFnMsg('error'));
507
+ }
508
+ if (notValidLogFn(warn)) {
509
+ throw createError(notFnMsg('warn'));
510
+ }
511
+ if (notValidLogFn(log)) {
512
+ throw createError(notFnMsg('log'));
513
+ }
514
+ if (notValidLogFn(info)) {
515
+ throw createError(notFnMsg('info'));
516
+ }
517
+ if (notValidLogFn(debug)) {
518
+ throw createError(notFnMsg('debug'));
519
+ }
520
+ const { setLogLevel, getLogLevel, shouldLog } = createLogLevelConfig();
521
+ const wrapLogFn = (fn, level) => {
522
+ if (fn === noop)
523
+ return fn;
524
+ const condition = () => shouldLog(level);
525
+ return createConditionalExecutionFunction(createErrorIgnoringFunction(fn), condition);
526
+ };
527
+ return freeze({
528
+ error: wrapLogFn(error, 'error'),
529
+ warn: wrapLogFn(warn, 'warn'),
530
+ log: wrapLogFn(log, 'log'),
531
+ info: wrapLogFn(info, 'info'),
532
+ debug: wrapLogFn(debug, 'debug'),
533
+ setLogLevel,
534
+ getLogLevel,
535
+ });
536
+ }
537
+ /**
538
+ * Validates whether a given value is a valid log function.
539
+ *
540
+ * @param fn - The value to validate
541
+ * @returns True if the value is not a function (invalid), false if it is valid
542
+ */
543
+ function notValidLogFn(fn) {
544
+ return getType(fn) !== 'function' && fn !== noop;
545
+ }
546
+ /**
547
+ * Generates an error message for invalid log function parameters.
548
+ *
549
+ * @param label - The name of the log function that failed validation
550
+ * @returns A formatted error message string
551
+ */
552
+ function notFnMsg(label) {
553
+ return `Cannot create a logger when ${label} is not a function`;
554
+ }
555
+
556
+ createLogger(error, warn, log, info, debug);
557
+
558
+ /**
559
+ * Global log level registry.
560
+ * Tracks all created scoped loggers to allow global log level changes.
561
+ */
562
+ const loggerRegistry = createSet();
563
+ /** Redacted placeholder for sensitive values */
564
+ const REDACTED = '[REDACTED]';
565
+ /**
566
+ * Patterns that indicate a sensitive key name.
567
+ * Keys containing these patterns will have their values sanitized.
568
+ */
569
+ const SENSITIVE_KEY_PATTERNS = [
570
+ /token/i,
571
+ /key/i,
572
+ /password/i,
573
+ /secret/i,
574
+ /credential/i,
575
+ /auth/i,
576
+ /bearer/i,
577
+ /api[_-]?key/i,
578
+ /private/i,
579
+ /passphrase/i,
580
+ ];
581
+ /**
582
+ * Checks if a key name indicates sensitive data.
583
+ *
584
+ * @param key - Key name to check
585
+ * @returns True if the key indicates sensitive data
586
+ */
587
+ function isSensitiveKey(key) {
588
+ return SENSITIVE_KEY_PATTERNS.some((pattern) => pattern.test(key));
589
+ }
590
+ /**
591
+ * Sanitizes an object by replacing sensitive values with REDACTED.
592
+ * This function recursively processes nested objects and arrays.
593
+ *
594
+ * @param obj - Object to sanitize
595
+ * @returns New object with sensitive values redacted
596
+ */
597
+ function sanitize(obj) {
598
+ if (obj === null || obj === undefined) {
599
+ return obj;
600
+ }
601
+ if (isArray(obj)) {
602
+ return obj.map((item) => sanitize(item));
603
+ }
604
+ if (typeof obj === 'object') {
605
+ const result = {};
606
+ for (const [key, value] of entries(obj)) {
607
+ if (isSensitiveKey(key)) {
608
+ result[key] = REDACTED;
609
+ }
610
+ else if (typeof value === 'object' && value !== null) {
611
+ result[key] = sanitize(value);
612
+ }
613
+ else {
614
+ result[key] = value;
615
+ }
616
+ }
617
+ return result;
618
+ }
619
+ return obj;
620
+ }
621
+ /**
622
+ * Formats a log message with optional metadata.
623
+ *
624
+ * @param namespace - Logger namespace prefix
625
+ * @param message - Log message
626
+ * @param meta - Optional metadata object
627
+ * @returns Formatted log string
628
+ */
629
+ function formatMessage(namespace, message, meta) {
630
+ const prefix = `[${namespace}]`;
631
+ if (meta && keys(meta).length > 0) {
632
+ const sanitizedMeta = sanitize(meta);
633
+ return `${prefix} ${message} ${stringify(sanitizedMeta)}`;
634
+ }
635
+ return `${prefix} ${message}`;
636
+ }
637
+ /**
638
+ * Creates a scoped logger with namespace prefix and optional secret sanitization.
639
+ * All log messages will be prefixed with [namespace] and sensitive metadata
640
+ * values will be automatically redacted.
641
+ *
642
+ * @param namespace - Logger namespace (e.g., 'project-scope', 'analyze')
643
+ * @param options - Logger configuration options
644
+ * @returns A configured scoped logger instance
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * const logger = createScopedLogger('project-scope')
649
+ * logger.setLogLevel('debug')
650
+ *
651
+ * // Basic logging
652
+ * logger.info('Starting analysis', { path: './project' })
653
+ *
654
+ * // Sensitive data is automatically redacted
655
+ * logger.debug('Config loaded', { apiKey: 'secret123' })
656
+ * // Output: [project-scope] Config loaded {"apiKey":"[REDACTED]"}
657
+ * ```
658
+ */
659
+ function createScopedLogger(namespace, options = {}) {
660
+ const { level = 'error', sanitizeSecrets = true } = options;
661
+ // Create wrapper functions that add namespace prefix and sanitization
662
+ const createLogFn = (baseFn) => (message, meta) => {
663
+ const processedMeta = sanitizeSecrets && meta ? sanitize(meta) : meta;
664
+ baseFn(formatMessage(namespace, message, processedMeta));
665
+ };
666
+ // Create base logger with wrapped functions
667
+ const baseLogger = createLogger(createLogFn(error), createLogFn(warn), createLogFn(log), createLogFn(info), createLogFn(debug));
668
+ // Set initial log level (use global override if set)
669
+ baseLogger.setLogLevel(level);
670
+ const scopedLogger = freeze({
671
+ error: (message, meta) => baseLogger.error(message, meta),
672
+ warn: (message, meta) => baseLogger.warn(message, meta),
673
+ log: (message, meta) => baseLogger.log(message, meta),
674
+ info: (message, meta) => baseLogger.info(message, meta),
675
+ debug: (message, meta) => baseLogger.debug(message, meta),
676
+ setLogLevel: baseLogger.setLogLevel,
677
+ getLogLevel: baseLogger.getLogLevel,
678
+ });
679
+ // Register logger for global level management
680
+ loggerRegistry.add(scopedLogger);
681
+ return scopedLogger;
682
+ }
683
+ /**
684
+ * Default logger instance for the project-scope library.
685
+ * Use this for general logging within the library.
686
+ *
687
+ * @example
688
+ * ```typescript
689
+ * import { logger } from '@hyperfrontend/project-scope/core'
690
+ *
691
+ * logger.setLogLevel('debug')
692
+ * logger.debug('Analyzing project', { path: './src' })
693
+ * ```
694
+ */
695
+ createScopedLogger('project-scope');
696
+
697
+ createScopedLogger('project-scope:fs');
698
+ /**
699
+ * Create a file system error with code and context.
700
+ *
701
+ * @param message - The error message describing what went wrong
702
+ * @param code - The category code for this type of filesystem failure
703
+ * @param context - Additional context including path, operation, and cause
704
+ * @returns A configured Error object with code and context properties
705
+ */
706
+ function createFileSystemError(message, code, context) {
707
+ const error = createError(message);
708
+ defineProperties(error, {
709
+ code: { value: code, enumerable: true },
710
+ context: { value: context, enumerable: true },
711
+ });
712
+ return error;
713
+ }
714
+ /**
715
+ * Read file if exists, return null otherwise.
716
+ *
717
+ * @param filePath - Path to file
718
+ * @param encoding - File encoding (default: utf-8)
719
+ * @returns File contents or null if file doesn't exist
720
+ */
721
+ function readFileIfExists(filePath, encoding = 'utf-8') {
722
+ if (!existsSync(filePath)) {
723
+ return null;
724
+ }
725
+ try {
726
+ return readFileSync(filePath, { encoding });
727
+ }
728
+ catch {
729
+ return null;
730
+ }
731
+ }
732
+
733
+ createScopedLogger('project-scope:fs:write');
734
+
735
+ /**
736
+ * Get file stats with error handling.
737
+ *
738
+ * @param filePath - Path to file
739
+ * @param followSymlinks - Whether to follow symlinks (default: true)
740
+ * @returns File stats or null if path doesn't exist
741
+ */
742
+ function getFileStat(filePath, followSymlinks = true) {
743
+ if (!existsSync(filePath)) {
744
+ return null;
745
+ }
746
+ try {
747
+ const stat = followSymlinks ? statSync(filePath) : lstatSync(filePath);
748
+ return {
749
+ isFile: stat.isFile(),
750
+ isDirectory: stat.isDirectory(),
751
+ isSymlink: stat.isSymbolicLink(),
752
+ size: stat.size,
753
+ created: stat.birthtime,
754
+ modified: stat.mtime,
755
+ accessed: stat.atime,
756
+ mode: stat.mode,
757
+ };
758
+ }
759
+ catch {
760
+ return null;
761
+ }
762
+ }
763
+ /**
764
+ * Check if path is a directory.
765
+ *
766
+ * @param dirPath - Path to check
767
+ * @returns True if path is a directory
768
+ */
769
+ function isDirectory(dirPath) {
770
+ const stats = getFileStat(dirPath);
771
+ return stats?.isDirectory ?? false;
772
+ }
773
+ /**
774
+ * Check if path exists.
775
+ *
776
+ * @param filePath - Path to check
777
+ * @returns True if path exists
778
+ */
779
+ function exists(filePath) {
780
+ return existsSync(filePath);
781
+ }
782
+
783
+ const fsDirLogger = createScopedLogger('project-scope:fs:dir');
784
+ /**
785
+ * List immediate contents of a directory.
786
+ *
787
+ * @param dirPath - Absolute or relative path to the directory
788
+ * @returns Array of entries with metadata for each file/directory
789
+ * @throws {Error} If directory doesn't exist or isn't a directory
790
+ *
791
+ * @example
792
+ * ```typescript
793
+ * import { readDirectory } from '@hyperfrontend/project-scope'
794
+ *
795
+ * const entries = readDirectory('./src')
796
+ * for (const entry of entries) {
797
+ * console.log(entry.name, entry.isFile ? 'file' : 'directory')
798
+ * }
799
+ * ```
800
+ */
801
+ function readDirectory(dirPath) {
802
+ fsDirLogger.debug('Reading directory', { path: dirPath });
803
+ if (!existsSync(dirPath)) {
804
+ fsDirLogger.debug('Directory not found', { path: dirPath });
805
+ throw createFileSystemError(`Directory not found: ${dirPath}`, 'FS_NOT_FOUND', { path: dirPath, operation: 'readdir' });
806
+ }
807
+ if (!isDirectory(dirPath)) {
808
+ fsDirLogger.debug('Path is not a directory', { path: dirPath });
809
+ throw createFileSystemError(`Not a directory: ${dirPath}`, 'FS_NOT_A_DIRECTORY', { path: dirPath, operation: 'readdir' });
810
+ }
811
+ try {
812
+ const entries = readdirSync(dirPath, { withFileTypes: true });
813
+ fsDirLogger.debug('Directory read complete', { path: dirPath, entryCount: entries.length });
814
+ return entries.map((entry) => ({
815
+ name: entry.name,
816
+ path: join$1(dirPath, entry.name),
817
+ isFile: entry.isFile(),
818
+ isDirectory: entry.isDirectory(),
819
+ isSymlink: entry.isSymbolicLink(),
820
+ }));
821
+ }
822
+ catch (error) {
823
+ fsDirLogger.warn('Failed to read directory', { path: dirPath, error: error instanceof Error ? error.message : String(error) });
824
+ throw createFileSystemError(`Failed to read directory: ${dirPath}`, 'FS_READ_ERROR', {
825
+ path: dirPath,
826
+ operation: 'readdir',
827
+ cause: error,
828
+ });
829
+ }
830
+ }
831
+
832
+ /**
833
+ * Join path segments.
834
+ * Uses platform-specific separators (e.g., / or \).
835
+ *
836
+ * @param paths - Path segments to join
837
+ * @returns Joined path
838
+ */
839
+ function join(...paths) {
840
+ return join$1(...paths);
841
+ }
842
+
843
+ createScopedLogger('project-scope:fs:traversal');
844
+
845
+ const packageLogger = createScopedLogger('project-scope:project:package');
846
+ /**
847
+ * Verifies that a value is an object with only string values,
848
+ * used for validating dependency maps and script definitions.
849
+ *
850
+ * @param value - Value to check
851
+ * @returns True if value is a record of strings
852
+ */
853
+ function isStringRecord(value) {
854
+ if (typeof value !== 'object' || value === null)
855
+ return false;
856
+ return values(value).every((v) => typeof v === 'string');
857
+ }
858
+ /**
859
+ * Extracts and normalizes the workspaces field from package.json,
860
+ * supporting both array format and object with packages array.
861
+ *
862
+ * @param value - Raw workspaces value from package.json
863
+ * @returns Normalized workspace patterns or undefined if invalid
864
+ */
865
+ function parseWorkspaces(value) {
866
+ if (isArray(value) && value.every((v) => typeof v === 'string')) {
867
+ return value;
868
+ }
869
+ if (typeof value === 'object' && value !== null) {
870
+ const obj = value;
871
+ if (isArray(obj['packages'])) {
872
+ return { packages: obj['packages'] };
873
+ }
874
+ }
875
+ return undefined;
876
+ }
877
+ /**
878
+ * Validate and normalize package.json data.
879
+ *
880
+ * @param data - Raw parsed data
881
+ * @returns Validated package.json
882
+ */
883
+ function validatePackageJson(data) {
884
+ if (typeof data !== 'object' || data === null) {
885
+ throw createError('package.json must be an object');
886
+ }
887
+ const pkg = data;
888
+ return {
889
+ name: typeof pkg['name'] === 'string' ? pkg['name'] : undefined,
890
+ version: typeof pkg['version'] === 'string' ? pkg['version'] : undefined,
891
+ description: typeof pkg['description'] === 'string' ? pkg['description'] : undefined,
892
+ main: typeof pkg['main'] === 'string' ? pkg['main'] : undefined,
893
+ module: typeof pkg['module'] === 'string' ? pkg['module'] : undefined,
894
+ browser: typeof pkg['browser'] === 'string' ? pkg['browser'] : undefined,
895
+ types: typeof pkg['types'] === 'string' ? pkg['types'] : undefined,
896
+ bin: typeof pkg['bin'] === 'string' || isStringRecord(pkg['bin']) ? pkg['bin'] : undefined,
897
+ scripts: isStringRecord(pkg['scripts']) ? pkg['scripts'] : undefined,
898
+ dependencies: isStringRecord(pkg['dependencies']) ? pkg['dependencies'] : undefined,
899
+ devDependencies: isStringRecord(pkg['devDependencies']) ? pkg['devDependencies'] : undefined,
900
+ peerDependencies: isStringRecord(pkg['peerDependencies']) ? pkg['peerDependencies'] : undefined,
901
+ optionalDependencies: isStringRecord(pkg['optionalDependencies']) ? pkg['optionalDependencies'] : undefined,
902
+ workspaces: parseWorkspaces(pkg['workspaces']),
903
+ exports: typeof pkg['exports'] === 'object' ? pkg['exports'] : undefined,
904
+ engines: isStringRecord(pkg['engines']) ? pkg['engines'] : undefined,
905
+ ...pkg,
906
+ };
907
+ }
908
+ /**
909
+ * Attempts to read and parse package.json if it exists,
910
+ * returning null on missing file or parse failure.
911
+ *
912
+ * @param projectPath - Project directory path or path to package.json
913
+ * @returns Parsed package.json or null if not found
914
+ */
915
+ function readPackageJsonIfExists(projectPath) {
916
+ const packageJsonPath = projectPath.endsWith('package.json') ? projectPath : join$1(projectPath, 'package.json');
917
+ const content = readFileIfExists(packageJsonPath);
918
+ if (!content) {
919
+ packageLogger.debug('Package.json not found', { path: packageJsonPath });
920
+ return null;
921
+ }
922
+ try {
923
+ const validated = validatePackageJson(parse(content));
924
+ packageLogger.debug('Package.json loaded', { path: packageJsonPath, name: validated.name });
925
+ return validated;
926
+ }
927
+ catch {
928
+ packageLogger.debug('Failed to parse package.json, returning null', { path: packageJsonPath });
929
+ return null;
930
+ }
931
+ }
932
+
933
+ /**
934
+ * Get combined dependencies from package.json.
935
+ * Merges dependencies, devDependencies, peerDependencies, and optionalDependencies.
936
+ *
937
+ * @param packageJson - The package.json object to extract dependencies from
938
+ * @returns Combined dependencies as a single record
939
+ */
940
+ function collectAllDependencies(packageJson) {
941
+ return {
942
+ ...packageJson?.dependencies,
943
+ ...packageJson?.devDependencies,
944
+ ...packageJson?.peerDependencies,
945
+ ...packageJson?.optionalDependencies,
946
+ };
947
+ }
948
+ /**
949
+ * Extract clean version from dependency version string.
950
+ * Removes semver prefixes like ^, ~, >=, etc.
951
+ * Uses character-by-character parsing to avoid ReDoS vulnerabilities.
952
+ *
953
+ * @param versionString - The version string with optional prefix characters
954
+ * @returns The cleaned version string without prefix characters
955
+ */
956
+ function parseVersionString(versionString) {
957
+ if (versionString === undefined || versionString === null)
958
+ return undefined;
959
+ // Manual parsing instead of regex to avoid ReDoS
960
+ let start = 0;
961
+ while (start < versionString.length) {
962
+ const char = versionString[start];
963
+ if (char !== '^' && char !== '~' && char !== '>' && char !== '=' && char !== '<') {
964
+ break;
965
+ }
966
+ start++;
967
+ }
968
+ return versionString.slice(start);
969
+ }
970
+ /**
971
+ * Find first matching config file in project.
972
+ * Note: Name avoids similarity to fs.readFile/fs.readFileSync.
973
+ *
974
+ * @param projectPath - The project directory path
975
+ * @param patterns - Array of config file patterns to search for
976
+ * @returns The first matching config file path or undefined
977
+ */
978
+ function locateConfigFile(projectPath, patterns) {
979
+ for (const pattern of patterns) {
980
+ const fullPath = join(projectPath, pattern);
981
+ if (exists(fullPath)) {
982
+ return pattern;
983
+ }
984
+ }
985
+ return undefined;
986
+ }
987
+ /**
988
+ * Find scripts containing a specific command.
989
+ *
990
+ * @param scripts - The scripts object from package.json
991
+ * @param command - The command string to search for
992
+ * @returns Array of script names that contain the command
993
+ */
994
+ function filterScriptsByCommand(scripts, command) {
995
+ if (!scripts)
996
+ return [];
997
+ return entries(scripts)
998
+ .filter(([, script]) => script.includes(command))
999
+ .map(([name]) => name);
1000
+ }
1001
+
1002
+ /**
1003
+ * Detect Express in project.
1004
+ *
1005
+ * @param projectPath - Project directory path
1006
+ * @param packageJson - Optional pre-loaded package.json
1007
+ * @returns Detection result or null if not detected
1008
+ */
1009
+ function expressDetector(projectPath, packageJson) {
1010
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1011
+ const sources = [];
1012
+ let confidence = 0;
1013
+ let version;
1014
+ const deps = collectAllDependencies(pkg);
1015
+ if (deps['express']) {
1016
+ confidence += 80;
1017
+ version = parseVersionString(deps['express']);
1018
+ sources.push({ type: 'package.json', field: 'dependencies.express' });
1019
+ }
1020
+ // @types/express (indicates usage)
1021
+ if (deps['@types/express']) {
1022
+ confidence += 10;
1023
+ sources.push({ type: 'package.json', field: 'dependencies.@types/express' });
1024
+ }
1025
+ const expressMiddleware = keys(deps).filter((d) => d.includes('express-') || d === 'body-parser' || d === 'cors' || d === 'helmet' || d === 'morgan');
1026
+ if (expressMiddleware.length > 0) {
1027
+ confidence += 10;
1028
+ sources.push({ type: 'package.json', field: 'dependencies (express middleware)' });
1029
+ }
1030
+ if (confidence === 0) {
1031
+ return null;
1032
+ }
1033
+ return {
1034
+ id: 'express',
1035
+ name: 'Express',
1036
+ version,
1037
+ confidence: min(confidence, 100),
1038
+ detectedFrom: sources,
1039
+ };
1040
+ }
1041
+
1042
+ /**
1043
+ * Detect NestJS in project.
1044
+ *
1045
+ * @param projectPath - Project directory path
1046
+ * @param packageJson - Optional pre-loaded package.json
1047
+ * @returns Detection result or null if not detected
1048
+ */
1049
+ function nestDetector(projectPath, packageJson) {
1050
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1051
+ const sources = [];
1052
+ let confidence = 0;
1053
+ let version;
1054
+ let configPath;
1055
+ const deps = collectAllDependencies(pkg);
1056
+ // @nestjs/core package
1057
+ if (deps['@nestjs/core']) {
1058
+ confidence += 70;
1059
+ version = parseVersionString(deps['@nestjs/core']);
1060
+ sources.push({ type: 'package.json', field: 'dependencies.@nestjs/core' });
1061
+ }
1062
+ // @nestjs/common
1063
+ if (deps['@nestjs/common']) {
1064
+ confidence += 15;
1065
+ sources.push({ type: 'package.json', field: 'dependencies.@nestjs/common' });
1066
+ }
1067
+ if (exists(join$1(projectPath, 'nest-cli.json'))) {
1068
+ confidence += 15;
1069
+ configPath = 'nest-cli.json';
1070
+ sources.push({ type: 'config-file', path: 'nest-cli.json' });
1071
+ }
1072
+ const nestPackages = keys(deps).filter((d) => d.startsWith('@nestjs/'));
1073
+ if (nestPackages.length > 2) {
1074
+ confidence += 5;
1075
+ sources.push({ type: 'package.json', field: 'dependencies (@nestjs packages)' });
1076
+ }
1077
+ if (confidence === 0) {
1078
+ return null;
1079
+ }
1080
+ return {
1081
+ id: 'nestjs',
1082
+ name: 'NestJS',
1083
+ version,
1084
+ configPath,
1085
+ confidence: min(confidence, 100),
1086
+ detectedFrom: sources,
1087
+ };
1088
+ }
1089
+
1090
+ /**
1091
+ * Detect Fastify in project.
1092
+ *
1093
+ * @param projectPath - Project directory path
1094
+ * @param packageJson - Optional pre-loaded package.json
1095
+ * @returns Detection result or null if not detected
1096
+ */
1097
+ function fastifyDetector(projectPath, packageJson) {
1098
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1099
+ const sources = [];
1100
+ let confidence = 0;
1101
+ let version;
1102
+ const deps = collectAllDependencies(pkg);
1103
+ if (deps['fastify']) {
1104
+ confidence += 80;
1105
+ version = parseVersionString(deps['fastify']);
1106
+ sources.push({ type: 'package.json', field: 'dependencies.fastify' });
1107
+ }
1108
+ const fastifyPlugins = keys(deps).filter((d) => d.startsWith('@fastify/') || d.startsWith('fastify-'));
1109
+ if (fastifyPlugins.length > 0) {
1110
+ confidence += 15;
1111
+ sources.push({ type: 'package.json', field: 'dependencies (fastify plugins)' });
1112
+ }
1113
+ // @types/fastify (older versions)
1114
+ if (deps['@types/fastify']) {
1115
+ confidence += 5;
1116
+ sources.push({ type: 'package.json', field: 'dependencies.@types/fastify' });
1117
+ }
1118
+ if (confidence === 0) {
1119
+ return null;
1120
+ }
1121
+ return {
1122
+ id: 'fastify',
1123
+ name: 'Fastify',
1124
+ version,
1125
+ confidence: min(confidence, 100),
1126
+ detectedFrom: sources,
1127
+ };
1128
+ }
1129
+
1130
+ /**
1131
+ * Detect Koa in project.
1132
+ *
1133
+ * @param projectPath - Project directory path
1134
+ * @param packageJson - Optional pre-loaded package.json
1135
+ * @returns Detection result or null if not detected
1136
+ */
1137
+ function koaDetector(projectPath, packageJson) {
1138
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1139
+ const sources = [];
1140
+ let confidence = 0;
1141
+ let version;
1142
+ const deps = collectAllDependencies(pkg);
1143
+ if (deps['koa']) {
1144
+ confidence += 80;
1145
+ version = parseVersionString(deps['koa']);
1146
+ sources.push({ type: 'package.json', field: 'dependencies.koa' });
1147
+ }
1148
+ // @types/koa
1149
+ if (deps['@types/koa']) {
1150
+ confidence += 10;
1151
+ sources.push({ type: 'package.json', field: 'dependencies.@types/koa' });
1152
+ }
1153
+ const koaMiddleware = keys(deps).filter((d) => d.startsWith('koa-') || d.startsWith('@koa/'));
1154
+ if (koaMiddleware.length > 0) {
1155
+ confidence += 10;
1156
+ sources.push({ type: 'package.json', field: 'dependencies (koa middleware)' });
1157
+ }
1158
+ if (confidence === 0) {
1159
+ return null;
1160
+ }
1161
+ return {
1162
+ id: 'koa',
1163
+ name: 'Koa',
1164
+ version,
1165
+ confidence: min(confidence, 100),
1166
+ detectedFrom: sources,
1167
+ };
1168
+ }
1169
+
1170
+ /**
1171
+ * Detect Hono in project.
1172
+ *
1173
+ * @param projectPath - Project directory path
1174
+ * @param packageJson - Optional pre-loaded package.json
1175
+ * @returns Detection result or null if not detected
1176
+ */
1177
+ function honoDetector(projectPath, packageJson) {
1178
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1179
+ const sources = [];
1180
+ let confidence = 0;
1181
+ let version;
1182
+ const deps = collectAllDependencies(pkg);
1183
+ if (deps['hono']) {
1184
+ confidence += 85;
1185
+ version = parseVersionString(deps['hono']);
1186
+ sources.push({ type: 'package.json', field: 'dependencies.hono' });
1187
+ }
1188
+ const honoAdapters = keys(deps).filter((d) => d.startsWith('@hono/'));
1189
+ if (honoAdapters.length > 0) {
1190
+ confidence += 15;
1191
+ sources.push({ type: 'package.json', field: 'dependencies (@hono adapters)' });
1192
+ }
1193
+ if (confidence === 0) {
1194
+ return null;
1195
+ }
1196
+ return {
1197
+ id: 'hono',
1198
+ name: 'Hono',
1199
+ version,
1200
+ confidence: min(confidence, 100),
1201
+ detectedFrom: sources,
1202
+ };
1203
+ }
1204
+
1205
+ /** All backend framework detectors */
1206
+ const backendDetectors = [
1207
+ { id: 'express', name: 'Express', detect: expressDetector },
1208
+ { id: 'nestjs', name: 'NestJS', detect: nestDetector },
1209
+ { id: 'fastify', name: 'Fastify', detect: fastifyDetector },
1210
+ { id: 'koa', name: 'Koa', detect: koaDetector },
1211
+ { id: 'hono', name: 'Hono', detect: honoDetector },
1212
+ ];
1213
+ /**
1214
+ * Detect all backend frameworks in project.
1215
+ *
1216
+ * @param projectPath - Project directory path
1217
+ * @param packageJson - Optional pre-loaded package.json
1218
+ * @returns Array of detected frameworks, sorted by confidence
1219
+ */
1220
+ function detectBackendFrameworks(projectPath, packageJson) {
1221
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1222
+ const results = [];
1223
+ for (const detector of backendDetectors) {
1224
+ const detection = detector.detect(projectPath, pkg ?? undefined);
1225
+ if (detection) {
1226
+ results.push(detection);
1227
+ }
1228
+ }
1229
+ return results.sort((a, b) => b.confidence - a.confidence);
1230
+ }
1231
+
1232
+ /** Config patterns for Webpack */
1233
+ const WEBPACK_CONFIG_PATTERNS = [
1234
+ 'webpack.config.js',
1235
+ 'webpack.config.ts',
1236
+ 'webpack.config.cjs',
1237
+ 'webpack.config.mjs',
1238
+ 'webpack.config.babel.js',
1239
+ ];
1240
+ /**
1241
+ * Detect Webpack in project.
1242
+ *
1243
+ * @param projectPath - Project directory path
1244
+ * @param packageJson - Optional pre-loaded package.json
1245
+ * @returns Detection result or null if not detected
1246
+ */
1247
+ function webpackDetector(projectPath, packageJson) {
1248
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1249
+ const sources = [];
1250
+ let confidence = 0;
1251
+ let version;
1252
+ const deps = collectAllDependencies(pkg);
1253
+ if (deps['webpack']) {
1254
+ confidence += 50;
1255
+ version = parseVersionString(deps['webpack']);
1256
+ sources.push({ type: 'package.json', field: 'dependencies.webpack' });
1257
+ }
1258
+ const configPath = locateConfigFile(projectPath, WEBPACK_CONFIG_PATTERNS);
1259
+ if (configPath) {
1260
+ confidence += 40;
1261
+ sources.push({ type: 'config-file', path: configPath });
1262
+ }
1263
+ if (deps['webpack-cli']) {
1264
+ confidence += 10;
1265
+ sources.push({ type: 'package.json', field: 'dependencies.webpack-cli' });
1266
+ }
1267
+ const scriptMatches = filterScriptsByCommand(pkg?.scripts, 'webpack');
1268
+ for (const name of scriptMatches) {
1269
+ confidence = min(confidence + 5, 100);
1270
+ sources.push({ type: 'package.json', field: `scripts.${name}` });
1271
+ }
1272
+ if (confidence === 0) {
1273
+ return null;
1274
+ }
1275
+ return {
1276
+ id: 'webpack',
1277
+ name: 'Webpack',
1278
+ version,
1279
+ configPath,
1280
+ confidence: min(confidence, 100),
1281
+ detectedFrom: sources,
1282
+ };
1283
+ }
1284
+
1285
+ /** Config patterns for Vite */
1286
+ const VITE_CONFIG_PATTERNS = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs', 'vite.config.cjs'];
1287
+ /**
1288
+ * Detect Vite in project.
1289
+ *
1290
+ * @param projectPath - Project directory path
1291
+ * @param packageJson - Optional pre-loaded package.json
1292
+ * @returns Detection result or null if not detected
1293
+ */
1294
+ function viteDetector(projectPath, packageJson) {
1295
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1296
+ const sources = [];
1297
+ let confidence = 0;
1298
+ let version;
1299
+ const deps = collectAllDependencies(pkg);
1300
+ if (deps['vite']) {
1301
+ confidence += 60;
1302
+ version = parseVersionString(deps['vite']);
1303
+ sources.push({ type: 'package.json', field: 'dependencies.vite' });
1304
+ }
1305
+ const configPath = locateConfigFile(projectPath, VITE_CONFIG_PATTERNS);
1306
+ if (configPath) {
1307
+ confidence += 35;
1308
+ sources.push({ type: 'config-file', path: configPath });
1309
+ }
1310
+ if (deps['vitest']) {
1311
+ confidence += 10;
1312
+ sources.push({ type: 'package.json', field: 'dependencies.vitest' });
1313
+ }
1314
+ const vitePlugins = keys(deps).filter((d) => d.startsWith('vite-plugin-') || d.startsWith('@vitejs/'));
1315
+ if (vitePlugins.length > 0) {
1316
+ confidence += 10;
1317
+ sources.push({ type: 'package.json', field: 'dependencies (vite plugins)' });
1318
+ }
1319
+ if (confidence === 0) {
1320
+ return null;
1321
+ }
1322
+ return {
1323
+ id: 'vite',
1324
+ name: 'Vite',
1325
+ version,
1326
+ configPath,
1327
+ confidence: min(confidence, 100),
1328
+ detectedFrom: sources,
1329
+ };
1330
+ }
1331
+
1332
+ /** Config patterns for Rollup */
1333
+ const ROLLUP_CONFIG_PATTERNS = ['rollup.config.js', 'rollup.config.ts', 'rollup.config.mjs', 'rollup.config.cjs'];
1334
+ /**
1335
+ * Detect Rollup in project.
1336
+ *
1337
+ * @param projectPath - Project directory path
1338
+ * @param packageJson - Optional pre-loaded package.json
1339
+ * @returns Detection result or null if not detected
1340
+ */
1341
+ function rollupDetector(projectPath, packageJson) {
1342
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1343
+ const sources = [];
1344
+ let confidence = 0;
1345
+ let version;
1346
+ const deps = collectAllDependencies(pkg);
1347
+ if (deps['rollup']) {
1348
+ confidence += 55;
1349
+ version = parseVersionString(deps['rollup']);
1350
+ sources.push({ type: 'package.json', field: 'dependencies.rollup' });
1351
+ }
1352
+ const configPath = locateConfigFile(projectPath, ROLLUP_CONFIG_PATTERNS);
1353
+ if (configPath) {
1354
+ confidence += 40;
1355
+ sources.push({ type: 'config-file', path: configPath });
1356
+ }
1357
+ const rollupPlugins = keys(deps).filter((d) => d.startsWith('@rollup/') || d.startsWith('rollup-plugin-'));
1358
+ if (rollupPlugins.length > 0) {
1359
+ confidence += 10;
1360
+ sources.push({ type: 'package.json', field: 'dependencies (rollup plugins)' });
1361
+ }
1362
+ const scriptMatches = filterScriptsByCommand(pkg?.scripts, 'rollup');
1363
+ for (const name of scriptMatches) {
1364
+ confidence = min(confidence + 5, 100);
1365
+ sources.push({ type: 'package.json', field: `scripts.${name}` });
1366
+ }
1367
+ if (confidence === 0) {
1368
+ return null;
1369
+ }
1370
+ return {
1371
+ id: 'rollup',
1372
+ name: 'Rollup',
1373
+ version,
1374
+ configPath,
1375
+ confidence: min(confidence, 100),
1376
+ detectedFrom: sources,
1377
+ };
1378
+ }
1379
+
1380
+ /**
1381
+ * Detect esbuild in project.
1382
+ *
1383
+ * @param projectPath - Project directory path
1384
+ * @param packageJson - Optional pre-loaded package.json
1385
+ * @returns Detection result or null if not detected
1386
+ */
1387
+ function esbuildDetector(projectPath, packageJson) {
1388
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1389
+ const sources = [];
1390
+ let confidence = 0;
1391
+ let version;
1392
+ const deps = collectAllDependencies(pkg);
1393
+ if (deps['esbuild']) {
1394
+ confidence += 70;
1395
+ version = parseVersionString(deps['esbuild']);
1396
+ sources.push({ type: 'package.json', field: 'dependencies.esbuild' });
1397
+ }
1398
+ const esbuildPlugins = keys(deps).filter((d) => d.includes('esbuild-plugin') || d.includes('esbuild-'));
1399
+ if (esbuildPlugins.length > 0) {
1400
+ confidence += 15;
1401
+ sources.push({ type: 'package.json', field: 'dependencies (esbuild plugins)' });
1402
+ }
1403
+ const scriptMatches = filterScriptsByCommand(pkg?.scripts, 'esbuild');
1404
+ for (const name of scriptMatches) {
1405
+ confidence = min(confidence + 10, 100);
1406
+ sources.push({ type: 'package.json', field: `scripts.${name}` });
1407
+ }
1408
+ if (confidence === 0) {
1409
+ return null;
1410
+ }
1411
+ return {
1412
+ id: 'esbuild',
1413
+ name: 'esbuild',
1414
+ version,
1415
+ confidence: min(confidence, 100),
1416
+ detectedFrom: sources,
1417
+ };
1418
+ }
1419
+
1420
+ /** Config patterns for Babel */
1421
+ const BABEL_CONFIG_PATTERNS = ['babel.config.js', 'babel.config.cjs', 'babel.config.mjs', 'babel.config.json', '.babelrc', '.babelrc.json', '.babelrc.js'];
1422
+ /**
1423
+ * Detect Babel in project.
1424
+ *
1425
+ * @param projectPath - Project directory path
1426
+ * @param packageJson - Optional pre-loaded package.json
1427
+ * @returns Detection result or null if not detected
1428
+ */
1429
+ function babelDetector(projectPath, packageJson) {
1430
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1431
+ const sources = [];
1432
+ let confidence = 0;
1433
+ let version;
1434
+ const deps = collectAllDependencies(pkg);
1435
+ if (deps['@babel/core']) {
1436
+ confidence += 50;
1437
+ version = parseVersionString(deps['@babel/core']);
1438
+ sources.push({ type: 'package.json', field: 'dependencies.@babel/core' });
1439
+ }
1440
+ const configPath = locateConfigFile(projectPath, BABEL_CONFIG_PATTERNS);
1441
+ if (configPath) {
1442
+ confidence += 40;
1443
+ sources.push({ type: 'config-file', path: configPath });
1444
+ }
1445
+ if (pkg && 'babel' in pkg) {
1446
+ confidence += 30;
1447
+ sources.push({ type: 'package.json', field: 'babel' });
1448
+ }
1449
+ const babelPackages = keys(deps).filter((d) => d.startsWith('@babel/'));
1450
+ if (babelPackages.length > 1) {
1451
+ confidence += 10;
1452
+ sources.push({ type: 'package.json', field: 'dependencies (@babel packages)' });
1453
+ }
1454
+ if (confidence === 0) {
1455
+ return null;
1456
+ }
1457
+ return {
1458
+ id: 'babel',
1459
+ name: 'Babel',
1460
+ version,
1461
+ configPath,
1462
+ confidence: min(confidence, 100),
1463
+ detectedFrom: sources,
1464
+ };
1465
+ }
1466
+
1467
+ /** Config patterns for SWC */
1468
+ const SWC_CONFIG_PATTERNS = ['.swcrc', 'swc.config.js'];
1469
+ /**
1470
+ * Detect SWC in project.
1471
+ *
1472
+ * @param projectPath - Project directory path
1473
+ * @param packageJson - Optional pre-loaded package.json
1474
+ * @returns Detection result or null if not detected
1475
+ */
1476
+ function swcDetector(projectPath, packageJson) {
1477
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1478
+ const sources = [];
1479
+ let confidence = 0;
1480
+ let version;
1481
+ const deps = collectAllDependencies(pkg);
1482
+ if (deps['@swc/core']) {
1483
+ confidence += 60;
1484
+ version = parseVersionString(deps['@swc/core']);
1485
+ sources.push({ type: 'package.json', field: 'dependencies.@swc/core' });
1486
+ }
1487
+ const configPath = locateConfigFile(projectPath, SWC_CONFIG_PATTERNS);
1488
+ if (configPath) {
1489
+ confidence += 35;
1490
+ sources.push({ type: 'config-file', path: configPath });
1491
+ }
1492
+ if (deps['@swc/cli']) {
1493
+ confidence += 10;
1494
+ sources.push({ type: 'package.json', field: 'dependencies.@swc/cli' });
1495
+ }
1496
+ const swcPlugins = keys(deps).filter((d) => d.startsWith('@swc/') || d.includes('swc-plugin'));
1497
+ if (swcPlugins.length > 1) {
1498
+ confidence += 5;
1499
+ sources.push({ type: 'package.json', field: 'dependencies (@swc packages)' });
1500
+ }
1501
+ if (confidence === 0) {
1502
+ return null;
1503
+ }
1504
+ return {
1505
+ id: 'swc',
1506
+ name: 'SWC',
1507
+ version,
1508
+ configPath,
1509
+ confidence: min(confidence, 100),
1510
+ detectedFrom: sources,
1511
+ };
1512
+ }
1513
+
1514
+ /** Config patterns for Parcel */
1515
+ const PARCEL_CONFIG_PATTERNS = ['.parcelrc'];
1516
+ /**
1517
+ * Detect Parcel in project.
1518
+ *
1519
+ * @param projectPath - Project directory path
1520
+ * @param packageJson - Optional pre-loaded package.json
1521
+ * @returns Detection result or null if not detected
1522
+ */
1523
+ function parcelDetector(projectPath, packageJson) {
1524
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1525
+ const sources = [];
1526
+ let confidence = 0;
1527
+ let version;
1528
+ const deps = collectAllDependencies(pkg);
1529
+ if (deps['parcel']) {
1530
+ confidence += 60;
1531
+ version = parseVersionString(deps['parcel']);
1532
+ sources.push({ type: 'package.json', field: 'dependencies.parcel' });
1533
+ }
1534
+ if (deps['parcel-bundler']) {
1535
+ confidence += 60;
1536
+ version = parseVersionString(deps['parcel-bundler']);
1537
+ sources.push({ type: 'package.json', field: 'dependencies.parcel-bundler' });
1538
+ }
1539
+ const configPath = locateConfigFile(projectPath, PARCEL_CONFIG_PATTERNS);
1540
+ if (configPath) {
1541
+ confidence += 30;
1542
+ sources.push({ type: 'config-file', path: configPath });
1543
+ }
1544
+ const scriptMatches = filterScriptsByCommand(pkg?.scripts, 'parcel');
1545
+ for (const name of scriptMatches) {
1546
+ confidence = min(confidence + 10, 100);
1547
+ sources.push({ type: 'package.json', field: `scripts.${name}` });
1548
+ }
1549
+ if (confidence === 0) {
1550
+ return null;
1551
+ }
1552
+ return {
1553
+ id: 'parcel',
1554
+ name: 'Parcel',
1555
+ version,
1556
+ configPath,
1557
+ confidence: min(confidence, 100),
1558
+ detectedFrom: sources,
1559
+ };
1560
+ }
1561
+
1562
+ /** All build tool detectors */
1563
+ const buildToolDetectors = [
1564
+ { id: 'webpack', name: 'Webpack', detect: webpackDetector },
1565
+ { id: 'vite', name: 'Vite', detect: viteDetector },
1566
+ { id: 'rollup', name: 'Rollup', detect: rollupDetector },
1567
+ { id: 'esbuild', name: 'esbuild', detect: esbuildDetector },
1568
+ { id: 'babel', name: 'Babel', detect: babelDetector },
1569
+ { id: 'swc', name: 'SWC', detect: swcDetector },
1570
+ { id: 'parcel', name: 'Parcel', detect: parcelDetector },
1571
+ ];
1572
+ /**
1573
+ * Detect all build tools in project.
1574
+ *
1575
+ * @param projectPath - Project directory path
1576
+ * @param packageJson - Optional pre-loaded package.json
1577
+ * @returns Array of detected build tools, sorted by confidence
1578
+ */
1579
+ function detectBuildTools(projectPath, packageJson) {
1580
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1581
+ const results = [];
1582
+ for (const detector of buildToolDetectors) {
1583
+ const detection = detector.detect(projectPath, pkg ?? undefined);
1584
+ if (detection) {
1585
+ results.push(detection);
1586
+ }
1587
+ }
1588
+ return results.sort((a, b) => b.confidence - a.confidence);
1589
+ }
1590
+
1591
+ /**
1592
+ * Detect React in project.
1593
+ *
1594
+ * @param projectPath - Project directory path
1595
+ * @param packageJson - Optional pre-loaded package.json
1596
+ * @returns Detection result or null if not detected
1597
+ */
1598
+ function reactDetector(projectPath, packageJson) {
1599
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1600
+ const sources = [];
1601
+ let confidence = 0;
1602
+ let version;
1603
+ const metaFrameworks = [];
1604
+ const deps = collectAllDependencies(pkg);
1605
+ if (deps['react']) {
1606
+ confidence += 60;
1607
+ version = parseVersionString(deps['react']);
1608
+ sources.push({ type: 'package.json', field: 'dependencies.react' });
1609
+ }
1610
+ if (deps['react-dom']) {
1611
+ confidence += 20;
1612
+ sources.push({ type: 'package.json', field: 'dependencies.react-dom' });
1613
+ }
1614
+ if (deps['react-native']) {
1615
+ confidence += 20;
1616
+ sources.push({ type: 'package.json', field: 'dependencies.react-native' });
1617
+ }
1618
+ const hasJsxFiles = exists(join$1(projectPath, 'src', 'App.tsx')) ||
1619
+ exists(join$1(projectPath, 'src', 'App.jsx')) ||
1620
+ exists(join$1(projectPath, 'src', 'index.tsx')) ||
1621
+ exists(join$1(projectPath, 'src', 'index.jsx'));
1622
+ if (hasJsxFiles) {
1623
+ confidence += 10;
1624
+ sources.push({ type: 'directory', path: 'src/*.tsx or src/*.jsx' });
1625
+ }
1626
+ if (deps['next']) {
1627
+ metaFrameworks.push({
1628
+ id: 'nextjs',
1629
+ name: 'Next.js',
1630
+ category: 'meta-framework',
1631
+ version: parseVersionString(deps['next']),
1632
+ confidence: 90,
1633
+ detectedFrom: [{ type: 'package.json', field: 'dependencies.next' }],
1634
+ });
1635
+ }
1636
+ if (deps['gatsby']) {
1637
+ metaFrameworks.push({
1638
+ id: 'gatsby',
1639
+ name: 'Gatsby',
1640
+ category: 'meta-framework',
1641
+ version: parseVersionString(deps['gatsby']),
1642
+ confidence: 90,
1643
+ detectedFrom: [{ type: 'package.json', field: 'dependencies.gatsby' }],
1644
+ });
1645
+ }
1646
+ if (deps['@remix-run/react'] || deps['remix']) {
1647
+ metaFrameworks.push({
1648
+ id: 'remix',
1649
+ name: 'Remix',
1650
+ category: 'meta-framework',
1651
+ version: parseVersionString(deps['@remix-run/react'] ?? deps['remix']),
1652
+ confidence: 90,
1653
+ detectedFrom: [{ type: 'package.json', field: 'dependencies.@remix-run/react' }],
1654
+ });
1655
+ }
1656
+ if (confidence === 0) {
1657
+ return null;
1658
+ }
1659
+ return {
1660
+ id: 'react',
1661
+ name: 'React',
1662
+ category: 'frontend',
1663
+ version,
1664
+ confidence: min(confidence, 100),
1665
+ detectedFrom: sources,
1666
+ metaFrameworks: metaFrameworks.length > 0 ? metaFrameworks : undefined,
1667
+ };
1668
+ }
1669
+
1670
+ /**
1671
+ * Detect Next.js in project.
1672
+ *
1673
+ * @param projectPath - Project directory path
1674
+ * @param packageJson - Optional pre-loaded package.json
1675
+ * @returns Detection result or null if not detected
1676
+ */
1677
+ function nextjsDetector(projectPath, packageJson) {
1678
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1679
+ const sources = [];
1680
+ let confidence = 0;
1681
+ let version;
1682
+ const deps = collectAllDependencies(pkg);
1683
+ if (deps['next']) {
1684
+ confidence += 70;
1685
+ version = parseVersionString(deps['next']);
1686
+ sources.push({ type: 'package.json', field: 'dependencies.next' });
1687
+ }
1688
+ if (exists(join$1(projectPath, 'next.config.js')) ||
1689
+ exists(join$1(projectPath, 'next.config.mjs')) ||
1690
+ exists(join$1(projectPath, 'next.config.ts'))) {
1691
+ confidence += 25;
1692
+ sources.push({ type: 'config-file', path: 'next.config.*' });
1693
+ }
1694
+ if (exists(join$1(projectPath, 'pages')) ||
1695
+ exists(join$1(projectPath, 'app')) ||
1696
+ exists(join$1(projectPath, 'src', 'pages')) ||
1697
+ exists(join$1(projectPath, 'src', 'app'))) {
1698
+ confidence += 5;
1699
+ sources.push({ type: 'directory', path: 'pages/ or app/' });
1700
+ }
1701
+ if (confidence === 0) {
1702
+ return null;
1703
+ }
1704
+ return {
1705
+ id: 'nextjs',
1706
+ name: 'Next.js',
1707
+ category: 'meta-framework',
1708
+ version,
1709
+ confidence: min(confidence, 100),
1710
+ detectedFrom: sources,
1711
+ };
1712
+ }
1713
+
1714
+ /**
1715
+ * Detect Remix in project.
1716
+ *
1717
+ * @param projectPath - Project directory path
1718
+ * @param packageJson - Optional pre-loaded package.json
1719
+ * @returns Detection result or null if not detected
1720
+ */
1721
+ function remixDetector(projectPath, packageJson) {
1722
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1723
+ const sources = [];
1724
+ let confidence = 0;
1725
+ let version;
1726
+ const deps = collectAllDependencies(pkg);
1727
+ // @remix-run packages
1728
+ if (deps['@remix-run/react']) {
1729
+ confidence += 70;
1730
+ version = parseVersionString(deps['@remix-run/react']);
1731
+ sources.push({ type: 'package.json', field: 'dependencies.@remix-run/react' });
1732
+ }
1733
+ if (deps['@remix-run/node'] || deps['@remix-run/cloudflare'] || deps['@remix-run/deno']) {
1734
+ confidence += 20;
1735
+ sources.push({ type: 'package.json', field: 'dependencies.@remix-run/*' });
1736
+ }
1737
+ if (exists(join$1(projectPath, 'remix.config.js')) || exists(join$1(projectPath, 'remix.config.ts'))) {
1738
+ confidence += 10;
1739
+ sources.push({ type: 'config-file', path: 'remix.config.*' });
1740
+ }
1741
+ if (confidence === 0) {
1742
+ return null;
1743
+ }
1744
+ return {
1745
+ id: 'remix',
1746
+ name: 'Remix',
1747
+ category: 'meta-framework',
1748
+ version,
1749
+ confidence: min(confidence, 100),
1750
+ detectedFrom: sources,
1751
+ };
1752
+ }
1753
+
1754
+ /**
1755
+ * Detect Gatsby in project.
1756
+ *
1757
+ * @param projectPath - Project directory path
1758
+ * @param packageJson - Optional pre-loaded package.json
1759
+ * @returns Detection result or null if not detected
1760
+ */
1761
+ function gatsbyDetector(projectPath, packageJson) {
1762
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1763
+ const sources = [];
1764
+ let confidence = 0;
1765
+ let version;
1766
+ const deps = collectAllDependencies(pkg);
1767
+ if (deps['gatsby']) {
1768
+ confidence += 70;
1769
+ version = parseVersionString(deps['gatsby']);
1770
+ sources.push({ type: 'package.json', field: 'dependencies.gatsby' });
1771
+ }
1772
+ if (exists(join$1(projectPath, 'gatsby-config.js')) || exists(join$1(projectPath, 'gatsby-config.ts'))) {
1773
+ confidence += 25;
1774
+ sources.push({ type: 'config-file', path: 'gatsby-config.*' });
1775
+ }
1776
+ const gatsbyPlugins = keys(deps).filter((d) => d.startsWith('gatsby-plugin-') || d.startsWith('gatsby-source-'));
1777
+ if (gatsbyPlugins.length > 0) {
1778
+ confidence += 5;
1779
+ sources.push({ type: 'package.json', field: 'dependencies (gatsby plugins)' });
1780
+ }
1781
+ if (confidence === 0) {
1782
+ return null;
1783
+ }
1784
+ return {
1785
+ id: 'gatsby',
1786
+ name: 'Gatsby',
1787
+ category: 'meta-framework',
1788
+ version,
1789
+ confidence: min(confidence, 100),
1790
+ detectedFrom: sources,
1791
+ };
1792
+ }
1793
+
1794
+ /**
1795
+ * Detect Vue in project.
1796
+ *
1797
+ * @param projectPath - Project directory path
1798
+ * @param packageJson - Optional pre-loaded package.json
1799
+ * @returns Detection result or null if not detected
1800
+ */
1801
+ function vueDetector(projectPath, packageJson) {
1802
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1803
+ const sources = [];
1804
+ let confidence = 0;
1805
+ let version;
1806
+ const metaFrameworks = [];
1807
+ const deps = collectAllDependencies(pkg);
1808
+ if (deps['vue']) {
1809
+ confidence += 70;
1810
+ version = parseVersionString(deps['vue']);
1811
+ sources.push({ type: 'package.json', field: 'dependencies.vue' });
1812
+ }
1813
+ if (deps['@vue/cli-service']) {
1814
+ confidence += 15;
1815
+ sources.push({ type: 'package.json', field: 'dependencies.@vue/cli-service' });
1816
+ }
1817
+ const hasVueFiles = exists(join$1(projectPath, 'src', 'App.vue')) || exists(join$1(projectPath, 'src', 'main.vue'));
1818
+ if (hasVueFiles) {
1819
+ confidence += 10;
1820
+ sources.push({ type: 'directory', path: 'src/*.vue' });
1821
+ }
1822
+ if (exists(join$1(projectPath, 'vue.config.js'))) {
1823
+ confidence += 5;
1824
+ sources.push({ type: 'config-file', path: 'vue.config.js' });
1825
+ }
1826
+ if (deps['nuxt'] || deps['nuxt3']) {
1827
+ metaFrameworks.push({
1828
+ id: 'nuxt',
1829
+ name: 'Nuxt',
1830
+ category: 'meta-framework',
1831
+ version: parseVersionString(deps['nuxt'] ?? deps['nuxt3']),
1832
+ confidence: 90,
1833
+ detectedFrom: [{ type: 'package.json', field: 'dependencies.nuxt' }],
1834
+ });
1835
+ }
1836
+ if (confidence === 0) {
1837
+ return null;
1838
+ }
1839
+ return {
1840
+ id: 'vue',
1841
+ name: 'Vue',
1842
+ category: 'frontend',
1843
+ version,
1844
+ confidence: min(confidence, 100),
1845
+ detectedFrom: sources,
1846
+ metaFrameworks: metaFrameworks.length > 0 ? metaFrameworks : undefined,
1847
+ };
1848
+ }
1849
+
1850
+ /**
1851
+ * Detect Nuxt in project.
1852
+ *
1853
+ * @param projectPath - Project directory path
1854
+ * @param packageJson - Optional pre-loaded package.json
1855
+ * @returns Detection result or null if not detected
1856
+ */
1857
+ function nuxtDetector(projectPath, packageJson) {
1858
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1859
+ const sources = [];
1860
+ let confidence = 0;
1861
+ let version;
1862
+ const deps = collectAllDependencies(pkg);
1863
+ if (deps['nuxt'] || deps['nuxt3']) {
1864
+ confidence += 70;
1865
+ version = parseVersionString(deps['nuxt'] ?? deps['nuxt3']);
1866
+ sources.push({ type: 'package.json', field: 'dependencies.nuxt' });
1867
+ }
1868
+ if (exists(join$1(projectPath, 'nuxt.config.js')) || exists(join$1(projectPath, 'nuxt.config.ts'))) {
1869
+ confidence += 25;
1870
+ sources.push({ type: 'config-file', path: 'nuxt.config.*' });
1871
+ }
1872
+ if (exists(join$1(projectPath, 'pages'))) {
1873
+ confidence += 5;
1874
+ sources.push({ type: 'directory', path: 'pages/' });
1875
+ }
1876
+ if (confidence === 0) {
1877
+ return null;
1878
+ }
1879
+ return {
1880
+ id: 'nuxt',
1881
+ name: 'Nuxt',
1882
+ category: 'meta-framework',
1883
+ version,
1884
+ confidence: min(confidence, 100),
1885
+ detectedFrom: sources,
1886
+ };
1887
+ }
1888
+
1889
+ /**
1890
+ * Detect Angular in project.
1891
+ *
1892
+ * @param projectPath - Project directory path
1893
+ * @param packageJson - Optional pre-loaded package.json
1894
+ * @returns Detection result or null if not detected
1895
+ */
1896
+ function angularDetector(projectPath, packageJson) {
1897
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1898
+ const sources = [];
1899
+ let confidence = 0;
1900
+ let version;
1901
+ const deps = collectAllDependencies(pkg);
1902
+ if (deps['@angular/core']) {
1903
+ confidence += 70;
1904
+ version = parseVersionString(deps['@angular/core']);
1905
+ sources.push({ type: 'package.json', field: 'dependencies.@angular/core' });
1906
+ }
1907
+ if (deps['@angular/cli']) {
1908
+ confidence += 15;
1909
+ sources.push({ type: 'package.json', field: 'dependencies.@angular/cli' });
1910
+ }
1911
+ if (exists(join$1(projectPath, 'angular.json'))) {
1912
+ confidence += 15;
1913
+ sources.push({ type: 'config-file', path: 'angular.json' });
1914
+ }
1915
+ if (deps['angular'] && !deps['@angular/core']) {
1916
+ return {
1917
+ id: 'angularjs',
1918
+ name: 'AngularJS (Legacy)',
1919
+ category: 'frontend',
1920
+ version: parseVersionString(deps['angular']),
1921
+ confidence: 80,
1922
+ detectedFrom: [{ type: 'package.json', field: 'dependencies.angular' }],
1923
+ };
1924
+ }
1925
+ if (confidence === 0) {
1926
+ return null;
1927
+ }
1928
+ return {
1929
+ id: 'angular',
1930
+ name: 'Angular',
1931
+ category: 'frontend',
1932
+ version,
1933
+ confidence: min(confidence, 100),
1934
+ detectedFrom: sources,
1935
+ };
1936
+ }
1937
+
1938
+ /**
1939
+ * Detect Svelte in project.
1940
+ *
1941
+ * @param projectPath - Project directory path
1942
+ * @param packageJson - Optional pre-loaded package.json
1943
+ * @returns Detection result or null if not detected
1944
+ */
1945
+ function svelteDetector(projectPath, packageJson) {
1946
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1947
+ const sources = [];
1948
+ let confidence = 0;
1949
+ let version;
1950
+ const metaFrameworks = [];
1951
+ const deps = collectAllDependencies(pkg);
1952
+ if (deps['svelte']) {
1953
+ confidence += 70;
1954
+ version = parseVersionString(deps['svelte']);
1955
+ sources.push({ type: 'package.json', field: 'dependencies.svelte' });
1956
+ }
1957
+ if (exists(join$1(projectPath, 'svelte.config.js'))) {
1958
+ confidence += 20;
1959
+ sources.push({ type: 'config-file', path: 'svelte.config.js' });
1960
+ }
1961
+ const hasSvelteFiles = exists(join$1(projectPath, 'src', 'App.svelte')) || exists(join$1(projectPath, 'src', 'routes'));
1962
+ if (hasSvelteFiles) {
1963
+ confidence += 10;
1964
+ sources.push({ type: 'directory', path: 'src/*.svelte or src/routes/' });
1965
+ }
1966
+ if (deps['@sveltejs/kit']) {
1967
+ metaFrameworks.push({
1968
+ id: 'sveltekit',
1969
+ name: 'SvelteKit',
1970
+ category: 'meta-framework',
1971
+ version: parseVersionString(deps['@sveltejs/kit']),
1972
+ confidence: 90,
1973
+ detectedFrom: [{ type: 'package.json', field: 'dependencies.@sveltejs/kit' }],
1974
+ });
1975
+ }
1976
+ if (confidence === 0) {
1977
+ return null;
1978
+ }
1979
+ return {
1980
+ id: 'svelte',
1981
+ name: 'Svelte',
1982
+ category: 'frontend',
1983
+ version,
1984
+ confidence: min(confidence, 100),
1985
+ detectedFrom: sources,
1986
+ metaFrameworks: metaFrameworks.length > 0 ? metaFrameworks : undefined,
1987
+ };
1988
+ }
1989
+
1990
+ /**
1991
+ * Detect SvelteKit in project.
1992
+ *
1993
+ * @param projectPath - Project directory path
1994
+ * @param packageJson - Optional pre-loaded package.json
1995
+ * @returns Detection result or null if not detected
1996
+ */
1997
+ function sveltekitDetector(projectPath, packageJson) {
1998
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
1999
+ const sources = [];
2000
+ let confidence = 0;
2001
+ let version;
2002
+ const deps = collectAllDependencies(pkg);
2003
+ // @sveltejs/kit package
2004
+ if (deps['@sveltejs/kit']) {
2005
+ confidence += 70;
2006
+ version = parseVersionString(deps['@sveltejs/kit']);
2007
+ sources.push({ type: 'package.json', field: 'dependencies.@sveltejs/kit' });
2008
+ }
2009
+ if (exists(join$1(projectPath, 'svelte.config.js'))) {
2010
+ confidence += 20;
2011
+ sources.push({ type: 'config-file', path: 'svelte.config.js' });
2012
+ }
2013
+ if (exists(join$1(projectPath, 'src', 'routes'))) {
2014
+ confidence += 10;
2015
+ sources.push({ type: 'directory', path: 'src/routes/' });
2016
+ }
2017
+ if (confidence === 0) {
2018
+ return null;
2019
+ }
2020
+ return {
2021
+ id: 'sveltekit',
2022
+ name: 'SvelteKit',
2023
+ category: 'meta-framework',
2024
+ version,
2025
+ confidence: min(confidence, 100),
2026
+ detectedFrom: sources,
2027
+ };
2028
+ }
2029
+
2030
+ /**
2031
+ * Detect Solid in project.
2032
+ *
2033
+ * @param projectPath - Project directory path
2034
+ * @param packageJson - Optional pre-loaded package.json
2035
+ * @returns Detection result or null if not detected
2036
+ */
2037
+ function solidDetector(projectPath, packageJson) {
2038
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2039
+ const sources = [];
2040
+ let confidence = 0;
2041
+ let version;
2042
+ const deps = collectAllDependencies(pkg);
2043
+ if (deps['solid-js']) {
2044
+ confidence += 70;
2045
+ version = parseVersionString(deps['solid-js']);
2046
+ sources.push({ type: 'package.json', field: 'dependencies.solid-js' });
2047
+ }
2048
+ if (deps['vite-plugin-solid']) {
2049
+ confidence += 20;
2050
+ sources.push({ type: 'package.json', field: 'dependencies.vite-plugin-solid' });
2051
+ }
2052
+ if (deps['solid-start'] || deps['@solidjs/start']) {
2053
+ confidence += 10;
2054
+ sources.push({ type: 'package.json', field: 'dependencies.solid-start' });
2055
+ }
2056
+ if (confidence === 0) {
2057
+ return null;
2058
+ }
2059
+ return {
2060
+ id: 'solid',
2061
+ name: 'Solid',
2062
+ category: 'frontend',
2063
+ version,
2064
+ confidence: min(confidence, 100),
2065
+ detectedFrom: sources,
2066
+ };
2067
+ }
2068
+
2069
+ /**
2070
+ * Detect Qwik in project.
2071
+ *
2072
+ * @param projectPath - Project directory path
2073
+ * @param packageJson - Optional pre-loaded package.json
2074
+ * @returns Detection result or null if not detected
2075
+ */
2076
+ function qwikDetector(projectPath, packageJson) {
2077
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2078
+ const sources = [];
2079
+ let confidence = 0;
2080
+ let version;
2081
+ const deps = collectAllDependencies(pkg);
2082
+ // @builder.io/qwik package
2083
+ if (deps['@builder.io/qwik']) {
2084
+ confidence += 70;
2085
+ version = parseVersionString(deps['@builder.io/qwik']);
2086
+ sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik' });
2087
+ }
2088
+ // @builder.io/qwik-city
2089
+ if (deps['@builder.io/qwik-city']) {
2090
+ confidence += 20;
2091
+ sources.push({ type: 'package.json', field: 'dependencies.@builder.io/qwik-city' });
2092
+ }
2093
+ if (exists(join$1(projectPath, 'qwik.config.ts')) || exists(join$1(projectPath, 'qwik.config.js'))) {
2094
+ confidence += 10;
2095
+ sources.push({ type: 'config-file', path: 'qwik.config.*' });
2096
+ }
2097
+ if (confidence === 0) {
2098
+ return null;
2099
+ }
2100
+ return {
2101
+ id: 'qwik',
2102
+ name: 'Qwik',
2103
+ category: 'frontend',
2104
+ version,
2105
+ confidence: min(confidence, 100),
2106
+ detectedFrom: sources,
2107
+ };
2108
+ }
2109
+
2110
+ /**
2111
+ * Detect Astro in project.
2112
+ *
2113
+ * @param projectPath - Project directory path
2114
+ * @param packageJson - Optional pre-loaded package.json
2115
+ * @returns Detection result or null if not detected
2116
+ */
2117
+ function astroDetector(projectPath, packageJson) {
2118
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2119
+ const sources = [];
2120
+ let confidence = 0;
2121
+ let version;
2122
+ const deps = collectAllDependencies(pkg);
2123
+ if (deps['astro']) {
2124
+ confidence += 70;
2125
+ version = parseVersionString(deps['astro']);
2126
+ sources.push({ type: 'package.json', field: 'dependencies.astro' });
2127
+ }
2128
+ if (exists(join$1(projectPath, 'astro.config.mjs')) ||
2129
+ exists(join$1(projectPath, 'astro.config.ts')) ||
2130
+ exists(join$1(projectPath, 'astro.config.js'))) {
2131
+ confidence += 25;
2132
+ sources.push({ type: 'config-file', path: 'astro.config.*' });
2133
+ }
2134
+ if (exists(join$1(projectPath, 'src', 'pages'))) {
2135
+ confidence += 5;
2136
+ sources.push({ type: 'directory', path: 'src/pages/' });
2137
+ }
2138
+ if (confidence === 0) {
2139
+ return null;
2140
+ }
2141
+ return {
2142
+ id: 'astro',
2143
+ name: 'Astro',
2144
+ category: 'meta-framework',
2145
+ version,
2146
+ confidence: min(confidence, 100),
2147
+ detectedFrom: sources,
2148
+ };
2149
+ }
2150
+
2151
+ /** All frontend framework detectors */
2152
+ const frameworkDetectors = [
2153
+ { id: 'react', name: 'React', category: 'frontend', detect: reactDetector },
2154
+ { id: 'nextjs', name: 'Next.js', category: 'meta-framework', detect: nextjsDetector },
2155
+ { id: 'remix', name: 'Remix', category: 'meta-framework', detect: remixDetector },
2156
+ { id: 'gatsby', name: 'Gatsby', category: 'meta-framework', detect: gatsbyDetector },
2157
+ { id: 'vue', name: 'Vue', category: 'frontend', detect: vueDetector },
2158
+ { id: 'nuxt', name: 'Nuxt', category: 'meta-framework', detect: nuxtDetector },
2159
+ { id: 'angular', name: 'Angular', category: 'frontend', detect: angularDetector },
2160
+ { id: 'svelte', name: 'Svelte', category: 'frontend', detect: svelteDetector },
2161
+ { id: 'sveltekit', name: 'SvelteKit', category: 'meta-framework', detect: sveltekitDetector },
2162
+ { id: 'solid', name: 'Solid', category: 'frontend', detect: solidDetector },
2163
+ { id: 'qwik', name: 'Qwik', category: 'frontend', detect: qwikDetector },
2164
+ { id: 'astro', name: 'Astro', category: 'meta-framework', detect: astroDetector },
2165
+ ];
2166
+ /**
2167
+ * Detect all frontend frameworks in project.
2168
+ *
2169
+ * @param projectPath - Project directory path
2170
+ * @param packageJson - Optional pre-loaded package.json
2171
+ * @returns Array of detected frameworks, sorted by confidence
2172
+ */
2173
+ function detectFrontendFrameworks(projectPath, packageJson) {
2174
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2175
+ const results = [];
2176
+ for (const detector of frameworkDetectors) {
2177
+ const detection = detector.detect(projectPath, pkg ?? undefined);
2178
+ if (detection) {
2179
+ results.push(detection);
2180
+ }
2181
+ }
2182
+ return results.sort((a, b) => b.confidence - a.confidence);
2183
+ }
2184
+
2185
+ /**
2186
+ * Detect AngularJS (1.x) in project.
2187
+ * AngularJS is the original Angular framework, distinct from Angular 2+.
2188
+ *
2189
+ * @param projectPath - Project directory path
2190
+ * @param packageJson - Optional pre-loaded package.json
2191
+ * @returns Detection result or null if not detected
2192
+ */
2193
+ function angularJSDetector(projectPath, packageJson) {
2194
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2195
+ const sources = [];
2196
+ let confidence = 0;
2197
+ let version;
2198
+ const deps = collectAllDependencies(pkg);
2199
+ // AngularJS package (angular, not @angular/core)
2200
+ if (deps['angular']) {
2201
+ confidence += 70;
2202
+ version = parseVersionString(deps['angular']);
2203
+ sources.push({ type: 'package.json', field: 'dependencies.angular' });
2204
+ }
2205
+ // AngularJS router
2206
+ if (deps['angular-route']) {
2207
+ confidence += 15;
2208
+ sources.push({ type: 'package.json', field: 'dependencies.angular-route' });
2209
+ }
2210
+ // AngularJS resource
2211
+ if (deps['angular-resource']) {
2212
+ confidence += 10;
2213
+ sources.push({ type: 'package.json', field: 'dependencies.angular-resource' });
2214
+ }
2215
+ // AngularJS animate
2216
+ if (deps['angular-animate']) {
2217
+ confidence += 5;
2218
+ sources.push({ type: 'package.json', field: 'dependencies.angular-animate' });
2219
+ }
2220
+ if (confidence === 0) {
2221
+ return null;
2222
+ }
2223
+ return {
2224
+ id: 'angularjs',
2225
+ name: 'AngularJS',
2226
+ category: 'legacy-frontend',
2227
+ version,
2228
+ confidence: min(confidence, 100),
2229
+ detectedFrom: sources,
2230
+ };
2231
+ }
2232
+
2233
+ /**
2234
+ * Detect Backbone.js in project.
2235
+ *
2236
+ * @param projectPath - Project directory path
2237
+ * @param packageJson - Optional pre-loaded package.json
2238
+ * @returns Detection result or null if not detected
2239
+ */
2240
+ function backboneDetector(projectPath, packageJson) {
2241
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2242
+ const sources = [];
2243
+ let confidence = 0;
2244
+ let version;
2245
+ const deps = collectAllDependencies(pkg);
2246
+ // Backbone package
2247
+ if (deps['backbone']) {
2248
+ confidence += 70;
2249
+ version = parseVersionString(deps['backbone']);
2250
+ sources.push({ type: 'package.json', field: 'dependencies.backbone' });
2251
+ // Underscore (commonly used with Backbone)
2252
+ if (deps['underscore']) {
2253
+ confidence += 15;
2254
+ sources.push({ type: 'package.json', field: 'dependencies.underscore' });
2255
+ }
2256
+ // Lodash can be used as underscore replacement
2257
+ if (deps['lodash']) {
2258
+ confidence += 5;
2259
+ sources.push({ type: 'package.json', field: 'dependencies.lodash' });
2260
+ }
2261
+ }
2262
+ // Marionette (Backbone framework)
2263
+ if (deps['backbone.marionette'] || deps['marionette']) {
2264
+ confidence += 10;
2265
+ sources.push({ type: 'package.json', field: 'dependencies.backbone.marionette' });
2266
+ }
2267
+ if (confidence === 0) {
2268
+ return null;
2269
+ }
2270
+ return {
2271
+ id: 'backbone',
2272
+ name: 'Backbone.js',
2273
+ category: 'legacy-frontend',
2274
+ version,
2275
+ confidence: min(confidence, 100),
2276
+ detectedFrom: sources,
2277
+ };
2278
+ }
2279
+
2280
+ /**
2281
+ * Detect Ember.js in project.
2282
+ *
2283
+ * @param projectPath - Project directory path
2284
+ * @param packageJson - Optional pre-loaded package.json
2285
+ * @returns Detection result or null if not detected
2286
+ */
2287
+ function emberDetector(projectPath, packageJson) {
2288
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2289
+ const sources = [];
2290
+ let confidence = 0;
2291
+ let version;
2292
+ const deps = collectAllDependencies(pkg);
2293
+ // Ember source package
2294
+ if (deps['ember-source']) {
2295
+ confidence += 70;
2296
+ version = parseVersionString(deps['ember-source']);
2297
+ sources.push({ type: 'package.json', field: 'dependencies.ember-source' });
2298
+ }
2299
+ // Ember CLI
2300
+ if (deps['ember-cli']) {
2301
+ confidence += 20;
2302
+ sources.push({ type: 'package.json', field: 'devDependencies.ember-cli' });
2303
+ }
2304
+ // Ember Data
2305
+ if (deps['ember-data']) {
2306
+ confidence += 10;
2307
+ sources.push({ type: 'package.json', field: 'dependencies.ember-data' });
2308
+ }
2309
+ if (confidence === 0) {
2310
+ return null;
2311
+ }
2312
+ return {
2313
+ id: 'ember',
2314
+ name: 'Ember.js',
2315
+ category: 'legacy-frontend',
2316
+ version,
2317
+ confidence: min(confidence, 100),
2318
+ detectedFrom: sources,
2319
+ };
2320
+ }
2321
+
2322
+ /**
2323
+ * Detect jQuery in project.
2324
+ *
2325
+ * @param projectPath - Project directory path
2326
+ * @param packageJson - Optional pre-loaded package.json
2327
+ * @returns Detection result or null if not detected
2328
+ */
2329
+ function jqueryDetector(projectPath, packageJson) {
2330
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2331
+ const sources = [];
2332
+ let confidence = 0;
2333
+ let version;
2334
+ const deps = collectAllDependencies(pkg);
2335
+ // jQuery package
2336
+ if (deps['jquery']) {
2337
+ confidence += 80;
2338
+ version = parseVersionString(deps['jquery']);
2339
+ sources.push({ type: 'package.json', field: 'dependencies.jquery' });
2340
+ }
2341
+ // jQuery UI
2342
+ if (deps['jquery-ui']) {
2343
+ confidence += 10;
2344
+ sources.push({ type: 'package.json', field: 'dependencies.jquery-ui' });
2345
+ }
2346
+ // jQuery plugins
2347
+ if (deps['jquery-validation']) {
2348
+ confidence += 5;
2349
+ sources.push({ type: 'package.json', field: 'dependencies.jquery-validation' });
2350
+ }
2351
+ if (confidence === 0) {
2352
+ return null;
2353
+ }
2354
+ return {
2355
+ id: 'jquery',
2356
+ name: 'jQuery',
2357
+ category: 'legacy-frontend',
2358
+ version,
2359
+ confidence: min(confidence, 100),
2360
+ detectedFrom: sources,
2361
+ };
2362
+ }
2363
+
2364
+ /** All legacy framework detectors */
2365
+ const legacyDetectors = [
2366
+ { id: 'angularjs', name: 'AngularJS', category: 'legacy-frontend', detect: angularJSDetector },
2367
+ { id: 'backbone', name: 'Backbone.js', category: 'legacy-frontend', detect: backboneDetector },
2368
+ { id: 'ember', name: 'Ember.js', category: 'legacy-frontend', detect: emberDetector },
2369
+ { id: 'jquery', name: 'jQuery', category: 'legacy-frontend', detect: jqueryDetector },
2370
+ ];
2371
+ /**
2372
+ * Detect all legacy frameworks in project.
2373
+ *
2374
+ * @param projectPath - Project directory path
2375
+ * @param packageJson - Optional pre-loaded package.json
2376
+ * @returns Array of detected legacy frameworks, sorted by confidence
2377
+ */
2378
+ function detectLegacyFrameworks(projectPath, packageJson) {
2379
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2380
+ const results = [];
2381
+ for (const detector of legacyDetectors) {
2382
+ const detection = detector.detect(projectPath, pkg ?? undefined);
2383
+ if (detection) {
2384
+ results.push(detection);
2385
+ }
2386
+ }
2387
+ return results.sort((a, b) => b.confidence - a.confidence);
2388
+ }
2389
+
2390
+ /** Config patterns for ESLint */
2391
+ const ESLINT_CONFIG_PATTERNS = [
2392
+ 'eslint.config.js',
2393
+ 'eslint.config.mjs',
2394
+ 'eslint.config.cjs',
2395
+ 'eslint.config.ts',
2396
+ '.eslintrc.js',
2397
+ '.eslintrc.cjs',
2398
+ '.eslintrc.json',
2399
+ '.eslintrc.yaml',
2400
+ '.eslintrc.yml',
2401
+ '.eslintrc',
2402
+ ];
2403
+ /**
2404
+ * Detect ESLint in project.
2405
+ *
2406
+ * @param projectPath - Project directory path
2407
+ * @param packageJson - Optional pre-loaded package.json
2408
+ * @returns Detection result or null if not detected
2409
+ */
2410
+ function eslintDetector(projectPath, packageJson) {
2411
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2412
+ const sources = [];
2413
+ let confidence = 0;
2414
+ let version;
2415
+ const deps = collectAllDependencies(pkg);
2416
+ if (deps['eslint']) {
2417
+ confidence += 50;
2418
+ version = parseVersionString(deps['eslint']);
2419
+ sources.push({ type: 'package.json', field: 'dependencies.eslint' });
2420
+ }
2421
+ const configPath = locateConfigFile(projectPath, ESLINT_CONFIG_PATTERNS);
2422
+ if (configPath) {
2423
+ confidence += 40;
2424
+ sources.push({ type: 'config-file', path: configPath });
2425
+ }
2426
+ if (pkg && 'eslintConfig' in pkg) {
2427
+ confidence += 30;
2428
+ sources.push({ type: 'package.json', field: 'eslintConfig' });
2429
+ }
2430
+ const eslintPlugins = keys(deps).filter((d) => d.startsWith('eslint-plugin-') || d.startsWith('@typescript-eslint/') || d.startsWith('eslint-config-'));
2431
+ if (eslintPlugins.length > 0) {
2432
+ confidence += 10;
2433
+ sources.push({ type: 'package.json', field: 'dependencies (eslint plugins)' });
2434
+ }
2435
+ const lintScript = pkg?.scripts?.['lint'] ?? '';
2436
+ if (lintScript.includes('eslint')) {
2437
+ confidence += 5;
2438
+ sources.push({ type: 'package.json', field: 'scripts.lint' });
2439
+ }
2440
+ if (confidence === 0) {
2441
+ return null;
2442
+ }
2443
+ return {
2444
+ id: 'eslint',
2445
+ name: 'ESLint',
2446
+ version,
2447
+ configPath,
2448
+ confidence: min(confidence, 100),
2449
+ detectedFrom: sources,
2450
+ };
2451
+ }
2452
+
2453
+ /** Config patterns for Prettier */
2454
+ const PRETTIER_CONFIG_PATTERNS = [
2455
+ 'prettier.config.js',
2456
+ 'prettier.config.mjs',
2457
+ 'prettier.config.cjs',
2458
+ '.prettierrc',
2459
+ '.prettierrc.json',
2460
+ '.prettierrc.yaml',
2461
+ '.prettierrc.yml',
2462
+ '.prettierrc.js',
2463
+ '.prettierrc.cjs',
2464
+ '.prettierrc.toml',
2465
+ ];
2466
+ /**
2467
+ * Detect Prettier in project.
2468
+ *
2469
+ * @param projectPath - Project directory path
2470
+ * @param packageJson - Optional pre-loaded package.json
2471
+ * @returns Detection result or null if not detected
2472
+ */
2473
+ function prettierDetector(projectPath, packageJson) {
2474
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2475
+ const sources = [];
2476
+ let confidence = 0;
2477
+ let version;
2478
+ const deps = collectAllDependencies(pkg);
2479
+ if (deps['prettier']) {
2480
+ confidence += 50;
2481
+ version = parseVersionString(deps['prettier']);
2482
+ sources.push({ type: 'package.json', field: 'dependencies.prettier' });
2483
+ }
2484
+ const configPath = locateConfigFile(projectPath, PRETTIER_CONFIG_PATTERNS);
2485
+ if (configPath) {
2486
+ confidence += 40;
2487
+ sources.push({ type: 'config-file', path: configPath });
2488
+ }
2489
+ if (pkg && 'prettier' in pkg) {
2490
+ confidence += 30;
2491
+ sources.push({ type: 'package.json', field: 'prettier' });
2492
+ }
2493
+ // .prettierignore file
2494
+ if (exists(join$1(projectPath, '.prettierignore'))) {
2495
+ confidence += 10;
2496
+ sources.push({ type: 'config-file', path: '.prettierignore' });
2497
+ }
2498
+ const prettierPlugins = keys(deps).filter((d) => d.startsWith('prettier-plugin-'));
2499
+ if (prettierPlugins.length > 0) {
2500
+ confidence += 5;
2501
+ sources.push({ type: 'package.json', field: 'dependencies (prettier plugins)' });
2502
+ }
2503
+ const formatScript = pkg?.scripts?.['format'] ?? pkg?.scripts?.['prettier'] ?? '';
2504
+ if (formatScript.includes('prettier')) {
2505
+ confidence += 5;
2506
+ sources.push({ type: 'package.json', field: 'scripts.format' });
2507
+ }
2508
+ if (confidence === 0) {
2509
+ return null;
2510
+ }
2511
+ return {
2512
+ id: 'prettier',
2513
+ name: 'Prettier',
2514
+ version,
2515
+ configPath,
2516
+ confidence: min(confidence, 100),
2517
+ detectedFrom: sources,
2518
+ };
2519
+ }
2520
+
2521
+ /** Config patterns for Stylelint */
2522
+ const STYLELINT_CONFIG_PATTERNS = [
2523
+ '.stylelintrc',
2524
+ '.stylelintrc.json',
2525
+ '.stylelintrc.js',
2526
+ 'stylelint.config.js',
2527
+ 'stylelint.config.cjs',
2528
+ ];
2529
+ /**
2530
+ * Detect Stylelint in project.
2531
+ *
2532
+ * @param projectPath - Project directory path
2533
+ * @param packageJson - Optional pre-loaded package.json
2534
+ * @returns Detection result or null if not detected
2535
+ */
2536
+ function stylelintDetector(projectPath, packageJson) {
2537
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2538
+ const sources = [];
2539
+ let confidence = 0;
2540
+ let configPath;
2541
+ let version;
2542
+ const deps = collectAllDependencies(pkg);
2543
+ if (deps['stylelint']) {
2544
+ confidence += 60;
2545
+ version = parseVersionString(deps['stylelint']);
2546
+ sources.push({ type: 'package.json', field: 'dependencies.stylelint' });
2547
+ }
2548
+ for (const config of STYLELINT_CONFIG_PATTERNS) {
2549
+ if (exists(join$1(projectPath, config))) {
2550
+ confidence += 35;
2551
+ configPath = config;
2552
+ sources.push({ type: 'config-file', path: config });
2553
+ break;
2554
+ }
2555
+ }
2556
+ const stylelintPlugins = keys(deps).filter((d) => d.startsWith('stylelint-'));
2557
+ if (stylelintPlugins.length > 0) {
2558
+ confidence += 5;
2559
+ sources.push({ type: 'package.json', field: 'dependencies (stylelint plugins)' });
2560
+ }
2561
+ if (confidence === 0) {
2562
+ return null;
2563
+ }
2564
+ return {
2565
+ id: 'stylelint',
2566
+ name: 'Stylelint',
2567
+ version,
2568
+ configPath,
2569
+ confidence: min(confidence, 100),
2570
+ detectedFrom: sources,
2571
+ };
2572
+ }
2573
+
2574
+ /**
2575
+ * Detect Biome in project.
2576
+ *
2577
+ * @param projectPath - Project directory path
2578
+ * @param packageJson - Optional pre-loaded package.json
2579
+ * @returns Detection result or null if not detected
2580
+ */
2581
+ function biomeDetector(projectPath, packageJson) {
2582
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2583
+ const sources = [];
2584
+ let confidence = 0;
2585
+ let configPath;
2586
+ let version;
2587
+ const deps = collectAllDependencies(pkg);
2588
+ // @biomejs/biome package
2589
+ if (deps['@biomejs/biome']) {
2590
+ confidence += 70;
2591
+ version = parseVersionString(deps['@biomejs/biome']);
2592
+ sources.push({ type: 'package.json', field: 'dependencies.@biomejs/biome' });
2593
+ }
2594
+ if (exists(join$1(projectPath, 'biome.json'))) {
2595
+ confidence += 30;
2596
+ configPath = 'biome.json';
2597
+ sources.push({ type: 'config-file', path: 'biome.json' });
2598
+ }
2599
+ if (!configPath && exists(join$1(projectPath, 'biome.jsonc'))) {
2600
+ confidence += 30;
2601
+ configPath = 'biome.jsonc';
2602
+ sources.push({ type: 'config-file', path: 'biome.jsonc' });
2603
+ }
2604
+ if (confidence === 0) {
2605
+ return null;
2606
+ }
2607
+ return {
2608
+ id: 'biome',
2609
+ name: 'Biome',
2610
+ version,
2611
+ configPath,
2612
+ confidence: min(confidence, 100),
2613
+ detectedFrom: sources,
2614
+ };
2615
+ }
2616
+
2617
+ /** All linting tool detectors */
2618
+ const lintingDetectors = [
2619
+ { id: 'eslint', name: 'ESLint', detect: eslintDetector },
2620
+ { id: 'prettier', name: 'Prettier', detect: prettierDetector },
2621
+ { id: 'stylelint', name: 'Stylelint', detect: stylelintDetector },
2622
+ { id: 'biome', name: 'Biome', detect: biomeDetector },
2623
+ ];
2624
+ /**
2625
+ * Detect all linting tools in project.
2626
+ *
2627
+ * @param projectPath - Project directory path
2628
+ * @param packageJson - Optional pre-loaded package.json
2629
+ * @returns Array of detected linting tools, sorted by confidence
2630
+ */
2631
+ function detectLintingTools(projectPath, packageJson) {
2632
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2633
+ const results = [];
2634
+ for (const detector of lintingDetectors) {
2635
+ const detection = detector.detect(projectPath, pkg ?? undefined);
2636
+ if (detection) {
2637
+ results.push(detection);
2638
+ }
2639
+ }
2640
+ return results.sort((a, b) => b.confidence - a.confidence);
2641
+ }
2642
+
2643
+ /**
2644
+ * Detect NX in project.
2645
+ *
2646
+ * @param workspacePath - Workspace directory path
2647
+ * @param packageJson - Optional pre-loaded package.json
2648
+ * @returns Detection result or null if not detected
2649
+ */
2650
+ function nxDetector(workspacePath, packageJson) {
2651
+ const pkg = packageJson ?? readPackageJsonIfExists(workspacePath);
2652
+ const sources = [];
2653
+ let confidence = 0;
2654
+ let version;
2655
+ let workspaceLayout;
2656
+ const nxJsonPath = join$1(workspacePath, 'nx.json');
2657
+ if (exists(nxJsonPath)) {
2658
+ confidence += 70;
2659
+ sources.push({ type: 'config-file', path: 'nx.json' });
2660
+ }
2661
+ const deps = collectAllDependencies(pkg);
2662
+ if (deps['nx']) {
2663
+ confidence += 20;
2664
+ version = parseVersionString(deps['nx']);
2665
+ sources.push({ type: 'package.json', field: 'dependencies.nx' });
2666
+ }
2667
+ const hasApps = exists(join$1(workspacePath, 'apps'));
2668
+ const hasLibs = exists(join$1(workspacePath, 'libs'));
2669
+ if (hasApps || hasLibs) {
2670
+ confidence += 10;
2671
+ sources.push({ type: 'directory', path: 'apps/ or libs/' });
2672
+ workspaceLayout = {
2673
+ appsDir: hasApps ? 'apps' : '',
2674
+ libsDir: hasLibs ? 'libs' : '',
2675
+ };
2676
+ }
2677
+ const nxPackages = keys(deps).filter((d) => d.startsWith('@nx/') || d.startsWith('@nrwl/'));
2678
+ if (nxPackages.length > 0) {
2679
+ confidence += 10;
2680
+ sources.push({ type: 'package.json', field: '@nx/* packages' });
2681
+ }
2682
+ if (confidence === 0) {
2683
+ return null;
2684
+ }
2685
+ return {
2686
+ id: 'nx',
2687
+ name: 'NX',
2688
+ version,
2689
+ configPath: exists(nxJsonPath) ? 'nx.json' : undefined,
2690
+ confidence: min(confidence, 100),
2691
+ detectedFrom: sources,
2692
+ workspaceLayout,
2693
+ };
2694
+ }
2695
+
2696
+ /**
2697
+ * Detect Turborepo in project.
2698
+ *
2699
+ * @param workspacePath - Workspace directory path
2700
+ * @param packageJson - Optional pre-loaded package.json
2701
+ * @returns Detection result or null if not detected
2702
+ */
2703
+ function turborepoDetector(workspacePath, packageJson) {
2704
+ const pkg = packageJson ?? readPackageJsonIfExists(workspacePath);
2705
+ const sources = [];
2706
+ let confidence = 0;
2707
+ let version;
2708
+ let configPath;
2709
+ const turboJsonPath = join$1(workspacePath, 'turbo.json');
2710
+ if (exists(turboJsonPath)) {
2711
+ confidence += 80;
2712
+ configPath = 'turbo.json';
2713
+ sources.push({ type: 'config-file', path: 'turbo.json' });
2714
+ }
2715
+ const deps = collectAllDependencies(pkg);
2716
+ if (deps['turbo']) {
2717
+ confidence += 15;
2718
+ version = parseVersionString(deps['turbo']);
2719
+ sources.push({ type: 'package.json', field: 'dependencies.turbo' });
2720
+ }
2721
+ const scripts = pkg?.scripts ?? {};
2722
+ if (values(scripts).some((s) => s?.includes('turbo'))) {
2723
+ confidence += 5;
2724
+ sources.push({ type: 'package.json', field: 'scripts (turbo commands)' });
2725
+ }
2726
+ if (confidence === 0) {
2727
+ return null;
2728
+ }
2729
+ return {
2730
+ id: 'turborepo',
2731
+ name: 'Turborepo',
2732
+ version,
2733
+ configPath,
2734
+ confidence: min(confidence, 100),
2735
+ detectedFrom: sources,
2736
+ };
2737
+ }
2738
+
2739
+ /**
2740
+ * Detect Lerna in project.
2741
+ *
2742
+ * @param workspacePath - Workspace directory path
2743
+ * @param packageJson - Optional pre-loaded package.json
2744
+ * @returns Detection result or null if not detected
2745
+ */
2746
+ function lernaDetector(workspacePath, packageJson) {
2747
+ const pkg = packageJson ?? readPackageJsonIfExists(workspacePath);
2748
+ const sources = [];
2749
+ let confidence = 0;
2750
+ let version;
2751
+ let configPath;
2752
+ const lernaJsonPath = join$1(workspacePath, 'lerna.json');
2753
+ if (exists(lernaJsonPath)) {
2754
+ confidence += 80;
2755
+ configPath = 'lerna.json';
2756
+ sources.push({ type: 'config-file', path: 'lerna.json' });
2757
+ }
2758
+ const deps = collectAllDependencies(pkg);
2759
+ if (deps['lerna']) {
2760
+ confidence += 15;
2761
+ version = parseVersionString(deps['lerna']);
2762
+ sources.push({ type: 'package.json', field: 'dependencies.lerna' });
2763
+ }
2764
+ if (exists(join$1(workspacePath, 'packages'))) {
2765
+ confidence += 5;
2766
+ sources.push({ type: 'directory', path: 'packages/' });
2767
+ }
2768
+ if (confidence === 0) {
2769
+ return null;
2770
+ }
2771
+ return {
2772
+ id: 'lerna',
2773
+ name: 'Lerna',
2774
+ version,
2775
+ configPath,
2776
+ confidence: min(confidence, 100),
2777
+ detectedFrom: sources,
2778
+ };
2779
+ }
2780
+
2781
+ /**
2782
+ * Detect Rush in project.
2783
+ *
2784
+ * @param workspacePath - Workspace directory path
2785
+ * @param packageJson - Optional pre-loaded package.json
2786
+ * @returns Detection result or null if not detected
2787
+ */
2788
+ function rushDetector(workspacePath, packageJson) {
2789
+ const pkg = packageJson ?? readPackageJsonIfExists(workspacePath);
2790
+ const sources = [];
2791
+ let confidence = 0;
2792
+ let version;
2793
+ let configPath;
2794
+ const rushJsonPath = join$1(workspacePath, 'rush.json');
2795
+ if (exists(rushJsonPath)) {
2796
+ confidence += 90;
2797
+ configPath = 'rush.json';
2798
+ sources.push({ type: 'config-file', path: 'rush.json' });
2799
+ }
2800
+ const deps = collectAllDependencies(pkg);
2801
+ if (deps['@microsoft/rush']) {
2802
+ confidence += 10;
2803
+ version = parseVersionString(deps['@microsoft/rush']);
2804
+ sources.push({ type: 'package.json', field: 'dependencies.@microsoft/rush' });
2805
+ }
2806
+ if (exists(join$1(workspacePath, 'common', 'config', 'rush'))) {
2807
+ confidence += 5;
2808
+ sources.push({ type: 'directory', path: 'common/config/rush/' });
2809
+ }
2810
+ if (confidence === 0) {
2811
+ return null;
2812
+ }
2813
+ return {
2814
+ id: 'rush',
2815
+ name: 'Rush',
2816
+ version,
2817
+ configPath,
2818
+ confidence: min(confidence, 100),
2819
+ detectedFrom: sources,
2820
+ };
2821
+ }
2822
+
2823
+ /**
2824
+ * Detect pnpm workspaces in project.
2825
+ *
2826
+ * @param workspacePath - Workspace directory path
2827
+ * @returns Detection result or null if not detected
2828
+ */
2829
+ function pnpmWorkspacesDetector(workspacePath) {
2830
+ const sources = [];
2831
+ let confidence = 0;
2832
+ let configPath;
2833
+ const pnpmWorkspacePath = join$1(workspacePath, 'pnpm-workspace.yaml');
2834
+ if (exists(pnpmWorkspacePath)) {
2835
+ confidence += 90;
2836
+ configPath = 'pnpm-workspace.yaml';
2837
+ sources.push({ type: 'config-file', path: 'pnpm-workspace.yaml' });
2838
+ }
2839
+ if (exists(join$1(workspacePath, 'pnpm-lock.yaml'))) {
2840
+ confidence += 10;
2841
+ sources.push({ type: 'lockfile', path: 'pnpm-lock.yaml' });
2842
+ }
2843
+ if (confidence === 0) {
2844
+ return null;
2845
+ }
2846
+ return {
2847
+ id: 'pnpm-workspaces',
2848
+ name: 'pnpm Workspaces',
2849
+ configPath,
2850
+ confidence: min(confidence, 100),
2851
+ detectedFrom: sources,
2852
+ };
2853
+ }
2854
+
2855
+ /**
2856
+ * Detect npm workspaces in project.
2857
+ *
2858
+ * @param workspacePath - Workspace directory path
2859
+ * @param packageJson - Optional pre-loaded package.json
2860
+ * @returns Detection result or null if not detected
2861
+ */
2862
+ function npmWorkspacesDetector(workspacePath, packageJson) {
2863
+ const pkg = packageJson ?? readPackageJsonIfExists(workspacePath);
2864
+ const sources = [];
2865
+ let confidence = 0;
2866
+ if (pkg?.workspaces) {
2867
+ confidence += 80;
2868
+ sources.push({ type: 'package.json', field: 'workspaces' });
2869
+ }
2870
+ if (exists(join$1(workspacePath, 'package-lock.json'))) {
2871
+ confidence += 10;
2872
+ sources.push({ type: 'lockfile', path: 'package-lock.json' });
2873
+ }
2874
+ if (exists(join$1(workspacePath, 'yarn.lock'))) {
2875
+ return null; // Let yarn workspace detector handle this
2876
+ }
2877
+ if (confidence === 0) {
2878
+ return null;
2879
+ }
2880
+ return {
2881
+ id: 'npm-workspaces',
2882
+ name: 'npm Workspaces',
2883
+ configPath: 'package.json',
2884
+ confidence: min(confidence, 100),
2885
+ detectedFrom: sources,
2886
+ };
2887
+ }
2888
+
2889
+ /**
2890
+ * Detect yarn workspaces in project.
2891
+ *
2892
+ * @param workspacePath - Workspace directory path
2893
+ * @param packageJson - Optional pre-loaded package.json
2894
+ * @returns Detection result or null if not detected
2895
+ */
2896
+ function yarnWorkspacesDetector(workspacePath, packageJson) {
2897
+ const pkg = packageJson ?? readPackageJsonIfExists(workspacePath);
2898
+ const sources = [];
2899
+ let confidence = 0;
2900
+ if (pkg?.workspaces) {
2901
+ confidence += 70;
2902
+ sources.push({ type: 'package.json', field: 'workspaces' });
2903
+ }
2904
+ if (exists(join$1(workspacePath, 'yarn.lock'))) {
2905
+ confidence += 20;
2906
+ sources.push({ type: 'lockfile', path: 'yarn.lock' });
2907
+ }
2908
+ if (exists(join$1(workspacePath, '.yarnrc.yml'))) {
2909
+ confidence += 10;
2910
+ sources.push({ type: 'config-file', path: '.yarnrc.yml' });
2911
+ }
2912
+ if (confidence === 0 || !pkg?.workspaces) {
2913
+ return null;
2914
+ }
2915
+ return {
2916
+ id: 'yarn-workspaces',
2917
+ name: 'Yarn Workspaces',
2918
+ configPath: 'package.json',
2919
+ confidence: min(confidence, 100),
2920
+ detectedFrom: sources,
2921
+ };
2922
+ }
2923
+
2924
+ /** All monorepo detectors */
2925
+ const monorepoDetectors = [
2926
+ { id: 'nx', name: 'NX', detect: nxDetector },
2927
+ { id: 'turborepo', name: 'Turborepo', detect: turborepoDetector },
2928
+ { id: 'lerna', name: 'Lerna', detect: lernaDetector },
2929
+ { id: 'rush', name: 'Rush', detect: rushDetector },
2930
+ { id: 'pnpm-workspaces', name: 'pnpm Workspaces', detect: pnpmWorkspacesDetector },
2931
+ { id: 'npm-workspaces', name: 'npm Workspaces', detect: npmWorkspacesDetector },
2932
+ { id: 'yarn-workspaces', name: 'Yarn Workspaces', detect: yarnWorkspacesDetector },
2933
+ ];
2934
+ /**
2935
+ * Detect all monorepo tools in project.
2936
+ *
2937
+ * @param workspacePath - Workspace directory path
2938
+ * @param packageJson - Optional pre-loaded package.json
2939
+ * @returns Array of detected monorepo tools, sorted by confidence
2940
+ */
2941
+ function detectMonorepoTools(workspacePath, packageJson) {
2942
+ const pkg = packageJson ?? readPackageJsonIfExists(workspacePath);
2943
+ const results = [];
2944
+ for (const detector of monorepoDetectors) {
2945
+ const detection = detector.detect(workspacePath, pkg ?? undefined);
2946
+ if (detection) {
2947
+ results.push(detection);
2948
+ }
2949
+ }
2950
+ return results.sort((a, b) => b.confidence - a.confidence);
2951
+ }
2952
+
2953
+ /** Config patterns for Jest */
2954
+ const JEST_CONFIG_PATTERNS = ['jest.config.js', 'jest.config.ts', 'jest.config.mjs', 'jest.config.cjs', 'jest.config.json'];
2955
+ /**
2956
+ * Detect Jest in project.
2957
+ *
2958
+ * @param projectPath - Project directory path
2959
+ * @param packageJson - Optional pre-loaded package.json
2960
+ * @returns Detection result or null if not detected
2961
+ */
2962
+ function jestDetector(projectPath, packageJson) {
2963
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
2964
+ const sources = [];
2965
+ let confidence = 0;
2966
+ let version;
2967
+ const deps = collectAllDependencies(pkg);
2968
+ if (deps['jest']) {
2969
+ confidence += 60;
2970
+ version = parseVersionString(deps['jest']);
2971
+ sources.push({ type: 'package.json', field: 'dependencies.jest' });
2972
+ }
2973
+ const configPath = locateConfigFile(projectPath, JEST_CONFIG_PATTERNS);
2974
+ if (configPath) {
2975
+ confidence += 30;
2976
+ sources.push({ type: 'config-file', path: configPath });
2977
+ }
2978
+ if (pkg && 'jest' in pkg) {
2979
+ confidence += 20;
2980
+ sources.push({ type: 'package.json', field: 'jest' });
2981
+ }
2982
+ const testScript = pkg?.scripts?.['test'] ?? '';
2983
+ if (testScript.includes('jest')) {
2984
+ confidence += 10;
2985
+ sources.push({ type: 'package.json', field: 'scripts.test' });
2986
+ }
2987
+ if (deps['@types/jest']) {
2988
+ confidence += 5;
2989
+ sources.push({ type: 'package.json', field: 'dependencies.@types/jest' });
2990
+ }
2991
+ if (deps['ts-jest']) {
2992
+ confidence += 5;
2993
+ sources.push({ type: 'package.json', field: 'dependencies.ts-jest' });
2994
+ }
2995
+ if (confidence === 0) {
2996
+ return null;
2997
+ }
2998
+ return {
2999
+ id: 'jest',
3000
+ name: 'Jest',
3001
+ type: 'unit',
3002
+ version,
3003
+ configPath,
3004
+ confidence: min(confidence, 100),
3005
+ detectedFrom: sources,
3006
+ };
3007
+ }
3008
+
3009
+ /** Config patterns for Vitest */
3010
+ const VITEST_CONFIG_PATTERNS = ['vitest.config.js', 'vitest.config.ts', 'vitest.config.mjs'];
3011
+ /**
3012
+ * Detect Vitest in project.
3013
+ *
3014
+ * @param projectPath - Project directory path
3015
+ * @param packageJson - Optional pre-loaded package.json
3016
+ * @returns Detection result or null if not detected
3017
+ */
3018
+ function vitestDetector(projectPath, packageJson) {
3019
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3020
+ const sources = [];
3021
+ let confidence = 0;
3022
+ let version;
3023
+ const deps = collectAllDependencies(pkg);
3024
+ if (deps['vitest']) {
3025
+ confidence += 70;
3026
+ version = parseVersionString(deps['vitest']);
3027
+ sources.push({ type: 'package.json', field: 'dependencies.vitest' });
3028
+ }
3029
+ const configPath = locateConfigFile(projectPath, VITEST_CONFIG_PATTERNS);
3030
+ if (configPath) {
3031
+ confidence += 25;
3032
+ sources.push({ type: 'config-file', path: configPath });
3033
+ }
3034
+ if (!configPath) {
3035
+ const viteConfig = exists(join$1(projectPath, 'vite.config.ts')) ||
3036
+ exists(join$1(projectPath, 'vite.config.js')) ||
3037
+ exists(join$1(projectPath, 'vite.config.mjs'));
3038
+ if (viteConfig && deps['vitest']) {
3039
+ confidence += 5;
3040
+ sources.push({ type: 'config-file', path: 'vite.config.*' });
3041
+ }
3042
+ }
3043
+ const testScript = pkg?.scripts?.['test'] ?? '';
3044
+ if (testScript.includes('vitest')) {
3045
+ confidence += 10;
3046
+ sources.push({ type: 'package.json', field: 'scripts.test' });
3047
+ }
3048
+ if (confidence === 0) {
3049
+ return null;
3050
+ }
3051
+ return {
3052
+ id: 'vitest',
3053
+ name: 'Vitest',
3054
+ type: 'unit',
3055
+ version,
3056
+ configPath,
3057
+ confidence: min(confidence, 100),
3058
+ detectedFrom: sources,
3059
+ };
3060
+ }
3061
+
3062
+ /** Config patterns for Mocha */
3063
+ const MOCHA_CONFIG_PATTERNS = ['.mocharc.js', '.mocharc.json', '.mocharc.yaml', '.mocharc.yml', 'mocha.opts'];
3064
+ /**
3065
+ * Detect Mocha in project.
3066
+ *
3067
+ * @param projectPath - Project directory path
3068
+ * @param packageJson - Optional pre-loaded package.json
3069
+ * @returns Detection result or null if not detected
3070
+ */
3071
+ function mochaDetector(projectPath, packageJson) {
3072
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3073
+ const sources = [];
3074
+ let confidence = 0;
3075
+ let version;
3076
+ const deps = collectAllDependencies(pkg);
3077
+ if (deps['mocha']) {
3078
+ confidence += 65;
3079
+ version = parseVersionString(deps['mocha']);
3080
+ sources.push({ type: 'package.json', field: 'dependencies.mocha' });
3081
+ }
3082
+ const configPath = locateConfigFile(projectPath, MOCHA_CONFIG_PATTERNS);
3083
+ if (configPath) {
3084
+ confidence += 30;
3085
+ sources.push({ type: 'config-file', path: configPath });
3086
+ }
3087
+ if (deps['@types/mocha']) {
3088
+ confidence += 5;
3089
+ sources.push({ type: 'package.json', field: 'dependencies.@types/mocha' });
3090
+ }
3091
+ if (deps['chai']) {
3092
+ confidence += 5;
3093
+ sources.push({ type: 'package.json', field: 'dependencies.chai' });
3094
+ }
3095
+ const testScript = pkg?.scripts?.['test'] ?? '';
3096
+ if (testScript.includes('mocha')) {
3097
+ confidence += 10;
3098
+ sources.push({ type: 'package.json', field: 'scripts.test' });
3099
+ }
3100
+ if (confidence === 0) {
3101
+ return null;
3102
+ }
3103
+ return {
3104
+ id: 'mocha',
3105
+ name: 'Mocha',
3106
+ type: 'unit',
3107
+ version,
3108
+ configPath,
3109
+ confidence: min(confidence, 100),
3110
+ detectedFrom: sources,
3111
+ };
3112
+ }
3113
+
3114
+ /** Config patterns for Cypress */
3115
+ const CYPRESS_CONFIG_PATTERNS = ['cypress.config.js', 'cypress.config.ts', 'cypress.config.mjs', 'cypress.json'];
3116
+ /**
3117
+ * Detect Cypress in project.
3118
+ *
3119
+ * @param projectPath - Project directory path
3120
+ * @param packageJson - Optional pre-loaded package.json
3121
+ * @returns Detection result or null if not detected
3122
+ */
3123
+ function cypressDetector(projectPath, packageJson) {
3124
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3125
+ const sources = [];
3126
+ let confidence = 0;
3127
+ let version;
3128
+ const deps = collectAllDependencies(pkg);
3129
+ if (deps['cypress']) {
3130
+ confidence += 60;
3131
+ version = parseVersionString(deps['cypress']);
3132
+ sources.push({ type: 'package.json', field: 'dependencies.cypress' });
3133
+ }
3134
+ const configPath = locateConfigFile(projectPath, CYPRESS_CONFIG_PATTERNS);
3135
+ if (configPath) {
3136
+ confidence += 30;
3137
+ sources.push({ type: 'config-file', path: configPath });
3138
+ }
3139
+ if (exists(join$1(projectPath, 'cypress'))) {
3140
+ confidence += 10;
3141
+ sources.push({ type: 'directory', path: 'cypress/' });
3142
+ }
3143
+ const e2eScript = pkg?.scripts?.['e2e'] ?? pkg?.scripts?.['test:e2e'] ?? '';
3144
+ if (e2eScript.includes('cypress')) {
3145
+ confidence += 5;
3146
+ sources.push({ type: 'package.json', field: 'scripts.e2e or scripts.test:e2e' });
3147
+ }
3148
+ if (confidence === 0) {
3149
+ return null;
3150
+ }
3151
+ return {
3152
+ id: 'cypress',
3153
+ name: 'Cypress',
3154
+ type: 'e2e',
3155
+ version,
3156
+ configPath,
3157
+ confidence: min(confidence, 100),
3158
+ detectedFrom: sources,
3159
+ };
3160
+ }
3161
+
3162
+ /** Config patterns for Playwright */
3163
+ const PLAYWRIGHT_CONFIG_PATTERNS = ['playwright.config.js', 'playwright.config.ts', 'playwright.config.mjs'];
3164
+ /**
3165
+ * Detect Playwright in project.
3166
+ *
3167
+ * @param projectPath - Project directory path
3168
+ * @param packageJson - Optional pre-loaded package.json
3169
+ * @returns Detection result or null if not detected
3170
+ */
3171
+ function playwrightDetector(projectPath, packageJson) {
3172
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3173
+ const sources = [];
3174
+ let confidence = 0;
3175
+ let version;
3176
+ const deps = collectAllDependencies(pkg);
3177
+ if (deps['@playwright/test']) {
3178
+ confidence += 70;
3179
+ version = parseVersionString(deps['@playwright/test']);
3180
+ sources.push({ type: 'package.json', field: 'dependencies.@playwright/test' });
3181
+ }
3182
+ if (deps['playwright']) {
3183
+ confidence += 50;
3184
+ version = version ?? parseVersionString(deps['playwright']);
3185
+ sources.push({ type: 'package.json', field: 'dependencies.playwright' });
3186
+ }
3187
+ const configPath = locateConfigFile(projectPath, PLAYWRIGHT_CONFIG_PATTERNS);
3188
+ if (configPath) {
3189
+ confidence += 25;
3190
+ sources.push({ type: 'config-file', path: configPath });
3191
+ }
3192
+ if (exists(join$1(projectPath, 'e2e')) || exists(join$1(projectPath, 'tests'))) {
3193
+ confidence += 5;
3194
+ sources.push({ type: 'directory', path: 'e2e/ or tests/' });
3195
+ }
3196
+ const e2eScript = pkg?.scripts?.['e2e'] ?? pkg?.scripts?.['test:e2e'] ?? '';
3197
+ if (e2eScript.includes('playwright')) {
3198
+ confidence += 5;
3199
+ sources.push({ type: 'package.json', field: 'scripts.e2e or scripts.test:e2e' });
3200
+ }
3201
+ if (confidence === 0) {
3202
+ return null;
3203
+ }
3204
+ return {
3205
+ id: 'playwright',
3206
+ name: 'Playwright',
3207
+ type: 'e2e',
3208
+ version,
3209
+ configPath,
3210
+ confidence: min(confidence, 100),
3211
+ detectedFrom: sources,
3212
+ };
3213
+ }
3214
+
3215
+ /** All testing framework detectors */
3216
+ const testingDetectors = [
3217
+ { id: 'jest', name: 'Jest', testType: 'unit', detect: jestDetector },
3218
+ { id: 'vitest', name: 'Vitest', testType: 'unit', detect: vitestDetector },
3219
+ { id: 'mocha', name: 'Mocha', testType: 'unit', detect: mochaDetector },
3220
+ { id: 'cypress', name: 'Cypress', testType: 'e2e', detect: cypressDetector },
3221
+ { id: 'playwright', name: 'Playwright', testType: 'e2e', detect: playwrightDetector },
3222
+ ];
3223
+ /**
3224
+ * Detect all testing frameworks in project.
3225
+ *
3226
+ * @param projectPath - Project directory path
3227
+ * @param packageJson - Optional pre-loaded package.json
3228
+ * @returns Array of detected testing frameworks, sorted by confidence
3229
+ */
3230
+ function detectTestingFrameworks(projectPath, packageJson) {
3231
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3232
+ const results = [];
3233
+ for (const detector of testingDetectors) {
3234
+ const detection = detector.detect(projectPath, pkg ?? undefined);
3235
+ if (detection) {
3236
+ results.push(detection);
3237
+ }
3238
+ }
3239
+ return results.sort((a, b) => b.confidence - a.confidence);
3240
+ }
3241
+
3242
+ /**
3243
+ * Check if tsconfig has strict mode enabled.
3244
+ *
3245
+ * @param projectPath - The project directory path
3246
+ * @returns True if strict mode is enabled, undefined if unable to determine
3247
+ */
3248
+ function checkTsConfigStrict(projectPath) {
3249
+ const tsconfigPath = join$1(projectPath, 'tsconfig.json');
3250
+ const content = readFileIfExists(tsconfigPath);
3251
+ if (!content)
3252
+ return undefined;
3253
+ try {
3254
+ // Simple JSON parsing - doesn't handle comments but good enough for strict check
3255
+ const cleanContent = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
3256
+ const parsed = parse(cleanContent);
3257
+ return parsed?.compilerOptions?.strict === true;
3258
+ }
3259
+ catch {
3260
+ return undefined;
3261
+ }
3262
+ }
3263
+ /**
3264
+ * Detect TypeScript in project.
3265
+ *
3266
+ * @param projectPath - Project directory path
3267
+ * @param packageJson - Optional pre-loaded package.json
3268
+ * @returns Detection result or null if not detected
3269
+ */
3270
+ function typescriptDetector(projectPath, packageJson) {
3271
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3272
+ const sources = [];
3273
+ let confidence = 0;
3274
+ let configPath;
3275
+ let version;
3276
+ const deps = collectAllDependencies(pkg);
3277
+ // TypeScript package
3278
+ if (deps['typescript']) {
3279
+ confidence += 50;
3280
+ version = parseVersionString(deps['typescript']);
3281
+ sources.push({ type: 'package.json', field: 'dependencies.typescript' });
3282
+ }
3283
+ // tsconfig.json
3284
+ if (exists(join$1(projectPath, 'tsconfig.json'))) {
3285
+ confidence += 40;
3286
+ configPath = 'tsconfig.json';
3287
+ sources.push({ type: 'config-file', path: 'tsconfig.json' });
3288
+ }
3289
+ // tsconfig.*.json variants
3290
+ const tsconfigVariants = ['tsconfig.build.json', 'tsconfig.lib.json', 'tsconfig.spec.json', 'tsconfig.app.json'];
3291
+ for (const variant of tsconfigVariants) {
3292
+ if (exists(join$1(projectPath, variant))) {
3293
+ confidence += 5;
3294
+ sources.push({ type: 'config-file', path: variant });
3295
+ break;
3296
+ }
3297
+ }
3298
+ // @types packages
3299
+ const typePackages = keys(deps).filter((d) => d.startsWith('@types/'));
3300
+ if (typePackages.length > 0) {
3301
+ confidence += 10;
3302
+ sources.push({ type: 'package.json', field: '@types/* packages' });
3303
+ }
3304
+ if (confidence === 0) {
3305
+ return null;
3306
+ }
3307
+ const strictMode = checkTsConfigStrict(projectPath);
3308
+ return {
3309
+ id: 'typescript',
3310
+ name: 'TypeScript',
3311
+ version,
3312
+ configPath,
3313
+ strictMode,
3314
+ confidence: min(confidence, 100),
3315
+ detectedFrom: sources,
3316
+ };
3317
+ }
3318
+ /**
3319
+ * Detect Flow in project.
3320
+ *
3321
+ * @param projectPath - Project directory path
3322
+ * @param packageJson - Optional pre-loaded package.json
3323
+ * @returns Detection result or null if not detected
3324
+ */
3325
+ function flowDetector(projectPath, packageJson) {
3326
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3327
+ const sources = [];
3328
+ let confidence = 0;
3329
+ let configPath;
3330
+ let version;
3331
+ const deps = collectAllDependencies(pkg);
3332
+ // flow-bin package
3333
+ if (deps['flow-bin']) {
3334
+ confidence += 60;
3335
+ version = parseVersionString(deps['flow-bin']);
3336
+ sources.push({ type: 'package.json', field: 'dependencies.flow-bin' });
3337
+ }
3338
+ // .flowconfig
3339
+ if (exists(join$1(projectPath, '.flowconfig'))) {
3340
+ confidence += 40;
3341
+ configPath = '.flowconfig';
3342
+ sources.push({ type: 'config-file', path: '.flowconfig' });
3343
+ }
3344
+ // flow-typed directory
3345
+ if (exists(join$1(projectPath, 'flow-typed'))) {
3346
+ confidence += 10;
3347
+ sources.push({ type: 'directory', path: 'flow-typed/' });
3348
+ }
3349
+ // @babel/preset-flow
3350
+ if (deps['@babel/preset-flow']) {
3351
+ confidence += 10;
3352
+ sources.push({ type: 'package.json', field: 'dependencies.@babel/preset-flow' });
3353
+ }
3354
+ if (confidence === 0) {
3355
+ return null;
3356
+ }
3357
+ return {
3358
+ id: 'flow',
3359
+ name: 'Flow',
3360
+ version,
3361
+ configPath,
3362
+ confidence: min(confidence, 100),
3363
+ detectedFrom: sources,
3364
+ };
3365
+ }
3366
+ /**
3367
+ * Check if a file contains JSDoc type annotations.
3368
+ *
3369
+ * @param content - The file content to check.
3370
+ * @returns `true` if the content contains JSDoc type annotations.
3371
+ */
3372
+ function hasJsDocTypes(content) {
3373
+ // Check for JSDoc type annotations
3374
+ return (content.includes('@type {') ||
3375
+ content.includes('@param {') ||
3376
+ content.includes('@returns {') ||
3377
+ content.includes('@typedef') ||
3378
+ content.includes('@template'));
3379
+ }
3380
+ /**
3381
+ * Detect JSDoc type annotations in project.
3382
+ *
3383
+ * @param projectPath - Project directory path
3384
+ * @param packageJson - Optional pre-loaded package.json
3385
+ * @returns Detection result or null if not detected
3386
+ */
3387
+ function jsdocDetector(projectPath, packageJson) {
3388
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3389
+ const sources = [];
3390
+ let confidence = 0;
3391
+ const deps = collectAllDependencies(pkg);
3392
+ // jsdoc package
3393
+ if (deps['jsdoc']) {
3394
+ confidence += 30;
3395
+ sources.push({ type: 'package.json', field: 'dependencies.jsdoc' });
3396
+ }
3397
+ // typescript with checkJs (JSDoc type checking)
3398
+ if (deps['typescript']) {
3399
+ // Check if checkJs is enabled in tsconfig
3400
+ const tsconfigPath = join$1(projectPath, 'tsconfig.json');
3401
+ const content = readFileIfExists(tsconfigPath);
3402
+ if (content) {
3403
+ try {
3404
+ const cleanContent = content.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, '');
3405
+ const parsed = parse(cleanContent);
3406
+ if (parsed?.compilerOptions?.checkJs === true || parsed?.compilerOptions?.allowJs === true) {
3407
+ confidence += 30;
3408
+ sources.push({ type: 'config-file', path: 'tsconfig.json (checkJs/allowJs)' });
3409
+ }
3410
+ }
3411
+ catch {
3412
+ // Ignore parsing errors
3413
+ }
3414
+ }
3415
+ }
3416
+ // Check for jsconfig.json (VS Code JS type checking)
3417
+ if (exists(join$1(projectPath, 'jsconfig.json'))) {
3418
+ confidence += 40;
3419
+ sources.push({ type: 'config-file', path: 'jsconfig.json' });
3420
+ }
3421
+ // Sample check for JSDoc annotations in source files
3422
+ const srcDir = join$1(projectPath, 'src');
3423
+ if (exists(srcDir)) {
3424
+ try {
3425
+ const entries = readDirectory(srcDir);
3426
+ const files = entries.filter((e) => e.isFile && (e.name.endsWith('.js') || e.name.endsWith('.mjs'))).map((e) => e.name);
3427
+ for (const file of files.slice(0, 3)) {
3428
+ const content = readFileIfExists(join$1(srcDir, file));
3429
+ if (content && hasJsDocTypes(content)) {
3430
+ confidence += 20;
3431
+ sources.push({ type: 'directory', path: `src/${file} (JSDoc annotations)` });
3432
+ break;
3433
+ }
3434
+ }
3435
+ }
3436
+ catch {
3437
+ // Ignore directory read errors
3438
+ }
3439
+ }
3440
+ if (confidence === 0) {
3441
+ return null;
3442
+ }
3443
+ return {
3444
+ id: 'jsdoc',
3445
+ name: 'JSDoc',
3446
+ confidence: min(confidence, 100),
3447
+ detectedFrom: sources,
3448
+ };
3449
+ }
3450
+ /** All type system detectors */
3451
+ const typeSystemDetectors = [
3452
+ { id: 'typescript', name: 'TypeScript', detect: typescriptDetector },
3453
+ { id: 'flow', name: 'Flow', detect: flowDetector },
3454
+ { id: 'jsdoc', name: 'JSDoc', detect: jsdocDetector },
3455
+ ];
3456
+ /**
3457
+ * Detect all type systems in project.
3458
+ *
3459
+ * @param projectPath - Project directory path
3460
+ * @param packageJson - Optional pre-loaded package.json
3461
+ * @returns Array of detected type systems, sorted by confidence
3462
+ */
3463
+ function detectTypeSystems(projectPath, packageJson) {
3464
+ const pkg = packageJson ?? readPackageJsonIfExists(projectPath);
3465
+ const results = [];
3466
+ for (const detector of typeSystemDetectors) {
3467
+ const detection = detector.detect(projectPath, pkg ?? undefined);
3468
+ if (detection) {
3469
+ results.push(detection);
3470
+ }
3471
+ }
3472
+ return results.sort((a, b) => b.confidence - a.confidence);
3473
+ }
3474
+
3475
+ const techLogger = createScopedLogger('project-scope:tech');
3476
+ /**
3477
+ * Cache for tech detection results.
3478
+ * TTL: 60 seconds (tech stack can change during active development)
3479
+ */
3480
+ const detectAllCache = createCache({ ttl: 60000, maxSize: 50 });
3481
+ /**
3482
+ * All available detectors organized by category.
3483
+ */
3484
+ const allDetectors = {
3485
+ build: buildToolDetectors,
3486
+ monorepo: monorepoDetectors,
3487
+ frontend: frameworkDetectors,
3488
+ backend: backendDetectors,
3489
+ legacy: legacyDetectors,
3490
+ testing: testingDetectors,
3491
+ types: typeSystemDetectors,
3492
+ linting: lintingDetectors,
3493
+ };
3494
+ /**
3495
+ * Check if the value is a DetectAllOptions object.
3496
+ *
3497
+ * @param value - Value to check
3498
+ * @returns True if value is DetectAllOptions
3499
+ */
3500
+ function isDetectAllOptions(value) {
3501
+ if (typeof value !== 'object' || value === null)
3502
+ return false;
3503
+ // DetectAllOptions has skipCache or packageJson fields specifically
3504
+ // PackageJson never has skipCache field
3505
+ return 'skipCache' in value || 'packageJson' in value;
3506
+ }
3507
+ /**
3508
+ * Run all technology detectors on a project.
3509
+ *
3510
+ * Results are cached for 60 seconds per project path to avoid
3511
+ * redundant file system operations on repeated calls.
3512
+ *
3513
+ * @param projectPath - Path to project directory
3514
+ * @param packageJsonOrOptions - Optional pre-loaded package.json or options object
3515
+ * @returns All detection results organized by category
3516
+ *
3517
+ * @example
3518
+ * ```typescript
3519
+ * import { detectAll } from '@hyperfrontend/project-scope'
3520
+ *
3521
+ * const detections = detectAll('./my-project')
3522
+ *
3523
+ * // Check frontend frameworks
3524
+ * for (const fw of detections.frontendFrameworks) {
3525
+ * console.log(`${fw.name} v${fw.version} (${fw.confidence}% confidence)`)
3526
+ * }
3527
+ *
3528
+ * // Check build tools
3529
+ * console.log('Build tools:', detections.buildTools.map(t => t.name))
3530
+ *
3531
+ * // Check testing frameworks
3532
+ * console.log('Testing:', detections.testingFrameworks.map(t => t.name))
3533
+ * ```
3534
+ */
3535
+ function detectAll(projectPath, packageJsonOrOptions) {
3536
+ // Handle backward-compatible arguments
3537
+ const options = isDetectAllOptions(packageJsonOrOptions) ? packageJsonOrOptions : { packageJson: packageJsonOrOptions };
3538
+ // Check cache first (unless skipCache is true)
3539
+ if (!options.skipCache) {
3540
+ const cached = detectAllCache.get(projectPath);
3541
+ if (cached) {
3542
+ techLogger.debug('Returning cached tech detection results', { projectPath });
3543
+ return cached;
3544
+ }
3545
+ }
3546
+ const pkg = options.packageJson ?? readPackageJsonIfExists(projectPath);
3547
+ techLogger.debug('Running all tech detectors', { projectPath });
3548
+ const result = {
3549
+ buildTools: buildToolDetectors
3550
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3551
+ .filter((r) => r !== null)
3552
+ .sort((a, b) => b.confidence - a.confidence),
3553
+ monorepo: monorepoDetectors
3554
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3555
+ .filter((r) => r !== null)
3556
+ .sort((a, b) => b.confidence - a.confidence),
3557
+ frontendFrameworks: frameworkDetectors
3558
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3559
+ .filter((r) => r !== null)
3560
+ .sort((a, b) => b.confidence - a.confidence),
3561
+ backendFrameworks: backendDetectors
3562
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3563
+ .filter((r) => r !== null)
3564
+ .sort((a, b) => b.confidence - a.confidence),
3565
+ legacyFrameworks: legacyDetectors
3566
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3567
+ .filter((r) => r !== null)
3568
+ .sort((a, b) => b.confidence - a.confidence),
3569
+ testingFrameworks: testingDetectors
3570
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3571
+ .filter((r) => r !== null)
3572
+ .sort((a, b) => b.confidence - a.confidence),
3573
+ typeSystem: typeSystemDetectors
3574
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3575
+ .filter((r) => r !== null)
3576
+ .sort((a, b) => b.confidence - a.confidence),
3577
+ linting: lintingDetectors
3578
+ .map((d) => d.detect(projectPath, pkg ?? undefined))
3579
+ .filter((r) => r !== null)
3580
+ .sort((a, b) => b.confidence - a.confidence),
3581
+ };
3582
+ techLogger.debug('Tech detection complete', {
3583
+ buildTools: result.buildTools.map((t) => t.id),
3584
+ frontendFrameworks: result.frontendFrameworks.map((f) => f.id),
3585
+ backendFrameworks: result.backendFrameworks.map((f) => f.id),
3586
+ legacyFrameworks: result.legacyFrameworks.map((f) => f.id),
3587
+ testingFrameworks: result.testingFrameworks.map((f) => f.id),
3588
+ });
3589
+ // Cache the result
3590
+ detectAllCache.set(projectPath, result);
3591
+ return result;
3592
+ }
3593
+ /**
3594
+ * Clear the tech detection cache.
3595
+ *
3596
+ * Useful for testing or when the project files have changed.
3597
+ */
3598
+ function clearTechDetectionCache() {
3599
+ detectAllCache.clear();
3600
+ }
3601
+
3602
+ export { BABEL_CONFIG_PATTERNS, CYPRESS_CONFIG_PATTERNS, ESLINT_CONFIG_PATTERNS, JEST_CONFIG_PATTERNS, MOCHA_CONFIG_PATTERNS, PARCEL_CONFIG_PATTERNS, PLAYWRIGHT_CONFIG_PATTERNS, PRETTIER_CONFIG_PATTERNS, ROLLUP_CONFIG_PATTERNS, STYLELINT_CONFIG_PATTERNS, SWC_CONFIG_PATTERNS, VITEST_CONFIG_PATTERNS, VITE_CONFIG_PATTERNS, WEBPACK_CONFIG_PATTERNS, allDetectors, angularDetector, angularJSDetector, astroDetector, babelDetector, backboneDetector, backendDetectors, biomeDetector, buildToolDetectors, clearTechDetectionCache, cypressDetector, detectAll, detectBackendFrameworks, detectBuildTools, detectFrontendFrameworks, detectLegacyFrameworks, detectLintingTools, detectMonorepoTools, detectTestingFrameworks, detectTypeSystems, emberDetector, esbuildDetector, eslintDetector, expressDetector, fastifyDetector, flowDetector, frameworkDetectors, gatsbyDetector, honoDetector, jestDetector, jqueryDetector, jsdocDetector, koaDetector, legacyDetectors, lernaDetector, lintingDetectors, mochaDetector, monorepoDetectors, nestDetector, nextjsDetector, npmWorkspacesDetector, nuxtDetector, nxDetector, parcelDetector, playwrightDetector, pnpmWorkspacesDetector, prettierDetector, qwikDetector, reactDetector, remixDetector, rollupDetector, rushDetector, solidDetector, stylelintDetector, svelteDetector, sveltekitDetector, swcDetector, testingDetectors, turborepoDetector, typeSystemDetectors, typescriptDetector, viteDetector, vitestDetector, vueDetector, webpackDetector, yarnWorkspacesDetector };
3603
+ //# sourceMappingURL=index.esm.js.map