@cdxgen/cdxgen 13.3.0 → 13.5.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 (372) hide show
  1. package/README.md +12 -9
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +115 -31
  4. package/bin/evinse.js +23 -1
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +115 -0
  7. package/bin/sign.js +45 -15
  8. package/bin/tracebom.js +5 -1
  9. package/bin/validate.js +84 -18
  10. package/bin/verify.js +155 -86
  11. package/data/cbomosdb-queries.json +13 -1
  12. package/data/component-tags.json +8 -0
  13. package/data/helpers/init.gradle +46 -21
  14. package/data/queries-darwin.json +25 -1
  15. package/data/queries-win.json +19 -1
  16. package/data/queries.json +41 -0
  17. package/data/remediations.json +344 -1
  18. package/data/rules/cbom-security.yaml +235 -4
  19. package/data/rules/dependency-sources.yaml +67 -0
  20. package/data/rules/go-binaries.yaml +46 -0
  21. package/data/rules/golem-go.yaml +80 -0
  22. package/data/rules/hbom-compliance.yaml +40 -22
  23. package/data/rules/hbom-performance.yaml +7 -1
  24. package/data/rules/host-topology.yaml +1 -1
  25. package/data/rules/obom-runtime.yaml +31 -2
  26. package/data/rules/php-runtime.yaml +143 -0
  27. package/data/rules/scala-evidence.yaml +115 -0
  28. package/lib/audit/index.js +52 -6
  29. package/lib/cli/bomAssembly.js +28 -3
  30. package/lib/cli/cliOptions.js +110 -3
  31. package/lib/cli/index.js +186 -58
  32. package/lib/cli/jsBom.js +50 -29
  33. package/lib/cli/jvmBom.js +779 -146
  34. package/lib/cli/managedBom.js +1021 -261
  35. package/lib/cli/nativeBom.js +364 -58
  36. package/lib/core/activity.js +12 -0
  37. package/lib/core/env.js +47 -17
  38. package/lib/core/fs.js +31 -21
  39. package/lib/core/httpClient.js +233 -69
  40. package/lib/core/pluginRun.js +184 -0
  41. package/lib/core/processTree.js +100 -0
  42. package/lib/core/projectConfig.js +1 -0
  43. package/lib/core/projectTooling.js +97 -0
  44. package/lib/core/propertySanitizer.js +72 -0
  45. package/lib/core/runState.js +39 -0
  46. package/lib/core/toml.js +72 -0
  47. package/lib/ecosystems/asarutils.js +4 -2
  48. package/lib/ecosystems/bunutils.js +5 -7
  49. package/lib/ecosystems/cargoCache.js +231 -8
  50. package/lib/ecosystems/cmakeResolver.js +10 -21
  51. package/lib/ecosystems/cppBuildContext.js +631 -0
  52. package/lib/ecosystems/cppEvidence.js +337 -22
  53. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  54. package/lib/ecosystems/denoutils.js +93 -66
  55. package/lib/ecosystems/ecosystems.js +1967 -788
  56. package/lib/ecosystems/golem.js +578 -18
  57. package/lib/ecosystems/gradleutils.js +340 -46
  58. package/lib/ecosystems/jsEvidence.js +85 -24
  59. package/lib/ecosystems/kosi.js +21 -4
  60. package/lib/ecosystems/npmutils.js +64 -0
  61. package/lib/ecosystems/parsers-dotnet.js +668 -153
  62. package/lib/ecosystems/parsers-elm.js +46 -28
  63. package/lib/ecosystems/parsers-gleam.js +3 -4
  64. package/lib/ecosystems/parsers-go.js +1283 -297
  65. package/lib/ecosystems/parsers-js.js +92 -26
  66. package/lib/ecosystems/parsers-julia.js +2 -3
  67. package/lib/ecosystems/parsers-jvm.js +463 -29
  68. package/lib/ecosystems/parsers-misc.js +405 -93
  69. package/lib/ecosystems/parsers-python.js +152 -20
  70. package/lib/ecosystems/parsers-rust.js +4 -4
  71. package/lib/ecosystems/parsers-terraform.js +1573 -19
  72. package/lib/ecosystems/pythonutils.js +102 -6
  73. package/lib/ecosystems/remote/huggingface.js +3 -0
  74. package/lib/ecosystems/rubyutils.js +73 -20
  75. package/lib/ecosystems/rusi.js +32 -9
  76. package/lib/ecosystems/sbtutils.js +243 -187
  77. package/lib/ecosystems/scalacliutils.js +343 -0
  78. package/lib/ecosystems/scalasem.js +2620 -0
  79. package/lib/ecosystems/terraformRegistry.js +288 -0
  80. package/lib/ecosystems/utils.js +2 -0
  81. package/lib/ecosystems/vendoredCode.js +156 -0
  82. package/lib/evinser/evinser.js +695 -78
  83. package/lib/evinser/swiftsem.js +68 -5
  84. package/lib/helpers/agenticToolutils.js +2 -2
  85. package/lib/helpers/bomSigner.js +576 -128
  86. package/lib/inventory/agentSkillSpec.js +17 -0
  87. package/lib/inventory/aiCollector.js +4 -1
  88. package/lib/inventory/analyzer.js +110 -127
  89. package/lib/inventory/apkInstalledDb.js +243 -0
  90. package/lib/inventory/atomUtils.js +204 -48
  91. package/lib/inventory/auditCategories.js +10 -3
  92. package/lib/inventory/buildToolRateLimit.js +144 -0
  93. package/lib/inventory/cacheCommands.js +2 -1
  94. package/lib/inventory/cacheDir.js +52 -9
  95. package/lib/inventory/certificates.js +2371 -0
  96. package/lib/inventory/cmakeBuildDirs.js +254 -0
  97. package/lib/inventory/communityAiConfigParser.js +4 -9
  98. package/lib/inventory/deps.js +507 -128
  99. package/lib/inventory/display.js +200 -3
  100. package/lib/inventory/dosai.js +747 -101
  101. package/lib/inventory/dosaiParsers.js +310 -15
  102. package/lib/inventory/dynamic.js +96 -8
  103. package/lib/inventory/envcontext.js +168 -5
  104. package/lib/inventory/evidenceUtils.js +590 -132
  105. package/lib/inventory/fetchBatch.js +574 -38
  106. package/lib/inventory/fetchDiskCache.js +597 -0
  107. package/lib/inventory/forgeEnricher.js +14 -5
  108. package/lib/inventory/formulationParsers.js +3 -2
  109. package/lib/inventory/goBuildInfo.js +140 -0
  110. package/lib/inventory/hbom.js +67 -7
  111. package/lib/inventory/hbomAnalysis.js +16 -0
  112. package/lib/inventory/hostTopology.js +21 -0
  113. package/lib/inventory/jvmLocalRepos.js +917 -0
  114. package/lib/inventory/mcpConfigParser.js +2 -3
  115. package/lib/inventory/osPackagePurl.js +244 -0
  116. package/lib/inventory/osPackageResolver.js +89 -87
  117. package/lib/inventory/osinfo.js +79 -31
  118. package/lib/inventory/osqueryTransform.js +114 -5
  119. package/lib/inventory/parentSelection.js +146 -0
  120. package/lib/inventory/phpBuildInfo.js +487 -0
  121. package/lib/inventory/phpHostProbe.js +641 -0
  122. package/lib/inventory/rootfsCerts.js +426 -0
  123. package/lib/inventory/rustFormulationParser.js +3 -4
  124. package/lib/inventory/scalaCoords.js +153 -0
  125. package/lib/inventory/source.js +215 -11
  126. package/lib/inventory/spdx.js +245 -0
  127. package/lib/inventory/traceRunner.js +93 -8
  128. package/lib/inventory/tracedCrypto.js +423 -0
  129. package/lib/managers/binary.js +529 -183
  130. package/lib/managers/docker.js +199 -44
  131. package/lib/managers/oci.js +24 -2
  132. package/lib/parsers/cmakeCache.js +69 -0
  133. package/lib/parsers/cmakeLists.js +16 -2
  134. package/lib/parsers/cmakePresets.js +400 -0
  135. package/lib/parsers/compileCommands.js +557 -0
  136. package/lib/parsers/email.js +277 -0
  137. package/lib/parsers/largeJson.js +34 -2
  138. package/lib/parsers/strictJson.js +322 -0
  139. package/lib/parsers/terraformConfig.js +1073 -0
  140. package/lib/parsers/terraformSource.js +652 -0
  141. package/lib/server/server.js +209 -1
  142. package/lib/stages/postgen/annotator.js +17 -1
  143. package/lib/stages/postgen/introspection/markers.js +1 -1
  144. package/lib/stages/postgen/introspection/reflect.js +4 -1
  145. package/lib/stages/postgen/postgen.js +20 -6
  146. package/lib/stages/postgen/ruleEngine.js +58 -1
  147. package/lib/stages/postgen/specVersionCompat.js +62 -0
  148. package/lib/stages/pregen/pregen.js +117 -5
  149. package/lib/validator/bomValidator.js +26 -5
  150. package/lib/validator/generated/runtime.mjs +13 -0
  151. package/lib/validator/generated/validate-1.6.mjs +26 -7
  152. package/lib/validator/generated/validate-1.7.mjs +31 -12
  153. package/lib/validator/generated/validate-2.0.mjs +30 -11
  154. package/lib/validator/index.js +25 -10
  155. package/lib/validator/reporters/json.js +1 -0
  156. package/package.json +23 -23
  157. package/types/bin/cdxgen-secure.d.ts +3 -0
  158. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  159. package/types/bin/repl.d.ts.map +1 -1
  160. package/types/lib/audit/index.d.ts.map +1 -1
  161. package/types/lib/cli/bomAssembly.d.ts +9 -2
  162. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  163. package/types/lib/cli/cliOptions.d.ts +43 -0
  164. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  165. package/types/lib/cli/index.d.ts.map +1 -1
  166. package/types/lib/cli/jsBom.d.ts.map +1 -1
  167. package/types/lib/cli/jvmBom.d.ts +44 -1
  168. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  169. package/types/lib/cli/managedBom.d.ts +7 -4
  170. package/types/lib/cli/managedBom.d.ts.map +1 -1
  171. package/types/lib/cli/nativeBom.d.ts +3 -1
  172. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  173. package/types/lib/core/activity.d.ts.map +1 -1
  174. package/types/lib/core/env.d.ts +27 -2
  175. package/types/lib/core/env.d.ts.map +1 -1
  176. package/types/lib/core/fs.d.ts.map +1 -1
  177. package/types/lib/core/httpClient.d.ts +13 -8
  178. package/types/lib/core/httpClient.d.ts.map +1 -1
  179. package/types/lib/core/pluginRun.d.ts +97 -0
  180. package/types/lib/core/pluginRun.d.ts.map +1 -0
  181. package/types/lib/core/processTree.d.ts +36 -0
  182. package/types/lib/core/processTree.d.ts.map +1 -0
  183. package/types/lib/core/projectConfig.d.ts.map +1 -1
  184. package/types/lib/core/projectTooling.d.ts +41 -0
  185. package/types/lib/core/projectTooling.d.ts.map +1 -0
  186. package/types/lib/core/propertySanitizer.d.ts +26 -0
  187. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  188. package/types/lib/core/runState.d.ts +29 -0
  189. package/types/lib/core/runState.d.ts.map +1 -0
  190. package/types/lib/core/toml.d.ts +20 -0
  191. package/types/lib/core/toml.d.ts.map +1 -0
  192. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  193. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  194. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  195. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  196. package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
  197. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  198. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  199. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  200. package/types/lib/ecosystems/cppEvidence.d.ts +19 -3
  201. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  202. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  203. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  204. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  205. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  206. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  207. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  208. package/types/lib/ecosystems/golem.d.ts +81 -1
  209. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  210. package/types/lib/ecosystems/gradleutils.d.ts +59 -7
  211. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  212. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  213. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  214. package/types/lib/ecosystems/npmutils.d.ts +15 -0
  215. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  216. package/types/lib/ecosystems/parsers-dotnet.d.ts +155 -5
  217. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  218. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  219. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  220. package/types/lib/ecosystems/parsers-go.d.ts +119 -5
  221. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  222. package/types/lib/ecosystems/parsers-js.d.ts +6 -1
  223. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  224. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  225. package/types/lib/ecosystems/parsers-jvm.d.ts +74 -3
  226. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  227. package/types/lib/ecosystems/parsers-misc.d.ts +21 -59
  228. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  229. package/types/lib/ecosystems/parsers-python.d.ts +57 -6
  230. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  231. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  232. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  233. package/types/lib/ecosystems/pythonutils.d.ts +26 -1
  234. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  235. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  236. package/types/lib/ecosystems/rubyutils.d.ts +21 -1
  237. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  238. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  239. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  240. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  241. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  242. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  243. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  244. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  245. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  246. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  247. package/types/lib/ecosystems/utils.d.ts +2 -2
  248. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  249. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  250. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  251. package/types/lib/evinser/evinser.d.ts +100 -8
  252. package/types/lib/evinser/evinser.d.ts.map +1 -1
  253. package/types/lib/evinser/swiftsem.d.ts +10 -0
  254. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  255. package/types/lib/helpers/bomSigner.d.ts +80 -4
  256. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  257. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  258. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  259. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  260. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  261. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  262. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  263. package/types/lib/inventory/atomUtils.d.ts +96 -10
  264. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  265. package/types/lib/inventory/auditCategories.d.ts +3 -0
  266. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  267. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  268. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  269. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  270. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  271. package/types/lib/inventory/cacheDir.d.ts +12 -4
  272. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  273. package/types/lib/inventory/certificates.d.ts +205 -0
  274. package/types/lib/inventory/certificates.d.ts.map +1 -0
  275. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  276. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  277. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  278. package/types/lib/inventory/deps.d.ts +61 -2
  279. package/types/lib/inventory/deps.d.ts.map +1 -1
  280. package/types/lib/inventory/display.d.ts +18 -0
  281. package/types/lib/inventory/display.d.ts.map +1 -1
  282. package/types/lib/inventory/dosai.d.ts +202 -14
  283. package/types/lib/inventory/dosai.d.ts.map +1 -1
  284. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  285. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  286. package/types/lib/inventory/dynamic.d.ts +16 -1
  287. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  288. package/types/lib/inventory/envcontext.d.ts +40 -2
  289. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  290. package/types/lib/inventory/evidenceUtils.d.ts +48 -14
  291. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  292. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  293. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  294. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  295. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  296. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  297. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  298. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  299. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  300. package/types/lib/inventory/hbom.d.ts.map +1 -1
  301. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  302. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  303. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  304. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  305. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  306. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  307. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  308. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  309. package/types/lib/inventory/osPackageResolver.d.ts +6 -2
  310. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  311. package/types/lib/inventory/osinfo.d.ts +31 -6
  312. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  313. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  314. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  315. package/types/lib/inventory/parentSelection.d.ts +34 -0
  316. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  317. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  318. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  319. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  320. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  321. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  322. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  323. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  324. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  325. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  326. package/types/lib/inventory/source.d.ts +28 -0
  327. package/types/lib/inventory/source.d.ts.map +1 -1
  328. package/types/lib/inventory/spdx.d.ts +24 -0
  329. package/types/lib/inventory/spdx.d.ts.map +1 -1
  330. package/types/lib/inventory/traceRunner.d.ts +16 -1
  331. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  332. package/types/lib/inventory/tracedCrypto.d.ts +64 -0
  333. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  334. package/types/lib/managers/binary.d.ts +10 -0
  335. package/types/lib/managers/binary.d.ts.map +1 -1
  336. package/types/lib/managers/docker.d.ts +12 -0
  337. package/types/lib/managers/docker.d.ts.map +1 -1
  338. package/types/lib/managers/oci.d.ts +3 -1
  339. package/types/lib/managers/oci.d.ts.map +1 -1
  340. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  341. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  342. package/types/lib/parsers/cmakeLists.d.ts +2 -1
  343. package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
  344. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  345. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  346. package/types/lib/parsers/compileCommands.d.ts +115 -0
  347. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  348. package/types/lib/parsers/email.d.ts +19 -0
  349. package/types/lib/parsers/email.d.ts.map +1 -0
  350. package/types/lib/parsers/largeJson.d.ts +13 -0
  351. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  352. package/types/lib/parsers/strictJson.d.ts +46 -0
  353. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  354. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  355. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  356. package/types/lib/parsers/terraformSource.d.ts +66 -0
  357. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  358. package/types/lib/server/server.d.ts +8 -0
  359. package/types/lib/server/server.d.ts.map +1 -1
  360. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  361. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  362. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  363. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  364. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  365. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  366. package/types/lib/stages/pregen/pregen.d.ts +14 -1
  367. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  368. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  369. package/types/lib/validator/index.d.ts +8 -0
  370. package/types/lib/validator/index.d.ts.map +1 -1
  371. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  372. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -1,10 +1,11 @@
