@cdxgen/cdxgen 13.4.0 → 13.6.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 (305) hide show
  1. package/README.md +9 -9
  2. package/bin/cdxgen-secure.js +15 -0
  3. package/bin/cdxgen.js +29 -5
  4. package/bin/evinse.js +8 -0
  5. package/bin/hbom.js +15 -7
  6. package/bin/repl.js +29 -0
  7. package/bin/validate.js +6 -0
  8. package/data/cbomosdb-queries.json +13 -1
  9. package/data/component-tags.json +8 -0
  10. package/data/license-aliases.json +24 -2
  11. package/data/queries-darwin.json +25 -1
  12. package/data/queries-win.json +19 -1
  13. package/data/queries.json +41 -0
  14. package/data/remediations.json +407 -1
  15. package/data/rules/ai-bom.yaml +1 -1
  16. package/data/rules/brotli-decoder.yaml +107 -0
  17. package/data/rules/cbom-security.yaml +235 -4
  18. package/data/rules/go-binaries.yaml +46 -0
  19. package/data/rules/golem-go.yaml +80 -0
  20. package/data/rules/hbom-compliance.yaml +40 -22
  21. package/data/rules/hbom-performance.yaml +7 -1
  22. package/data/rules/host-topology.yaml +1 -1
  23. package/data/rules/obom-runtime.yaml +31 -2
  24. package/data/rules/php-runtime.yaml +143 -0
  25. package/data/rules/vscode-extensions.yaml +75 -0
  26. package/data/spdx-license-list.json +121 -7
  27. package/data/spdx-licenses.json +69 -0
  28. package/data/spdx.schema.json +845 -2
  29. package/lib/audit/index.js +14 -0
  30. package/lib/cli/bomAssembly.js +120 -6
  31. package/lib/cli/cliOptions.js +110 -3
  32. package/lib/cli/index.js +182 -6
  33. package/lib/cli/jsBom.js +153 -64
  34. package/lib/cli/jvmBom.js +363 -66
  35. package/lib/cli/managedBom.js +853 -87
  36. package/lib/cli/nativeBom.js +267 -33
  37. package/lib/core/boundedFile.js +344 -0
  38. package/lib/core/brotliProbe.js +182 -0
  39. package/lib/core/env.js +38 -8
  40. package/lib/core/fs.js +40 -19
  41. package/lib/core/httpClient.js +172 -46
  42. package/lib/core/lockPaths.js +38 -0
  43. package/lib/core/paths.js +24 -1
  44. package/lib/core/processTree.js +5 -0
  45. package/lib/core/projectConfig.js +1 -0
  46. package/lib/core/projectTooling.js +97 -0
  47. package/lib/core/propertySanitizer.js +72 -0
  48. package/lib/core/toolRegistry.js +170 -0
  49. package/lib/ecosystems/cppEvidence.js +263 -106
  50. package/lib/ecosystems/cppIncludeResolver.js +16 -5
  51. package/lib/ecosystems/ecosystems.js +25 -10
  52. package/lib/ecosystems/gemHostProbe.js +301 -0
  53. package/lib/ecosystems/golem.js +550 -6
  54. package/lib/ecosystems/gradleutils.js +858 -244
  55. package/lib/ecosystems/parsers-bazel.js +107 -6
  56. package/lib/ecosystems/parsers-dotnet.js +117 -18
  57. package/lib/ecosystems/parsers-go.js +1090 -367
  58. package/lib/ecosystems/parsers-js.js +505 -22
  59. package/lib/ecosystems/parsers-jvm.js +259 -17
  60. package/lib/ecosystems/parsers-misc.js +438 -64
  61. package/lib/ecosystems/parsers-python.js +100 -12
  62. package/lib/ecosystems/pythonutils.js +128 -2
  63. package/lib/ecosystems/rubyutils.js +544 -34
  64. package/lib/ecosystems/rusi.js +10 -0
  65. package/lib/ecosystems/scalacliutils.js +4 -1
  66. package/lib/ecosystems/scalasem.js +798 -151
  67. package/lib/ecosystems/vendoredCode.js +237 -3
  68. package/lib/ecosystems/venvProbe.js +360 -0
  69. package/lib/evinser/evinser.js +472 -61
  70. package/lib/evinser/swiftsem.js +68 -5
  71. package/lib/helpers/agenticToolutils.js +413 -93
  72. package/lib/helpers/vsixutils.js +1574 -315
  73. package/lib/inventory/agentSkillSpec.js +17 -0
  74. package/lib/inventory/apkInstalledDb.js +243 -0
  75. package/lib/inventory/atomUtils.js +110 -0
  76. package/lib/inventory/auditCategories.js +3 -3
  77. package/lib/inventory/brotliDecoder.js +142 -0
  78. package/lib/inventory/buildToolRateLimit.js +1 -1
  79. package/lib/inventory/cacheDir.js +52 -9
  80. package/lib/inventory/cbomutils.js +56 -7
  81. package/lib/inventory/certificates.js +2371 -0
  82. package/lib/inventory/communityAiConfigParser.js +4 -9
  83. package/lib/inventory/deps.js +41 -4
  84. package/lib/inventory/display.js +200 -3
  85. package/lib/inventory/dosai.js +639 -63
  86. package/lib/inventory/dotnetRuntime.js +217 -0
  87. package/lib/inventory/dynamic.js +45 -3
  88. package/lib/inventory/envcontext.js +475 -18
  89. package/lib/inventory/evidenceUtils.js +447 -23
  90. package/lib/inventory/fetchBatch.js +1 -1
  91. package/lib/inventory/formulationParsers.js +32 -3
  92. package/lib/inventory/goBuildInfo.js +140 -0
  93. package/lib/inventory/gtfobins.js +13 -0
  94. package/lib/inventory/hbom.js +67 -7
  95. package/lib/inventory/hbomAnalysis.js +16 -0
  96. package/lib/inventory/hostRuntimeProbe.js +686 -0
  97. package/lib/inventory/hostTopology.js +21 -0
  98. package/lib/inventory/jvmLocalRepos.js +9 -2
  99. package/lib/inventory/jvmToolEnv.js +29 -0
  100. package/lib/inventory/licenseEnhancer.js +12 -0
  101. package/lib/inventory/mavenCentralMirror.js +534 -0
  102. package/lib/inventory/nodeBuildInfo.js +218 -0
  103. package/lib/inventory/nodeHostProbe.js +202 -0
  104. package/lib/inventory/osPackagePurl.js +244 -0
  105. package/lib/inventory/osPackageResolver.js +271 -87
  106. package/lib/inventory/osinfo.js +79 -31
  107. package/lib/inventory/osqueryTransform.js +114 -5
  108. package/lib/inventory/phpBuildInfo.js +438 -0
  109. package/lib/inventory/phpHostProbe.js +185 -0
  110. package/lib/inventory/rootfsCerts.js +426 -0
  111. package/lib/inventory/rubyBuildInfo.js +972 -0
  112. package/lib/inventory/rubyHostProbe.js +218 -0
  113. package/lib/inventory/scalaCoords.js +4 -3
  114. package/lib/inventory/scalaDedupe.js +440 -0
  115. package/lib/inventory/source.js +256 -5
  116. package/lib/inventory/spdx.js +144 -0
  117. package/lib/inventory/traceRunner.js +5 -1
  118. package/lib/inventory/tracedCrypto.js +129 -0
  119. package/lib/managers/binary.js +866 -186
  120. package/lib/managers/docker.js +245 -12
  121. package/lib/parsers/email.js +277 -0
  122. package/lib/parsers/largeJson.js +62 -13
  123. package/lib/parsers/vscodeExtensionId.js +183 -0
  124. package/lib/server/server.js +198 -2
  125. package/lib/stages/postgen/annotator.js +17 -1
  126. package/lib/stages/postgen/introspection/reflect.js +4 -1
  127. package/lib/stages/postgen/postgen.js +5 -5
  128. package/lib/stages/postgen/ruleEngine.js +71 -6
  129. package/lib/stages/postgen/specVersionCompat.js +30 -0
  130. package/lib/stages/pregen/envAudit.js +1 -0
  131. package/lib/stages/pregen/pregen.js +231 -62
  132. package/lib/validator/bomValidator.js +32 -6
  133. package/lib/validator/generated/runtime.mjs +13 -0
  134. package/lib/validator/generated/validate-1.6.mjs +27 -8
  135. package/lib/validator/generated/validate-1.7.mjs +32 -13
  136. package/lib/validator/generated/validate-2.0.mjs +31 -12
  137. package/lib/validator/index.js +1 -1
  138. package/package.json +19 -18
  139. package/types/bin/cdxgen-secure.d.ts +3 -0
  140. package/types/bin/cdxgen-secure.d.ts.map +1 -0
  141. package/types/bin/repl.d.ts.map +1 -1
  142. package/types/lib/audit/index.d.ts.map +1 -1
  143. package/types/lib/cli/bomAssembly.d.ts +22 -3
  144. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  145. package/types/lib/cli/cliOptions.d.ts +43 -0
  146. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  147. package/types/lib/cli/index.d.ts.map +1 -1
  148. package/types/lib/cli/jsBom.d.ts +1 -17
  149. package/types/lib/cli/jsBom.d.ts.map +1 -1
  150. package/types/lib/cli/jvmBom.d.ts +20 -0
  151. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  152. package/types/lib/cli/managedBom.d.ts.map +1 -1
  153. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  154. package/types/lib/core/boundedFile.d.ts +70 -0
  155. package/types/lib/core/boundedFile.d.ts.map +1 -0
  156. package/types/lib/core/brotliProbe.d.ts +37 -0
  157. package/types/lib/core/brotliProbe.d.ts.map +1 -0
  158. package/types/lib/core/env.d.ts +22 -2
  159. package/types/lib/core/env.d.ts.map +1 -1
  160. package/types/lib/core/fs.d.ts.map +1 -1
  161. package/types/lib/core/httpClient.d.ts +24 -0
  162. package/types/lib/core/httpClient.d.ts.map +1 -1
  163. package/types/lib/core/lockPaths.d.ts +19 -0
  164. package/types/lib/core/lockPaths.d.ts.map +1 -0
  165. package/types/lib/core/processTree.d.ts +2 -0
  166. package/types/lib/core/processTree.d.ts.map +1 -1
  167. package/types/lib/core/projectConfig.d.ts.map +1 -1
  168. package/types/lib/core/projectTooling.d.ts +41 -0
  169. package/types/lib/core/projectTooling.d.ts.map +1 -0
  170. package/types/lib/core/propertySanitizer.d.ts +26 -0
  171. package/types/lib/core/propertySanitizer.d.ts.map +1 -1
  172. package/types/lib/core/toolRegistry.d.ts +36 -0
  173. package/types/lib/core/toolRegistry.d.ts.map +1 -0
  174. package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
  175. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  176. package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
  177. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
  178. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  179. package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
  180. package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
  181. package/types/lib/ecosystems/golem.d.ts +81 -1
  182. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  183. package/types/lib/ecosystems/gradleutils.d.ts +57 -21
  184. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  185. package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
  186. package/types/lib/ecosystems/parsers-dotnet.d.ts +40 -4
  187. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  188. package/types/lib/ecosystems/parsers-go.d.ts +85 -13
  189. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  190. package/types/lib/ecosystems/parsers-js.d.ts +17 -20
  191. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  192. package/types/lib/ecosystems/parsers-jvm.d.ts +28 -2
  193. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  194. package/types/lib/ecosystems/parsers-misc.d.ts +37 -58
  195. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  196. package/types/lib/ecosystems/parsers-python.d.ts +14 -3
  197. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  198. package/types/lib/ecosystems/pythonutils.d.ts +19 -1
  199. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  200. package/types/lib/ecosystems/rubyutils.d.ts +144 -12
  201. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  202. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  203. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
  204. package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
  205. package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
  206. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
  207. package/types/lib/ecosystems/venvProbe.d.ts +40 -0
  208. package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
  209. package/types/lib/evinser/evinser.d.ts +88 -8
  210. package/types/lib/evinser/evinser.d.ts.map +1 -1
  211. package/types/lib/evinser/swiftsem.d.ts +10 -0
  212. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  213. package/types/lib/inventory/agentSkillSpec.d.ts +13 -0
  214. package/types/lib/inventory/agentSkillSpec.d.ts.map +1 -0
  215. package/types/lib/inventory/apkInstalledDb.d.ts +42 -0
  216. package/types/lib/inventory/apkInstalledDb.d.ts.map +1 -0
  217. package/types/lib/inventory/atomUtils.d.ts +33 -0
  218. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  219. package/types/lib/inventory/brotliDecoder.d.ts +58 -0
  220. package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
  221. package/types/lib/inventory/cacheDir.d.ts +12 -4
  222. package/types/lib/inventory/cacheDir.d.ts.map +1 -1
  223. package/types/lib/inventory/cbomutils.d.ts +17 -0
  224. package/types/lib/inventory/cbomutils.d.ts.map +1 -1
  225. package/types/lib/inventory/certificates.d.ts +205 -0
  226. package/types/lib/inventory/certificates.d.ts.map +1 -0
  227. package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
  228. package/types/lib/inventory/deps.d.ts +2 -1
  229. package/types/lib/inventory/deps.d.ts.map +1 -1
  230. package/types/lib/inventory/display.d.ts +18 -0
  231. package/types/lib/inventory/display.d.ts.map +1 -1
  232. package/types/lib/inventory/dosai.d.ts +162 -7
  233. package/types/lib/inventory/dosai.d.ts.map +1 -1
  234. package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
  235. package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
  236. package/types/lib/inventory/dynamic.d.ts +6 -1
  237. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  238. package/types/lib/inventory/envcontext.d.ts +92 -8
  239. package/types/lib/inventory/envcontext.d.ts.map +1 -1
  240. package/types/lib/inventory/evidenceUtils.d.ts +29 -10
  241. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  242. package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
  243. package/types/lib/inventory/goBuildInfo.d.ts +10 -0
  244. package/types/lib/inventory/goBuildInfo.d.ts.map +1 -0
  245. package/types/lib/inventory/gtfobins.d.ts.map +1 -1
  246. package/types/lib/inventory/hbom.d.ts.map +1 -1
  247. package/types/lib/inventory/hbomAnalysis.d.ts +10 -0
  248. package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -1
  249. package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
  250. package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
  251. package/types/lib/inventory/hostTopology.d.ts.map +1 -1
  252. package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
  253. package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
  254. package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
  255. package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
  256. package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
  257. package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
  258. package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
  259. package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
  260. package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
  261. package/types/lib/inventory/osPackagePurl.d.ts +99 -0
  262. package/types/lib/inventory/osPackagePurl.d.ts.map +1 -0
  263. package/types/lib/inventory/osPackageResolver.d.ts +44 -2
  264. package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
  265. package/types/lib/inventory/osinfo.d.ts +31 -6
  266. package/types/lib/inventory/osinfo.d.ts.map +1 -1
  267. package/types/lib/inventory/osqueryTransform.d.ts +51 -0
  268. package/types/lib/inventory/osqueryTransform.d.ts.map +1 -1
  269. package/types/lib/inventory/phpBuildInfo.d.ts +142 -0
  270. package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -0
  271. package/types/lib/inventory/phpHostProbe.d.ts +35 -0
  272. package/types/lib/inventory/phpHostProbe.d.ts.map +1 -0
  273. package/types/lib/inventory/rootfsCerts.d.ts +27 -0
  274. package/types/lib/inventory/rootfsCerts.d.ts.map +1 -0
  275. package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
  276. package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
  277. package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
  278. package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
  279. package/types/lib/inventory/source.d.ts +38 -0
  280. package/types/lib/inventory/source.d.ts.map +1 -1
  281. package/types/lib/inventory/spdx.d.ts +15 -0
  282. package/types/lib/inventory/spdx.d.ts.map +1 -1
  283. package/types/lib/inventory/traceRunner.d.ts +4 -1
  284. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  285. package/types/lib/inventory/tracedCrypto.d.ts +22 -0
  286. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -1
  287. package/types/lib/managers/binary.d.ts +10 -0
  288. package/types/lib/managers/binary.d.ts.map +1 -1
  289. package/types/lib/managers/docker.d.ts +24 -0
  290. package/types/lib/managers/docker.d.ts.map +1 -1
  291. package/types/lib/parsers/email.d.ts +19 -0
  292. package/types/lib/parsers/email.d.ts.map +1 -0
  293. package/types/lib/parsers/largeJson.d.ts +17 -3
  294. package/types/lib/parsers/largeJson.d.ts.map +1 -1
  295. package/types/lib/server/server.d.ts +8 -0
  296. package/types/lib/server/server.d.ts.map +1 -1
  297. package/types/lib/stages/postgen/annotator.d.ts.map +1 -1
  298. package/types/lib/stages/postgen/introspection/reflect.d.ts.map +1 -1
  299. package/types/lib/stages/postgen/ruleEngine.d.ts +7 -4
  300. package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
  301. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  302. package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
  303. package/types/lib/stages/pregen/pregen.d.ts +26 -9
  304. package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
  305. package/types/lib/validator/bomValidator.d.ts.map +1 -1
