@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
@@ -0,0 +1,170 @@
1
+ import { basename } from "node:path";
2
+
3
+ import { registerRunReset } from "./runState.js";
4
+
5
+ /**
6
+ * Run-scoped registry of the external tools cdxgen actually executed, feeding
7
+ * the `metadata.tools` section (issue #598). `safeSpawnSync` records a command
8
+ * once the process has started, so a binary that is missing or a spawn that
9
+ * was blocked never appears, and the stages that already know a tool's
10
+ * version - such as the JVM tool resolution in pregen - enrich the entry; no
11
+ * process is ever spawned just to learn a version.
12
+ *
13
+ * The registry is filled in as the run progresses and read once per BOM when
14
+ * the metadata section is assembled. A run that spawns nothing external keeps
15
+ * it empty, which is what keeps such BOMs byte-identical to the previous
16
+ * behaviour. It is per-run state: a long-lived process such as the server
17
+ * empties it between scans through {@link resetRunState}.
18
+ *
19
+ * @type {Map<string, {name: string, version: (string|undefined)}>}
20
+ */
21
+ const EXECUTED_TOOLS = new Map();
22
+
23
+ /**
24
+ * Versions stages already resolved for a tool, keyed like EXECUTED_TOOLS. They
25
+ * are applied only to tools that actually started, so provisioning a tool the
26
+ * run then never invokes adds nothing to `metadata.tools`.
27
+ *
28
+ * @type {Map<string, string>}
29
+ */
30
+ const KNOWN_VERSIONS = new Map();
31
+
32
+ /**
33
+ * Interpreter and shell binaries that wrap the real work. They appear on
34
+ * nearly every spawn, so recording them would drown the tools that actually
35
+ * generated the BOM.
36
+ *
37
+ * @type {Set<string>}
38
+ */
39
+ const WRAPPER_BASENAMES = new Set([
40
+ "bash",
41
+ "cmd",
42
+ "cmd.exe",
43
+ "node",
44
+ "powershell",
45
+ "powershell.exe",
46
+ "pwsh",
47
+ "sh",
48
+ "zsh",
49
+ ]);
50
+
51
+ /**
52
+ * Strip a trailing Windows executable suffix from a binary name.
53
+ *
54
+ * @param {string} name Binary name
55
+ * @returns {string} Name without a trailing `.exe`, `.bat`, or `.cmd`
56
+ */
57
+ function trimWindowsSuffix(name) {
58
+ for (const suffix of [".exe", ".bat", ".cmd"]) {
59
+ if (name.endsWith(suffix)) {
60
+ return name.slice(0, -suffix.length);
61
+ }
62
+ }
63
+ return name;
64
+ }
65
+
66
+ /**
67
+ * Name a tool the way the registry keys it: the basename without a Windows
68
+ * executable suffix, and without the `-cdxgen-<os>-<arch>` suffix of a
69
+ * bundled cdxgen-plugins-bin helper, so `trivy-cdxgen-linux-amd64` is `trivy`
70
+ * on every platform and matches its plugin manifest entry.
71
+ *
72
+ * @param {string} name Tool name as invoked (a bare name or a path)
73
+ * @returns {string} Tool name
74
+ */
75
+ function toolNameOf(name) {
76
+ const toolName = trimWindowsSuffix(basename(`${name}`.split("\\").join("/")));
77
+ const pluginSuffixAt = toolName.lastIndexOf("-cdxgen-");
78
+ if (
79
+ pluginSuffixAt > 0 &&
80
+ toolName.slice(pluginSuffixAt + "-cdxgen-".length).split("-").length === 2
81
+ ) {
82
+ return toolName.slice(0, pluginSuffixAt);
83
+ }
84
+ return toolName;
85
+ }
86
+
87
+ /**
88
+ * Record an external tool cdxgen executed. The first version seen wins unless
89
+ * it was unknown, so an enriched probe never overwrites a concrete version
90
+ * with `undefined` and a later concrete version fills an earlier gap.
91
+ *
92
+ * @param {string} name Tool name as invoked (a bare name or a path)
93
+ * @param {string|undefined} version Version when cheaply known
94
+ */
95
+ export function recordExecutedTool(name, version) {
96
+ if (typeof name !== "string" || !name) {
97
+ return;
98
+ }
99
+ const toolName = toolNameOf(name);
100
+ if (!toolName || WRAPPER_BASENAMES.has(toolName)) {
101
+ return;
102
+ }
103
+ const key = toolName.toLowerCase();
104
+ const existing = EXECUTED_TOOLS.get(key);
105
+ if (!existing) {
106
+ EXECUTED_TOOLS.set(key, {
107
+ name: toolName,
108
+ version: typeof version === "string" && version ? version : undefined,
109
+ });
110
+ return;
111
+ }
112
+ if (typeof version === "string" && version && !existing.version) {
113
+ existing.version = version;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Remember the version a stage already resolved for a tool, such as the JVM
119
+ * build tool pregen provisioned. The version shows in `metadata.tools` only if
120
+ * the tool is executed; the first version recorded wins.
121
+ *
122
+ * @param {string} name Tool name as it will be invoked (a bare name or a path)
123
+ * @param {string} version Resolved version
124
+ */
125
+ export function recordToolVersion(name, version) {
126
+ if (typeof name !== "string" || !name) {
127
+ return;
128
+ }
129
+ if (typeof version !== "string" || !version) {
130
+ return;
131
+ }
132
+ const key = toolNameOf(name).toLowerCase();
133
+ if (key && !KNOWN_VERSIONS.has(key)) {
134
+ KNOWN_VERSIONS.set(key, version);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Snapshot of the tools recorded so far, sorted by name so the assembled
140
+ * `metadata.tools` diff stays stable between runs.
141
+ *
142
+ * @returns {Array<{name: string, version: (string|undefined)}>} Sorted tool records
143
+ */
144
+ export function getExecutedTools() {
145
+ return [...EXECUTED_TOOLS.entries()]
146
+ .map(([key, tool]) => ({
147
+ name: tool.name,
148
+ version: tool.version || KNOWN_VERSIONS.get(key),
149
+ }))
150
+ .sort((a, b) =>
151
+ a.name === b.name
152
+ ? 0
153
+ : a.name.toLowerCase() < b.name.toLowerCase()
154
+ ? -1
155
+ : 1,
156
+ );
157
+ }
158
+
159
+ /**
160
+ * Clear the registry. The server calls it between scans through the run-state
161
+ * reset; tests call it directly.
162
+ *
163
+ * @returns {void}
164
+ */
165
+ export function resetExecutedTools() {
166
+ EXECUTED_TOOLS.clear();
167
+ KNOWN_VERSIONS.clear();
168
+ }
169
+
170
+ registerRunReset("toolRegistry.executedTools", resetExecutedTools);
@@ -19,6 +19,7 @@ import {
19
19
  import { build } from "@cdxgen/cdx-purl";
20
20
 
21
21
  import { DEBUG_MODE } from "../core/activity.js";
22
+ import { readRegularTextFile } from "../core/boundedFile.js";
22
23
  import { getAllFiles, safeExistsSync } from "../core/fs.js";
23
24
  import { thoughtLog } from "../core/logger.js";
24
25
  import { CPP_STD_MODULES } from "../core/state.js";
@@ -29,6 +30,7 @@ import { locateGenericPackage } from "../inventory/purl.js";
29
30
  import {
30
31
  createIncludeAttributor,
31
32
  parseCIncludeSlices,
33
+ readVcpkgInstalledIndex,
32
34
  } from "./cppIncludeResolver.js";
33
35
  import {
34
36
  parseCmakeLikeFile,
@@ -218,6 +220,243 @@ export function hasCppSources(src, options = {}) {
218
220
  );
219
221
  }
220
222
 
223
+ const MAX_VCPKG_MANIFEST_BYTES = 4 * 1024 * 1024;
224
+
225
+ /**
226
+ * The ports vcpkg installed into the `vcpkg_installed` directories of a
227
+ * project and its build trees, by lower-case port name.
228
+ *
229
+ * @param {string} src project directory
230
+ * @param {Object} options CLI options, for the build-directory candidates
231
+ * @returns {Map<string, {version: string, triplet: string}>}
232
+ */
233
+ export function installedVcpkgPorts(src, options = {}) {
234
+ const ports = new Map();
235
+ const installedDirs = [src, ...cmakeBuildDirCandidates(src, options)]
236
+ .map((dir) => join(dir, "vcpkg_installed"))
237
+ .filter((dir) => safeExistsSync(dir));
238
+ if (!installedDirs.length) {
239
+ return ports;
240
+ }
241
+ for (const { port, version, triplet, portVersion } of readVcpkgInstalledIndex(
242
+ installedDirs,
243
+ ).values()) {
244
+ if (!ports.has(port.toLowerCase())) {
245
+ ports.set(port.toLowerCase(), {
246
+ version,
247
+ triplet,
248
+ ...(portVersion ? { portVersion } : {}),
249
+ });
250
+ }
251
+ }
252
+ return ports;
253
+ }
254
+
255
+ /**
256
+ * The version a vcpkg manifest pins for a port in its `overrides`, from any of
257
+ * the four version fields vcpkg accepts, and the port version, which is a
258
+ * packaging revision of the same upstream release.
259
+ * https://learn.microsoft.com/vcpkg/reference/vcpkg-json#overrides
260
+ *
261
+ * @param {Object} override one `overrides` entry
262
+ * @returns {{version: string, portVersion?: string}|undefined}
263
+ */
264
+ function vcpkgOverrideVersion(override) {
265
+ if (!override || typeof override !== "object") {
266
+ return undefined;
267
+ }
268
+ const version = [
269
+ "version",
270
+ "version-semver",
271
+ "version-date",
272
+ "version-string",
273
+ ]
274
+ .map((field) => override[field])
275
+ .find((value) => typeof value === "string" && value.trim());
276
+ if (!version) {
277
+ return undefined;
278
+ }
279
+ const portVersion = Number(override["port-version"]);
280
+ return Number.isInteger(portVersion) && portVersion > 0
281
+ ? { version: version.trim(), portVersion: String(portVersion) }
282
+ : { version: version.trim() };
283
+ }
284
+
285
+ /**
286
+ * Read a vcpkg manifest (vcpkg.json) in a directory: the project it describes
287
+ * and the ports it declares. A port's version comes from what vcpkg installed
288
+ * in the build tree when that is known, otherwise from the manifest's
289
+ * `overrides`, which pin it exactly. A `version>=` constraint is only a
290
+ * minimum, so it is kept as a property, never as the version.
291
+ * https://learn.microsoft.com/vcpkg/reference/vcpkg-json
292
+ *
293
+ * The read is cheap and runs no tool, so it does not wait for the atom-based
294
+ * analysis that getCppModules adds.
295
+ *
296
+ * @param {string} src directory that may hold vcpkg.json
297
+ * @param {Object} [options] options
298
+ * @param {Map<string, {version: string, triplet: string}>} [options.installedPorts]
299
+ * ports vcpkg installed, by lower-case name
300
+ * @param {function(string): boolean} [options.isListed] whether another source
301
+ * already lists a port by that name
302
+ * @returns {{parentComponent: Object|undefined, pkgList: Object[]}}
303
+ */
304
+ export function parseVcpkgManifest(src, options = {}) {
305
+ const pkgType = "generic";
306
+ const result = { parentComponent: undefined, pkgList: [] };
307
+ const manifestFile = join(src, "vcpkg.json");
308
+ if (!safeExistsSync(manifestFile)) {
309
+ return result;
310
+ }
311
+ let vcPkgData;
312
+ try {
313
+ // A regular file only: a FIFO named vcpkg.json would block a plain read.
314
+ vcPkgData = JSON.parse(
315
+ readRegularTextFile(manifestFile, MAX_VCPKG_MANIFEST_BYTES) ?? "",
316
+ );
317
+ } catch {
318
+ return result;
319
+ }
320
+ if (!vcPkgData || typeof vcPkgData !== "object") {
321
+ return result;
322
+ }
323
+ const installedPorts = options.installedPorts || new Map();
324
+ const isListed = options.isListed || (() => false);
325
+ const overrides = new Map();
326
+ if (Array.isArray(vcPkgData.overrides)) {
327
+ for (const override of vcPkgData.overrides) {
328
+ const version = vcpkgOverrideVersion(override);
329
+ if (typeof override?.name === "string" && version) {
330
+ overrides.set(override.name.toLowerCase(), version);
331
+ }
332
+ }
333
+ }
334
+ if (vcPkgData.name) {
335
+ const parentPurl = build({
336
+ type: pkgType,
337
+ namespace: null,
338
+ name: vcPkgData.name,
339
+ version: vcPkgData.version || vcPkgData["version-string"] || null,
340
+ });
341
+ const parentComponent = {
342
+ name: vcPkgData.name,
343
+ version: vcPkgData.version || vcPkgData["version-string"] || "",
344
+ description: vcPkgData.description,
345
+ license: vcPkgData.license,
346
+ purl: parentPurl,
347
+ type: "application",
348
+ "bom-ref": decodeURIComponent(parentPurl),
349
+ };
350
+ if (vcPkgData.homepage) {
351
+ parentComponent.homepage = { url: vcPkgData.homepage };
352
+ }
353
+ // The builtin baseline is the vcpkg commit that pins every port version,
354
+ // so it identifies the version set the manifest resolves against.
355
+ if (typeof vcPkgData["builtin-baseline"] === "string") {
356
+ parentComponent.properties = [
357
+ {
358
+ name: "cdx:vcpkg:baseline",
359
+ value: vcPkgData["builtin-baseline"],
360
+ },
361
+ ];
362
+ }
363
+ result.parentComponent = parentComponent;
364
+ }
365
+ if (!Array.isArray(vcPkgData.dependencies)) {
366
+ return result;
367
+ }
368
+ const added = new Set();
369
+ for (const avcdep of vcPkgData.dependencies) {
370
+ let avcpkgName;
371
+ let scope;
372
+ let vcpkgFeatures;
373
+ let minimumVersion;
374
+ if (typeof avcdep === "string" || avcdep instanceof String) {
375
+ avcpkgName = `${avcdep}`;
376
+ } else if (avcdep && typeof avcdep === "object" && avcdep.name) {
377
+ avcpkgName = avcdep.name;
378
+ if (avcdep.host) {
379
+ scope = "optional";
380
+ }
381
+ vcpkgFeatures = Array.isArray(avcdep.features)
382
+ ? avcdep.features
383
+ .map((feature) =>
384
+ typeof feature === "string" ? feature : feature?.name,
385
+ )
386
+ .filter(Boolean)
387
+ .join(",")
388
+ : undefined;
389
+ if (typeof avcdep["version>="] === "string") {
390
+ minimumVersion = avcdep["version>="];
391
+ }
392
+ }
393
+ // Is this a dependency we haven't seen before including the all lower and upper case version?
394
+ if (!avcpkgName || isListed(avcpkgName) || added.has(avcpkgName)) {
395
+ continue;
396
+ }
397
+ added.add(avcpkgName);
398
+ // the version vcpkg installed, when the build tree has the port, else the
399
+ // version the manifest's overrides pin
400
+ const installed = installedPorts.get(avcpkgName.toLowerCase());
401
+ const overridden = overrides.get(avcpkgName.toLowerCase());
402
+ const version = installed?.version || overridden?.version || "";
403
+ const pkgPurl = build({
404
+ type: pkgType,
405
+ namespace: null,
406
+ name: avcpkgName,
407
+ version: version || null,
408
+ });
409
+ result.pkgList.push({
410
+ group: "",
411
+ name: avcpkgName,
412
+ type: pkgType,
413
+ version,
414
+ purl: pkgPurl,
415
+ scope,
416
+ "bom-ref": decodeURIComponent(pkgPurl),
417
+ properties: [
418
+ { name: "cdx:vcpkg:declared", value: "true" },
419
+ ...(vcpkgFeatures
420
+ ? [{ name: "cdx:vcpkg:features", value: vcpkgFeatures }]
421
+ : []),
422
+ ...(installed
423
+ ? [{ name: "cdx:vcpkg:triplet", value: installed.triplet }]
424
+ : []),
425
+ ...(!installed && overridden
426
+ ? [{ name: "cdx:vcpkg:versionSource", value: "override" }]
427
+ : []),
428
+ ...((installed ? installed.portVersion : overridden?.portVersion)
429
+ ? [
430
+ {
431
+ name: "cdx:vcpkg:portVersion",
432
+ value: installed
433
+ ? installed.portVersion
434
+ : overridden.portVersion,
435
+ },
436
+ ]
437
+ : []),
438
+ ...(minimumVersion
439
+ ? [{ name: "cdx:vcpkg:minimumVersion", value: minimumVersion }]
440
+ : []),
441
+ ],
442
+ evidence: {
443
+ identity: {
444
+ field: "purl",
445
+ confidence: 0.5,
446
+ methods: [
447
+ {
448
+ technique: "source-code-analysis",
449
+ confidence: 0.5,
450
+ value: `Filename ${manifestFile}`,
451
+ },
452
+ ],
453
+ },
454
+ },
455
+ });
456
+ }
457
+ return result;
458
+ }
459
+
221
460
  /**
222
461
  * Method to find c/c++ modules by collecting usages with atom
223
462
  *
@@ -263,111 +502,24 @@ export function getCppModules(
263
502
  const vcpkgPorts = attributeInclude.vcpkgPorts();
264
503
  // Let's look for any vcpkg.json file to tell us about the directory we're scanning
265
504
  // users can use this file to give us a clue even if they do not use vcpkg library manager
266
- if (safeExistsSync(join(src, "vcpkg.json"))) {
267
- const vcPkgData = JSON.parse(
268
- readFileSync(join(src, "vcpkg.json"), { encoding: "utf-8" }),
269
- );
270
- if (vcPkgData && Object.keys(vcPkgData).length && vcPkgData.name) {
271
- const parentPurl = build({
272
- type: pkgType,
273
- namespace: "" || null,
274
- name: vcPkgData.name,
275
- version: vcPkgData.version || "" || null,
276
- });
277
- parentComponent = {
278
- name: vcPkgData.name,
279
- version: vcPkgData.version || "",
280
- description: vcPkgData.description,
281
- license: vcPkgData.license,
282
- purl: parentPurl,
283
- type: "application",
284
- "bom-ref": decodeURIComponent(parentPurl),
285
- };
286
- if (vcPkgData.homepage) {
287
- parentComponent.homepage = { url: vcPkgData.homepage };
288
- }
289
- // The builtin baseline is the vcpkg commit that pins every port version,
290
- // so it identifies the version set the manifest resolves against.
291
- if (typeof vcPkgData["builtin-baseline"] === "string") {
292
- parentComponent.properties = [
293
- {
294
- name: "cdx:vcpkg:baseline",
295
- value: vcPkgData["builtin-baseline"],
296
- },
297
- ];
298
- }
299
- // Are there any dependencies declared in vcpkg.json
300
- if (vcPkgData.dependencies && Array.isArray(vcPkgData.dependencies)) {
301
- for (const avcdep of vcPkgData.dependencies) {
302
- let avcpkgName;
303
- let scope;
304
- let vcpkgFeatures;
305
- if (typeof avcdep === "string" || avcdep instanceof String) {
306
- avcpkgName = avcdep;
307
- } else if (Object.keys(avcdep).length && avcdep.name) {
308
- avcpkgName = avcdep.name;
309
- if (avcdep.host) {
310
- scope = "optional";
311
- }
312
- vcpkgFeatures = Array.isArray(avcdep.features)
313
- ? avcdep.features.join(",")
314
- : undefined;
315
- }
316
- // Is this a dependency we haven't seen before including the all lower and upper case version?
317
- if (
318
- avcpkgName &&
319
- !epkgMap[epkgKey("", avcpkgName)] &&
320
- !epkgMap[epkgKey("", avcpkgName.toLowerCase())] &&
321
- !epkgMap[epkgKey("", avcpkgName.toUpperCase())]
322
- ) {
323
- // the version vcpkg installed, when the build tree has the port
324
- const installed = vcpkgPorts.get(avcpkgName.toLowerCase());
325
- const pkgPurl = build({
326
- type: pkgType,
327
- namespace: "" || null,
328
- name: avcpkgName,
329
- version: installed?.version || null,
330
- });
331
- const apkg = {
332
- group: "",
333
- name: avcpkgName,
334
- type: pkgType,
335
- version: installed?.version || "",
336
- purl: pkgPurl,
337
- scope,
338
- "bom-ref": decodeURIComponent(pkgPurl),
339
- properties: [
340
- { name: "cdx:vcpkg:declared", value: "true" },
341
- ...(vcpkgFeatures
342
- ? [{ name: "cdx:vcpkg:features", value: vcpkgFeatures }]
343
- : []),
344
- ...(installed
345
- ? [{ name: "cdx:vcpkg:triplet", value: installed.triplet }]
346
- : []),
347
- ],
348
- evidence: {
349
- identity: {
350
- field: "purl",
351
- confidence: 0.5,
352
- methods: [
353
- {
354
- technique: "source-code-analysis",
355
- confidence: 0.5,
356
- value: `Filename ${join(src, "vcpkg.json")}`,
357
- },
358
- ],
359
- },
360
- },
361
- };
362
- if (!pkgAddedMap[avcpkgName]) {
363
- pkgList.push(apkg);
364
- dependsOn.add(apkg["bom-ref"]);
365
- pkgAddedMap[avcpkgName] = true;
366
- }
367
- }
368
- }
369
- }
370
- } // if
505
+ const manifest = parseVcpkgManifest(src, {
506
+ installedPorts: vcpkgPorts,
507
+ isListed: (name) =>
508
+ Boolean(
509
+ epkgMap[epkgKey("", name)] ||
510
+ epkgMap[epkgKey("", name.toLowerCase())] ||
511
+ epkgMap[epkgKey("", name.toUpperCase())],
512
+ ),
513
+ });
514
+ if (manifest.parentComponent) {
515
+ parentComponent = manifest.parentComponent;
516
+ }
517
+ for (const apkg of manifest.pkgList) {
518
+ if (!pkgAddedMap[apkg.name]) {
519
+ pkgList.push(apkg);
520
+ dependsOn.add(apkg["bom-ref"]);
521
+ pkgAddedMap[apkg.name] = true;
522
+ }
371
523
  }
372
524
  // xmake locks its resolved requirements in `xmake-requires.lock`; the
373
525
  // entries are additions to whatever other evidence collectors found.
@@ -464,7 +616,12 @@ export function getCppModules(
464
616
  name: target.port,
465
617
  version: target.version || null,
466
618
  }),
467
- properties: [{ name: "cdx:vcpkg:triplet", value: target.triplet }],
619
+ properties: [
620
+ { name: "cdx:vcpkg:triplet", value: target.triplet },
621
+ ...(target.portVersion
622
+ ? [{ name: "cdx:vcpkg:portVersion", value: target.portVersion }]
623
+ : []),
624
+ ],
468
625
  technique: "manifest-analysis",
469
626
  };
470
627
  } else {
@@ -70,7 +70,7 @@ export function parseCIncludeSlices(sliceData) {
70
70
  * line relative to the installed directory.
71
71
  *
72
72
  * @param {string[]} installedDirs `vcpkg_installed` directories
73
- * @returns {Map<string, {port: string, version: string, triplet: string}>}
73
+ * @returns {Map<string, {port: string, version: string, triplet: string, portVersion?: string}>}
74
74
  */
75
75
  export function readVcpkgInstalledIndex(installedDirs) {
76
76
  const index = new Map();
@@ -90,7 +90,9 @@ export function readVcpkgInstalledIndex(installedDirs) {
90
90
  continue;
91
91
  }
92
92
  const port = stem.slice(0, first);
93
- const version = stem.slice(first + 1, last);
93
+ // vcpkg spells a port version as `<version>#<port-version>`; the port
94
+ // version is a packaging revision of the same upstream release.
95
+ const [version, portVersion] = stem.slice(first + 1, last).split("#");
94
96
  const triplet = stem.slice(last + 1);
95
97
  let text = "";
96
98
  try {
@@ -103,7 +105,12 @@ export function readVcpkgInstalledIndex(installedDirs) {
103
105
  if (!rel || rel.endsWith("/")) {
104
106
  continue;
105
107
  }
106
- index.set(resolve(dir, rel), { port, version, triplet });
108
+ index.set(resolve(dir, rel), {
109
+ port,
110
+ version,
111
+ triplet,
112
+ ...(portVersion ? { portVersion } : {}),
113
+ });
107
114
  }
108
115
  }
109
116
  }
@@ -282,9 +289,13 @@ export function createIncludeAttributor(context) {
282
289
  attribute.vcpkgPorts = () => {
283
290
  const ports = new Map();
284
291
  if (installedDirs.length) {
285
- for (const { port, version, triplet } of vcpkg().values()) {
292
+ for (const { port, version, triplet, portVersion } of vcpkg().values()) {
286
293
  if (!ports.has(port)) {
287
- ports.set(port, { version, triplet });
294
+ ports.set(port, {
295
+ version,
296
+ triplet,
297
+ ...(portVersion ? { portVersion } : {}),
298
+ });
288
299
  }
289
300
  }
290
301
  }
@@ -77,6 +77,7 @@ import {
77
77
  findMavenCoordinatesBySha1,
78
78
  inferMavenCoordinatesFromPath,
79
79
  } from "../inventory/jvmLocalRepos.js";
80
+ import { mavenCentralFetchUrl } from "../inventory/mavenCentralMirror.js";
80
81
  import {
81
82
  applyPurl,
82
83
  genericPurl,
@@ -92,6 +93,7 @@ import {
92
93
  guessLicenseId,
93
94
  spdxLicenses,
94
95
  } from "../inventory/spdx.js";
96
+ import { parseSpdxExpression } from "../inventory/spdxExpression.js";
95
97
  import { extractLicenseText, extractRepoUrl } from "../parsers/htmlExtract.js";
96
98
  import { xml2js } from "../parsers/xml.js";
97
99
  import {
@@ -1050,7 +1052,8 @@ export async function getMvnMetadata(
1050
1052
  }
1051
1053
  const repos = {
1052
1054
  MAVEN_CENTRAL_URL: withTrailingSlash(
1053
- readEnvironmentVariable("MAVEN_CENTRAL_URL") ||
1055
+ mavenCentralFetchUrl() ||
1056
+ readEnvironmentVariable("MAVEN_CENTRAL_URL") ||
1054
1057
  "https://repo1.maven.org/maven2/",
1055
1058
  ),
1056
1059
  ANDROID_MAVEN_URL: withTrailingSlash(
@@ -1771,6 +1774,10 @@ export function parseBdistMetadata(mDataFile, rawMetadata = undefined) {
1771
1774
  }
1772
1775
  const lines = mData.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
1773
1776
  let isBody = false;
1777
+ // PEP 639 (metadata 2.4) `License-Expression` is a validated SPDX expression
1778
+ // that wins over the free-text `License` field whichever header comes first.
1779
+ let licenseExpression;
1780
+ let licenseName;
1774
1781
  for (const line of lines) {
1775
1782
  if (line.trim() === "") {
1776
1783
  isBody = true;
@@ -1797,17 +1804,11 @@ export function parseBdistMetadata(mDataFile, rawMetadata = undefined) {
1797
1804
  pkg.author = value;
1798
1805
  break;
1799
1806
  case "license-expression":
1800
- pkg.licenses.push({
1801
- expression: value,
1802
- });
1807
+ licenseExpression = value;
1803
1808
  break;
1804
1809
  case "license":
1805
- if (value !== "UNKNOWN" && pkg.licenses.length === 0) {
1806
- pkg.licenses.push({
1807
- license: {
1808
- name: value,
1809
- },
1810
- });
1810
+ if (value !== "UNKNOWN" && licenseName === undefined) {
1811
+ licenseName = value;
1811
1812
  }
1812
1813
  break;
1813
1814
  case "home-page":
@@ -1867,6 +1868,20 @@ export function parseBdistMetadata(mDataFile, rawMetadata = undefined) {
1867
1868
  break;
1868
1869
  }
1869
1870
  }
1871
+ if (licenseExpression && parseSpdxExpression(licenseExpression).valid) {
1872
+ // PEP 639 guarantees a valid SPDX expression here, including `LicenseRef-`
1873
+ // document references, which travel as an expression and never as an id.
1874
+ pkg.licenses = [{ expression: licenseExpression }];
1875
+ } else {
1876
+ if (licenseExpression && DEBUG_MODE) {
1877
+ console.log(
1878
+ `License-Expression "${licenseExpression}" of ${pkg.name} is not a valid SPDX expression, so the older license fields are used`,
1879
+ );
1880
+ }
1881
+ if (licenseName) {
1882
+ pkg.licenses = [{ license: { name: licenseName } }];
1883
+ }
1884
+ }
1870
1885
  if (mDataFile) {
1871
1886
  pkg.evidence = {
1872
1887
  identity: {