@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
package/lib/cli/jvmBom.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Buffer } from "node:buffer";
2
- import { readFileSync } from "node:fs";
2
+ import { readdirSync, readFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { basename, dirname, join, resolve, sep } from "node:path";
5
5
  import process from "node:process";
@@ -35,16 +35,23 @@ import {
35
35
  } from "../core/fs.js";
36
36
  import { thoughtLog } from "../core/logger.js";
37
37
  import { dirNameStr } from "../core/paths.js";
38
+ import {
39
+ isSecureModeActive,
40
+ reportProjectToolingSkipped,
41
+ } from "../core/projectTooling.js";
38
42
  import { extractJarArchive, getMvnMetadata } from "../ecosystems/ecosystems.js";
39
43
  import {
40
44
  buildGradleCommandArguments,
41
45
  buildObjectForGradleModule,
42
46
  collectGradleDependencies,
43
47
  executeParallelGradleProperties,
48
+ findMillDaemons,
44
49
  getGradleCommand,
45
50
  getMavenCommand,
46
51
  getMillCommand,
52
+ getMillVersion,
47
53
  hasGradleInvocationFailure,
54
+ isProjectToolingAllowed,
48
55
  parseGradleDep,
49
56
  parseGradleInfoLogsForUrls,
50
57
  parseGradleProperties,
@@ -52,29 +59,42 @@ import {
52
59
  parseGradleVersionCatalog,
53
60
  recordGradleInvocationFailure,
54
61
  splitOutputByGradleProjects,
62
+ stopMillDaemons,
55
63
  } from "../ecosystems/gradleutils.js";
56
64
  import {
57
65
  parseModuleBazel,
58
66
  parseModuleBazelLock,
59
67
  } from "../ecosystems/parsers-bazel.js";
60
68
  import {
69
+ MAVEN_MODULE_TREE_STEM,
61
70
  parseBazelActionGraph,
62
71
  parseBazelSkyframe,
63
72
  parseMavenTree,
64
73
  parseMavenTreeJson,
65
74
  parseMillDependency,
66
75
  parsePom,
76
+ parsePomWithLocalParents,
77
+ readMavenModuleTrees,
67
78
  } from "../ecosystems/parsers-jvm.js";
68
79
  import {
69
80
  addPlugin,
70
81
  cleanupPlugin,
71
82
  determineSbtVersion,
72
83
  discoverSbtProjects,
84
+ isSbt2Version,
73
85
  parseSbtLock,
74
86
  parseSbtProjects,
75
87
  parseSbtRootProject,
76
88
  parseSbtTree,
89
+ sbt2DependencyTreeCommand,
90
+ sbtSpawnSync,
77
91
  } from "../ecosystems/sbtutils.js";
92
+ import { collectScalaCliComponents } from "../ecosystems/scalacliutils.js";
93
+ import { collectScalaJsNpmComponents } from "../ecosystems/scalasem.js";
94
+ import {
95
+ isBuildToolRateLimited,
96
+ noteBuildToolRateLimit,
97
+ } from "../inventory/buildToolRateLimit.js";
78
98
  import {
79
99
  collectJarNS,
80
100
  collectMvnDependencies,
@@ -85,8 +105,15 @@ import {
85
105
  attachIdentityTools,
86
106
  extractToolRefs,
87
107
  } from "../inventory/evidenceUtils.js";
108
+ import {
109
+ coursierCacheDir,
110
+ findLocalMavenArtifact,
111
+ locateInCoursierCache,
112
+ projectSettingsArgs,
113
+ } from "../inventory/jvmLocalRepos.js";
88
114
  import { readGradleWrapperVersion } from "../inventory/jvmToolEnv.js";
89
115
  import { applyPurl, mavenPurl } from "../inventory/purl.js";
116
+ import { publishedArtifactId } from "../inventory/scalaCoords.js";
90
117
  import {
91
118
  buildBomNSData,
92
119
  createDefaultParentComponent,
@@ -136,6 +163,127 @@ export const GRADLE_INIT_SCRIPT = resolve(
136
163
  export const SBT_CACHE_DIR =
137
164
  readEnvironmentVariable("SBT_CACHE_DIR") || join(homedir(), ".ivy2", "cache");
138
165
 
166
+ /**
167
+ * Collect the jars in the caches sbt uses: the Coursier cache, which sbt 1.3
168
+ * and later resolve into, and the Ivy cache named by SBT_CACHE_DIR, which
169
+ * older builds used.
170
+ *
171
+ * @returns {Promise<Object>} Jar namespace mapping, as collectJarNS returns.
172
+ */
173
+ async function collectSbtCacheDependencies() {
174
+ let nsMapping = {};
175
+ for (const cacheDir of [coursierCacheDir(), SBT_CACHE_DIR]) {
176
+ if (cacheDir && safeExistsSync(cacheDir)) {
177
+ nsMapping = { ...nsMapping, ...(await collectJarNS(cacheDir)) };
178
+ }
179
+ }
180
+ return nsMapping;
181
+ }
182
+
183
+ /**
184
+ * The jar of a resolved sbt dependency in the user's own caches: the Coursier
185
+ * cache sbt 1.3 and later resolve into, the other local Maven caches, and the
186
+ * Ivy cache older builds used.
187
+ *
188
+ * @param {Object} pkg Package with group, name and version.
189
+ * @returns {string|undefined} Path of the jar.
190
+ */
191
+ function localSbtDependencyJar(pkg) {
192
+ // Scala components are named without their binary suffix; the jar has it.
193
+ const name = publishedArtifactId(pkg);
194
+ const coursierDir = locateInCoursierCache(pkg.group, name, pkg.version)?.dir;
195
+ const coursierJar =
196
+ coursierDir && join(coursierDir, `${name}-${pkg.version}.jar`);
197
+ if (coursierJar && safeExistsSync(coursierJar)) {
198
+ return coursierJar;
199
+ }
200
+ const jarPath = findLocalMavenArtifact(pkg.group, name, pkg.version)?.jarPath;
201
+ if (jarPath) {
202
+ return jarPath;
203
+ }
204
+ for (const typeDir of ["jars", "bundles"]) {
205
+ const ivyJar = join(
206
+ SBT_CACHE_DIR,
207
+ pkg.group,
208
+ name,
209
+ typeDir,
210
+ `${name}-${pkg.version}.jar`,
211
+ );
212
+ if (safeExistsSync(ivyJar)) {
213
+ return ivyJar;
214
+ }
215
+ }
216
+ return undefined;
217
+ }
218
+
219
+ /**
220
+ * Collect the namespaces of the jars of resolved sbt dependencies. sbt has
221
+ * just downloaded them into the user's caches, so each is read from there;
222
+ * the rest of the cache is never walked.
223
+ *
224
+ * @param {Object[]} pkgList Packages from the sbt dependency trees.
225
+ * @param {Object} [known] Jar namespace mapping already collected. Packages
226
+ * it covers are skipped.
227
+ * @returns {Promise<Object>} Jar namespace mapping keyed by package purl.
228
+ */
229
+ export async function collectSbtDependencyJars(pkgList, known = {}) {
230
+ const nsMapping = {};
231
+ for (const pkg of pkgList || []) {
232
+ // Applications are the build's own projects, which no cache holds.
233
+ if (
234
+ !pkg?.purl ||
235
+ !pkg.group ||
236
+ !pkg.name ||
237
+ !pkg.version ||
238
+ pkg.type === "application" ||
239
+ known[pkg.purl] ||
240
+ nsMapping[pkg.purl]
241
+ ) {
242
+ continue;
243
+ }
244
+ const jarPath = localSbtDependencyJar(pkg);
245
+ if (!jarPath) {
246
+ continue;
247
+ }
248
+ const [entry] = Object.values(await collectJarNS(jarPath));
249
+ if (entry) {
250
+ nsMapping[pkg.purl] = entry;
251
+ }
252
+ }
253
+ return nsMapping;
254
+ }
255
+
256
+ /**
257
+ * The purls and bom-refs of a parent component and the module components
258
+ * nested under it.
259
+ *
260
+ * @param {Object} parentComponent Parent component.
261
+ * @returns {Set<string>} Purls and bom-refs.
262
+ */
263
+ export function ownComponentRefs(parentComponent) {
264
+ const refs = new Set();
265
+ // The Gradle path nests modules that can point back at the parent, so the
266
+ // walk remembers what it has seen.
267
+ const seen = new Set();
268
+ const stack = parentComponent ? [parentComponent] : [];
269
+ while (stack.length) {
270
+ const component = stack.pop();
271
+ if (!component || typeof component !== "object" || seen.has(component)) {
272
+ continue;
273
+ }
274
+ seen.add(component);
275
+ for (const ref of [component.purl, component["bom-ref"]]) {
276
+ if (ref) {
277
+ refs.add(ref);
278
+ }
279
+ }
280
+ if (Array.isArray(component.components)) {
281
+ stack.push(...component.components);
282
+ }
283
+ }
284
+ return refs;
285
+ }
286
+
139
287
  /**
140
288
  * Function to create bom string for Java jars
141
289
  *
@@ -179,6 +327,8 @@ export async function createJarBom(path, options) {
179
327
  false,
180
328
  true,
181
329
  );
330
+ } else if (options.useSbtCache) {
331
+ nsMapping = await collectSbtCacheDependencies();
182
332
  }
183
333
  if (path.endsWith(".jar")) {
184
334
  jarFiles = [resolve(path)];
@@ -207,14 +357,17 @@ export async function createJarBom(path, options) {
207
357
  if (dlist?.length) {
208
358
  pkgList = pkgList.concat(dlist);
209
359
  }
210
- if (pkgList.length) {
211
- pkgList = await getMvnMetadata(pkgList);
212
- }
213
360
  // Clean up
214
361
  if (tempDir?.startsWith(getTmpDir())) {
215
362
  safeRmSync(tempDir, { recursive: true, force: true });
216
363
  }
217
364
  }
365
+ // Metadata is resolved once for the whole scan. Inside the loop every jar
366
+ // re-resolved the packages of all the jars before it, and lookups that had
367
+ // failed were sent again each time.
368
+ if (pkgList.length) {
369
+ pkgList = await getMvnMetadata(pkgList);
370
+ }
218
371
  pkgList = pkgList.concat(await convertJarNSToPackages(nsMapping));
219
372
  return buildBomNSData(options, pkgList, "maven", {
220
373
  src: path,
@@ -222,6 +375,37 @@ export async function createJarBom(path, options) {
222
375
  });
223
376
  }
224
377
 
378
+ /**
379
+ * Whether a project keeps source directories of its own. Used to keep the
380
+ * root project of an sbt build: a root with sources is usually the
381
+ * application itself and merely aggregates the modules for convenience,
382
+ * while a root without sources is a pure aggregator whose own resolution
383
+ * adds nothing.
384
+ *
385
+ * @param {string} basePath Directory of the sbt build
386
+ * @returns {boolean} True when the build root has main sources
387
+ */
388
+ function sbtRootHasOwnSources(basePath) {
389
+ for (const language of ["scala", "java", "kotlin"]) {
390
+ if (safeExistsSync(join(basePath, "src", "main", language))) {
391
+ return true;
392
+ }
393
+ }
394
+ // A Play application keeps its sources in app/ instead of src/main
395
+ const appDir = join(basePath, "app");
396
+ if (safeExistsSync(appDir)) {
397
+ try {
398
+ const entries = readdirSync(appDir, { withFileTypes: true });
399
+ return entries.some(
400
+ (entry) => entry.isDirectory() || entry.name.endsWith(".scala"),
401
+ );
402
+ } catch (_err) {
403
+ return false;
404
+ }
405
+ }
406
+ return false;
407
+ }
408
+
225
409
  /**
226
410
  * Discover the real sbt project ids for a build by invoking sbt's own
227
411
  * `projects` command. This is more reliable than scraping the build files with
@@ -229,19 +413,28 @@ export async function createJarBom(path, options) {
229
413
  * (commented-out code, examples, values that merely resemble project defs) that
230
414
  * can lead to hangs when those bogus scopes are later passed to `dependencyTree`.
231
415
  *
416
+ * The root project is kept when it has sources of its own, since it is then
417
+ * the application rather than a pure aggregator.
418
+ *
232
419
  * Falls back to the regex-based {@link discoverSbtProjects} heuristic when the
233
420
  * sbt invocation fails or yields nothing useful.
234
421
  *
235
422
  * @param {string} basePath Directory of the sbt build
236
423
  * @param {string} sbtCmd sbt executable
237
424
  * @param {Object} env Environment for the spawned process
425
+ * @param {string[]} [launcherArgs] Launcher options placed before the sbt arguments
238
426
  * @returns {string[]} List of sbt project ids
239
427
  */
240
- export function discoverSbtProjectsFromCmd(basePath, sbtCmd, env) {
428
+ export function discoverSbtProjectsFromCmd(
429
+ basePath,
430
+ sbtCmd,
431
+ env,
432
+ launcherArgs = [],
433
+ ) {
241
434
  try {
242
- const result = safeSpawnSync(
435
+ const result = sbtSpawnSync(
243
436
  sbtCmd,
244
- ["-batch", "-no-colors", '"projects"'],
437
+ [...launcherArgs, "-batch", "-no-colors", '"projects"'],
245
438
  {
246
439
  cwd: basePath,
247
440
  shell: true,
@@ -250,13 +443,16 @@ export function discoverSbtProjectsFromCmd(basePath, sbtCmd, env) {
250
443
  env,
251
444
  encoding: "utf-8",
252
445
  },
446
+ true,
253
447
  );
254
448
  if (result.status === 0 && !result.error) {
255
449
  const { projects, root } = parseSbtProjects(result.stdout || "");
256
- // Exclude the aggregating root project - its own dependencyTree is
257
- // usually a subset of the aggregated subprojects and re-resolving it
258
- // only adds noise/time.
259
- const subprojects = projects.filter((p) => p !== root);
450
+ // Exclude the aggregating root project unless it has sources of its
451
+ // own: its dependencyTree is then the application's own dependencies
452
+ // and not a subset of the aggregated subprojects.
453
+ const subprojects = sbtRootHasOwnSources(basePath)
454
+ ? projects
455
+ : projects.filter((p) => p !== root);
260
456
  if (subprojects.length > 0) {
261
457
  if (DEBUG_MODE) {
262
458
  console.log(
@@ -301,6 +497,63 @@ export function discoverSbtProjectsFromCmd(basePath, sbtCmd, env) {
301
497
  return discoverSbtProjects(basePath);
302
498
  }
303
499
 
500
+ /**
501
+ * Read one pom.xml without running Maven: the module it declares and its
502
+ * direct dependencies. Versions resolve from the pom's properties and
503
+ * dependencyManagement and from parent poms inside the scanned directory.
504
+ * Versions that an imported BOM or a parent outside the directory manages
505
+ * stay empty, and transitive dependencies are unknown.
506
+ *
507
+ * No dependency edges are produced. The build-fidelity rules (BF-JVM-001,
508
+ * BF-GEN-001) recognise a pom read without Maven by its missing graph, and a
509
+ * graph of direct edges alone would pass for a resolved one.
510
+ *
511
+ * @param {string} pomFile pom.xml to read
512
+ * @param {string} rootPath Scanned directory
513
+ * @param {Map} [cache] Parent pom results shared by the poms of one scan
514
+ * @returns {{moduleComponent: Object|undefined, pkgList: Object[]}}
515
+ */
516
+ export function readMavenModuleStatically(pomFile, rootPath, cache) {
517
+ let pomMap;
518
+ try {
519
+ pomMap = parsePomWithLocalParents(pomFile, rootPath, cache);
520
+ } catch (_err) {
521
+ return { moduleComponent: undefined, pkgList: [] };
522
+ }
523
+ const pkgList = [];
524
+ for (const dep of pomMap?.dependencies || []) {
525
+ const purl = mavenPurl(
526
+ dep.group || undefined,
527
+ dep.name,
528
+ dep.version || undefined,
529
+ dep.qualifiers,
530
+ );
531
+ pkgList.push(
532
+ purl ? { ...dep, purl, "bom-ref": decodeURIComponent(purl) } : dep,
533
+ );
534
+ }
535
+ const { artifactId, groupId, packaging, version } = pomMap?.properties || {};
536
+ const modulePurl = artifactId
537
+ ? mavenPurl(groupId || undefined, artifactId, version || undefined, {
538
+ type: packaging || "jar",
539
+ })
540
+ : undefined;
541
+ if (!modulePurl) {
542
+ return { moduleComponent: undefined, pkgList };
543
+ }
544
+ const moduleComponent = {
545
+ group: groupId || "",
546
+ name: artifactId,
547
+ version: version || "",
548
+ qualifiers: { type: packaging || "jar" },
549
+ type: "application",
550
+ purl: modulePurl,
551
+ "bom-ref": decodeURIComponent(modulePurl),
552
+ properties: [{ name: "internal:SrcFile", value: pomFile }],
553
+ };
554
+ return { moduleComponent, pkgList };
555
+ }
556
+
304
557
  /**
305
558
  * Function to create bom string for Java projects
306
559
  *
@@ -320,6 +573,8 @@ export async function createJavaBom(path, options) {
320
573
  let tools;
321
574
  let possible_misses = false;
322
575
  let parallelPropTaskOut = "";
576
+ // Mill daemons already running belong to the user and outlive the scan
577
+ const millDaemonsBefore = findMillDaemons(path);
323
578
  try {
324
579
  // war/ear mode
325
580
  if (path.endsWith(".war") || path.endsWith(".jar")) {
@@ -329,8 +584,10 @@ export async function createJavaBom(path, options) {
329
584
  console.log(`Retrieving packages from ${path}`);
330
585
  }
331
586
  const tempDir = safeMkdtempSync(join(getTmpDir(), "war-deps-"));
332
- jarNSMapping = await collectJarNS(tempDir);
333
- pkgList = await extractJarArchive(path, tempDir, jarNSMapping);
587
+ // The nested jars are read from the extraction directory and their
588
+ // namespaces attached to each component there; the directory is
589
+ // removed afterwards, so no jar paths are kept for evinse.
590
+ pkgList = await extractJarArchive(path, tempDir);
334
591
  if (pkgList.length) {
335
592
  pkgList = await getMvnMetadata(pkgList);
336
593
  }
@@ -456,8 +713,75 @@ export async function createJavaBom(path, options) {
456
713
  }
457
714
  const firstPom = pomFiles.length ? pomFiles[0] : undefined;
458
715
  let mavenCmd = getMavenCommand(path, path, options);
716
+ // Executing Maven evaluates the pom's build section, loads `.mvn/`
717
+ // configuration such as jvm.config or extensions.xml, and resolves
718
+ // build extensions, so it belongs to the dependency-tooling surface
719
+ // disabled by --no-install-deps and the pre-build lifecycle. The scan
720
+ // then keeps only the direct dependencies each pom declares.
721
+ const mavenToolingAllowed = isProjectToolingAllowed(options);
722
+ if (!mavenToolingAllowed) {
723
+ reportProjectToolingSkipped(
724
+ options,
725
+ "jvm.maven.tooling-disabled",
726
+ "Skipping Maven since dependency installation is disabled. Only the direct dependencies declared in pom.xml files will be used.",
727
+ {
728
+ ecosystem: "java",
729
+ tool: "maven",
730
+ impact: "transitive-deps",
731
+ detail:
732
+ "Dependency installation is disabled, so Maven was not run and only the direct dependencies parsed from pom.xml are available.",
733
+ },
734
+ );
735
+ }
736
+ // Poms read without Maven: what their local parents define is shared
737
+ // across the scan, and each pom adds its module and direct dependencies.
738
+ const staticPomCache = new Map();
739
+ const addStaticMavenModule = (pomFile) => {
740
+ const staticModule = readMavenModuleStatically(
741
+ pomFile,
742
+ path,
743
+ staticPomCache,
744
+ );
745
+ if (staticModule.pkgList.length) {
746
+ pkgList = pkgList.concat(staticModule.pkgList);
747
+ }
748
+ const moduleComponent = staticModule.moduleComponent;
749
+ if (!moduleComponent) {
750
+ return;
751
+ }
752
+ if (!Object.keys(parentComponent).length) {
753
+ parentComponent = moduleComponent;
754
+ parentComponent.components = [];
755
+ } else if (
756
+ parentComponent["bom-ref"] !== moduleComponent["bom-ref"] &&
757
+ !parentComponent.components?.some(
758
+ (c) => c["bom-ref"] === moduleComponent["bom-ref"],
759
+ )
760
+ ) {
761
+ parentComponent.components = parentComponent.components || [];
762
+ parentComponent.components.push(moduleComponent);
763
+ }
764
+ };
765
+ // Pom files whose own tree a recursive run of their aggregator already
766
+ // returned. Each is skipped when its turn comes, so a reactor is run
767
+ // once from its aggregator instead of once per member.
768
+ const coveredPomFiles = new Set();
459
769
  for (const f of pomFiles) {
460
770
  const basePath = dirname(f);
771
+ if (coveredPomFiles.has(f)) {
772
+ if (DEBUG_MODE) {
773
+ console.log(
774
+ `Skipped ${basePath}: the recursive dependency:tree run of its aggregator already resolved it.`,
775
+ );
776
+ }
777
+ continue;
778
+ }
779
+ // Without Maven every pom is read on its own, Quarkus modules too:
780
+ // the plugin run that would have covered them does not happen.
781
+ if (!mavenToolingAllowed) {
782
+ addStaticMavenModule(f);
783
+ continue;
784
+ }
461
785
  if (
462
786
  isQuarkus &&
463
787
  !options.deep &&
@@ -468,15 +792,37 @@ export async function createJavaBom(path, options) {
468
792
  }
469
793
  continue;
470
794
  }
471
- const settingsXml = join(basePath, "settings.xml");
472
- if (safeExistsSync(settingsXml)) {
473
- console.log(
474
- `maven settings.xml found in ${basePath}. Please set the MVN_ARGS environment variable based on the full mvn build command used for this project.\nExample: MVN_ARGS='--settings ${settingsXml}'`,
475
- );
795
+ if (isBuildToolRateLimited("maven")) {
796
+ // Every further Maven run in this scan would pay the same back-off
797
+ // for the same empty answer, so Maven is not asked again. The
798
+ // direct dependencies the pom declares are kept, as for a pom whose
799
+ // run failed.
800
+ if (DEBUG_MODE) {
801
+ console.log(
802
+ `Parsing ${f} without Maven: the repository rate limited Maven in this scan.`,
803
+ );
804
+ }
805
+ addStaticMavenModule(f);
806
+ continue;
476
807
  }
477
808
  if (mavenCmd?.endsWith("mvn")) {
478
809
  mavenCmd = getMavenCommand(basePath, path, options);
479
810
  }
811
+ // A settings.xml beside the pom is passed as global settings, so the
812
+ // user's own settings.xml, with its mirrors and credentials, still
813
+ // applies on top of it.
814
+ const projectSettings = projectSettingsArgs(basePath, {
815
+ secureMode: isSecureModeActive(),
816
+ });
817
+ if (projectSettings.args.length) {
818
+ console.log(
819
+ `Passing ${projectSettings.settingsFile} to Maven as global settings (-gs), so your own settings.xml still applies. Name a settings file in MVN_ARGS to change this.`,
820
+ );
821
+ } else if (projectSettings.skipped === "secure-mode") {
822
+ console.log(
823
+ `Ignoring ${projectSettings.settingsFile} in secure mode. If you trust it, pass it with MVN_ARGS='--global-settings ${projectSettings.settingsFile}'.`,
824
+ );
825
+ }
480
826
  // Should we attempt to resolve class names
481
827
  if (options.resolveClass || options.deep) {
482
828
  const tmpjarNSMapping = await collectMvnDependencies(
@@ -490,13 +836,18 @@ export async function createJavaBom(path, options) {
490
836
  }
491
837
  }
492
838
  // Use the cyclonedx maven plugin if there is no preference for maven deps tree
493
- if (!useMavenDepsTree) {
839
+ if (!useMavenDepsTree && !isBuildToolRateLimited("maven")) {
494
840
  thoughtLog("The user wants me to use the cyclonedx-maven plugin.");
495
841
  console.log(`Executing '${mavenCmd}' in`, basePath);
496
842
  result = safeSpawnSync(mavenCmd, mvnArgs, {
497
843
  cwd: basePath,
498
844
  shell: isWin,
499
845
  });
846
+ noteBuildToolRateLimit("maven", commandOutputText(result), {
847
+ command: `${basename(mavenCmd)} ${mvnArgs.join(" ")}`,
848
+ exitCode:
849
+ typeof result.status === "number" ? result.status : undefined,
850
+ });
500
851
  // Check if the cyclonedx plugin created the required bom.json file
501
852
  // Sometimes the plugin fails silently for complex maven projects
502
853
  bomJsonFiles = getAllFiles(
@@ -522,8 +873,20 @@ export async function createJavaBom(path, options) {
522
873
  ) {
523
874
  const tempRoot = getTmpDir();
524
875
  const tempDir = safeMkdtempSync(join(tempRoot, "cdxgen-mvn-"));
525
- const tempMvnTree = join(tempDir, "cdxgen-mvn-tree.json");
526
- const tempMvnTreeText = join(tempDir, "cdxgen-mvn-tree.txt");
876
+ // The recursive run writes one tree per reactor module into these
877
+ // directories, so the aggregator's run returns every module.
878
+ const treeJsonDir = join(tempDir, "tree-json");
879
+ const treeTextDir = join(tempDir, "tree-text");
880
+ safeMkdirSync(treeJsonDir);
881
+ safeMkdirSync(treeTextDir);
882
+ const tempMvnTree = join(
883
+ treeJsonDir,
884
+ `${MAVEN_MODULE_TREE_STEM}.json`,
885
+ );
886
+ const tempMvnTreeText = join(
887
+ treeTextDir,
888
+ `${MAVEN_MODULE_TREE_STEM}.txt`,
889
+ );
527
890
  const tempMvnParentTree = join(
528
891
  tempDir,
529
892
  "cdxgen-mvn-parent-tree.json",
@@ -543,14 +906,11 @@ export async function createJavaBom(path, options) {
543
906
  addArgs = parseMavenArgs(readEnvironmentVariable("MVN_ARGS"));
544
907
  mvnTreeArgs = mvnTreeArgs.concat(addArgs);
545
908
  }
546
- // Automatically use settings.xml to improve the success for fallback
547
- if (safeExistsSync(settingsXml)) {
548
- mvnTreeArgs.push("-s");
549
- mvnTreeArgs.push(settingsXml);
550
- }
909
+ mvnTreeArgs = mvnTreeArgs.concat(projectSettings.args);
551
910
  // For the first pom alone, we need to execute first in non-recursive mode to capture
552
911
  // the parent component. Then, we execute all of them in recursive mode
553
- if (f === firstPom) {
912
+ // A rate limit the --deep copy-dependencies run met ends it too.
913
+ if (f === firstPom && !isBuildToolRateLimited("maven")) {
554
914
  thoughtLog(
555
915
  "What is the parent component here? Let's use maven command to find out.",
556
916
  );
@@ -560,14 +920,19 @@ export async function createJavaBom(path, options) {
560
920
  `-DoutputFile=${tempMvnParentTree}`,
561
921
  "-DoutputType=json",
562
922
  ];
563
- if (addArgs.length) {
564
- findParentComponentArgs =
565
- findParentComponentArgs.concat(addArgs);
566
- }
923
+ findParentComponentArgs = findParentComponentArgs.concat(
924
+ addArgs,
925
+ projectSettings.args,
926
+ );
567
927
  result = safeSpawnSync(mavenCmd, findParentComponentArgs, {
568
928
  cwd: basePath,
569
929
  shell: isWin,
570
930
  });
931
+ noteBuildToolRateLimit("maven", commandOutputText(result), {
932
+ command: `${basename(mavenCmd)} ${findParentComponentArgs.join(" ")}`,
933
+ exitCode:
934
+ typeof result.status === "number" ? result.status : undefined,
935
+ });
571
936
  // If json is empty or unparseable, fallback to text parsing
572
937
  let emptyJson = !safeExistsSync(tempMvnParentTree);
573
938
  if (safeExistsSync(tempMvnParentTree)) {
@@ -583,16 +948,15 @@ export async function createJavaBom(path, options) {
583
948
  }
584
949
  }
585
950
  }
586
- if (result.status !== 0 || result.error || emptyJson) {
951
+ if (
952
+ !isBuildToolRateLimited("maven") &&
953
+ (result.status !== 0 || result.error || emptyJson)
954
+ ) {
587
955
  findParentComponentArgs = [
588
956
  "dependency:tree",
589
957
  "-N",
590
958
  `-DoutputFile=${tempMvnParentTreeText}`,
591
- ];
592
- if (addArgs.length) {
593
- findParentComponentArgs =
594
- findParentComponentArgs.concat(addArgs);
595
- }
959
+ ].concat(addArgs, projectSettings.args);
596
960
  result = safeSpawnSync(mavenCmd, findParentComponentArgs, {
597
961
  cwd: basePath,
598
962
  shell: isWin,
@@ -632,49 +996,60 @@ export async function createJavaBom(path, options) {
632
996
  `Executing '${basename(mavenCmd)} dependency:tree ...' in ${basePath}`,
633
997
  );
634
998
  }
635
- result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
636
- cwd: basePath,
637
- shell: isWin,
638
- });
639
- let emptyJson = !safeExistsSync(tempMvnTree);
640
- if (safeExistsSync(tempMvnTree)) {
641
- const mvnTreeString = readFileSync(tempMvnTree, {
642
- encoding: "utf-8",
643
- });
644
- const parsedList = parseMavenTreeJson(mvnTreeString, f);
645
- if (!parsedList?.pkgList?.length) {
646
- emptyJson = true;
647
- // Remove the invalid json file so text output is preferred below.
648
- if (safeExistsSync(tempMvnTree)) {
649
- safeUnlinkSync(tempMvnTree);
650
- }
651
- }
652
- }
653
- if (result.status !== 0 || result.error || emptyJson) {
654
- mvnTreeArgs = [
655
- "dependency:tree",
656
- `-DoutputFile=${tempMvnTreeText}`,
657
- ];
658
- if (addArgs.length) {
659
- mvnTreeArgs = mvnTreeArgs.concat(addArgs);
660
- }
661
- if (safeExistsSync(settingsXml)) {
662
- mvnTreeArgs.push("-s");
663
- mvnTreeArgs.push(settingsXml);
664
- }
999
+ if (isBuildToolRateLimited("maven")) {
1000
+ // The parent or plugin run already met the rate limit, so the
1001
+ // tree run is skipped rather than repeated for the same answer.
1002
+ result = { status: 1 };
1003
+ } else {
665
1004
  result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
666
1005
  cwd: basePath,
667
1006
  shell: isWin,
668
1007
  });
1008
+ noteBuildToolRateLimit("maven", commandOutputText(result), {
1009
+ command: `${basename(mavenCmd)} ${mvnTreeArgs.join(" ")}`,
1010
+ exitCode:
1011
+ typeof result.status === "number" ? result.status : undefined,
1012
+ });
1013
+ const emptyJson = !readMavenModuleTrees(
1014
+ treeJsonDir,
1015
+ f,
1016
+ ).trees.some(
1017
+ (tree) =>
1018
+ parseMavenTreeJson(tree.text, tree.pomFile)?.pkgList?.length,
1019
+ );
1020
+ if (emptyJson) {
1021
+ // Remove the invalid json trees so the text output is
1022
+ // preferred below.
1023
+ safeRmSync(treeJsonDir, { recursive: true, force: true });
1024
+ }
1025
+ if (
1026
+ !isBuildToolRateLimited("maven") &&
1027
+ (result.status !== 0 || result.error || emptyJson)
1028
+ ) {
1029
+ mvnTreeArgs = [
1030
+ "dependency:tree",
1031
+ `-DoutputFile=${tempMvnTreeText}`,
1032
+ ].concat(addArgs, projectSettings.args);
1033
+ result = safeSpawnSync(mavenCmd, mvnTreeArgs, {
1034
+ cwd: basePath,
1035
+ shell: isWin,
1036
+ });
1037
+ noteBuildToolRateLimit("maven", commandOutputText(result), {
1038
+ command: `${basename(mavenCmd)} ${mvnTreeArgs.join(" ")}`,
1039
+ exitCode:
1040
+ typeof result.status === "number"
1041
+ ? result.status
1042
+ : undefined,
1043
+ });
1044
+ }
669
1045
  }
670
1046
  if (result.status !== 0 || result.error) {
671
1047
  possible_misses = true;
672
1048
  // Our approach to recursively invoking the maven plugin for each sub-module is bound to result in failures
673
1049
  // These could be due to a range of reasons that are covered below.
674
1050
  if (
675
- pomFiles.length === 1 ||
676
- DEBUG_MODE ||
677
- PREFER_MAVEN_DEPS_TREE
1051
+ !isBuildToolRateLimited("maven") &&
1052
+ (pomFiles.length === 1 || DEBUG_MODE || PREFER_MAVEN_DEPS_TREE)
678
1053
  ) {
679
1054
  if (result.stdout) {
680
1055
  console.log(result.stdout);
@@ -767,22 +1142,20 @@ export async function createJavaBom(path, options) {
767
1142
  thoughtLog(
768
1143
  "**MAVEN**: There appear to be build errors, so the SBOM will be incomplete.",
769
1144
  );
770
- const pomMap = parsePom(f);
771
- const dlist = pomMap?.dependencies || [];
772
- if (dlist.length) {
773
- pkgList = pkgList.concat(dlist);
774
- }
1145
+ addStaticMavenModule(f);
775
1146
  } else {
776
- const treeFile = safeExistsSync(tempMvnTreeText)
777
- ? tempMvnTreeText
778
- : tempMvnTree;
779
- if (safeExistsSync(treeFile)) {
780
- const mvnTreeString = readFileSync(treeFile, {
781
- encoding: "utf-8",
782
- });
783
- const parsedList = treeFile.endsWith(".json")
784
- ? parseMavenTreeJson(mvnTreeString, f)
785
- : parseMavenTree(mvnTreeString, f);
1147
+ // The text trees exist only when the JSON run failed and was
1148
+ // retried. Each reactor module has its own tree, and a member
1149
+ // whose tree is here is not run again on its own.
1150
+ let moduleTrees = readMavenModuleTrees(treeTextDir, f);
1151
+ if (!moduleTrees.trees.length) {
1152
+ moduleTrees = readMavenModuleTrees(treeJsonDir, f);
1153
+ }
1154
+ for (const modulePom of moduleTrees.covered) {
1155
+ coveredPomFiles.add(modulePom);
1156
+ }
1157
+ for (const tree of moduleTrees.trees) {
1158
+ const parsedList = parseMavenTree(tree.text, tree.pomFile);
786
1159
  const dlist = parsedList.pkgList || [];
787
1160
  const tmpParentComponent = dlist.splice(0, 1)[0];
788
1161
  if (tmpParentComponent) {
@@ -1046,8 +1419,7 @@ export async function createJavaBom(path, options) {
1046
1419
  // `gradle properties` evaluates the project's build scripts, so it is
1047
1420
  // part of the dependency-tooling surface gated by --no-install-deps
1048
1421
  // (and the pre-build lifecycle).
1049
- const gradleToolingAllowed =
1050
- options.installDeps !== false || options.introspect === true;
1422
+ const gradleToolingAllowed = isProjectToolingAllowed(options);
1051
1423
  if (gradleToolingAllowed) {
1052
1424
  parallelPropTaskOut = executeParallelGradleProperties(
1053
1425
  gradleRootPath,
@@ -1219,6 +1591,14 @@ export async function createJavaBom(path, options) {
1219
1591
  );
1220
1592
  const allOutputs = [];
1221
1593
  for (const gradleArg of gradleArguments) {
1594
+ if (isBuildToolRateLimited("gradle")) {
1595
+ if (DEBUG_MODE) {
1596
+ console.log(
1597
+ "Skipping the remaining gradle dependency tasks: the repository rate limited gradle in this scan.",
1598
+ );
1599
+ }
1600
+ break;
1601
+ }
1222
1602
  if (DEBUG_MODE) {
1223
1603
  console.log(
1224
1604
  `Executing ${gradleCmd} with arguments ${gradleArg.join(" ").substring(0, 150)}... in ${gradleRootPath}`,
@@ -1234,6 +1614,13 @@ export async function createJavaBom(path, options) {
1234
1614
  cwd: gradleRootPath,
1235
1615
  shell: isWin,
1236
1616
  });
1617
+ // The dependencies task exits 0 even when resolution failed under a
1618
+ // rate limit, so the output decides, not the status.
1619
+ noteBuildToolRateLimit("gradle", commandOutputText(sresult), {
1620
+ command: `${basename(gradleCmd)} ${finalGradleArg.join(" ")}`,
1621
+ exitCode:
1622
+ typeof sresult.status === "number" ? sresult.status : undefined,
1623
+ });
1237
1624
  if (sresult.status !== 0 || sresult.error) {
1238
1625
  if (options.failOnError || DEBUG_MODE) {
1239
1626
  console.error(sresult.stdout, sresult.stderr);
@@ -1644,8 +2031,6 @@ export async function createJavaBom(path, options) {
1644
2031
  `${options.multiProject ? "**/" : ""}build.sbt.lock`,
1645
2032
  options,
1646
2033
  );
1647
- const tempCacheDir = safeMkdtempSync(join(getTmpDir(), "sbt-cache-"));
1648
- safeMkdirSync(tempCacheDir, { recursive: true });
1649
2034
  if (
1650
2035
  sbtProjects?.length &&
1651
2036
  isPackageManagerAllowed(
@@ -1662,7 +2047,7 @@ export async function createJavaBom(path, options) {
1662
2047
  pkgList = pkgList.concat(dlist);
1663
2048
  }
1664
2049
  }
1665
- } else if (options.installDeps === false && !options.introspect) {
2050
+ } else if (!isProjectToolingAllowed(options)) {
1666
2051
  // Executing sbt evaluates the project's build definition (arbitrary
1667
2052
  // Scala), so it belongs to the dependency-tooling surface disabled
1668
2053
  // by --no-install-deps and the pre-build lifecycle.
@@ -1711,20 +2096,46 @@ export async function createJavaBom(path, options) {
1711
2096
  }
1712
2097
  }
1713
2098
  safeWriteSync(tempSbtPlugins, sbtPluginDefinition);
1714
- let sbtExtraArgs = "";
2099
+ // sbt resolves into the user's own caches, so a project that has been
2100
+ // built resolves without the network. The dependency trees download
2101
+ // the jar of every dependency they list, which is all --deep reads.
1715
2102
  const env = { ...process.env };
1716
- // We need to collect the jars from the cache
1717
- if (options.deep) {
1718
- sbtExtraArgs = " updateClassifiers";
1719
- env["COURSIER_CACHE"] = tempCacheDir;
1720
- env["SBT_IVY_HOME"] = tempCacheDir;
1721
- }
1722
2103
  for (const i in sbtProjects) {
2104
+ if (isBuildToolRateLimited("sbt")) {
2105
+ // Every remaining sbt build in this scan resolves from the same
2106
+ // rate limited repository.
2107
+ if (DEBUG_MODE) {
2108
+ console.log(
2109
+ `Skipping ${sbtProjects[i]}: the repository rate limited sbt in this scan.`,
2110
+ );
2111
+ }
2112
+ break;
2113
+ }
1723
2114
  const basePath = sbtProjects[i];
1724
2115
  const dlFile = join(tempDir, `dl-${i}.tmp`);
2116
+ // The sbt version of the project itself decides the invocation
2117
+ // form: sbt 2 both joins separate command-line arguments into a
2118
+ // single command line and no longer offers `dependencyTree / toFile`.
2119
+ const projectSbtVersion = determineSbtVersion(basePath) || sbtVersion;
2120
+ const isSbt2 = isSbt2Version(projectSbtVersion);
2121
+ // The sbt 2 launcher runs a thin client by default. When a server is
2122
+ // already running for the build, an IDE's for example, the client
2123
+ // prints neither the project list nor the trees, so sbt runs
2124
+ // in-process instead. sbt.bat has no `--server` option.
2125
+ const sbtLauncherArgs = isSbt2 && !isWin ? ["--server"] : [];
2126
+ const sbtServerFile = join(
2127
+ basePath,
2128
+ "project",
2129
+ "target",
2130
+ "active.json",
2131
+ );
2132
+ const sbtServerWasRunning = isSbt2 && safeExistsSync(sbtServerFile);
1725
2133
  let sbtArgs = [];
1726
2134
  let pluginFile = null;
2135
+ let pluginAdded = false;
1727
2136
  const subDlFiles = [];
2137
+ const failedSubprojects = [];
2138
+ let subprojects = [];
1728
2139
  if (standalonePluginFile) {
1729
2140
  sbtArgs = [
1730
2141
  `-addPluginSbtFile=${tempSbtPlugins}`,
@@ -1738,15 +2149,23 @@ export async function createJavaBom(path, options) {
1738
2149
  // `dependencyTree` fan-out yields a far more complete tree than a
1739
2150
  // single root resolution (which only captures the root project's own
1740
2151
  // libraryDependencies). See https://github.com/cdxgen/cdxgen/issues/4291
1741
- const subprojects = discoverSbtProjectsFromCmd(
2152
+ subprojects = discoverSbtProjectsFromCmd(
1742
2153
  basePath,
1743
2154
  SBT_CMD,
1744
2155
  env,
2156
+ sbtLauncherArgs,
1745
2157
  );
1746
- if (subprojects.length > 0 && useSlashSyntax) {
1747
- sbtArgs = [
1748
- `'set ThisBuild / asciiGraphWidth := 800'${sbtExtraArgs}`,
1749
- ];
2158
+ if (isSbt2) {
2159
+ const trees = subprojects.length
2160
+ ? subprojects.map((sp) => ({
2161
+ project: sp,
2162
+ file: join(tempDir, `dl-${i}-${sp}.tmp`),
2163
+ }))
2164
+ : [{ file: dlFile }];
2165
+ subDlFiles.push(...trees.map((t) => t.file));
2166
+ sbtArgs = [sbt2DependencyTreeCommand(trees)];
2167
+ } else if (subprojects.length > 0 && useSlashSyntax) {
2168
+ sbtArgs = ["'set ThisBuild / asciiGraphWidth := 800'"];
1750
2169
  for (const sp of subprojects) {
1751
2170
  const subDlFile = join(tempDir, `dl-${i}-${sp}.tmp`);
1752
2171
  subDlFiles.push(subDlFile);
@@ -1756,21 +2175,27 @@ export async function createJavaBom(path, options) {
1756
2175
  }
1757
2176
  } else if (useSlashSyntax) {
1758
2177
  sbtArgs = [
1759
- `'set ThisBuild / asciiGraphWidth := 800'${sbtExtraArgs} "dependencyTree / toFile ${dlFile} --force"`,
2178
+ `'set ThisBuild / asciiGraphWidth := 800' "dependencyTree / toFile ${dlFile} --force"`,
1760
2179
  ];
1761
2180
  subDlFiles.push(dlFile);
1762
2181
  } else {
1763
2182
  sbtArgs = [
1764
- `'set asciiGraphWidth in ThisBuild := 800'${sbtExtraArgs} "dependencyTree::toFile ${dlFile} --force"`,
2183
+ `'set asciiGraphWidth in ThisBuild := 800' "dependencyTree::toFile ${dlFile} --force"`,
1765
2184
  ];
1766
2185
  subDlFiles.push(dlFile);
1767
2186
  }
1768
- pluginFile = addPlugin(basePath, sbtPluginDefinition);
2187
+ // sbt 2 enables its dependency tree plugin by default, so the
2188
+ // build's plugins.sbt stays untouched and the build definition is
2189
+ // not recompiled for the scan.
2190
+ if (!isSbt2) {
2191
+ pluginFile = addPlugin(basePath, sbtPluginDefinition);
2192
+ pluginAdded = true;
2193
+ }
1769
2194
  }
1770
2195
  // Run sbt in non-interactive batch mode so it never blocks waiting for
1771
2196
  // input on stdin (which can otherwise appear as an indefinite hang) and
1772
2197
  // does not emit ANSI colour codes into the dependency tree output.
1773
- sbtArgs = ["-batch", "-no-colors", ...sbtArgs];
2198
+ sbtArgs = [...sbtLauncherArgs, "-batch", "-no-colors", ...sbtArgs];
1774
2199
  console.log(
1775
2200
  "Executing",
1776
2201
  SBT_CMD,
@@ -1781,16 +2206,31 @@ export async function createJavaBom(path, options) {
1781
2206
  tempSbtgDir,
1782
2207
  );
1783
2208
  // Note that the command has to be invoked with `shell: true` to properly execut sbt
1784
- const result = safeSpawnSync(SBT_CMD, sbtArgs, {
1785
- cwd: basePath,
1786
- shell: true,
1787
- // The sbt commands below embed their own shell quoting, which the
1788
- // shell must strip for sbt to see separate commands.
1789
- cdxgenRawShellArgs: true,
1790
- env,
2209
+ // sbt 2 is the exception: its ;-joined command argument contains a
2210
+ // semicolon, so on POSIX the launcher is spawned directly and the
2211
+ // argument reaches sbt verbatim.
2212
+ const result = sbtSpawnSync(
2213
+ SBT_CMD,
2214
+ sbtArgs,
2215
+ {
2216
+ cwd: basePath,
2217
+ shell: isWin || !isSbt2 || standalonePluginFile,
2218
+ // The sbt commands below embed their own shell quoting, which the
2219
+ // shell must strip for sbt to see separate commands.
2220
+ cdxgenRawShellArgs: true,
2221
+ env,
2222
+ },
2223
+ !isSbt2 || standalonePluginFile,
2224
+ );
2225
+ noteBuildToolRateLimit("sbt", commandOutputText(result), {
2226
+ command: `${SBT_CMD} ${sbtArgs.join(" ")}`,
2227
+ exitCode:
2228
+ typeof result.status === "number" ? result.status : undefined,
1791
2229
  });
1792
2230
  if (result.status !== 0 || result.error) {
1793
- console.error(result.stdout, result.stderr);
2231
+ if (!isBuildToolRateLimited("sbt")) {
2232
+ console.error(result.stdout, result.stderr);
2233
+ }
1794
2234
  recordDegradation("jvm.sbt.no-lockfile", {
1795
2235
  ecosystem: "java",
1796
2236
  tool: "sbt",
@@ -1801,15 +2241,20 @@ export async function createJavaBom(path, options) {
1801
2241
  detail:
1802
2242
  "The sbt dependencyTree command did not succeed, so the declared sbt dependencies are the only evidence available.",
1803
2243
  });
1804
- console.log(
1805
- "1. Check if scala and sbt is installed and available in PATH. Only scala 2.10 + sbt 0.13.6+ and 2.12 + sbt 1.0+ is supported for now.",
1806
- );
1807
- console.log(
1808
- "2. Check if the plugin net.virtual-void:sbt-dependency-graph 0.10.0-RC1 can be used in the environment",
1809
- );
1810
- console.log(
1811
- "3. Consider creating a lockfile using sbt-dependency-lock plugin. See https://github.com/stringbean/sbt-dependency-lock",
1812
- );
2244
+ if (!isBuildToolRateLimited("sbt")) {
2245
+ console.log(
2246
+ "1. Check if scala and sbt is installed and available in PATH. Only scala 2.10 + sbt 0.13.6+, 2.12 + sbt 1.0+ and sbt 2.x are supported for now.",
2247
+ );
2248
+ // sbt 2 brings its own dependency tree, so no plugin is added.
2249
+ if (!isSbt2) {
2250
+ console.log(
2251
+ "2. Check if the plugin net.virtual-void:sbt-dependency-graph 0.10.0-RC1 can be used in the environment",
2252
+ );
2253
+ }
2254
+ console.log(
2255
+ `${isSbt2 ? 2 : 3}. Consider creating a lockfile using sbt-dependency-lock plugin. See https://github.com/stringbean/sbt-dependency-lock`,
2256
+ );
2257
+ }
1813
2258
  deferFailOnError(options, {
1814
2259
  ecosystem: "java",
1815
2260
  tool: "sbt",
@@ -1819,13 +2264,95 @@ export async function createJavaBom(path, options) {
1819
2264
  detail: "the sbt dependencyTree command failed",
1820
2265
  });
1821
2266
  }
1822
- if (!standalonePluginFile) {
2267
+ // One failing subproject (for example an unresolvable SNAPSHOT)
2268
+ // fails the combined command and would zero the whole SBOM. Retry
2269
+ // each subproject on its own and keep the trees that succeed. A rate
2270
+ // limited repository ends the retries as well: each would download
2271
+ // the same artifacts against the same 429.
2272
+ if (
2273
+ !standalonePluginFile &&
2274
+ subprojects.length > 0 &&
2275
+ (result.status !== 0 || result.error) &&
2276
+ !isBuildToolRateLimited("sbt")
2277
+ ) {
2278
+ for (const sp of subprojects) {
2279
+ const subDlFile = join(tempDir, `dl-${i}-${sp}.tmp`);
2280
+ if (safeExistsSync(subDlFile)) {
2281
+ // The combined run produced this tree before it failed.
2282
+ continue;
2283
+ }
2284
+ if (DEBUG_MODE) {
2285
+ console.log(`Retrying the sbt tree of ${sp} on its own.`);
2286
+ }
2287
+ let retryArgs;
2288
+ if (isSbt2) {
2289
+ retryArgs = [
2290
+ ...sbtLauncherArgs,
2291
+ "-batch",
2292
+ "-no-colors",
2293
+ sbt2DependencyTreeCommand([{ project: sp, file: subDlFile }]),
2294
+ ];
2295
+ } else if (useSlashSyntax) {
2296
+ retryArgs = [
2297
+ "-batch",
2298
+ "-no-colors",
2299
+ "'set ThisBuild / asciiGraphWidth := 800'",
2300
+ `"${sp}/dependencyTree / toFile ${subDlFile} --force"`,
2301
+ ];
2302
+ } else {
2303
+ continue;
2304
+ }
2305
+ sbtSpawnSync(
2306
+ SBT_CMD,
2307
+ retryArgs,
2308
+ {
2309
+ cwd: basePath,
2310
+ shell: isWin || !isSbt2,
2311
+ cdxgenRawShellArgs: true,
2312
+ env,
2313
+ },
2314
+ !isSbt2,
2315
+ );
2316
+ // The tree file is already listed for parsing, so a retry only
2317
+ // has to produce it.
2318
+ if (!safeExistsSync(subDlFile)) {
2319
+ failedSubprojects.push(sp);
2320
+ }
2321
+ }
2322
+ if (failedSubprojects.length) {
2323
+ console.log(
2324
+ `The sbt dependency trees of ${failedSubprojects.join(", ")} could not be resolved.`,
2325
+ );
2326
+ }
2327
+ }
2328
+ // Where the thin client still runs, it leaves the server it started
2329
+ // behind. A server that was running before the scan is not ours to
2330
+ // stop.
2331
+ if (isSbt2 && !sbtServerWasRunning && safeExistsSync(sbtServerFile)) {
2332
+ sbtSpawnSync(SBT_CMD, ["-batch", "-no-colors", "shutdown"], {
2333
+ cwd: basePath,
2334
+ shell: isWin,
2335
+ env,
2336
+ });
2337
+ }
2338
+ // Only a plugins.sbt cdxgen amended is restored: without a backup,
2339
+ // the cleanup removes the file as one cdxgen created.
2340
+ if (pluginAdded) {
1823
2341
  cleanupPlugin(basePath, pluginFile);
1824
2342
  }
1825
2343
  const sbtSubprojectRoots = [];
1826
2344
  for (const subDlFile of subDlFiles) {
1827
2345
  if (safeExistsSync(subDlFile)) {
1828
- const retMap = await parseSbtTree(subDlFile);
2346
+ // The trees are temporary files that go away with the scan, so
2347
+ // the components name the build definition they were resolved
2348
+ // from instead: build.sbt, or for a build written only in
2349
+ // project/*.scala, the build.properties beside it.
2350
+ const sbtBuildFile =
2351
+ [
2352
+ join(basePath, "build.sbt"),
2353
+ join(basePath, "project", "build.properties"),
2354
+ ].find((f) => safeExistsSync(f)) || basePath;
2355
+ const retMap = await parseSbtTree(subDlFile, sbtBuildFile);
1829
2356
  if (retMap.pkgList?.length) {
1830
2357
  const tmpParentComponent = retMap.pkgList.splice(0, 1)[0];
1831
2358
  tmpParentComponent.type = "application";
@@ -1873,19 +2400,38 @@ export async function createJavaBom(path, options) {
1873
2400
  "bom-ref": decodeURIComponent(rootPurl),
1874
2401
  };
1875
2402
  parentComponent = rootComponent;
1876
- dependencies.push({
1877
- ref: decodeURIComponent(rootPurl),
1878
- dependsOn: sbtSubprojectRoots
1879
- .map((c) => c["bom-ref"])
1880
- .filter(Boolean),
1881
- });
2403
+ // Merged rather than appended, so the root lists every subproject
2404
+ // once, and a root project with sources of its own, whose tree is
2405
+ // the synthetic root itself, neither depends on itself nor gets a
2406
+ // second dependency entry.
2407
+ dependencies = mergeDependencies(
2408
+ dependencies,
2409
+ [
2410
+ {
2411
+ ref: rootComponent["bom-ref"],
2412
+ dependsOn: sbtSubprojectRoots
2413
+ .map((c) => c["bom-ref"])
2414
+ .filter(Boolean),
2415
+ },
2416
+ ],
2417
+ rootComponent,
2418
+ );
1882
2419
  } else if (sbtSubprojectRoots.length > 0 && !parentComponent) {
1883
2420
  parentComponent = sbtSubprojectRoots[0];
1884
2421
  }
2422
+ if (failedSubprojects.length && parentComponent?.name) {
2423
+ parentComponent.properties ??= [];
2424
+ parentComponent.properties.push({
2425
+ name: "cdx:sbt:failedProjects",
2426
+ value: failedSubprojects.join(", "),
2427
+ });
2428
+ }
1885
2429
  }
1886
2430
 
1887
- // Cleanup
1888
- safeUnlinkSync(tempSbtPlugins);
2431
+ // The trees were parsed inside the loop, so neither directory is
2432
+ // needed any more.
2433
+ safeRmSync(tempDir, { recursive: true, force: true });
2434
+ safeRmSync(tempSbtgDir, { recursive: true, force: true });
1889
2435
  } // else
1890
2436
 
1891
2437
  if (DEBUG_MODE) {
@@ -1893,7 +2439,10 @@ export async function createJavaBom(path, options) {
1893
2439
  }
1894
2440
  // Should we attempt to resolve class names
1895
2441
  if (options.resolveClass || options.deep) {
1896
- const tmpjarNSMapping = await collectJarNS(tempCacheDir);
2442
+ const tmpjarNSMapping = await collectSbtDependencyJars(
2443
+ pkgList,
2444
+ jarNSMapping,
2445
+ );
1897
2446
  if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
1898
2447
  jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
1899
2448
  }
@@ -1917,16 +2466,10 @@ export async function createJavaBom(path, options) {
1917
2466
  }
1918
2467
  }
1919
2468
  }
1920
- if (tempCacheDir?.startsWith(getTmpDir())) {
1921
- safeRmSync(tempCacheDir, {
1922
- recursive: true,
1923
- force: true,
1924
- });
1925
- }
1926
2469
 
1927
2470
  if (
1928
2471
  millFiles?.length &&
1929
- (options.installDeps !== false || options.introspect === true) &&
2472
+ isProjectToolingAllowed(options) &&
1930
2473
  isPackageManagerAllowed(
1931
2474
  "mill",
1932
2475
  ["bazel", "sbt", "gradle", "maven"],
@@ -1940,6 +2483,12 @@ export async function createJavaBom(path, options) {
1940
2483
  options,
1941
2484
  );
1942
2485
  const millCmd = getMillCommand(millRootPath, options);
2486
+ // Mill 1.0 replaced the ivyDepsTree task with showMvnDepsTree
2487
+ const millVersion = getMillVersion(millRootPath, millCmd);
2488
+ const millTreeTask =
2489
+ millVersion != null && gte(millVersion, "1.0.0")
2490
+ ? "showMvnDepsTree"
2491
+ : "ivyDepsTree";
1943
2492
  const millCommonArgs = [
1944
2493
  "--color",
1945
2494
  "false",
@@ -1951,7 +2500,7 @@ export async function createJavaBom(path, options) {
1951
2500
  if (!["true", "1"].includes(readEnvironmentVariable("MILL_USE_SERVER"))) {
1952
2501
  millCommonArgs.unshift("--no-server");
1953
2502
  }
1954
- const millArgs = [...millCommonArgs, "__.ivyDepsTree"];
2503
+ const millArgs = [...millCommonArgs, `__.${millTreeTask}`];
1955
2504
  if (DEBUG_MODE) {
1956
2505
  console.log("Executing", millCmd, "in", millRootPath);
1957
2506
  }
@@ -1960,6 +2509,11 @@ export async function createJavaBom(path, options) {
1960
2509
  shell: isWin,
1961
2510
  maxBuffer: MAX_BUFFER * 10,
1962
2511
  });
2512
+ noteBuildToolRateLimit("mill", commandOutputText(sresult), {
2513
+ command: `${basename(millCmd)} ${millArgs.join(" ")}`,
2514
+ exitCode:
2515
+ typeof sresult.status === "number" ? sresult.status : undefined,
2516
+ });
1963
2517
  if (sresult.status !== 0 || sresult.error) {
1964
2518
  if (options.failOnError || DEBUG_MODE) {
1965
2519
  console.error(sresult.stdout, sresult.stderr);
@@ -1969,10 +2523,14 @@ export async function createJavaBom(path, options) {
1969
2523
  tool: "mill",
1970
2524
  exitCode:
1971
2525
  typeof sresult.status === "number" ? sresult.status : undefined,
1972
- detail: "the mill ivyDepsTree command failed",
2526
+ detail: "the mill dependency tree command failed",
1973
2527
  });
1974
2528
  }
1975
- const millResolveArgs = [...millCommonArgs, "resolve", "__.ivyDepsTree"];
2529
+ const millResolveArgs = [
2530
+ ...millCommonArgs,
2531
+ "resolve",
2532
+ `__.${millTreeTask}`,
2533
+ ];
1976
2534
  if (DEBUG_MODE) {
1977
2535
  console.log(
1978
2536
  "Executing",
@@ -1982,6 +2540,9 @@ export async function createJavaBom(path, options) {
1982
2540
  millRootPath,
1983
2541
  );
1984
2542
  }
2543
+ // The resolve run only lists the build's tasks and downloads nothing,
2544
+ // so it runs even after a rate limit: the trees of the modules that did
2545
+ // resolve under --keep-going are read through it.
1985
2546
  sresult = safeSpawnSync(millCmd, millResolveArgs, {
1986
2547
  cwd: millRootPath,
1987
2548
  shell: isWin,
@@ -2004,6 +2565,8 @@ export async function createJavaBom(path, options) {
2004
2565
  const modules = sstdout
2005
2566
  .trim()
2006
2567
  .split("\n")
2568
+ // Mill 1.x prints headers around the module list
2569
+ .filter((a) => a.endsWith(`.${millTreeTask}`))
2007
2570
  .map((a) => a.substring(0, a.lastIndexOf(".")))
2008
2571
  .filter((a) =>
2009
2572
  ["true", "1"].includes(readEnvironmentVariable("MILL_EXCLUDE_TEST"))
@@ -2016,7 +2579,13 @@ export async function createJavaBom(path, options) {
2016
2579
  relations.set(parentComponent["bom-ref"], []);
2017
2580
  for (const module of modules) {
2018
2581
  moduleBomRefs.push(
2019
- parseMillDependency(module, packages, relations, millRootPath),
2582
+ parseMillDependency(
2583
+ module,
2584
+ packages,
2585
+ relations,
2586
+ millRootPath,
2587
+ `${millTreeTask}.log`,
2588
+ ),
2020
2589
  );
2021
2590
  }
2022
2591
  for (const module of moduleBomRefs) {
@@ -2069,6 +2638,34 @@ export async function createJavaBom(path, options) {
2069
2638
  }
2070
2639
  }
2071
2640
  }
2641
+ // scala-cli projects declare everything with //> using directives in
2642
+ // their sources and have none of the build files handled above.
2643
+ if (
2644
+ !pomFiles?.length &&
2645
+ !gradleFiles?.length &&
2646
+ !millFiles?.length &&
2647
+ !sbtProjects.length &&
2648
+ isPackageManagerAllowed(
2649
+ "scala-cli",
2650
+ ["bazel", "sbt", "gradle", "maven", "mill"],
2651
+ options,
2652
+ )
2653
+ ) {
2654
+ const dlist = collectScalaCliComponents(path, options);
2655
+ if (dlist?.length) {
2656
+ thoughtLog(
2657
+ `**SCALA-CLI**: Found ${dlist.length} declared dependencies.`,
2658
+ );
2659
+ pkgList = pkgList.concat(dlist);
2660
+ if (!parentComponent?.name) {
2661
+ parentComponent = createDefaultParentComponent(
2662
+ path,
2663
+ "maven",
2664
+ options,
2665
+ );
2666
+ }
2667
+ }
2668
+ }
2072
2669
  } catch (err) {
2073
2670
  if (!isDeferredFailOnError(err)) {
2074
2671
  throw err;
@@ -2078,11 +2675,47 @@ export async function createJavaBom(path, options) {
2078
2675
  thoughtLog(
2079
2676
  "Completing the java BOM with the components collected before the deferred failure.",
2080
2677
  );
2678
+ } finally {
2679
+ // A build this scan ran can have started a Mill daemon of its own
2680
+ // without --no-server; stop those even when a later step failed.
2681
+ stopMillDaemons(path, millDaemonsBefore, options);
2682
+ }
2683
+ // A Scala.js build bundles npm packages: scalajs-bundler installs them
2684
+ // under target, and a bundler workspace keeps its lock file beside the
2685
+ // build. Their npm components join the BOM so module imports can be
2686
+ // attributed to them, and the project depends on the workspaces' direct
2687
+ // dependencies.
2688
+ try {
2689
+ const scalajsNpm = await collectScalaJsNpmComponents(
2690
+ path,
2691
+ pkgList,
2692
+ options,
2693
+ );
2694
+ if (scalajsNpm.components.length) {
2695
+ pkgList = pkgList.concat(scalajsNpm.components);
2696
+ const npmEdges = [...scalajsNpm.dependencies];
2697
+ if (parentComponent?.["bom-ref"] && scalajsNpm.roots.length) {
2698
+ npmEdges.push({
2699
+ ref: parentComponent["bom-ref"],
2700
+ dependsOn: scalajsNpm.roots,
2701
+ });
2702
+ }
2703
+ dependencies = mergeDependencies(dependencies, npmEdges, parentComponent);
2704
+ }
2705
+ } catch (err) {
2706
+ if (DEBUG_MODE) {
2707
+ console.log("Unable to collect the Scala.js npm dependencies", err);
2708
+ }
2081
2709
  }
2082
2710
  // Deduplication and metadata belong to whatever was collected, so they run
2083
2711
  // on the deferred path too.
2084
2712
  pkgList = trimComponents(pkgList);
2085
- pkgList = await getMvnMetadata(pkgList, jarNSMapping, options.deep);
2713
+ // --deep collects namespaces; it does not opt in to remote licence lookups,
2714
+ // which FETCH_LICENSE controls. The project's own modules are never looked
2715
+ // up remotely: they are not published.
2716
+ pkgList = await getMvnMetadata(pkgList, jarNSMapping, false, {
2717
+ skipPurls: ownComponentRefs(parentComponent),
2718
+ });
2086
2719
  return buildBomNSData(options, pkgList, "maven", {
2087
2720
  src: path,
2088
2721
  nsMapping: jarNSMapping,