@@ -1,6 +1,5 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { basename, dirname, join, relative } from "node:path";
3
- import process from "node:process";
4
3
  import { URL } from "node:url";
5
4
 
6
5
  import { build } from "@cdxgen/cdx-purl";
@@ -889,6 +888,61 @@ export function enrichFromInstalledMetadata(pkgList, installedMetadata) {
889
888
  return enriched;
890
889
  }
891
890
 
891
+ // The interpreter cdxgen assumes for a lock file's environment. A marker that
892
+ // tests the implementation against it is decidable without a target machine.
893
+ const IMPLEMENTATION_CLAUSE_RE =
894
+ /^(platform_python_implementation|implementation_name)\s*(==|!=)\s*(['"])([A-Za-z]+)\3$/;
895
+
896
+ function implementationClauseIsFalse(clause) {
897
+ const match = IMPLEMENTATION_CLAUSE_RE.exec(clause.trim());
898
+ if (!match) {
899
+ return false;
900
+ }
901
+ const expected = match[1] === "implementation_name" ? "cpython" : "CPython";
902
+ const equal = match[4] === expected;
903
+ return match[2] === "==" ? !equal : equal;
904
+ }
905
+
906
+ /**
907
+ * Whether an environment marker is false for every CPython interpreter. Only
908
+ * `platform_python_implementation` and `implementation_name` clauses are
909
+ * decided; anything else, or a marker with parentheses, is treated as possibly
910
+ * true, so the answer errs towards keeping a package.
911
+ *
912
+ * @param {string} marker PEP 508 environment marker
913
+ * @returns {boolean} true when no CPython environment satisfies the marker
914
+ */
915
+ export function markerExcludesCPython(marker) {
916
+ if (typeof marker !== "string" || !marker.trim() || /[()]/.test(marker)) {
917
+ return false;
918
+ }
919
+ return marker
920
+ .split(/\s+or\s+/)
921
+ .every((disjunct) =>
922
+ disjunct.split(/\s+and\s+/).some(implementationClauseIsFalse),
923
+ );
924
+ }
925
+
926
+ function recordMarkerEdge(markerEdges, depKey, apkgDep) {
927
+ if (!depKey) {
928
+ return;
929
+ }
930
+ const edge = markerEdges.get(depKey) || {
931
+ total: 0,
932
+ cpythonExcluded: 0,
933
+ markers: new Set(),
934
+ };
935
+ edge.total += 1;
936
+ const marker = typeof apkgDep === "object" ? apkgDep?.marker : undefined;
937
+ if (typeof marker === "string" && marker.trim()) {
938
+ edge.markers.add(marker.trim());
939
+ if (markerExcludesCPython(marker)) {
940
+ edge.cpythonExcluded += 1;
941
+ }
942
+ }
943
+ markerEdges.set(depKey, edge);
944
+ }
945
+
892
946
  /**
893
947
  * Method to parse python lock files such as poetry.lock, pdm.lock, uv.lock, and pylock.toml.
894
948
  *
@@ -912,6 +966,8 @@ export async function parsePyLockData(
912
966
  const depsMap = {};
913
967
  const existingPkgMap = {};
914
968
  const pkgBomRefMap = {};
969
+ // Incoming edges per dependency, with the environment markers they carry.
970
+ const markerEdges = new Map();
915
971
  let directDepsKeys = {};
916
972
  let groupDepsKeys = {};
917
973
  let dependencySourceMap = {};
@@ -1335,9 +1391,16 @@ export async function parsePyLockData(
1335
1391
  devDependencies.length ||
1336
1392
  optionalDependencies.length
1337
1393
  ) {
1338
- if (Array.isArray(apkg.dependencies)) {
1394
+ // A package whose only dependencies are optional (urllib3 has no
1395
+ // required ones, only the `brotli`, `socks` and `zstd` extras) has no
1396
+ // `dependencies` key in uv.lock, but its extras are still edges.
1397
+ if (
1398
+ Array.isArray(apkg.dependencies) ||
1399
+ (!apkg.dependencies &&
1400
+ (devDependencies.length || optionalDependencies.length))
1401
+ ) {
1339
1402
  // pdm.lock files
1340
- let allDeps = apkg.dependencies;
1403
+ let allDeps = apkg.dependencies || [];
1341
1404
  allDeps = allDeps.concat(devDependencies);
1342
1405
  allDeps = allDeps.concat(optionalDependencies);
1343
1406
  for (const apkgDep of allDeps) {
@@ -1349,6 +1412,7 @@ export async function parsePyLockData(
1349
1412
  const depPkgRef =
1350
1413
  existingPkgMap[nameLower] || existingPkgMap[nameStr];
1351
1414
  depsMap[pkg["bom-ref"]].add(depPkgRef || nameStr);
1415
+ recordMarkerEdge(markerEdges, depPkgRef || nameLower, apkgDep);
1352
1416
  // Propagate the workspace properties to the child components
1353
1417
  if (depPkgRef && pkgBomRefMap[depPkgRef]) {
1354
1418
  const dependentPkg = pkgBomRefMap[depPkgRef];
@@ -1422,6 +1486,28 @@ export async function parsePyLockData(
1422
1486
  }
1423
1487
  }
1424
1488
  }
1489
+ // A package reached only through edges whose environment marker excludes
1490
+ // CPython (brotlicffi is `platform_python_implementation != 'CPython'`) is
1491
+ // not installed by a CPython environment built from this lock, so it is
1492
+ // optional rather than part of the CPython closure. A direct dependency of
1493
+ // the project is left alone: the manifest asked for it.
1494
+ for (const [depRef, edge] of markerEdges) {
1495
+ const dependentPkg = pkgBomRefMap[depRef];
1496
+ if (
1497
+ !dependentPkg ||
1498
+ !edge.total ||
1499
+ edge.cpythonExcluded !== edge.total ||
1500
+ directDepsKeys[normalizePythonDependencyKey(dependentPkg.name)]
1501
+ ) {
1502
+ continue;
1503
+ }
1504
+ dependentPkg.scope = "optional";
1505
+ dependentPkg.properties = dependentPkg.properties || [];
1506
+ dependentPkg.properties.push({
1507
+ name: "cdx:pip:markers",
1508
+ value: Array.from(edge.markers).sort().join(" or "),
1509
+ });
1510
+ }
1425
1511
  // Seed the parent component's first-level edges from the manifest. Lock
1426
1512
  // files describe the resolved closure but do not say which of the locked
1427
1513
  // packages the root project directly depends on; parsePyProjectTomlFile
@@ -2205,28 +2291,30 @@ export function parsePixiTomlFile(pixiToml) {
2205
2291
  }
2206
2292
 
2207
2293
  /**
2208
- * Method to run cli command `pixi install`
2209
- *
2294
+ * Method to run cli command `pixi install` in the project directory.
2210
2295
  *
2296
+ * @param {string} projectPath Directory containing pixi.toml
2297
+ * @returns {boolean} true when pixi install succeeded
2211
2298
  */
2212
- export function generatePixiLockFile(_path) {
2213
- const result = safeSpawnSync("pixi", ["install"]);
2299
+ export function generatePixiLockFile(projectPath) {
2300
+ const result = safeSpawnSync("pixi", ["install"], { cwd: projectPath });
2214
2301
 
2215
2302
  if (result.status !== 0) {
2216
- // Handle errors
2303
+ // Handle errors. The caller carries on without a lock file: exiting here
2304
+ // would also stop a long-running cdxgen server.
2217
2305
  if (result.error && result.error.code === "ENOENT") {
2218
2306
  console.error(
2219
2307
  "Error: pixi command not found. Make sure pixi.js is installed globally.",
2220
2308
  );
2221
2309
  } else {
2222
2310
  console.error(
2223
- `Error executing pixi install: ${result.error || result.stderr.toString()}`,
2311
+ `Error executing pixi install: ${result.error || result.stderr?.toString()}`,
2224
2312
  );
2225
2313
  }
2226
- process.exit(1);
2227
- } else {
2228
- console.log("Dependencies installed successfully.");
2314
+ return false;
2229
2315
  }
2316
+ console.log("Dependencies installed successfully.");
2317
+ return true;
2230
2318
  }
2231
2319
 
2232
2320
  /**
@@ -11,6 +11,7 @@ import {
11
11
  import process from "node:process";
12
12
 
13
13
  import { build } from "@cdxgen/cdx-purl";
14
+ import { coerce, gte } from "semver";
14
15
 
15
16
  import {
16
17
  DEBUG_MODE,
@@ -25,6 +26,7 @@ import {
25
26
  PYPI_MODULE_PACKAGE_MAPPING,
26
27
  PYTHON_STD_MODULES,
27
28
  } from "../core/state.js";
29
+ import { parseToml } from "../core/toml.js";
28
30
  import { findAppModules } from "../inventory/atomUtils.js";
29
31
  import { flattenDeps } from "../inventory/deps.js";
30
32
  import { pypiBomRef } from "../inventory/purl.js";
@@ -612,6 +614,128 @@ export function createUVLock(basePath, options) {
612
614
  }
613
615
  }
614
616
 
617
+ const CONSTRAINT_OPERATORS = [">=", "<=", "==", "!=", "~=", ">", "<", "^", "="];
618
+ const LOWER_BOUND_OPERATORS = new Set([">=", ">", "==", "~=", "^", "="]);
619
+
620
+ /**
621
+ * Check whether a single version constraint puts the lower bound at version 2
622
+ * or higher.
623
+ *
624
+ * @param {string} constraint Constraint such as ">=2.0.0" or "<3.0.0"
625
+ * @returns {boolean} true when the constraint requires version 2 or newer
626
+ */
627
+ function requiresVersion2(constraint) {
628
+ let operator = "";
629
+ let version = constraint.trim();
630
+ for (const candidate of CONSTRAINT_OPERATORS) {
631
+ if (version.startsWith(candidate)) {
632
+ operator = candidate;
633
+ version = version.slice(candidate.length).trim();
634
+ break;
635
+ }
636
+ }
637
+ if (!operator || !LOWER_BOUND_OPERATORS.has(operator)) {
638
+ return false;
639
+ }
640
+ const coerced = coerce(version);
641
+ return coerced !== null && gte(coerced, "2.0.0");
642
+ }
643
+
644
+ /**
645
+ * Check whether a build requirement names poetry-core with a lower bound of
646
+ * version 2 or higher.
647
+ *
648
+ * @param {string} requirement Build requirement such as "poetry-core>=2.0.0"
649
+ * @returns {boolean} true when poetry-core 2 or newer is required
650
+ */
651
+ function requiresPoetryCore2(requirement) {
652
+ if (typeof requirement !== "string") {
653
+ return false;
654
+ }
655
+ const entry = requirement.trim().toLowerCase();
656
+ if (!entry.startsWith("poetry-core")) {
657
+ return false;
658
+ }
659
+ let spec = entry.slice("poetry-core".length);
660
+ if (spec.startsWith("[")) {
661
+ const extrasEnd = spec.indexOf("]");
662
+ if (extrasEnd === -1) {
663
+ return false;
664
+ }
665
+ spec = spec.slice(extrasEnd + 1);
666
+ }
667
+ const next = spec.charAt(0);
668
+ if (next && next !== " " && next !== "\t" && !"><=!~^".includes(next)) {
669
+ return false;
670
+ }
671
+ return spec
672
+ .trim()
673
+ .split(",")
674
+ .some((constraint) => requiresVersion2(constraint));
675
+ }
676
+
677
+ /**
678
+ * Check whether a pyproject.toml requires poetry-core 2 or newer to build.
679
+ *
680
+ * @param {string} pyprojectFile Path to pyproject.toml
681
+ * @returns {boolean} true when the project needs Poetry 2 or newer
682
+ */
683
+ export function isPoetry2Project(pyprojectFile) {
684
+ if (!pyprojectFile || !safeExistsSync(pyprojectFile)) {
685
+ return false;
686
+ }
687
+ let config;
688
+ try {
689
+ config = parseToml(readFileSync(pyprojectFile, "utf-8"));
690
+ } catch (_err) {
691
+ return false;
692
+ }
693
+ const requires = config?.["build-system"]?.requires;
694
+ if (!Array.isArray(requires)) {
695
+ return false;
696
+ }
697
+ return requires.some((requirement) => requiresPoetryCore2(requirement));
698
+ }
699
+
700
+ /**
701
+ * Find the pyproject.toml belonging to a poetry trigger file.
702
+ *
703
+ * @param {string} triggerFile pyproject.toml or poetry.lock path
704
+ * @returns {string|null} Path to pyproject.toml, or null when there is none
705
+ */
706
+ function findPoetryPyprojectFile(triggerFile) {
707
+ if (!triggerFile) {
708
+ return null;
709
+ }
710
+ if (triggerFile.endsWith("pyproject.toml")) {
711
+ return triggerFile;
712
+ }
713
+ if (triggerFile.endsWith("poetry.lock")) {
714
+ const sibling = join(dirname(triggerFile), "pyproject.toml");
715
+ return safeExistsSync(sibling) ? sibling : null;
716
+ }
717
+ return null;
718
+ }
719
+
720
+ /**
721
+ * Build the poetry install arguments for a poetry project.
722
+ *
723
+ * Poetry 2 installs only the main dependency group and no extras by default,
724
+ * so projects requiring poetry-core 2 or newer also get --all-groups and
725
+ * --all-extras. Callers prepend the python invocation such as -m poetry.
726
+ *
727
+ * @param {string} pyprojectFile pyproject.toml or poetry.lock path of the project
728
+ * @returns {string[]} poetry install arguments
729
+ */
730
+ export function getPoetryInstallArgs(pyprojectFile) {
731
+ const args = ["install", "-n", "--no-root"];
732
+ const resolvedFile = findPoetryPyprojectFile(pyprojectFile);
733
+ if (resolvedFile && isPoetry2Project(resolvedFile)) {
734
+ args.push("--all-groups", "--all-extras");
735
+ }
736
+ return args;
737
+ }
738
+
615
739
  /**
616
740
  * Execute pip freeze by creating a virtual env in a temp directory and construct the dependency tree
617
741
  *
@@ -742,7 +866,9 @@ export async function getPipFrozenTree(
742
866
  shell: isWin,
743
867
  });
744
868
  thoughtLog("Performing poetry install");
745
- let poetryInstallArgs = ["-m", "poetry", "install", "-n", "--no-root"];
869
+ let poetryInstallArgs = ["-m", "poetry"].concat(
870
+ getPoetryInstallArgs(reqOrSetupFile),
871
+ );
746
872
  if (isSecureMode) {
747
873
  poetryInstallArgs.unshift("-S");
748
874
  }
@@ -756,7 +882,7 @@ export async function getPipFrozenTree(
756
882
  thoughtLog(
757
883
  "Hmm, poetry doesn't seem to be available as a module. Perhaps it was installed directly 🤔?",
758
884
  );
759
- poetryInstallArgs = ["install", "-n", "--no-root"];
885
+ poetryInstallArgs = getPoetryInstallArgs(reqOrSetupFile);
760
886
  // Attempt to perform poetry install
761
887
  result = safeSpawnSync("poetry", poetryInstallArgs, {
762
888
  cwd: basePath,