1
1
  import { Buffer } from "node:buffer";
2
- import { chmodSync, readFileSync } from "node:fs";
2
+ import { chmodSync, readdirSync, readFileSync } from "node:fs";
3
3
  import { homedir, platform } from "node:os";
4
4
  import { basename, join, resolve } from "node:path";
5
+ import process from "node:process";
5
6
 
6
7
  import { build } from "@cdxgen/cdx-purl";
7
- import * as toml from "smol-toml";
8
+ import { valid } from "semver";
8
9
 
9
10
  import {
10
11
  DEBUG_MODE,
@@ -22,6 +23,12 @@ import {
22
23
  import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
23
24
  import { thoughtLog } from "../core/logger.js";
24
25
  import { isWin } from "../core/paths.js";
26
+ import { isProjectToolingAllowed } from "../core/projectTooling.js";
27
+ import { parseToml } from "../core/toml.js";
28
+ import {
29
+ isBuildToolRateLimited,
30
+ noteBuildToolRateLimit,
31
+ } from "../inventory/buildToolRateLimit.js";
25
32
  import { collectJarNS } from "../inventory/deps.js";
26
33
  import { minimumGradleVersionForJava } from "../inventory/jvmToolEnv.js";
27
34
  import { applyPurl, tryBuildPurl } from "../inventory/purl.js";
@@ -38,13 +45,16 @@ function isJvmToolPinned() {
38
45
  );
39
46
  }
40
47
 
48
+ // The gate lives in lib/core so that every layer can reach it; it stays
49
+ // exported here for existing callers.
50
+ export { isProjectToolingAllowed };
51
+
41
52
  /**
42
53
  * Method to return the gradle command to use.
43
54
  *
44
55
  * Project-local wrapper scripts (`gradlew`) are attacker-controlled files in
45
- * the scanned project, so they are only used when dependency tooling is
46
- * allowed (`options.installDeps !== false`); otherwise the system Gradle is
47
- * used.
56
+ * the scanned project, so they are only used when the project's own tooling
57
+ * may run (see isProjectToolingAllowed); otherwise the system Gradle is used.
48
58
  *
49
59
  * @param {string} srcPath Path to look for gradlew wrapper
50
60
  * @param {string|null} rootPath Root directory to look for gradlew wrapper
@@ -53,10 +63,7 @@ function isJvmToolPinned() {
53
63
  export function getGradleCommand(srcPath, rootPath, options = {}) {
54
64
  let gradleCmd = "gradle";
55
65
  const pinnedToolSelected = isJvmToolPinned();
56
- // Introspection mode exists to observe the project's own toolchain, so it
57
- // keeps using wrappers even when dependency installation is disabled.
58
- const allowProjectWrappers =
59
- options.installDeps !== false || options.introspect === true;
66
+ const allowProjectWrappers = isProjectToolingAllowed(options);
60
67
 
61
68
  let findGradleFile = "gradlew";
62
69
  if (platform() === "win32") {
@@ -152,6 +159,65 @@ export function getGradleCommand(srcPath, rootPath, options = {}) {
152
159
  return gradleCmd;
153
160
  }
154
161
 
162
+ /**
163
+ * System properties that force the evaluation model cdxgen's Gradle
164
+ * introspection depends on.
165
+ *
166
+ * The bundled init script registers `afterEvaluate` on every project and a
167
+ * `taskGraph.whenReady` listener, so it cannot run under isolated projects
168
+ * (which forbid reaching into another project's model and fail the build at
169
+ * configuration time, see issue #4444) nor under the configuration cache (whose
170
+ * reuse skips init scripts entirely, dropping the `<CDXGEN:repository>` and
171
+ * `<CDXGEN:includedBuild>` markers even when the first run succeeded). Both
172
+ * features are commonly enabled from `gradle.properties` (e.g. okhttp), which
173
+ * command-line `-D` flags override, so every cdxgen invocation turns them
174
+ * off: these are ephemeral introspection builds, not the user's build. The
175
+ * isolated-projects property is spelled `org.gradle.unsafe.isolated-projects`
176
+ * from Gradle 8.6 until Gradle 9.7 renamed it, so both spellings are forced
177
+ * off to cover every Gradle in between. The configuration cache likewise
178
+ * keeps its pre-8.1 `org.gradle.unsafe.configuration-cache` spelling.
179
+ *
180
+ * Each feature is handled as one group. Gradle disables a feature when any of
181
+ * its spellings is explicitly `false`, so if the user already chose a value
182
+ * via `GRADLE_ARGS`, either as a `-D` property under any spelling or as the
183
+ * `--[no-]isolated-projects` / `--[no-]configuration-cache` option (which
184
+ * beats `-D` properties), cdxgen adds no override for that feature at all.
185
+ *
186
+ * @param {string[]} gradleArguments User-supplied general gradle arguments.
187
+ * @returns {string[]} The `-D` flags to inject before the user's arguments.
188
+ */
189
+ function gradleEvaluationOverrides(gradleArguments) {
190
+ const features = [
191
+ {
192
+ option: "isolated-projects",
193
+ properties: [
194
+ "org.gradle.isolated-projects",
195
+ "org.gradle.unsafe.isolated-projects",
196
+ ],
197
+ },
198
+ {
199
+ option: "configuration-cache",
200
+ properties: [
201
+ "org.gradle.configuration-cache",
202
+ "org.gradle.unsafe.configuration-cache",
203
+ ],
204
+ },
205
+ ];
206
+ const overrides = [];
207
+ for (const { option, properties } of features) {
208
+ const userChose = gradleArguments.some(
209
+ (arg) =>
210
+ arg === `--${option}` ||
211
+ arg === `--no-${option}` ||
212
+ properties.some((property) => arg.startsWith(`-D${property}=`)),
213
+ );
214
+ if (!userChose) {
215
+ overrides.push(...properties.map((property) => `-D${property}=false`));
216
+ }
217
+ }
218
+ return overrides;
219
+ }
220
+
155
221
  /**
156
222
  * Method to combine the general gradle arguments, the sub-commands and the sub-commands' arguments in the correct way
157
223
  *
@@ -175,6 +241,7 @@ export function buildGradleCommandArguments(
175
241
  "--no-parallel",
176
242
  ]
177
243
  .concat(getGradleDaemonParameter())
244
+ .concat(gradleEvaluationOverrides(gradleArguments))
178
245
  .concat(gradleArguments);
179
246
  const maxCliArgsLength = isWin
180
247
  ? 7500 - gradleCommandLength - mainGradleArguments.join(" ").length - 2
@@ -217,47 +284,87 @@ function getGradleDaemonParameter() {
217
284
  }
218
285
  }
219
286
 
287
+ /**
288
+ * Parse a Gradle task header line such as `> Task :app:dependencies FAILED`
289
+ * into the path of the project that owns the task and the task's name.
290
+ *
291
+ * @param {string} line A line of Gradle output
292
+ * @returns {{projectPath: string, taskName: string}|undefined} The owning
293
+ * project's path (`""` for a root task such as `:dependencies`) and the task
294
+ * name, or undefined when the line is not a task header
295
+ */
296
+ function parseGradleTaskHeader(line) {
297
+ if (!line.startsWith("> Task :")) {
298
+ return undefined;
299
+ }
300
+ // The path is the first token: a status such as UP-TO-DATE or FAILED may
301
+ // follow it, and a CRLF line keeps its carriage return.
302
+ const taskPath = line.slice("> Task ".length).trim().split(/\s+/)[0];
303
+ const lastColon = taskPath.lastIndexOf(":");
304
+ return {
305
+ projectPath: taskPath.slice(0, lastColon),
306
+ taskName: taskPath.slice(lastColon + 1),
307
+ };
308
+ }
309
+
220
310
  /**
221
311
  * Method to split the output produced by Gradle using parallel processing by project
222
312
  *
313
+ * Gradle groups console output by task and prints a `> Task :path` header
314
+ * before each group, repeating it whenever a task's output resumes after
315
+ * another task's. Each group is therefore keyed by the project in its own
316
+ * header (`> Task :app:dependencies` belongs to `:app`), never by whatever
317
+ * report header happened to precede it, and the groups of one project are
318
+ * concatenated in order, so reordering or interrupting the task reports cannot
319
+ * move a report to another project or overwrite one (issue 4465). The groups
320
+ * of a root task (`:dependencies`) all take the root project's name from the
321
+ * `Root project '…'` header that only a root task prints, so a continuation
322
+ * group, which carries no report header, stays with the rest of the root
323
+ * report. The groups of other tasks are dropped along with their headers.
324
+ *
223
325
  * @param {string} rawOutput Full output produced by Gradle using parallel processing
224
326
  * @param {string[]} relevantTasks The list of gradle tasks whose output need to be considered.
225
327
  * @returns {map} Map with subProject names as keys and corresponding dependency task outputs as values.
226
328
  */
227
329
  export function splitOutputByGradleProjects(rawOutput, relevantTasks) {
228
330
  const outputSplitBySubprojects = new Map();
229
- let subProjectOut = "";
230
- const outSplitByLine = rawOutput.split("\n");
231
- let currentProjectName = "root";
232
- const regexPatternForRelevantTasks = `.*:(${relevantTasks.join("|")})(?=s|\r|$)`;
233
- const regexForRelevantTasks = new RegExp(regexPatternForRelevantTasks);
234
- for (const [i, line] of outSplitByLine.entries()) {
235
- //filter out everything before first task output
236
- if (!line.startsWith("> Task :") && subProjectOut === "") {
237
- continue;
331
+ const relevantTaskNames = new Set(relevantTasks);
332
+ const taskChunks = [];
333
+ // Lines before the first task header (build logs) belong to no task.
334
+ let currentChunk;
335
+ for (const line of rawOutput.split("\n")) {
336
+ const taskHeader = parseGradleTaskHeader(line);
337
+ if (taskHeader) {
338
+ currentChunk = relevantTaskNames.has(taskHeader.taskName)
339
+ ? { projectPath: taskHeader.projectPath, lines: [line] }
340
+ : undefined;
341
+ if (currentChunk) {
342
+ taskChunks.push(currentChunk);
343
+ }
344
+ } else if (currentChunk) {
345
+ currentChunk.lines.push(line);
238
346
  }
239
-
240
- //ignore output of irrelevant tasks
241
- if (line.startsWith("> Task :") && !regexForRelevantTasks.test(line)) {
347
+ }
348
+ let rootProjectName;
349
+ for (const chunk of taskChunks) {
350
+ if (chunk.projectPath) {
242
351
  continue;
243
352
  }
244
-
245
- if (line.startsWith("Root project '") || line.startsWith("Project ':")) {
246
- currentProjectName = line.split("'")[1];
247
- outputSplitBySubprojects.set(currentProjectName, "");
248
- }
249
- // if previous subProject has ended, push to array and reset subProject string
250
- if (line.startsWith("> Task :") && subProjectOut !== "") {
251
- outputSplitBySubprojects.set(currentProjectName, subProjectOut);
252
- subProjectOut = "";
253
- }
254
- //if in subproject block, keep appending to string
255
- subProjectOut += `${line}\n`;
256
- //if end of last dependencies output block, push to array
257
- if (i === outSplitByLine.length - 1) {
258
- outputSplitBySubprojects.set(currentProjectName, subProjectOut);
353
+ const rootHeader = chunk.lines.find((line) =>
354
+ line.startsWith("Root project '"),
355
+ );
356
+ if (rootHeader) {
357
+ rootProjectName = rootHeader.split("'")[1];
358
+ break;
259
359
  }
260
360
  }
361
+ for (const chunk of taskChunks) {
362
+ const projectName = chunk.projectPath || rootProjectName || "root";
363
+ outputSplitBySubprojects.set(
364
+ projectName,
365
+ `${outputSplitBySubprojects.get(projectName) || ""}${chunk.lines.join("\n")}\n`,
366
+ );
367
+ }
261
368
  return outputSplitBySubprojects;
262
369
  }
263
370
 
@@ -397,6 +504,11 @@ export function executeParallelGradleProperties(
397
504
  extraArgs = [],
398
505
  options = {},
399
506
  ) {
507
+ // This runs once per properties round, so a rate limit an earlier round met
508
+ // ends the later rounds too.
509
+ if (isBuildToolRateLimited("gradle")) {
510
+ return "";
511
+ }
400
512
  const gradleCmd = getGradleCommand(dir, null, options);
401
513
  const gradleArgs = buildGradleCommandArguments(
402
514
  extraArgs.concat(
@@ -423,6 +535,22 @@ export function executeParallelGradleProperties(
423
535
  cwd: dir,
424
536
  shell: isWin,
425
537
  });
538
+ // A properties invocation needs the same repository the build does, so a
539
+ // 429 answer here ends every further gradle invocation in this scan. The
540
+ // dependencies task can even exit 0 under a rate limit, so the output is
541
+ // read whatever the status is. The properties this invocation did print
542
+ // are kept.
543
+ if (
544
+ noteBuildToolRateLimit("gradle", commandOutputText(result), {
545
+ command: `${basename(gradleCmd)} ${gradleArg.join(" ")}`,
546
+ exitCode: typeof result.status === "number" ? result.status : undefined,
547
+ })
548
+ ) {
549
+ if (result.stdout) {
550
+ allOutputs.push(result.stdout);
551
+ }
552
+ break;
553
+ }
426
554
  if (result.status !== 0 || result.error) {
427
555
  recordGradleInvocationFailure(result, {
428
556
  command: `${basename(gradleCmd)} ${gradleArg.join(" ")}`,
@@ -1222,15 +1350,18 @@ export async function collectGradleDependencies(
1222
1350
  * Method to return the mill command to use.
1223
1351
  *
1224
1352
  * The project-local `mill` script is an attacker-controlled file in the
1225
- * scanned project, so it is only used when dependency tooling is allowed
1226
- * (`options.installDeps !== false`).
1353
+ * scanned project, so it is only used when the project's own tooling may run
1354
+ * (see isProjectToolingAllowed).
1227
1355
  *
1228
1356
  * @param {string} srcPath Path to look for mill wrapper
1229
1357
  * @param {Object} [options] CLI options (`installDeps` gates wrapper use)
1230
1358
  */
1231
1359
  export function getMillCommand(srcPath, options = {}) {
1232
1360
  let millCmd = `mill${platform() === "win32" ? ".bat" : ""}`;
1233
- if (options.installDeps !== false && safeExistsSync(join(srcPath, millCmd))) {
1361
+ if (
1362
+ isProjectToolingAllowed(options) &&
1363
+ safeExistsSync(join(srcPath, millCmd))
1364
+ ) {
1234
1365
  // Use local mill wrapper if available
1235
1366
  // Enable execute permission
1236
1367
  try {
@@ -1244,12 +1375,178 @@ export function getMillCommand(srcPath, options = {}) {
1244
1375
  return millCmd;
1245
1376
  }
1246
1377
 
1378
+ /**
1379
+ * Determine the Mill version of a build.
1380
+ *
1381
+ * The version is read from the `.mill-version` file, from the
1382
+ * `//| mill-version:` header of the build file, or from the launcher itself.
1383
+ *
1384
+ * @param {string} millRootPath Root of the Mill build
1385
+ * @param {string} millCmd Mill command to use for the fallback probe
1386
+ * @returns {string|null} The Mill version, or null when it cannot be determined
1387
+ */
1388
+ export function getMillVersion(millRootPath, millCmd) {
1389
+ const millVersionFile = join(millRootPath, ".mill-version");
1390
+ if (safeExistsSync(millVersionFile)) {
1391
+ const version = (readFileSync(millVersionFile, "utf-8") || "").trim();
1392
+ if (valid(version)) {
1393
+ return version;
1394
+ }
1395
+ }
1396
+ for (const buildFile of ["build.mill", "build.sc"]) {
1397
+ const buildFilePath = join(millRootPath, buildFile);
1398
+ if (!safeExistsSync(buildFilePath)) {
1399
+ continue;
1400
+ }
1401
+ const headerMatch = /^\/\/[|\s]\s*mill-version:\s*(\S+)/m.exec(
1402
+ readFileSync(buildFilePath, "utf-8"),
1403
+ );
1404
+ if (headerMatch?.[1] && valid(headerMatch[1])) {
1405
+ return headerMatch[1];
1406
+ }
1407
+ }
1408
+ const versionResult = safeSpawnSync(millCmd, ["--no-server", "--version"], {
1409
+ cwd: millRootPath,
1410
+ shell: isWin,
1411
+ });
1412
+ const versionMatch = /version\s+(\S+)/.exec(`${versionResult.stdout || ""}`);
1413
+ return versionMatch?.[1] && valid(versionMatch[1]) ? versionMatch[1] : null;
1414
+ }
1415
+
1416
+ /**
1417
+ * Find the Mill daemons running under a tree.
1418
+ *
1419
+ * Daemons record their process id in `out/mill-daemon*` (Mill 1.x) or
1420
+ * `out/mill-server*` directories, directly or one level below (Mill 0.12
1421
+ * keeps one subdirectory per server). Only processes that are still alive
1422
+ * are returned.
1423
+ *
1424
+ * @param {string} rootPath Tree to search
1425
+ * @returns {Map<string, Set<number>>} Live daemon process ids by the root of
1426
+ * the build they belong to
1427
+ */
1428
+ export function findMillDaemons(rootPath) {
1429
+ const daemons = new Map();
1430
+ const readPid = (dir) => {
1431
+ for (const candidate of [dir, ...safeSubdirs(dir)]) {
1432
+ try {
1433
+ const pid = Number.parseInt(
1434
+ readFileSync(join(candidate, "processId"), "utf-8").trim(),
1435
+ 10,
1436
+ );
1437
+ if (pid > 0 && isProcessAlive(pid)) {
1438
+ return pid;
1439
+ }
1440
+ } catch (_err) {
1441
+ // no process id recorded in this directory
1442
+ }
1443
+ }
1444
+ return undefined;
1445
+ };
1446
+ const visit = (dir, depth) => {
1447
+ if (depth > 4) {
1448
+ return;
1449
+ }
1450
+ let entries;
1451
+ try {
1452
+ entries = readdirSync(dir, { withFileTypes: true });
1453
+ } catch (_err) {
1454
+ return;
1455
+ }
1456
+ for (const entry of entries) {
1457
+ if (!entry.isDirectory()) {
1458
+ continue;
1459
+ }
1460
+ if (entry.name === "out") {
1461
+ for (const name of safeSubdirs(join(dir, entry.name))) {
1462
+ const base = basename(name);
1463
+ if (
1464
+ !base.startsWith("mill-daemon") &&
1465
+ !base.startsWith("mill-server")
1466
+ ) {
1467
+ continue;
1468
+ }
1469
+ const pid = readPid(name);
1470
+ if (pid) {
1471
+ if (!daemons.has(dir)) {
1472
+ daemons.set(dir, new Set());
1473
+ }
1474
+ daemons.get(dir).add(pid);
1475
+ }
1476
+ }
1477
+ } else if (!["target", "node_modules", ".git"].includes(entry.name)) {
1478
+ visit(join(dir, entry.name), depth + 1);
1479
+ }
1480
+ }
1481
+ };
1482
+ visit(rootPath, 0);
1483
+ return daemons;
1484
+ }
1485
+
1486
+ function safeSubdirs(dir) {
1487
+ try {
1488
+ return readdirSync(dir, { withFileTypes: true })
1489
+ .filter((entry) => entry.isDirectory())
1490
+ .map((entry) => join(dir, entry.name));
1491
+ } catch (_err) {
1492
+ return [];
1493
+ }
1494
+ }
1495
+
1496
+ function isProcessAlive(pid) {
1497
+ try {
1498
+ process.kill(pid, 0);
1499
+ return true;
1500
+ } catch (err) {
1501
+ return err?.code === "EPERM";
1502
+ }
1503
+ }
1504
+
1505
+ /**
1506
+ * Stop the Mill daemons that started under the scanned tree during the scan.
1507
+ *
1508
+ * cdxgen's own Mill calls run with --no-server, but a project build cdxgen
1509
+ * runs can shell out to Mill without it and leave a daemon behind. Daemons
1510
+ * that were already running when the scan started belong to the user, for
1511
+ * example to an IDE, and are left alone; every other live daemon is shut
1512
+ * down through the launcher of the build it belongs to.
1513
+ *
1514
+ * @param {string} rootPath Scanned tree
1515
+ * @param {Map<string, Set<number>>} [runningBefore] Result of
1516
+ * {@link findMillDaemons} taken before the scan
1517
+ * @param {Object} [options] CLI options
1518
+ */
1519
+ export function stopMillDaemons(
1520
+ rootPath,
1521
+ runningBefore = new Map(),
1522
+ options = {},
1523
+ ) {
1524
+ // The daemon PID files live in the scanned tree. When the scan was not
1525
+ // allowed to run Mill it started no daemon, and running the build's own
1526
+ // launcher here would execute a script from the scanned project.
1527
+ if (!isProjectToolingAllowed(options)) {
1528
+ return;
1529
+ }
1530
+ for (const [buildRoot, pids] of findMillDaemons(rootPath)) {
1531
+ const before = runningBefore.get(buildRoot) || new Set();
1532
+ if ([...pids].every((pid) => before.has(pid))) {
1533
+ continue;
1534
+ }
1535
+ const launcher = getMillCommand(buildRoot, options);
1536
+ console.log("Stopping the Mill daemon the scan started in", buildRoot);
1537
+ safeSpawnSync(launcher, ["shutdown"], {
1538
+ cwd: buildRoot,
1539
+ shell: isWin,
1540
+ });
1541
+ }
1542
+ }
1543
+
1247
1544
  /**
1248
1545
  * Method to return the maven command to use.
1249
1546
  *
1250
1547
  * Project-local wrapper scripts (`mvnw`) are attacker-controlled files in the
1251
- * scanned project, so they are only used when dependency tooling is allowed
1252
- * (`options.installDeps !== false`); otherwise the installed Maven is used.
1548
+ * scanned project, so they are only used when the project's own tooling may
1549
+ * run (see isProjectToolingAllowed); otherwise the installed Maven is used.
1253
1550
  *
1254
1551
  * @param {string} srcPath Path to look for maven wrapper
1255
1552
  * @param {string} rootPath Root directory to look for maven wrapper
@@ -1261,10 +1558,7 @@ export function getMavenCommand(srcPath, rootPath, options = {}) {
1261
1558
  let isWrapperReady = false;
1262
1559
  let isWrapperFound = false;
1263
1560
  const pinnedToolSelected = isJvmToolPinned();
1264
- // Introspection mode exists to observe the project's own toolchain, so it
1265
- // keeps using wrappers even when dependency installation is disabled.
1266
- const allowProjectWrappers =
1267
- options.installDeps !== false || options.introspect === true;
1561
+ const allowProjectWrappers = isProjectToolingAllowed(options);
1268
1562
  let findMavenFile = "mvnw";
1269
1563
  let mavenWrapperCmd = null;
1270
1564
  if (platform() === "win32") {
@@ -1445,7 +1739,7 @@ export function getMavenCommand(srcPath, rootPath, options = {}) {
1445
1739
  export function parseGradleVersionCatalog(catalogFile) {
1446
1740
  let catalog;
1447
1741
  try {
1448
- catalog = toml.parse(readFileSync(catalogFile, "utf-8"));
1742
+ catalog = parseToml(readFileSync(catalogFile, "utf-8"));
1449
1743
  } catch (error) {
1450
1744
  console.warn(`Failed to parse ${catalogFile}: ${error.message}`);
1451
1745
  return [];
@@ -6,7 +6,7 @@
6
6
  // import parsers-js directly instead of having parsePkgJson threaded in as an
7
7
  // injected helper from the CLI.
8
8
 
9
- import { join, resolve } from "node:path";
9
+ import { dirname, isAbsolute, join, resolve } from "node:path";
10
10
 
11
11
  import { safeExistsSync } from "../core/fs.js";
12
12
  import { createOccurrenceEvidence } from "../inventory/evidenceUtils.js";
@@ -104,6 +104,85 @@ function applyNpmBinCommandEvidence(pkg, allImports) {
104
104
  }
105
105
  }
106
106
 
107
+ /**
108
+ * Full npm package name (`@scope/name` or `name`) of a component or manifest.
109
+ *
110
+ * @param {{group?: string, name?: string}} pkg Component or parsed manifest.
111
+ * @returns {string} The package name npm installs it under.
112
+ */
113
+ function npmPackageName({ group, name }) {
114
+ const scope = group?.startsWith("@") ? group.substring(1) : group;
115
+ return scope ? `@${scope}/${name}` : name;
116
+ }
117
+
118
+ /**
119
+ * Fill in a component's missing npm metadata (description, author, license,
120
+ * homepage, repository) from the package.json of its installed copy.
121
+ *
122
+ * `internal:LocalNodeModulesPath` holds either an absolute install directory
123
+ * (pnpm) or an arborist `node.location`. A location that starts with
124
+ * `node_modules/` is relative to the directory of the component's
125
+ * `internal:SrcFile`. Other relative locations, such as a workspace member or a
126
+ * package nested under one, have no recorded base and are skipped; the deep npm
127
+ * parse reads those manifests from the arborist node itself. The manifest is
128
+ * used only when it names the component's package and version, and it fills
129
+ * only the fields the component lacks.
130
+ *
131
+ * @param {object} pkg Package component to enrich in place.
132
+ */
133
+ async function fillMissingMetadataFromLocalNodeModules(pkg) {
134
+ if (pkg.description || !Array.isArray(pkg.properties)) {
135
+ return;
136
+ }
137
+ const propValue = (propName) =>
138
+ pkg.properties.find((property) => property.name === propName)?.value;
139
+ const localNodeModulesPath = propValue("internal:LocalNodeModulesPath");
140
+ if (!localNodeModulesPath) {
141
+ return;
142
+ }
143
+ let pkgJsonFile;
144
+ if (isAbsolute(localNodeModulesPath)) {
145
+ pkgJsonFile = join(localNodeModulesPath, "package.json");
146
+ } else {
147
+ const srcFile = propValue("internal:SrcFile");
148
+ if (!srcFile || !localNodeModulesPath.startsWith("node_modules/")) {
149
+ return;
150
+ }
151
+ pkgJsonFile = resolve(
152
+ dirname(srcFile),
153
+ localNodeModulesPath,
154
+ "package.json",
155
+ );
156
+ }
157
+ if (!safeExistsSync(pkgJsonFile)) {
158
+ return;
159
+ }
160
+ const lnmPkgList = await parsePkgJson(pkgJsonFile, true);
161
+ const lnmMetadata = lnmPkgList?.length === 1 ? lnmPkgList[0] : undefined;
162
+ if (
163
+ !lnmMetadata ||
164
+ npmPackageName(lnmMetadata) !== npmPackageName(pkg) ||
165
+ lnmMetadata.version !== pkg.version
166
+ ) {
167
+ return;
168
+ }
169
+ if (!pkg.description && lnmMetadata.description) {
170
+ pkg.description = lnmMetadata.description;
171
+ }
172
+ if (!pkg.author && lnmMetadata.author) {
173
+ pkg.author = lnmMetadata.author;
174
+ }
175
+ if (!pkg.license && lnmMetadata.license) {
176
+ pkg.license = lnmMetadata.license;
177
+ }
178
+ if (!pkg.homepage && lnmMetadata.homepage) {
179
+ pkg.homepage = lnmMetadata.homepage;
180
+ }
181
+ if (!pkg.repository && lnmMetadata.repository) {
182
+ pkg.repository = lnmMetadata.repository;
183
+ }
184
+ }
185
+
107
186
  /**
108
187
  * Attach JS/TS import and export usage evidence to matching package components.
109
188
  *
@@ -136,6 +215,11 @@ export async function addEvidenceForImports(
136
215
  if (group === "@types") {
137
216
  continue;
138
217
  }
218
+ // Runs ahead of the alias loop, which a matching import ends with a break,
219
+ // so that every component is enriched whichever alias the source imports.
220
+ if (deep) {
221
+ await fillMissingMetadataFromLocalNodeModules(pkg);
222
+ }
139
223
  const aliases = group?.length
140
224
  ? [name, `${group}/${name}`, `@${group}/${name}`]
141
225
  : [name];
@@ -282,29 +366,6 @@ export async function addEvidenceForImports(
282
366
  }
283
367
  break;
284
368
  }
285
- // Capture metadata such as description from local node_modules in deep mode
286
- if (deep && !pkg.description && pkg.properties) {
287
- let localNodeModulesPath;
288
- for (const aprop of pkg.properties) {
289
- if (aprop.name === "internal:LocalNodeModulesPath") {
290
- localNodeModulesPath = resolve(join(aprop.value, "package.json"));
291
- break;
292
- }
293
- }
294
- if (localNodeModulesPath && safeExistsSync(localNodeModulesPath)) {
295
- const lnmPkgList = await parsePkgJson(localNodeModulesPath, true);
296
- if (lnmPkgList && lnmPkgList.length === 1) {
297
- const lnmMetadata = lnmPkgList[0];
298
- if (lnmMetadata && Object.keys(lnmMetadata).length) {
299
- pkg.description = lnmMetadata.description;
300
- pkg.author = lnmMetadata.author;
301
- pkg.license = lnmMetadata.license;
302
- pkg.homepage = lnmMetadata.homepage;
303
- pkg.repository = lnmMetadata.repository;
304
- }
305
- }
306
- }
307
- }
308
369
  } // for alias
309
370
  // Trim the properties
310
371
  applyNpmBinCommandEvidence(pkg, allImports);
@@ -10,6 +10,7 @@ import {
10
10
  safeRmSync,
11
11
  safeSpawnSync,
12
12
  } from "../core/fs.js";
13
+ import { resolvePluginSourceDir } from "../core/pluginRun.js";
13
14
  import { resolvePluginBinary } from "../inventory/plugins.js";
14
15
  import {
15
16
  parsePurlLoose,
@@ -174,14 +175,25 @@ export function analyzeKosiProject(src, options = {}) {
174
175
  );
175
176
  return undefined;
176
177
  }
178
+ // cdxgen may run with its working directory already inside the project
179
+ // while the source argument is relative to a repository root; a plain
180
+ // resolve would join the two into a directory that does not exist.
181
+ const analysisDir = resolvePluginSourceDir(src, [
182
+ "pom.xml",
183
+ "build.gradle",
184
+ "build.gradle.kts",
185
+ "settings.gradle",
186
+ "settings.gradle.kts",
187
+ "classpath.txt",
188
+ ]);
177
189
  const tempDir = safeMkdtempSync(join(getTmpDir(), "kosi-"));
178
190
  try {
179
191
  const outputFile = join(tempDir, "kosi-all.json");
180
- const allArgs = kosiArgs(kosi, src, outputFile, "all");
192
+ const allArgs = kosiArgs(kosi, analysisDir, outputFile, "all");
181
193
  if (DEBUG_MODE) {
182
194
  console.log("Executing", kosi.cmd, allArgs.join(" "));
183
195
  }
184
- const report = runKosi(kosi, src, allArgs, outputFile, options);
196
+ const report = runKosi(kosi, analysisDir, allArgs, outputFile, options);
185
197
  if (!report) {
186
198
  return undefined;
187
199
  }
@@ -191,10 +203,15 @@ export function analyzeKosiProject(src, options = {}) {
191
203
  // all-pass's. Losing it would silently drop the filtered evidence,
192
204
  // so a failure here names itself but keeps the all-pass evidence.
193
205
  const reachableFile = join(tempDir, "kosi-reachable.json");
194
- const reachableArgs = kosiArgs(kosi, src, reachableFile, "reachable");
206
+ const reachableArgs = kosiArgs(
207
+ kosi,
208
+ analysisDir,
209
+ reachableFile,
210
+ "reachable",
211
+ );
195
212
  const reachableReport = runKosi(
196
213
  kosi,
197
- src,
214
+ analysisDir,
198
215
  reachableArgs,
199
216
  reachableFile,
200
217
  options,