@cdxgen/cdxgen 13.0.1 → 13.2.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 (286) hide show
  1. package/README.md +30 -16
  2. package/bin/audit.js +6 -0
  3. package/bin/cdxgen.js +141 -7
  4. package/bin/evinse.js +3 -1
  5. package/data/component-tags.json +8 -1
  6. package/data/crypto-oid.json +88 -12
  7. package/data/frameworks-list.json +86 -1
  8. package/data/remediations.json +1167 -0
  9. package/data/rules/ai-bom.yaml +78 -0
  10. package/data/rules/asar-archives.yaml +1 -1
  11. package/data/rules/build-fidelity.yaml +782 -0
  12. package/data/rules/cargo-rust.yaml +132 -0
  13. package/data/rules/mcp-servers.yaml +122 -0
  14. package/data/rules/package-integrity.yaml +13 -13
  15. package/lib/audit/index.js +48 -4
  16. package/lib/audit/scoring.js +2 -8
  17. package/lib/audit/targets.js +6 -1
  18. package/lib/cli/bomAssembly.js +32 -1
  19. package/lib/cli/cliOptions.js +94 -7
  20. package/lib/cli/index.js +268 -49
  21. package/lib/cli/jsBom.js +91 -5
  22. package/lib/cli/jvmBom.js +1515 -1314
  23. package/lib/cli/managedBom.js +249 -28
  24. package/lib/cli/nativeBom.js +986 -10
  25. package/lib/core/activity.js +120 -2
  26. package/lib/core/buildLedger.js +799 -0
  27. package/lib/core/deferredExit.js +141 -0
  28. package/lib/core/env.js +108 -2
  29. package/lib/core/fs.js +60 -0
  30. package/lib/core/logger.js +12 -99
  31. package/lib/core/severity.js +16 -0
  32. package/lib/core/syncFileWriter.js +106 -0
  33. package/lib/core/userAgent.js +151 -0
  34. package/lib/ecosystems/cargoCache.js +364 -0
  35. package/lib/ecosystems/caxa.js +13 -5
  36. package/lib/ecosystems/cppEvidence.js +40 -2
  37. package/lib/ecosystems/ecosystems.js +250 -91
  38. package/lib/ecosystems/golem.js +11 -1
  39. package/lib/ecosystems/gradleutils.js +498 -17
  40. package/lib/ecosystems/kosi.js +1256 -0
  41. package/lib/ecosystems/parsers-crystal.js +179 -0
  42. package/lib/ecosystems/parsers-dotnet.js +69 -39
  43. package/lib/ecosystems/parsers-elm.js +816 -0
  44. package/lib/ecosystems/parsers-gleam.js +1 -1
  45. package/lib/ecosystems/parsers-js.js +26 -0
  46. package/lib/ecosystems/parsers-julia.js +268 -0
  47. package/lib/ecosystems/parsers-jvm.js +9 -0
  48. package/lib/ecosystems/parsers-lua.js +218 -0
  49. package/lib/ecosystems/parsers-misc.js +188 -2
  50. package/lib/ecosystems/parsers-nim.js +221 -0
  51. package/lib/ecosystems/parsers-opam.js +180 -0
  52. package/lib/ecosystems/parsers-perl.js +277 -0
  53. package/lib/ecosystems/parsers-python.js +172 -36
  54. package/lib/ecosystems/parsers-r.js +148 -0
  55. package/lib/ecosystems/parsers-rebar.js +411 -0
  56. package/lib/ecosystems/parsers-rust.js +944 -19
  57. package/lib/ecosystems/parsers-spack.js +204 -0
  58. package/lib/ecosystems/parsers-stack.js +264 -0
  59. package/lib/ecosystems/parsers-terraform.js +194 -0
  60. package/lib/ecosystems/pypiClassifiers.js +326 -0
  61. package/lib/ecosystems/pythonutils.js +49 -1
  62. package/lib/ecosystems/rubyutils.js +13 -0
  63. package/lib/ecosystems/rusi.js +126 -6
  64. package/lib/evinser/evinser.js +141 -13
  65. package/lib/helpers/agenticToolutils.js +925 -0
  66. package/lib/helpers/remote/dependency-track.js +81 -14
  67. package/lib/inventory/agentFormulationParser.js +57 -4
  68. package/lib/inventory/aiCollector.js +504 -10
  69. package/lib/inventory/aiInventory.js +12 -0
  70. package/lib/inventory/analyzer.js +488 -30
  71. package/lib/inventory/astWorker.js +6 -0
  72. package/lib/inventory/auditCategories.js +17 -0
  73. package/lib/inventory/cbomutils.js +30 -0
  74. package/lib/inventory/cdxrs.js +7 -2
  75. package/lib/inventory/communityAiConfigParser.js +215 -2
  76. package/lib/inventory/depsUtils.js +72 -0
  77. package/lib/inventory/dosai.js +508 -22
  78. package/lib/inventory/envcontext.js +215 -24
  79. package/lib/inventory/evidenceUtils.js +13 -0
  80. package/lib/inventory/fetchBatch.js +131 -2
  81. package/lib/inventory/fetchRate.js +74 -11
  82. package/lib/inventory/forgeEnricher.js +3 -2
  83. package/lib/inventory/jvmToolEnv.js +594 -0
  84. package/lib/inventory/mcpConfigParser.js +166 -2
  85. package/lib/inventory/mcpDiscovery.js +11 -0
  86. package/lib/inventory/mcpServerManifest.js +389 -0
  87. package/lib/inventory/mcpbBundle.js +419 -0
  88. package/lib/inventory/osinfo.js +1 -0
  89. package/lib/inventory/plugins.js +3 -0
  90. package/lib/inventory/protobom.js +54 -88
  91. package/lib/inventory/protobomLoader.js +4 -0
  92. package/lib/inventory/rustFormulationParser.js +1 -1
  93. package/lib/inventory/source.js +10 -4
  94. package/lib/inventory/stylePackageRefs.js +41 -0
  95. package/lib/inventory/svelteUtils.js +524 -0
  96. package/lib/inventory/toolRequirements.js +903 -0
  97. package/lib/managers/binary.js +24 -0
  98. package/lib/managers/docker.js +26 -5
  99. package/lib/parsers/huggingfaceUtils.js +15 -7
  100. package/lib/parsers/onnx.js +294 -0
  101. package/lib/parsers/safetensors.js +114 -0
  102. package/lib/server/server.js +1 -1
  103. package/lib/stages/postgen/annotationBuilder.js +97 -0
  104. package/lib/stages/postgen/auditBom.js +76 -79
  105. package/lib/stages/postgen/introspection/annotate.js +320 -0
  106. package/lib/stages/postgen/introspection/commandFacts.js +293 -0
  107. package/lib/stages/postgen/introspection/emit.js +192 -0
  108. package/lib/stages/postgen/introspection/formulationEvidence.js +427 -0
  109. package/lib/stages/postgen/introspection/markers.js +250 -0
  110. package/lib/stages/postgen/introspection/reflect.js +1214 -0
  111. package/lib/stages/postgen/introspection/report.js +1208 -0
  112. package/lib/stages/postgen/introspection/score.js +1067 -0
  113. package/lib/stages/postgen/introspection/shapeCommand.js +304 -0
  114. package/lib/stages/postgen/parentEdge.js +156 -0
  115. package/lib/stages/postgen/postgen.js +284 -3
  116. package/lib/stages/postgen/ruleEngine.js +17 -0
  117. package/lib/stages/pregen/envAudit.js +42 -2
  118. package/lib/stages/pregen/pregen.js +1171 -6
  119. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
  120. package/lib/validator/bomValidator.js +11 -2
  121. package/package.json +26 -26
  122. package/types/lib/audit/index.d.ts.map +1 -1
  123. package/types/lib/audit/scoring.d.ts +2 -8
  124. package/types/lib/audit/scoring.d.ts.map +1 -1
  125. package/types/lib/audit/targets.d.ts.map +1 -1
  126. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  127. package/types/lib/cli/cliOptions.d.ts +2 -1
  128. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  129. package/types/lib/cli/index.d.ts +10 -2
  130. package/types/lib/cli/index.d.ts.map +1 -1
  131. package/types/lib/cli/jsBom.d.ts +23 -0
  132. package/types/lib/cli/jsBom.d.ts.map +1 -1
  133. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  134. package/types/lib/cli/managedBom.d.ts +33 -0
  135. package/types/lib/cli/managedBom.d.ts.map +1 -1
  136. package/types/lib/cli/nativeBom.d.ts +88 -8
  137. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  138. package/types/lib/core/activity.d.ts.map +1 -1
  139. package/types/lib/core/buildLedger.d.ts +272 -0
  140. package/types/lib/core/buildLedger.d.ts.map +1 -0
  141. package/types/lib/core/deferredExit.d.ts +84 -0
  142. package/types/lib/core/deferredExit.d.ts.map +1 -0
  143. package/types/lib/core/env.d.ts +43 -1
  144. package/types/lib/core/env.d.ts.map +1 -1
  145. package/types/lib/core/fs.d.ts +21 -0
  146. package/types/lib/core/fs.d.ts.map +1 -1
  147. package/types/lib/core/logger.d.ts +2 -11
  148. package/types/lib/core/logger.d.ts.map +1 -1
  149. package/types/lib/core/severity.d.ts +11 -0
  150. package/types/lib/core/severity.d.ts.map +1 -0
  151. package/types/lib/core/syncFileWriter.d.ts +33 -0
  152. package/types/lib/core/syncFileWriter.d.ts.map +1 -0
  153. package/types/lib/core/userAgent.d.ts +56 -0
  154. package/types/lib/core/userAgent.d.ts.map +1 -0
  155. package/types/lib/ecosystems/cargoCache.d.ts +75 -0
  156. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
  157. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  158. package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
  159. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  160. package/types/lib/ecosystems/ecosystems.d.ts +15 -1
  161. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  162. package/types/lib/ecosystems/gradleutils.d.ts +53 -0
  163. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  164. package/types/lib/ecosystems/kosi.d.ts +73 -0
  165. package/types/lib/ecosystems/kosi.d.ts.map +1 -0
  166. package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
  167. package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
  168. package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
  169. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  170. package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
  171. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
  172. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  173. package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
  174. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
  175. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  176. package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
  177. package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
  178. package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
  179. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  180. package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
  181. package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
  182. package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
  183. package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
  184. package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
  185. package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
  186. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  187. package/types/lib/ecosystems/parsers-r.d.ts +35 -0
  188. package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
  189. package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
  190. package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
  191. package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
  192. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  193. package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
  194. package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
  195. package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
  196. package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
  197. package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
  198. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
  199. package/types/lib/ecosystems/pypiClassifiers.d.ts +80 -0
  200. package/types/lib/ecosystems/pypiClassifiers.d.ts.map +1 -0
  201. package/types/lib/ecosystems/pythonutils.d.ts +21 -1
  202. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  203. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  205. package/types/lib/evinser/evinser.d.ts +34 -1
  206. package/types/lib/evinser/evinser.d.ts.map +1 -1
  207. package/types/lib/helpers/agenticToolutils.d.ts +87 -0
  208. package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
  209. package/types/lib/helpers/remote/dependency-track.d.ts +45 -4
  210. package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -1
  211. package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
  212. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  213. package/types/lib/inventory/aiInventory.d.ts.map +1 -1
  214. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  215. package/types/lib/inventory/auditCategories.d.ts +12 -0
  216. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  217. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  218. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  219. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  220. package/types/lib/inventory/dosai.d.ts +43 -0
  221. package/types/lib/inventory/dosai.d.ts.map +1 -1
  222. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  223. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  224. package/types/lib/inventory/fetchBatch.d.ts +43 -0
  225. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  226. package/types/lib/inventory/fetchRate.d.ts +9 -6
  227. package/types/lib/inventory/fetchRate.d.ts.map +1 -1
  228. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  229. package/types/lib/inventory/jvmToolEnv.d.ts +190 -0
  230. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -0
  231. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  232. package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
  233. package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
  234. package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
  235. package/types/lib/inventory/mcpbBundle.d.ts +19 -0
  236. package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
  237. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  238. package/types/lib/inventory/plugins.d.ts.map +1 -1
  239. package/types/lib/inventory/protobom.d.ts +18 -13
  240. package/types/lib/inventory/protobom.d.ts.map +1 -1
  241. package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
  242. package/types/lib/inventory/source.d.ts.map +1 -1
  243. package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
  244. package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
  245. package/types/lib/inventory/svelteUtils.d.ts +124 -0
  246. package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
  247. package/types/lib/inventory/toolRequirements.d.ts +216 -0
  248. package/types/lib/inventory/toolRequirements.d.ts.map +1 -0
  249. package/types/lib/managers/binary.d.ts.map +1 -1
  250. package/types/lib/managers/docker.d.ts.map +1 -1
  251. package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
  252. package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
  253. package/types/lib/parsers/onnx.d.ts +28 -0
  254. package/types/lib/parsers/onnx.d.ts.map +1 -0
  255. package/types/lib/parsers/safetensors.d.ts +22 -0
  256. package/types/lib/parsers/safetensors.d.ts.map +1 -0
  257. package/types/lib/stages/postgen/annotationBuilder.d.ts +53 -0
  258. package/types/lib/stages/postgen/annotationBuilder.d.ts.map +1 -0
  259. package/types/lib/stages/postgen/auditBom.d.ts.map +1 -1
  260. package/types/lib/stages/postgen/introspection/annotate.d.ts +81 -0
  261. package/types/lib/stages/postgen/introspection/annotate.d.ts.map +1 -0
  262. package/types/lib/stages/postgen/introspection/commandFacts.d.ts +32 -0
  263. package/types/lib/stages/postgen/introspection/commandFacts.d.ts.map +1 -0
  264. package/types/lib/stages/postgen/introspection/emit.d.ts +71 -0
  265. package/types/lib/stages/postgen/introspection/emit.d.ts.map +1 -0
  266. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts +154 -0
  267. package/types/lib/stages/postgen/introspection/formulationEvidence.d.ts.map +1 -0
  268. package/types/lib/stages/postgen/introspection/markers.d.ts +58 -0
  269. package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -0
  270. package/types/lib/stages/postgen/introspection/reflect.d.ts +77 -0
  271. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -0
  272. package/types/lib/stages/postgen/introspection/report.d.ts +85 -0
  273. package/types/lib/stages/postgen/introspection/report.d.ts.map +1 -0
  274. package/types/lib/stages/postgen/introspection/score.d.ts +149 -0
  275. package/types/lib/stages/postgen/introspection/score.d.ts.map +1 -0
  276. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts +79 -0
  277. package/types/lib/stages/postgen/introspection/shapeCommand.d.ts.map +1 -0
  278. package/types/lib/stages/postgen/parentEdge.d.ts +35 -0
  279. package/types/lib/stages/postgen/parentEdge.d.ts.map +1 -0
  280. package/types/lib/stages/postgen/postgen.d.ts +35 -2
  281. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  282. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  283. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  284. package/types/lib/stages/pregen/pregen.d.ts +131 -0
  285. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  286. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -0,0 +1,594 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { readEnvironmentVariable } from "../core/activity.js";
5
+ import { JVM_BUILD_TOOL_TYPE_PREFIXES } from "../core/env.js";
6
+ import { safeExistsSync } from "../core/fs.js";
7
+ import {
8
+ compareVersions,
9
+ extractJavaMajor,
10
+ isPrereleaseVersion,
11
+ } from "./toolRequirements.js";
12
+
13
+ /**
14
+ * The general-purpose version comparator, prerelease classifier, and JDK
15
+ * major extractor live in `toolRequirements.js` so every ecosystem shares one
16
+ * comparator; they are re-exported here under the names this module's
17
+ * consumers already use.
18
+ */
19
+ export {
20
+ compareVersions as compareSdkmanVersions,
21
+ extractJavaMajor,
22
+ isPrereleaseVersion,
23
+ };
24
+
25
+ /**
26
+ * Upper bound on the `.sdkmanrc` content that is scanned, so that a corrupt or
27
+ * hostile file cannot force cdxgen to walk unbounded content.
28
+ *
29
+ * @type {number}
30
+ */
31
+ const MAX_SDKMANRC_BYTES = 64 * 1024;
32
+
33
+ /**
34
+ * Maximum accepted length for a single sdkman version identifier.
35
+ *
36
+ * @type {number}
37
+ */
38
+ const MAX_VERSION_TOKEN_LENGTH = 64;
39
+
40
+ /**
41
+ * sdkman candidate names that cdxgen can provision for JVM builds. The keys
42
+ * are the canonical candidate identifiers used with `sdk install <candidate>`.
43
+ */
44
+ const JVM_BUILD_TOOL_CANDIDATES = ["maven", "gradle", "sbt", "scala"];
45
+
46
+ /**
47
+ * Anchored validator for sdkman version identifiers such as `3.9.9`,
48
+ * `4.0.0-rc-5`, `8.14`, `21.0.7-tem`, or `26.0.2+1.1-tem`. Versions are
49
+ * interpolated into a shell command by the sdkman installer, so every
50
+ * identifier must be a single safe token: it must start with a digit and may
51
+ * only contain digits, ascii letters, dots, hyphens, and plus signs (`+` has
52
+ * no shell meaning and sdkman uses it in Temurin respin identifiers).
53
+ *
54
+ * Every separator must be followed by at least one alphanumeric character, and
55
+ * a single repeated group covers both the dotted numeric part and the vendor
56
+ * or prerelease suffix. That leaves each character exactly one role, so the
57
+ * pattern matches in linear time on untrusted input.
58
+ *
59
+ * @param {string} version Candidate version identifier
60
+ * @returns {boolean} True when the identifier is safe to pass to sdkman
61
+ */
62
+ export function isValidSdkmanVersion(version) {
63
+ return (
64
+ typeof version === "string" &&
65
+ version.length > 0 &&
66
+ version.length <= MAX_VERSION_TOKEN_LENGTH &&
67
+ /^\d+(?:[-.+][0-9A-Za-z]+)*$/.test(version)
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Parse a versioned JVM build tool project type such as `maven3.9.9`,
73
+ * `mvn3.9.9`, `gradle8.14`, `sbt1.10`, or `scala3.6.4` into its sdkman
74
+ * candidate name and version token.
75
+ *
76
+ * The returned object distinguishes between a syntactically valid pin and a
77
+ * rejected one so callers can print a precise CLI error instead of silently
78
+ * ignoring the type.
79
+ *
80
+ * @param {string} projectType Project type argument from the CLI
81
+ * @returns {Object|undefined} `{ tool, version, valid }` when the type looks
82
+ * like a versioned JVM build tool, `undefined` otherwise. `valid` is false
83
+ * when the version token fails validation.
84
+ */
85
+ export function parseJvmToolProjectType(projectType) {
86
+ const pt = `${projectType || ""}`.toLowerCase();
87
+ for (const prefix of JVM_BUILD_TOOL_TYPE_PREFIXES) {
88
+ if (!pt.startsWith(prefix)) {
89
+ continue;
90
+ }
91
+ const version = pt.slice(prefix.length);
92
+ if (!/^\d/.test(version)) {
93
+ // Bare aliases such as "maven" or unrelated types are not versioned pins.
94
+ continue;
95
+ }
96
+ const tool = prefix === "mvn" ? "maven" : prefix;
97
+ return { tool, version, valid: isValidSdkmanVersion(version) };
98
+ }
99
+ return undefined;
100
+ }
101
+
102
+ /**
103
+ * Parse a `.sdkmanrc` environment file into the sdkman candidates it pins.
104
+ * Only the JVM-related candidates cdxgen knows how to provision are kept;
105
+ * unknown keys are ignored so newer sdkman files remain readable. Malformed
106
+ * lines are skipped instead of aborting the whole file.
107
+ *
108
+ * @param {string} filePath Project directory that may contain a `.sdkmanrc`
109
+ * @returns {Object|undefined} Map of candidate name to exact version
110
+ * identifier, or `undefined` when the file is missing or unreadable.
111
+ */
112
+ export function parseSdkmanrc(filePath) {
113
+ const sdkmanrcFile = join(filePath, ".sdkmanrc");
114
+ if (!safeExistsSync(sdkmanrcFile)) {
115
+ return undefined;
116
+ }
117
+ let content;
118
+ try {
119
+ content = readFileSync(sdkmanrcFile, {
120
+ encoding: "utf-8",
121
+ flag: "r",
122
+ }).slice(0, MAX_SDKMANRC_BYTES);
123
+ } catch (_e) {
124
+ return undefined;
125
+ }
126
+ const pins = {};
127
+ const acceptedCandidates = new Set(["java", ...JVM_BUILD_TOOL_CANDIDATES]);
128
+ for (const rawLine of content.split(/\r?\n/)) {
129
+ const line = rawLine.trim();
130
+ if (!line || line.startsWith("#") || line.startsWith("!")) {
131
+ continue;
132
+ }
133
+ const separatorIndex = line.indexOf("=");
134
+ if (separatorIndex <= 0) {
135
+ continue;
136
+ }
137
+ const candidate = line.slice(0, separatorIndex).trim().toLowerCase();
138
+ const version = line.slice(separatorIndex + 1).trim();
139
+ if (!acceptedCandidates.has(candidate)) {
140
+ continue;
141
+ }
142
+ if (!isValidSdkmanVersion(version)) {
143
+ continue;
144
+ }
145
+ if (candidate === "java" && !version.includes("-")) {
146
+ // sdkman java identifiers carry a vendor suffix, e.g. 21.0.7-tem. A
147
+ // bare number such as "21" is not installable, so skip it.
148
+ continue;
149
+ }
150
+ pins[candidate] = version;
151
+ }
152
+ return Object.keys(pins).length ? pins : undefined;
153
+ }
154
+
155
+ /**
156
+ * Read the version pinned by a Gradle wrapper's `gradle-wrapper.properties`.
157
+ * The `distributionUrl` value may escape colons as `\:` per the Java
158
+ * properties format; the escapes are removed before parsing.
159
+ *
160
+ * @param {string} rootPath Directory holding the `gradle/wrapper` folder
161
+ * @returns {Object|undefined} `{ version, distributionUrl, distributionSha256Sum? }`
162
+ * or `undefined` when the wrapper is missing or unparsable.
163
+ */
164
+ export function readGradleWrapperVersion(rootPath) {
165
+ const wrapperPropsFile = join(
166
+ rootPath,
167
+ "gradle",
168
+ "wrapper",
169
+ "gradle-wrapper.properties",
170
+ );
171
+ if (!safeExistsSync(wrapperPropsFile)) {
172
+ return undefined;
173
+ }
174
+ let propContent;
175
+ try {
176
+ propContent = readFileSync(wrapperPropsFile, {
177
+ encoding: "utf-8",
178
+ flag: "r",
179
+ });
180
+ } catch (_e) {
181
+ return undefined;
182
+ }
183
+ const properties = parsePropertiesContent(propContent);
184
+ const distributionUrl = properties.distributionUrl?.replaceAll("\\:", ":");
185
+ if (!distributionUrl) {
186
+ return undefined;
187
+ }
188
+ const version = extractVersionFromZipBasename(distributionUrl);
189
+ if (!version) {
190
+ return undefined;
191
+ }
192
+ const wrapperInfo = { version, distributionUrl };
193
+ const distributionSha256Sum = properties.distributionSha256Sum;
194
+ // The checksum is copied into the BOM as a CycloneDX hash, whose schema
195
+ // accepts hex digests only. Anything else in the properties file is dropped
196
+ // rather than emitted as a hash that would fail validation downstream.
197
+ if (/^[a-fA-F0-9]{64}$/.test(`${distributionSha256Sum}`)) {
198
+ wrapperInfo.distributionSha256Sum = distributionSha256Sum;
199
+ }
200
+ return wrapperInfo;
201
+ }
202
+
203
+ /**
204
+ * Read the version pinned by a Maven wrapper's `maven-wrapper.properties`.
205
+ * Legacy wrappers (pre wrapper-distribution downloads) lack a
206
+ * `distributionUrl` and are treated as unpinned.
207
+ *
208
+ * @param {string} rootPath Directory holding the `.mvn/wrapper` folder
209
+ * @returns {Object|undefined} `{ version, distributionUrl }` or `undefined`
210
+ * when the wrapper is missing or unpinned.
211
+ */
212
+ export function readMavenWrapperVersion(rootPath) {
213
+ const wrapperPropsFile = join(
214
+ rootPath,
215
+ ".mvn",
216
+ "wrapper",
217
+ "maven-wrapper.properties",
218
+ );
219
+ if (!safeExistsSync(wrapperPropsFile)) {
220
+ return undefined;
221
+ }
222
+ let propContent;
223
+ try {
224
+ propContent = readFileSync(wrapperPropsFile, {
225
+ encoding: "utf-8",
226
+ flag: "r",
227
+ });
228
+ } catch (_e) {
229
+ return undefined;
230
+ }
231
+ const properties = parsePropertiesContent(propContent);
232
+ const distributionUrl = properties.distributionUrl?.replaceAll("\\:", ":");
233
+ if (!distributionUrl) {
234
+ return undefined;
235
+ }
236
+ const version = extractVersionFromZipBasename(distributionUrl);
237
+ if (!version) {
238
+ return undefined;
239
+ }
240
+ return { version, distributionUrl };
241
+ }
242
+
243
+ /**
244
+ * Read the sbt version declared in `project/build.properties`. Kept here as a
245
+ * small properties-file read so environment preparation does not depend on
246
+ * higher layers.
247
+ *
248
+ * @param {string} projectPath SBT project directory
249
+ * @returns {string|undefined} Declared sbt version, when present and valid.
250
+ */
251
+ export function readSbtBuildPropertiesVersion(projectPath) {
252
+ const buildPropsFile = join(projectPath, "project", "build.properties");
253
+ if (!safeExistsSync(buildPropsFile)) {
254
+ return undefined;
255
+ }
256
+ let propContent;
257
+ try {
258
+ propContent = readFileSync(buildPropsFile, {
259
+ encoding: "utf-8",
260
+ flag: "r",
261
+ });
262
+ } catch (_e) {
263
+ return undefined;
264
+ }
265
+ const sbtVersion = parsePropertiesContent(propContent)["sbt.version"];
266
+ return isValidSdkmanVersion(sbtVersion) ? sbtVersion : undefined;
267
+ }
268
+
269
+ /**
270
+ * Parse the text content of a Java `.properties` file into a flat map. Only
271
+ * the keys cdxgen consumes are of interest, so a line-based parse with
272
+ * `key=value` splitting is sufficient and avoids regex-heavy parsing of
273
+ * untrusted files.
274
+ *
275
+ * @param {string} content Properties file content
276
+ * @returns {Object} Map of property name to raw value
277
+ */
278
+ function parsePropertiesContent(content) {
279
+ const properties = {};
280
+ for (const rawLine of content.split(/\r?\n/)) {
281
+ const line = rawLine.trim();
282
+ if (!line || line.startsWith("#") || line.startsWith("!")) {
283
+ continue;
284
+ }
285
+ const separatorIndex = line.indexOf("=");
286
+ if (separatorIndex <= 0) {
287
+ continue;
288
+ }
289
+ const key = line.slice(0, separatorIndex).trim();
290
+ const value = line.slice(separatorIndex + 1).trim();
291
+ if (key && !(key in properties)) {
292
+ properties[key] = value;
293
+ }
294
+ }
295
+ return properties;
296
+ }
297
+
298
+ /**
299
+ * Extract a version identifier from the basename of a distribution zip such
300
+ * as `gradle-8.14.3-bin.zip` or `apache-maven-3.9.9-bin.zip` using token
301
+ * splits instead of a capture-everything regex.
302
+ *
303
+ * @param {string} url Distribution URL
304
+ * @returns {string|undefined} Validated version identifier.
305
+ */
306
+ function extractVersionFromZipBasename(url) {
307
+ const pathSeparatorIndex = Math.max(
308
+ url.lastIndexOf("/"),
309
+ url.lastIndexOf("\\"),
310
+ );
311
+ const basename =
312
+ pathSeparatorIndex >= 0 ? url.slice(pathSeparatorIndex + 1) : url;
313
+ for (const token of basename.split("-")) {
314
+ if (/^\d/.test(token) && isValidSdkmanVersion(token)) {
315
+ return token;
316
+ }
317
+ }
318
+ return undefined;
319
+ }
320
+
321
+ /**
322
+ * Minimum JDK major version required to run each build tool, keyed by the
323
+ * tool's major version line. Versions absent from the table fall back to the
324
+ * tool's `default` entry.
325
+ *
326
+ * Sources: Maven release history (maven.apache.org/docs/history.html), the
327
+ * Gradle compatibility table (docs.gradle.org), and the sbt setup notes.
328
+ *
329
+ * @type {Object<string, Object<number, number>|number>}
330
+ */
331
+ export const TOOL_JDK_REQUIREMENTS = {
332
+ maven: { default: 8, 3: 8, 4: 17 },
333
+ gradle: { default: 8, 8: 8, 9: 17 },
334
+ sbt: { default: 8, 1: 8, 2: 17 },
335
+ scala: { default: 8, 2: 8, 3: 8 },
336
+ };
337
+
338
+ /**
339
+ * Minimum Gradle version that supports a given JDK major, for the
340
+ * combinations where running an older Gradle on a newer JDK is known to fail.
341
+ * Used to warn instead of guess-installing when the environment contradicts
342
+ * the project's Gradle pin.
343
+ *
344
+ * @type {Object<number, string>}
345
+ */
346
+ export const GRADLE_JAVA_CAPS = {
347
+ 17: "7.3",
348
+ 21: "8.5",
349
+ 23: "8.10",
350
+ 25: "9.1.0",
351
+ 26: "9.4.0",
352
+ };
353
+
354
+ /** JDK major that satisfies every current build tool minimum. */
355
+ export const DEFAULT_JAVA_MAJOR = 21;
356
+
357
+ /**
358
+ * Look up the minimum JDK major required to run a tool version.
359
+ *
360
+ * @param {string} tool Tool name (maven, gradle, sbt, scala)
361
+ * @param {string} version Tool version identifier
362
+ * @returns {number|undefined} Minimum JDK major, when known.
363
+ */
364
+ export function minimumJdkForToolVersion(tool, version) {
365
+ const toolRequirements = TOOL_JDK_REQUIREMENTS[`${tool}`.toLowerCase()];
366
+ if (!toolRequirements || !isValidSdkmanVersion(`${version}`)) {
367
+ return undefined;
368
+ }
369
+ const major = Number.parseInt(`${version}`, 10);
370
+ if (Number.isNaN(major)) {
371
+ return undefined;
372
+ }
373
+ return toolRequirements[major] ?? toolRequirements.default;
374
+ }
375
+
376
+ /**
377
+ * Compute the highest JDK major required across a set of tool pins.
378
+ *
379
+ * @param {Array<{tool: string, version: string}>} toolPins Pinned tools
380
+ * @returns {number|undefined} Highest required JDK major, when any pin maps.
381
+ */
382
+ export function determineRequiredJavaVersion(toolPins) {
383
+ let required;
384
+ for (const pin of toolPins || []) {
385
+ const minimum = minimumJdkForToolVersion(pin?.tool, pin?.version);
386
+ if (
387
+ minimum !== undefined &&
388
+ (required === undefined || minimum > required)
389
+ ) {
390
+ required = minimum;
391
+ }
392
+ }
393
+ return required;
394
+ }
395
+
396
+ /**
397
+ * Minimum Gradle version that can run on the given JDK major, when such a cap
398
+ * is known.
399
+ *
400
+ * @param {number} javaMajor JDK major version
401
+ * @returns {string|undefined} Minimum Gradle version, when capped.
402
+ */
403
+ export function minimumGradleVersionForJava(javaMajor) {
404
+ return GRADLE_JAVA_CAPS[javaMajor];
405
+ }
406
+
407
+ /**
408
+ * Collect the version tokens printed by `sdk list <candidate>`. The output is
409
+ * a fixed-width table whose cells may carry `+`, `*`, or `>` status markers;
410
+ * every token that passes the sdkman version validator is kept, which
411
+ * naturally drops banners, legends, and shell noise.
412
+ *
413
+ * @param {string} stdout Raw `sdk list` output
414
+ * @returns {string[]} Version identifiers found in the output.
415
+ */
416
+ export function extractSdkListVersions(stdout) {
417
+ if (typeof stdout !== "string") {
418
+ return [];
419
+ }
420
+ const versions = [];
421
+ for (const rawLine of stdout.split(/\r?\n/)) {
422
+ for (const token of rawLine.trim().split(/\s+/)) {
423
+ const cleaned = token.replace(/^[+*>]+/, "");
424
+ if (cleaned && isValidSdkmanVersion(cleaned)) {
425
+ versions.push(cleaned);
426
+ }
427
+ }
428
+ }
429
+ return versions;
430
+ }
431
+
432
+ /**
433
+ * Resolve a partial version prefix such as `3.9` or `8.14` to the newest
434
+ * matching version in `sdk list` output. Stable versions are preferred unless
435
+ * the requested prefix itself is a prerelease.
436
+ *
437
+ * @param {string} stdout Raw `sdk list <candidate>` output
438
+ * @param {string} prefix Partial or exact version prefix; empty matches all
439
+ * @returns {string|undefined} Best matching version identifier.
440
+ */
441
+ export function resolvePartialVersion(stdout, prefix) {
442
+ const versions = extractSdkListVersions(stdout);
443
+ const wantedPrefix = `${prefix || ""}`;
444
+ const allowPrerelease = isPrereleaseVersion(wantedPrefix);
445
+ let best;
446
+ for (const version of versions) {
447
+ const matches =
448
+ !wantedPrefix ||
449
+ version === wantedPrefix ||
450
+ version.startsWith(`${wantedPrefix}.`) ||
451
+ version.startsWith(`${wantedPrefix}-`);
452
+ if (!matches) {
453
+ continue;
454
+ }
455
+ if (!allowPrerelease && isPrereleaseVersion(version)) {
456
+ continue;
457
+ }
458
+ if (best === undefined || compareVersions(version, best) > 0) {
459
+ best = version;
460
+ }
461
+ }
462
+ return best;
463
+ }
464
+
465
+ /**
466
+ * Environment variable hints that carry a tool version, used by container
467
+ * images and CI setups to describe the preinstalled JVM toolchain.
468
+ *
469
+ * @type {Object<string, string>}
470
+ */
471
+ export const JVM_TOOL_VERSION_ENV_HINTS = {
472
+ maven: "MAVEN_VERSION",
473
+ gradle: "GRADLE_VERSION",
474
+ sbt: "SBT_VERSION",
475
+ scala: "SCALA_VERSION",
476
+ };
477
+
478
+ /**
479
+ * Environment variable overrides that declare the sdkman version to use for
480
+ * a tool, mirroring the existing `JAVA<NN>_TOOL` pattern.
481
+ *
482
+ * @type {Object<string, string>}
483
+ */
484
+ export const JVM_TOOL_VERSION_OVERRIDES = {
485
+ maven: "MAVEN_TOOL",
486
+ gradle: "GRADLE_TOOL",
487
+ sbt: "SBT_TOOL",
488
+ scala: "SCALA_TOOL",
489
+ };
490
+
491
+ /**
492
+ * Read the `MAVEN_TOOL`/`GRADLE_TOOL`/`SBT_TOOL`/`SCALA_TOOL` override for a
493
+ * tool, when set to a valid sdkman version identifier.
494
+ *
495
+ * @param {string} tool Tool name
496
+ * @returns {string|undefined} Overridden version identifier.
497
+ */
498
+ export function readToolVersionOverride(tool) {
499
+ const envName = JVM_TOOL_VERSION_OVERRIDES[tool];
500
+ if (!envName) {
501
+ return undefined;
502
+ }
503
+ const value = readEnvironmentVariable(envName);
504
+ return isValidSdkmanVersion(value) ? value : undefined;
505
+ }
506
+
507
+ /**
508
+ * Read the container/CI version hint (`MAVEN_VERSION` etc.) for a tool, when
509
+ * set to a valid sdkman version identifier.
510
+ *
511
+ * @param {string} tool Tool name
512
+ * @returns {string|undefined} Hinted version identifier.
513
+ */
514
+ export function readToolVersionEnvHint(tool) {
515
+ const envName = JVM_TOOL_VERSION_ENV_HINTS[tool];
516
+ if (!envName) {
517
+ return undefined;
518
+ }
519
+ const value = readEnvironmentVariable(envName);
520
+ return isValidSdkmanVersion(value) ? value : undefined;
521
+ }
522
+
523
+ /**
524
+ * Well-known build files scanned for the JDK major a project targets.
525
+ *
526
+ * @type {string[]}
527
+ */
528
+ const JAVA_BUILD_FILES = ["build.gradle", "build.gradle.kts", "pom.xml"];
529
+
530
+ /**
531
+ * Detect the JDK major a project asks for from its build files: Gradle
532
+ * toolchain declarations (`JavaLanguageVersion.of(N)`) and Maven compiler or
533
+ * enforcer properties (`maven.compiler.release`, `maven.compiler.source`,
534
+ * `requireJavaVersion`). The scan is intentionally line-based and
535
+ * conservative: it only recognises whole-number values.
536
+ *
537
+ * @param {string} projectPath Project directory
538
+ * @returns {number|undefined} JDK major requested by the project.
539
+ */
540
+ export function detectProjectJavaMajor(projectPath) {
541
+ if (!projectPath) {
542
+ return undefined;
543
+ }
544
+ for (const buildFileName of JAVA_BUILD_FILES) {
545
+ const buildFile = join(projectPath, buildFileName);
546
+ if (!safeExistsSync(buildFile)) {
547
+ continue;
548
+ }
549
+ let content;
550
+ try {
551
+ content = readFileSync(buildFile, { encoding: "utf-8", flag: "r" });
552
+ } catch (_e) {
553
+ continue;
554
+ }
555
+ const detected = scanBuildFileForJavaMajor(content);
556
+ if (detected !== undefined) {
557
+ return detected;
558
+ }
559
+ }
560
+ return undefined;
561
+ }
562
+
563
+ /**
564
+ * Scan Gradle or Maven build file content for the JDK major it targets.
565
+ *
566
+ * @param {string} content Build file content
567
+ * @returns {number|undefined} JDK major, when recognisable.
568
+ */
569
+ function scanBuildFileForJavaMajor(content) {
570
+ for (const rawLine of content.split(/\r?\n/)) {
571
+ const line = rawLine.trim();
572
+ let match = line.match(/JavaLanguageVersion\.of\((\d{1,2})\)/);
573
+ if (match) {
574
+ return Number.parseInt(match[1], 10);
575
+ }
576
+ match = line.match(
577
+ /<(?:maven\.compiler\.release|maven\.compiler\.source|release|source)>(?:1\.)?(\d{1,2})</,
578
+ );
579
+ if (match) {
580
+ return Number.parseInt(match[1], 10);
581
+ }
582
+ // Enforcer requireJavaVersion ranges such as <version>[17,)</version>.
583
+ // Dependency and plugin version ranges share this element name, so only
584
+ // values that are plausible JDK majors are accepted.
585
+ match = line.match(/<version>\[(?:1\.)?(\d{1,2})[,)\]]/);
586
+ if (match) {
587
+ const major = Number.parseInt(match[1], 10);
588
+ if (major >= 8) {
589
+ return major;
590
+ }
591
+ }
592
+ }
593
+ return undefined;
594
+ }