@cdxgen/cdxgen 13.2.0 → 13.4.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 (311) hide show
  1. package/README.md +33 -14
  2. package/bin/cdxgen.js +310 -138
  3. package/bin/evinse.js +22 -0
  4. package/bin/repl.js +86 -0
  5. package/bin/sign.js +119 -15
  6. package/bin/tracebom.js +69 -5
  7. package/bin/validate.js +107 -19
  8. package/bin/verify.js +172 -63
  9. package/data/helpers/init.gradle +46 -21
  10. package/data/remediations.json +85 -0
  11. package/data/rules/dependency-sources.yaml +67 -0
  12. package/data/rules/scala-evidence.yaml +115 -0
  13. package/lib/audit/index.js +38 -6
  14. package/lib/cli/bomAssembly.js +105 -15
  15. package/lib/cli/cliOptions.js +93 -1
  16. package/lib/cli/index.js +156 -58
  17. package/lib/cli/jsBom.js +99 -53
  18. package/lib/cli/jvmBom.js +708 -147
  19. package/lib/cli/managedBom.js +415 -224
  20. package/lib/cli/nativeBom.js +264 -44
  21. package/lib/core/activity.js +12 -0
  22. package/lib/core/env.js +22 -15
  23. package/lib/core/fs.js +176 -7
  24. package/lib/core/httpClient.js +159 -24
  25. package/lib/core/metadataProperties.js +97 -0
  26. package/lib/core/pluginRun.js +184 -0
  27. package/lib/core/processTree.js +100 -0
  28. package/lib/core/projectConfig.js +31 -4
  29. package/lib/core/runState.js +39 -0
  30. package/lib/core/toml.js +72 -0
  31. package/lib/ecosystems/asarutils.js +4 -2
  32. package/lib/ecosystems/bunutils.js +274 -108
  33. package/lib/ecosystems/cargoCache.js +231 -8
  34. package/lib/ecosystems/caxa.js +434 -2
  35. package/lib/ecosystems/cmakeResolver.js +76 -21
  36. package/lib/ecosystems/cppBuildContext.js +631 -0
  37. package/lib/ecosystems/cppEvidence.js +399 -51
  38. package/lib/ecosystems/cppIncludeResolver.js +294 -0
  39. package/lib/ecosystems/denoutils.js +93 -66
  40. package/lib/ecosystems/ecosystems.js +1983 -793
  41. package/lib/ecosystems/golem.js +28 -12
  42. package/lib/ecosystems/gradleutils.js +356 -37
  43. package/lib/ecosystems/jsEvidence.js +104 -44
  44. package/lib/ecosystems/kosi.js +31 -7
  45. package/lib/ecosystems/npmutils.js +174 -9
  46. package/lib/ecosystems/parsers-dotnet.js +616 -159
  47. package/lib/ecosystems/parsers-elm.js +46 -28
  48. package/lib/ecosystems/parsers-gleam.js +3 -4
  49. package/lib/ecosystems/parsers-go.js +371 -70
  50. package/lib/ecosystems/parsers-js.js +263 -76
  51. package/lib/ecosystems/parsers-julia.js +2 -3
  52. package/lib/ecosystems/parsers-jvm.js +204 -12
  53. package/lib/ecosystems/parsers-misc.js +929 -216
  54. package/lib/ecosystems/parsers-python.js +167 -24
  55. package/lib/ecosystems/parsers-rust.js +28 -22
  56. package/lib/ecosystems/parsers-terraform.js +1573 -19
  57. package/lib/ecosystems/pythonutils.js +102 -6
  58. package/lib/ecosystems/remote/huggingface.js +3 -0
  59. package/lib/ecosystems/rubyutils.js +1492 -182
  60. package/lib/ecosystems/rusi.js +54 -10
  61. package/lib/ecosystems/sbtutils.js +243 -187
  62. package/lib/ecosystems/scalacliutils.js +342 -0
  63. package/lib/ecosystems/scalasem.js +2618 -0
  64. package/lib/ecosystems/terraformRegistry.js +288 -0
  65. package/lib/ecosystems/utils.js +2 -0
  66. package/lib/ecosystems/vendoredCode.js +156 -0
  67. package/lib/evinser/evinser.js +1008 -191
  68. package/lib/evinser/swiftsem.js +1914 -302
  69. package/lib/helpers/agenticToolutils.js +2 -2
  70. package/lib/helpers/bomSigner.js +1101 -222
  71. package/lib/inventory/aiCollector.js +4 -1
  72. package/lib/inventory/analyzer.js +110 -127
  73. package/lib/inventory/atomUtils.js +703 -43
  74. package/lib/inventory/auditCategories.js +7 -0
  75. package/lib/inventory/buildToolRateLimit.js +144 -0
  76. package/lib/inventory/cacheCommands.js +2 -1
  77. package/lib/inventory/cdxrs.js +103 -12
  78. package/lib/inventory/cmakeBuildDirs.js +254 -0
  79. package/lib/inventory/deps.js +501 -124
  80. package/lib/inventory/depsUtils.js +183 -11
  81. package/lib/inventory/display.js +4 -5
  82. package/lib/inventory/dosai.js +798 -57
  83. package/lib/inventory/dosaiParsers.js +310 -15
  84. package/lib/inventory/dynamic.js +90 -6
  85. package/lib/inventory/evidenceUtils.js +166 -113
  86. package/lib/inventory/fetchBatch.js +690 -55
  87. package/lib/inventory/fetchDiskCache.js +597 -0
  88. package/lib/inventory/forgeEnricher.js +14 -5
  89. package/lib/inventory/jvmLocalRepos.js +917 -0
  90. package/lib/inventory/mcpConfigParser.js +2 -3
  91. package/lib/inventory/parentSelection.js +146 -0
  92. package/lib/inventory/protobom.js +8 -1
  93. package/lib/inventory/purl.js +152 -3
  94. package/lib/inventory/rustFormulationParser.js +3 -4
  95. package/lib/inventory/sbomDocument.js +2 -70
  96. package/lib/inventory/scalaCoords.js +153 -0
  97. package/lib/inventory/source.js +45 -27
  98. package/lib/inventory/spdx.js +101 -0
  99. package/lib/inventory/traceRunner.js +295 -25
  100. package/lib/inventory/tracedCrypto.js +294 -0
  101. package/lib/managers/binary.js +115 -24
  102. package/lib/managers/docker.js +467 -46
  103. package/lib/managers/oci.js +63 -3
  104. package/lib/parsers/cmakeCache.js +69 -0
  105. package/lib/parsers/cmakeLists.js +466 -0
  106. package/lib/parsers/cmakePresets.js +400 -0
  107. package/lib/parsers/compileCommands.js +557 -0
  108. package/lib/parsers/largeJson.js +454 -0
  109. package/lib/parsers/npmrc.js +19 -1
  110. package/lib/parsers/strictJson.js +322 -0
  111. package/lib/parsers/terraformConfig.js +1073 -0
  112. package/lib/parsers/terraformSource.js +652 -0
  113. package/lib/server/server.js +23 -0
  114. package/lib/stages/postgen/introspection/markers.js +1 -1
  115. package/lib/stages/postgen/postgen.js +83 -16
  116. package/lib/stages/postgen/specVersionCompat.js +32 -0
  117. package/lib/validator/bomValidator.js +6 -16
  118. package/lib/validator/index.js +64 -11
  119. package/lib/validator/reporters/console.js +3 -0
  120. package/lib/validator/reporters/json.js +2 -0
  121. package/lib/validator/reporters/sarif.js +7 -0
  122. package/package.json +23 -23
  123. package/types/lib/audit/index.d.ts.map +1 -1
  124. package/types/lib/cli/bomAssembly.d.ts +2 -1
  125. package/types/lib/cli/bomAssembly.d.ts.map +1 -1
  126. package/types/lib/cli/cliOptions.d.ts +33 -1
  127. package/types/lib/cli/cliOptions.d.ts.map +1 -1
  128. package/types/lib/cli/index.d.ts.map +1 -1
  129. package/types/lib/cli/jsBom.d.ts.map +1 -1
  130. package/types/lib/cli/jvmBom.d.ts +24 -1
  131. package/types/lib/cli/jvmBom.d.ts.map +1 -1
  132. package/types/lib/cli/managedBom.d.ts +7 -4
  133. package/types/lib/cli/managedBom.d.ts.map +1 -1
  134. package/types/lib/cli/nativeBom.d.ts +3 -1
  135. package/types/lib/cli/nativeBom.d.ts.map +1 -1
  136. package/types/lib/core/activity.d.ts.map +1 -1
  137. package/types/lib/core/env.d.ts +9 -0
  138. package/types/lib/core/env.d.ts.map +1 -1
  139. package/types/lib/core/fs.d.ts +55 -0
  140. package/types/lib/core/fs.d.ts.map +1 -1
  141. package/types/lib/core/httpClient.d.ts +13 -8
  142. package/types/lib/core/httpClient.d.ts.map +1 -1
  143. package/types/lib/core/metadataProperties.d.ts +39 -0
  144. package/types/lib/core/metadataProperties.d.ts.map +1 -0
  145. package/types/lib/core/pluginRun.d.ts +97 -0
  146. package/types/lib/core/pluginRun.d.ts.map +1 -0
  147. package/types/lib/core/processTree.d.ts +36 -0
  148. package/types/lib/core/processTree.d.ts.map +1 -0
  149. package/types/lib/core/projectConfig.d.ts +6 -2
  150. package/types/lib/core/projectConfig.d.ts.map +1 -1
  151. package/types/lib/core/runState.d.ts +29 -0
  152. package/types/lib/core/runState.d.ts.map +1 -0
  153. package/types/lib/core/toml.d.ts +20 -0
  154. package/types/lib/core/toml.d.ts.map +1 -0
  155. package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
  156. package/types/lib/ecosystems/bunutils.d.ts +12 -5
  157. package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
  158. package/types/lib/ecosystems/cargoCache.d.ts +12 -0
  159. package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
  160. package/types/lib/ecosystems/caxa.d.ts +14 -0
  161. package/types/lib/ecosystems/caxa.d.ts.map +1 -1
  162. package/types/lib/ecosystems/cmakeResolver.d.ts +28 -3
  163. package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
  164. package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
  165. package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
  166. package/types/lib/ecosystems/cppEvidence.d.ts +17 -1
  167. package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
  168. package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
  169. package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
  170. package/types/lib/ecosystems/denoutils.d.ts +9 -3
  171. package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
  172. package/types/lib/ecosystems/ecosystems.d.ts +62 -13
  173. package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
  174. package/types/lib/ecosystems/golem.d.ts.map +1 -1
  175. package/types/lib/ecosystems/gradleutils.d.ts +70 -4
  176. package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
  177. package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
  178. package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
  179. package/types/lib/ecosystems/kosi.d.ts +13 -0
  180. package/types/lib/ecosystems/kosi.d.ts.map +1 -1
  181. package/types/lib/ecosystems/npmutils.d.ts +57 -0
  182. package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
  183. package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
  184. package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
  185. package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
  186. package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
  187. package/types/lib/ecosystems/parsers-go.d.ts +54 -2
  188. package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
  189. package/types/lib/ecosystems/parsers-js.d.ts +16 -5
  190. package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
  191. package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
  192. package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
  193. package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
  194. package/types/lib/ecosystems/parsers-misc.d.ts +70 -4
  195. package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
  196. package/types/lib/ecosystems/parsers-python.d.ts +53 -3
  197. package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
  198. package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
  199. package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
  200. package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
  201. package/types/lib/ecosystems/pythonutils.d.ts +27 -2
  202. package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
  203. package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
  204. package/types/lib/ecosystems/rubyutils.d.ts +164 -3
  205. package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
  206. package/types/lib/ecosystems/rusi.d.ts +13 -0
  207. package/types/lib/ecosystems/rusi.d.ts.map +1 -1
  208. package/types/lib/ecosystems/sbtutils.d.ts +88 -2
  209. package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
  210. package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
  211. package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
  212. package/types/lib/ecosystems/scalasem.d.ts +311 -0
  213. package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
  214. package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
  215. package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
  216. package/types/lib/ecosystems/utils.d.ts +2 -2
  217. package/types/lib/ecosystems/utils.d.ts.map +1 -1
  218. package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
  219. package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
  220. package/types/lib/evinser/evinser.d.ts +132 -15
  221. package/types/lib/evinser/evinser.d.ts.map +1 -1
  222. package/types/lib/evinser/swiftsem.d.ts +280 -15
  223. package/types/lib/evinser/swiftsem.d.ts.map +1 -1
  224. package/types/lib/helpers/bomSigner.d.ts +182 -13
  225. package/types/lib/helpers/bomSigner.d.ts.map +1 -1
  226. package/types/lib/inventory/aiCollector.d.ts.map +1 -1
  227. package/types/lib/inventory/analyzer.d.ts.map +1 -1
  228. package/types/lib/inventory/atomUtils.d.ts +219 -5
  229. package/types/lib/inventory/atomUtils.d.ts.map +1 -1
  230. package/types/lib/inventory/auditCategories.d.ts +3 -0
  231. package/types/lib/inventory/auditCategories.d.ts.map +1 -1
  232. package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
  233. package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
  234. package/types/lib/inventory/cacheCommands.d.ts +2 -1
  235. package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
  236. package/types/lib/inventory/cdxrs.d.ts +8 -3
  237. package/types/lib/inventory/cdxrs.d.ts.map +1 -1
  238. package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
  239. package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
  240. package/types/lib/inventory/deps.d.ts +59 -1
  241. package/types/lib/inventory/deps.d.ts.map +1 -1
  242. package/types/lib/inventory/depsUtils.d.ts +15 -0
  243. package/types/lib/inventory/depsUtils.d.ts.map +1 -1
  244. package/types/lib/inventory/display.d.ts.map +1 -1
  245. package/types/lib/inventory/dosai.d.ts +121 -9
  246. package/types/lib/inventory/dosai.d.ts.map +1 -1
  247. package/types/lib/inventory/dosaiParsers.d.ts +53 -7
  248. package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
  249. package/types/lib/inventory/dynamic.d.ts +10 -0
  250. package/types/lib/inventory/dynamic.d.ts.map +1 -1
  251. package/types/lib/inventory/evidenceUtils.d.ts +19 -4
  252. package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
  253. package/types/lib/inventory/fetchBatch.d.ts +83 -14
  254. package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
  255. package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
  256. package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
  257. package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
  258. package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
  259. package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
  260. package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
  261. package/types/lib/inventory/parentSelection.d.ts +34 -0
  262. package/types/lib/inventory/parentSelection.d.ts.map +1 -0
  263. package/types/lib/inventory/plugins.d.ts.map +1 -1
  264. package/types/lib/inventory/protobom.d.ts.map +1 -1
  265. package/types/lib/inventory/purl.d.ts +71 -2
  266. package/types/lib/inventory/purl.d.ts.map +1 -1
  267. package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
  268. package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
  269. package/types/lib/inventory/scalaCoords.d.ts +80 -0
  270. package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
  271. package/types/lib/inventory/source.d.ts.map +1 -1
  272. package/types/lib/inventory/spdx.d.ts +9 -0
  273. package/types/lib/inventory/spdx.d.ts.map +1 -1
  274. package/types/lib/inventory/traceRunner.d.ts +59 -1
  275. package/types/lib/inventory/traceRunner.d.ts.map +1 -1
  276. package/types/lib/inventory/tracedCrypto.d.ts +42 -0
  277. package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
  278. package/types/lib/managers/binary.d.ts +30 -1
  279. package/types/lib/managers/binary.d.ts.map +1 -1
  280. package/types/lib/managers/docker.d.ts +29 -0
  281. package/types/lib/managers/docker.d.ts.map +1 -1
  282. package/types/lib/managers/oci.d.ts +17 -1
  283. package/types/lib/managers/oci.d.ts.map +1 -1
  284. package/types/lib/parsers/cmakeCache.d.ts +16 -0
  285. package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
  286. package/types/lib/parsers/cmakeLists.d.ts +77 -0
  287. package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
  288. package/types/lib/parsers/cmakePresets.d.ts +78 -0
  289. package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
  290. package/types/lib/parsers/compileCommands.d.ts +115 -0
  291. package/types/lib/parsers/compileCommands.d.ts.map +1 -0
  292. package/types/lib/parsers/largeJson.d.ts +52 -0
  293. package/types/lib/parsers/largeJson.d.ts.map +1 -0
  294. package/types/lib/parsers/npmrc.d.ts +5 -0
  295. package/types/lib/parsers/npmrc.d.ts.map +1 -1
  296. package/types/lib/parsers/strictJson.d.ts +46 -0
  297. package/types/lib/parsers/strictJson.d.ts.map +1 -0
  298. package/types/lib/parsers/terraformConfig.d.ts +70 -0
  299. package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
  300. package/types/lib/parsers/terraformSource.d.ts +66 -0
  301. package/types/lib/parsers/terraformSource.d.ts.map +1 -0
  302. package/types/lib/server/server.d.ts.map +1 -1
  303. package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
  304. package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
  305. package/types/lib/validator/bomValidator.d.ts.map +1 -1
  306. package/types/lib/validator/index.d.ts +18 -1
  307. package/types/lib/validator/index.d.ts.map +1 -1
  308. package/types/lib/validator/reporters/console.d.ts.map +1 -1
  309. package/types/lib/validator/reporters/json.d.ts.map +1 -1
  310. package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
  311. package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
@@ -18,7 +18,7 @@ import { recordDegradation } from "../core/buildLedger.js";
18
18
  import { shouldFetchPackageMetadata } from "../core/env.js";
19
19
  import { safeExistsSync } from "../core/fs.js";
20
20
  import { analyzeSuspiciousJsFile } from "../inventory/analyzer.js";
21
- import { encodeForPurl, npmPurl } from "../inventory/purl.js";
21
+ import { npmPurl, parsePurlLoose } from "../inventory/purl.js";
22
22
  import Arborist from "../third-party/arborist/lib/index.js";
23
23
  import { getNpmMetadata } from "./ecosystems.js";
24
24
  import {
@@ -31,6 +31,7 @@ import {
31
31
  loadNpmrcConfig,
32
32
  normalizePnpmLockKey,
33
33
  parsePnpmGitLockKey,
34
+ resolveNpmLicense,
34
35
  setNpmDevelopmentProperty,
35
36
  setNpmOptionalProperty,
36
37
  setNpmPeerProperty,
@@ -135,6 +136,11 @@ const NPM_LIFECYCLE_JS_SOURCE_FILE_PATTERN = /\.[cm]?[jt]sx?$/i;
135
136
 
136
137
  const SHELL_ENV_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=.*/;
137
138
 
139
+ // The root component's name when neither package.json, --project-name, nor
140
+ // the directory names one (a tree rooted at a file-system root): a purl needs
141
+ // a name.
142
+ const UNNAMED_NPM_PROJECT = "unnamed-project";
143
+
138
144
  function splitLifecycleScriptCommands(scriptValue) {
139
145
  const commands = [];
140
146
  let current = "";
@@ -432,6 +438,9 @@ function analyzeNpmLifecycleScripts(pkgJsonFile, scripts) {
432
438
  * identity evidence and skip npm registry metadata fetching.
433
439
  * @param {boolean} [securityProps=false] When true, append npm security-relevant
434
440
  * properties such as lifecycle scripts, binaries, native addons, and deprecation notices.
441
+ * @param {Object} [options] Options
442
+ * @param {boolean} [options.fetchMetadata=true] When false, skip the npm
443
+ * registry round so that a caller can make one round for many manifests.
435
444
  * @returns {Promise<Array<object>>} Parsed component list, enriched with npm
436
445
  * registry metadata when available and not in simple mode.
437
446
  */
@@ -439,11 +448,15 @@ export async function parsePkgJson(
439
448
  pkgJsonFile,
440
449
  simple = false,
441
450
  securityProps = false,
451
+ options = {},
442
452
  ) {
443
453
  const pkgList = [];
444
454
  if (safeExistsSync(pkgJsonFile)) {
445
455
  try {
446
- const pkgData = JSON.parse(readFileSync(pkgJsonFile, "utf8"));
456
+ // npm and Node.js both accept a package.json that starts with a UTF-8 BOM
457
+ const pkgData = JSON.parse(
458
+ readFileSync(pkgJsonFile, "utf8").replace(/^\uFEFF/, ""),
459
+ );
447
460
  const pkgIdentifier = parsePackageJsonName(pkgData.name);
448
461
  let name = pkgIdentifier.fullName || pkgData.name;
449
462
  if (!name && !pkgJsonFile.includes("node_modules")) {
@@ -471,7 +484,7 @@ export async function parsePkgJson(
471
484
  purl: purl,
472
485
  "bom-ref": decodeURIComponent(purl),
473
486
  author: authorString,
474
- license: pkgData.license,
487
+ license: resolveNpmLicense(pkgData),
475
488
  };
476
489
  if (pkgData.homepage) {
477
490
  apkg.homepage = { url: pkgData.homepage };
@@ -662,7 +675,12 @@ export async function parsePkgJson(
662
675
  // continue regardless of error
663
676
  }
664
677
  }
665
- if (!simple && shouldFetchPackageMetadata() && pkgList?.length) {
678
+ if (
679
+ !simple &&
680
+ options.fetchMetadata !== false &&
681
+ shouldFetchPackageMetadata() &&
682
+ pkgList?.length
683
+ ) {
666
684
  if (DEBUG_MODE) {
667
685
  console.log(
668
686
  `About to fetch npm registry metadata for ${pkgList.length} packages in parsePkgJson`,
@@ -899,6 +917,9 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
899
917
 
900
918
  let pkg;
901
919
  let purlString;
920
+ // The license the installed package.json declares, for a package whose
921
+ // lockfile entry carries none (see the spoofing check below).
922
+ let installedManifestLicense;
902
923
  const author = nodePackage.author;
903
924
  const authorString =
904
925
  author instanceof Object
@@ -908,22 +929,36 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
908
929
  : author;
909
930
  if (node === rootNode) {
910
931
  const projectGroup = options.projectGroup;
932
+ // A package.json without a name still installs, so the root of a deep
933
+ // tree can lack one: name the root component after its directory, the
934
+ // way the scans without node_modules already do, rather than fail to
935
+ // build its purl (issue 4440). A tree rooted at a file-system root has
936
+ // no directory name either, and neither has the --project-name the CLI
937
+ // derives from it.
911
938
  const projectName =
912
- "project-name" in options ? options.projectName : node.packageName;
939
+ ("project-name" in options && options.projectName) ||
940
+ node.packageName ||
941
+ basename(node.path || "") ||
942
+ node.name ||
943
+ UNNAMED_NPM_PROJECT;
913
944
  const projectVersion = options.projectVersion || node.version;
914
945
  // A package.json need not declare a version, and npm installs without
915
946
  // one, so the root of a lockfile can reach here with none. A purl carries
916
947
  // no version rather than an empty one.
917
- purlString = Purl.parse(
918
- `pkg:npm/${projectGroup ? `${encodeURIComponent(projectGroup).replace(/%2F/g, "/")}/` : ""}${encodeURIComponent(projectName).replace(/%2F/g, "/")}${projectVersion ? `@${projectVersion}` : ""}`,
919
- )
920
- .toString()
921
- .replace(/%2F/g, "/");
948
+ purlString = (
949
+ projectGroup
950
+ ? build({
951
+ type: "npm",
952
+ namespace: projectGroup,
953
+ name: projectName,
954
+ version: projectVersion || null,
955
+ })
956
+ : npmPurl(projectName, projectVersion)
957
+ ).replace(/%2F/g, "/");
922
958
  pkg = {
923
959
  author: authorString,
924
960
  group: options.projectGroup || "",
925
- name:
926
- "project-name" in options ? options.projectName : node.packageName,
961
+ name: projectName,
927
962
  version: options.projectVersion || node.version,
928
963
  type: "application",
929
964
  purl: purlString,
@@ -959,13 +994,19 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
959
994
  });
960
995
  }
961
996
  } else {
997
+ // A package.json without a name installs as a file: or workspace
998
+ // dependency too, and arborist then has no packageName for it: such a
999
+ // package goes by the name its folder has in the tree, which is also
1000
+ // the name the edges into it carry (issue 4440).
1001
+ const packageName =
1002
+ node.packageName || node.name || basename(node.path || "");
962
1003
  let namespace = "";
963
- let name = node.packageName;
964
- if (node.packageName.startsWith("@")) {
965
- const slashIndex = node.packageName.indexOf("/");
1004
+ let name = packageName;
1005
+ if (packageName.startsWith("@")) {
1006
+ const slashIndex = packageName.indexOf("/");
966
1007
  if (slashIndex > 0) {
967
- namespace = node.packageName.substring(0, slashIndex);
968
- name = node.packageName.substring(slashIndex + 1);
1008
+ namespace = packageName.substring(0, slashIndex);
1009
+ name = packageName.substring(slashIndex + 1);
969
1010
  }
970
1011
  }
971
1012
  let qualifiers = null;
@@ -1078,9 +1119,17 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1078
1119
  });
1079
1120
  }
1080
1121
  if (node?.binPaths?.length) {
1122
+ // Absolute bin paths would leak the generating machine's home
1123
+ // directory into the SBOM and differ between checkouts, so they are
1124
+ // emitted relative to the project root the package was resolved from,
1125
+ // with forward slashes on every platform (issue 4467).
1081
1126
  pkg.properties.push({
1082
1127
  name: "cdx:npm:binPaths",
1083
- value: node.binPaths.join(", "),
1128
+ value: node.binPaths
1129
+ .map((binPath) =>
1130
+ relative(srcFilePath, binPath).split(_sep).join("/"),
1131
+ )
1132
+ .join(", "),
1084
1133
  });
1085
1134
  }
1086
1135
  if (nodePackage?.bin) {
@@ -1149,9 +1198,14 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1149
1198
  try {
1150
1199
  const onDiskPkg =
1151
1200
  diskPkg || JSON.parse(readFileSync(packageJsonPath, "utf8"));
1152
- if (!onDiskPkg.name || onDiskPkg.name !== node.packageName) {
1201
+ // A package that declares no name on disk and none in the lockfile
1202
+ // (a nameless file: dependency) disagrees with nothing.
1203
+ if (
1204
+ (onDiskPkg.name || node.packageName) &&
1205
+ onDiskPkg.name !== node.packageName
1206
+ ) {
1153
1207
  console.warn(
1154
- `\x1b[1;35mWARNING: Package name spoofing detected for ${node.packageName}! Lockfile says ${node.packageName}, but disk says ${onDiskPkg.name}.\x1b[0m`,
1208
+ `\x1b[1;35mWARNING: Package name spoofing detected for ${packageName}! Lockfile says ${node.packageName}, but disk says ${onDiskPkg.name}.\x1b[0m`,
1155
1209
  );
1156
1210
  if (onDiskPkg.name) {
1157
1211
  pkg.properties.push({
@@ -1160,9 +1214,12 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1160
1214
  });
1161
1215
  }
1162
1216
  }
1163
- if (!onDiskPkg.version || onDiskPkg.version !== node.version) {
1217
+ if (
1218
+ (onDiskPkg.version || node.version) &&
1219
+ onDiskPkg.version !== node.version
1220
+ ) {
1164
1221
  console.warn(
1165
- `\x1b[1;35mWARNING: Package version spoofing detected for ${node.packageName}! Lockfile says ${node.version}, but disk says ${onDiskPkg.version}.\x1b[0m`,
1222
+ `\x1b[1;35mWARNING: Package version spoofing detected for ${packageName}! Lockfile says ${node.version}, but disk says ${onDiskPkg.version}.\x1b[0m`,
1166
1223
  );
1167
1224
  if (onDiskPkg.version) {
1168
1225
  pkg.properties.push({
@@ -1171,6 +1228,17 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1171
1228
  });
1172
1229
  }
1173
1230
  }
1231
+ // npm records only the modern `license` field in the lockfile, so a
1232
+ // package that declares the legacy `licenses` array (issue 4466) has
1233
+ // no license in the tree loaded from it. The installed manifest is
1234
+ // then the only local declaration, and only when it is the package
1235
+ // the lockfile describes.
1236
+ if (
1237
+ onDiskPkg.name === node.packageName &&
1238
+ onDiskPkg.version === node.version
1239
+ ) {
1240
+ installedManifestLicense = resolveNpmLicense(onDiskPkg);
1241
+ }
1174
1242
  } catch (_err) {
1175
1243
  // ignore
1176
1244
  }
@@ -1226,9 +1294,11 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1226
1294
  pkg.description = nodePackage.description;
1227
1295
  }
1228
1296
  }
1229
- if (nodePackage?.license) {
1297
+ const npmLicense =
1298
+ resolveNpmLicense(nodePackage) || installedManifestLicense;
1299
+ if (npmLicense) {
1230
1300
  // License will be overridden if shouldFetchLicense() is enabled
1231
- pkg.license = nodePackage.license;
1301
+ pkg.license = npmLicense;
1232
1302
  }
1233
1303
  const deprecatedMessage = nodePackage?.deprecated;
1234
1304
  if (deprecatedMessage) {
@@ -1602,7 +1672,7 @@ export async function parsePkgLock(pkgLockFile, options = {}) {
1602
1672
  `About to fetch npm registry metadata for ${pkgList.length} packages in parsePkgLock`,
1603
1673
  );
1604
1674
  }
1605
- pkgList = await getNpmMetadata(pkgList);
1675
+ pkgList = await getNpmMetadata(pkgList, undefined, { npmrcConfig });
1606
1676
  return { pkgList, dependenciesList };
1607
1677
  }
1608
1678
  return {
@@ -1778,17 +1848,15 @@ function _resolveYarnDependencyBomRef(
1778
1848
  packageNameToUse,
1779
1849
  );
1780
1850
  if (matchingWorkspace) {
1781
- const versionMatch = matchingWorkspace.match(/@([^@]+)$/);
1782
- if (versionMatch) {
1783
- resolvedVersion = versionMatch[1];
1784
- }
1851
+ // The workspace reference may be an escaped purl, so its version is read
1852
+ // through the purl parser rather than cut out of the string.
1853
+ resolvedVersion =
1854
+ parsePurlLoose(matchingWorkspace)?.version || resolvedVersion;
1785
1855
  }
1786
1856
  }
1787
- let purlPart = `pkg:npm/${encodeURIComponent(packageNameToUse).replace(/%2F/g, "/")}`;
1788
- if (resolvedVersion?.length) {
1789
- purlPart = `${purlPart}@${resolvedVersion}`;
1790
- }
1791
- return decodeURIComponent(Purl.parse(purlPart).toString());
1857
+ return decodeURIComponent(
1858
+ npmPurl(packageNameToUse, resolvedVersion?.length ? resolvedVersion : null),
1859
+ );
1792
1860
  }
1793
1861
 
1794
1862
  function _collectYarnRootDependencyRefs(
@@ -1919,7 +1987,7 @@ function _markYarnDependencyScopeClosures(
1919
1987
  * @param {Object} parentComponent parent component
1920
1988
  * @param {Array[String]} workspacePackages Workspace packages
1921
1989
  * @param {Object} workspaceSrcFiles Workspace package.json files
1922
- * @param {Object} _workspaceDirectDeps Direct dependencies of each workspace
1990
+ * @param {Object} workspaceDirectDeps Declared dependencies (name to range) of each workspace package
1923
1991
  * @param {Object} depsWorkspaceRefs Workspace references for each dependency
1924
1992
  */
1925
1993
  export async function parseYarnLock(
@@ -1927,12 +1995,14 @@ export async function parseYarnLock(
1927
1995
  parentComponent = null,
1928
1996
  workspacePackages = [],
1929
1997
  workspaceSrcFiles = {},
1930
- _workspaceDirectDeps = {},
1998
+ workspaceDirectDeps = {},
1931
1999
  depsWorkspaceRefs = {},
1932
2000
  ) {
1933
2001
  let pkgList = [];
1934
2002
  const dependenciesList = [];
1935
2003
  const depKeys = {};
2004
+ // The lock keys and the resolved version required to solve the dependency tree
2005
+ let identMap = {};
1936
2006
  let yarnRootRefs = _createYarnRootDependencyRefs();
1937
2007
  const yarnOptionalDependencyRefs = new Set();
1938
2008
  const npmrcConfig = loadNpmrcConfig(dirname(yarnLockFile));
@@ -1951,8 +2021,7 @@ export async function parseYarnLock(
1951
2021
  const pkgAddedMap = {};
1952
2022
  // Map to track workspace package PURL replacements from 0.0.0-use.local to actual versions
1953
2023
  const workspacePurlMap = {};
1954
- // This would have the keys and the resolved version required to solve the dependency tree
1955
- const identMap = yarnLockToIdentMap(lockData);
2024
+ identMap = yarnLockToIdentMap(lockData);
1956
2025
  yarnRootRefs = _collectYarnRootDependencyRefs(
1957
2026
  yarnLockFile,
1958
2027
  identMap,
@@ -2020,8 +2089,8 @@ export async function parseYarnLock(
2020
2089
  const actualWorkspacePurl = workspacePackages.find(
2021
2090
  (wp) =>
2022
2091
  wp.startsWith(expectedPurlStart) ||
2023
- wp.includes(
2024
- `pkg:npm/${encodeForPurl(group ? `${group}/${name}` : name)}@`,
2092
+ wp.startsWith(
2093
+ `${npmPurl(group ? `${group}/${name}` : name)}@`,
2025
2094
  ),
2026
2095
  );
2027
2096
  // Also check if this package already exists in pkgList with a real version
@@ -2286,23 +2355,21 @@ export async function parseYarnLock(
2286
2355
  if (DEBUG_MODE) {
2287
2356
  console.log(`Processing workspace package: "${workspacePkg}"`);
2288
2357
  }
2289
- let purlObj;
2290
- // For workspace packages that already have versions in the PURL format
2291
- if (
2292
- workspacePkg.includes("@") &&
2293
- workspacePkg.lastIndexOf("@") > workspacePkg.indexOf("/")
2294
- ) {
2295
- // This is a scoped package with version like "npm:@swc/helpers@0.4.14"
2296
- const lastAtIndex = workspacePkg.lastIndexOf("@");
2297
- const purlWithoutVersion = workspacePkg.substring(0, lastAtIndex);
2298
- const version = workspacePkg.substring(lastAtIndex + 1);
2299
- // Parse the PURL without version first, then set the version.
2300
- purlObj = Purl.parse(purlWithoutVersion);
2301
- purlObj.version = version;
2302
- } else {
2303
- // Parse the PURL as-is (no version or simple package)
2304
- purlObj = Purl.parse(workspacePkg);
2358
+ // Workspace references arrive both escaped (built by cdx-purl) and in
2359
+ // the decoded bom-ref form, so they are split structurally and
2360
+ // rebuilt. Copying the version text across forms is what turned
2361
+ // `3.0.0+ws` into `3.0.0%252Bws`.
2362
+ const workspaceParts = parsePurlLoose(workspacePkg);
2363
+ if (!workspaceParts) {
2364
+ continue;
2305
2365
  }
2366
+ const purlObj = new Purl({
2367
+ type: "npm",
2368
+ namespace: workspaceParts.namespace,
2369
+ name: workspaceParts.name,
2370
+ version: workspaceParts.version,
2371
+ subpath: workspaceParts.subpath,
2372
+ });
2306
2373
 
2307
2374
  // Create the workspace component using the properly parsed PURL
2308
2375
  const workspaceComponent = {
@@ -2405,6 +2472,41 @@ export async function parseYarnLock(
2405
2472
  }
2406
2473
  }
2407
2474
  }
2475
+ // A yarn v1 lock file has no entries for workspace packages, so a member
2476
+ // would otherwise have no dependency entry and nothing would reach it.
2477
+ // Each member gets one built from the dependencies its own package.json
2478
+ // declares; the root's entry, built from every dependency entry, then
2479
+ // includes the member too. A lock file that already recorded the member
2480
+ // (yarn berry) keeps the entry it produced.
2481
+ const recordedRefs = new Set(dependenciesList.map((d) => d.ref));
2482
+ for (const workspacePkg of workspacePackages) {
2483
+ const workspaceRef = decodeURIComponent(workspacePkg);
2484
+ if (
2485
+ recordedRefs.has(workspaceRef) ||
2486
+ !pkgList.some((pkg) => pkg["bom-ref"] === workspaceRef)
2487
+ ) {
2488
+ continue;
2489
+ }
2490
+ const dependsOn = new Set();
2491
+ for (const [depName, depRange] of Object.entries(
2492
+ workspaceDirectDeps?.[workspacePkg] || {},
2493
+ )) {
2494
+ const depRef = _resolveYarnDependencyBomRef(
2495
+ depName,
2496
+ String(depRange),
2497
+ identMap,
2498
+ workspacePackages,
2499
+ );
2500
+ if (depRef && depRef !== workspaceRef) {
2501
+ dependsOn.add(depRef);
2502
+ }
2503
+ }
2504
+ dependenciesList.push({
2505
+ ref: workspaceRef,
2506
+ dependsOn: [...dependsOn].sort(),
2507
+ });
2508
+ recordedRefs.add(workspaceRef);
2509
+ }
2408
2510
  }
2409
2511
 
2410
2512
  _markYarnDependencyScopeClosures(
@@ -2420,7 +2522,7 @@ export async function parseYarnLock(
2420
2522
  `About to fetch npm registry metadata for ${pkgList.length} packages in parseYarnLock`,
2421
2523
  );
2422
2524
  }
2423
- pkgList = await getNpmMetadata(pkgList);
2525
+ pkgList = await getNpmMetadata(pkgList, undefined, { npmrcConfig });
2424
2526
  return {
2425
2527
  pkgList,
2426
2528
  dependenciesList,
@@ -2439,6 +2541,7 @@ export async function parseYarnLock(
2439
2541
  */
2440
2542
  export async function parseNodeShrinkwrap(swFile) {
2441
2543
  const pkgList = [];
2544
+ const npmrcConfig = loadNpmrcConfig(dirname(swFile));
2442
2545
  if (safeExistsSync(swFile)) {
2443
2546
  const lockData = JSON.parse(readFileSync(swFile, "utf8"));
2444
2547
  const pkgKeys = Object.keys(lockData);
@@ -2497,7 +2600,7 @@ export async function parseNodeShrinkwrap(swFile) {
2497
2600
  `About to fetch npm registry metadata for ${pkgList.length} packages in parseNodeShrinkwrap`,
2498
2601
  );
2499
2602
  }
2500
- return await getNpmMetadata(pkgList);
2603
+ return await getNpmMetadata(pkgList, undefined, { npmrcConfig });
2501
2604
  }
2502
2605
  return pkgList;
2503
2606
  }
@@ -2646,7 +2749,10 @@ export function stripPnpmPeerSuffix(version) {
2646
2749
  * {@link stripPnpmPeerSuffix}.
2647
2750
  *
2648
2751
  * @param {string|object} depPkg Dependency version string or package object.
2649
- * @param {string} relativePath Base path for resolving link/file references.
2752
+ * @param {string} relativePath Base path for resolving link/file references:
2753
+ * the directory of the importer that declares the dependency. Without it the
2754
+ * path is tried against the working directory, which only works when cdxgen
2755
+ * runs from the lock file's directory.
2650
2756
  * @returns {Promise<string|undefined>} The resolved published version.
2651
2757
  */
2652
2758
  export async function getVersionNumPnpm(depPkg, relativePath) {
@@ -2662,13 +2768,13 @@ export async function getVersionNumPnpm(depPkg, relativePath) {
2662
2768
  : join(version.replaceAll("../", "packages/"), "package.json");
2663
2769
  if (safeExistsSync(relativePkgJson)) {
2664
2770
  const importedComponentObj = await parsePkgJson(relativePkgJson, true);
2665
- version = importedComponentObj[0].version;
2771
+ version = importedComponentObj[0]?.version;
2666
2772
  } else if (safeExistsSync(join(version, "package.json"))) {
2667
2773
  const importedComponentObj = await parsePkgJson(
2668
2774
  join(version, "package.json"),
2669
2775
  true,
2670
2776
  );
2671
- version = importedComponentObj[0].version;
2777
+ version = importedComponentObj[0]?.version;
2672
2778
  } else if (
2673
2779
  safeExistsSync(join(version.replaceAll("../", ""), "package.json"))
2674
2780
  ) {
@@ -2676,7 +2782,7 @@ export async function getVersionNumPnpm(depPkg, relativePath) {
2676
2782
  join(version.replaceAll("../", ""), "package.json"),
2677
2783
  true,
2678
2784
  );
2679
- version = importedComponentObj[0].version;
2785
+ version = importedComponentObj[0]?.version;
2680
2786
  }
2681
2787
  } else {
2682
2788
  // version: 3.0.1(ajv@8.14.0) or, under lockfile 5, 3.0.1_ajv@8.14.0
@@ -2685,6 +2791,25 @@ export async function getVersionNumPnpm(depPkg, relativePath) {
2685
2791
  return version;
2686
2792
  }
2687
2793
 
2794
+ /**
2795
+ * Split the package name of a pnpm `file:` lock key into scope and name.
2796
+ *
2797
+ * @param {string} fullName Package name as written before `@file:`, eg `@scope/pkg`
2798
+ * @returns {{group: string, name: string}} Scope (with its `@`) and bare name
2799
+ */
2800
+ function splitPnpmFilePackageName(fullName) {
2801
+ if (fullName.startsWith("@")) {
2802
+ const slash = fullName.indexOf("/");
2803
+ if (slash > 0) {
2804
+ return {
2805
+ group: fullName.slice(0, slash),
2806
+ name: fullName.slice(slash + 1),
2807
+ };
2808
+ }
2809
+ }
2810
+ return { group: "", name: fullName };
2811
+ }
2812
+
2688
2813
  /**
2689
2814
  * Resolve a pnpm dependency to its PURL string.
2690
2815
  *
@@ -2723,11 +2848,16 @@ export async function getPnpmDepPurl(
2723
2848
  }
2724
2849
  gitPkgRefs[versionObj.version] = { group, name, version };
2725
2850
  } else {
2851
+ const rawSpec =
2852
+ typeof versionObj?.version === "string" ? versionObj.version : "";
2726
2853
  version = await getVersionNumPnpm(depPkg, relativePath);
2727
2854
  const gitEntry =
2728
2855
  gitPkgRefs[packageName] ||
2729
2856
  gitPkgRefs[version] ||
2730
- gitPkgRefs[normalizePnpmLockKey(`${packageName}@${version}`)];
2857
+ gitPkgRefs[normalizePnpmLockKey(`${packageName}@${version}`)] ||
2858
+ (rawSpec.startsWith("file:")
2859
+ ? gitPkgRefs[normalizePnpmLockKey(`${packageName}@${rawSpec}`)]
2860
+ : undefined);
2731
2861
  if (gitEntry) {
2732
2862
  group = gitEntry.group;
2733
2863
  name = gitEntry.name;
@@ -2980,7 +3110,8 @@ export async function pnpmMetadata(pkgList, lockFilePath) {
2980
3110
  // Silently ignore parsing errors for individual packages
2981
3111
  if (DEBUG_MODE) {
2982
3112
  console.log(
2983
- `Failed to parse package.json at ${packageJsonPath}:`,
3113
+ "Failed to parse package.json at %s:",
3114
+ packageJsonPath,
2984
3115
  error.message,
2985
3116
  );
2986
3117
  }
@@ -3148,6 +3279,33 @@ export async function parsePnpmLock(
3148
3279
  npmrcConfig,
3149
3280
  ),
3150
3281
  );
3282
+ // A `file:` package is keyed by its path (`localdep@file:localdep.tgz`) and
3283
+ // pnpm copies the manifest version into its `packages` entry. Registering
3284
+ // it lets an importer that names the path resolve to the same ref the
3285
+ // package loop below builds, instead of a version made from the path.
3286
+ if (lockfileVersion >= 9) {
3287
+ for (const [lockKey, pkgEntry] of Object.entries(
3288
+ yamlObj.packages || {},
3289
+ )) {
3290
+ const fileAt = lockKey.indexOf("@file:", 1);
3291
+ if (fileAt < 1 || !pkgEntry?.version) {
3292
+ continue;
3293
+ }
3294
+ const normalizedKey = normalizePnpmLockKey(lockKey);
3295
+ if (gitPkgRefs[normalizedKey]) {
3296
+ continue;
3297
+ }
3298
+ const { group, name } = splitPnpmFilePackageName(
3299
+ lockKey.slice(0, fileAt),
3300
+ );
3301
+ gitPkgRefs[normalizedKey] = {
3302
+ group,
3303
+ name,
3304
+ version: String(pkgEntry.version),
3305
+ vcsUrl: lockKey.slice(fileAt + 1),
3306
+ };
3307
+ }
3308
+ }
3151
3309
  // This logic matches the pnpm list command to include only direct dependencies
3152
3310
  if (ppurl !== "" && yamlObj?.importers) {
3153
3311
  // In lock file version 6, direct dependencies is under importers
@@ -3184,7 +3342,7 @@ export async function parsePnpmLock(
3184
3342
  rootDevDeps[rdk],
3185
3343
  rdk,
3186
3344
  gitPkgRefs,
3187
- undefined,
3345
+ pnpmLockDir,
3188
3346
  githubServerHost,
3189
3347
  npmrcConfig,
3190
3348
  );
@@ -3206,7 +3364,7 @@ export async function parsePnpmLock(
3206
3364
  rootOptionalDeps[rdk] || rootPeerDeps[rdk],
3207
3365
  rdk,
3208
3366
  gitPkgRefs,
3209
- undefined,
3367
+ pnpmLockDir,
3210
3368
  githubServerHost,
3211
3369
  npmrcConfig,
3212
3370
  );
@@ -3229,7 +3387,7 @@ export async function parsePnpmLock(
3229
3387
  rootDirectDeps[dk],
3230
3388
  dk,
3231
3389
  gitPkgRefs,
3232
- undefined,
3390
+ pnpmLockDir,
3233
3391
  githubServerHost,
3234
3392
  npmrcConfig,
3235
3393
  );
@@ -3340,7 +3498,7 @@ export async function parsePnpmLock(
3340
3498
  versionObj,
3341
3499
  cdk,
3342
3500
  gitPkgRefs,
3343
- importedComponentName,
3501
+ join(pnpmLockDir, importedComponentName),
3344
3502
  githubServerHost,
3345
3503
  npmrcConfig,
3346
3504
  );
@@ -3357,7 +3515,10 @@ export async function parsePnpmLock(
3357
3515
  }
3358
3516
  }
3359
3517
  for (const cdk of Object.keys(componentDevDeps)) {
3360
- const version = await getVersionNumPnpm(componentDevDeps[cdk]);
3518
+ const version = await getVersionNumPnpm(
3519
+ componentDevDeps[cdk],
3520
+ join(pnpmLockDir, importedComponentName),
3521
+ );
3361
3522
  const dpurl = build({
3362
3523
  type: "npm",
3363
3524
  namespace: "" || null,
@@ -3375,6 +3536,7 @@ export async function parsePnpmLock(
3375
3536
  })) {
3376
3537
  const version = await getVersionNumPnpm(
3377
3538
  componentOptionalDeps[cdk] || componentPeerDeps[cdk],
3539
+ join(pnpmLockDir, importedComponentName),
3378
3540
  );
3379
3541
  const dpurl = build({
3380
3542
  type: "npm",
@@ -3399,6 +3561,11 @@ export async function parsePnpmLock(
3399
3561
  // snapshots is a new key under lockfile version 9
3400
3562
  const snapshots = yamlObj.snapshots || {};
3401
3563
  const pkgKeys = { ...Object.keys(packages), ...Object.keys(snapshots) };
3564
+ // A lock entry named `__proto__` is not a real package: emitting it as a
3565
+ // component produces SBOM data that breaks naive JSON consumers, and the
3566
+ // name can never come from the npm registry. Legitimate names such as
3567
+ // `constructor` or `prototype` are unaffected.
3568
+ const protoLockKey = /(^|\/)__proto__(@|$)/;
3402
3569
  for (const k in pkgKeys) {
3403
3570
  // Eg: @babel/code-frame/7.10.1
3404
3571
  // In lockfileVersion 6, /@babel/code-frame@7.18.6
@@ -3407,6 +3574,9 @@ export async function parsePnpmLock(
3407
3574
  if (lockfileVersion >= 6 && fullName.includes("(")) {
3408
3575
  fullName = fullName.split("(")[0];
3409
3576
  }
3577
+ if (protoLockKey.test(pkgKeys[k]) || protoLockKey.test(fullName)) {
3578
+ continue;
3579
+ }
3410
3580
  const parts = fullName.split("/");
3411
3581
  const packageNode =
3412
3582
  packages[pkgKeys[k]] ||
@@ -3465,9 +3635,17 @@ export async function parsePnpmLock(
3465
3635
  }
3466
3636
  let opurlString;
3467
3637
  if (vers.includes("@")) {
3468
- opurlString = Purl.parse(
3469
- `pkg:npm/${vers.replace(/^@/g, "%40")}`,
3470
- ).toString();
3638
+ // An aliased optional dependency records `name@version`, and the
3639
+ // version may carry semver build metadata that must be escaped.
3640
+ const aliasParts = parsePurlLoose(`pkg:npm/${vers}`);
3641
+ opurlString = aliasParts
3642
+ ? npmPurl(
3643
+ aliasParts.namespace
3644
+ ? `${aliasParts.namespace}/${aliasParts.name}`
3645
+ : aliasParts.name,
3646
+ aliasParts.version,
3647
+ )
3648
+ : npmPurl(opkgName, vers);
3471
3649
  } else {
3472
3650
  opurlString = npmPurl(opkgName, vers);
3473
3651
  }
@@ -3536,8 +3714,17 @@ export async function parsePnpmLock(
3536
3714
  possibleHttpParts[possibleHttpParts.length - 1].startsWith("file")
3537
3715
  ) {
3538
3716
  srcUrl = possibleHttpParts[possibleHttpParts.length - 1];
3539
- name = fullName.replace(`@${srcUrl}`, "");
3540
- version = "";
3717
+ ({ group, name } = splitPnpmFilePackageName(
3718
+ fullName.replace(`@${srcUrl}`, ""),
3719
+ ));
3720
+ // The key names the path, not a version. pnpm copies the version
3721
+ // from the package's own manifest into its `packages` entry, and
3722
+ // that may carry semver build metadata (`2.0.0+local`).
3723
+ version = String(
3724
+ packages[pkgKeys[k]]?.version ||
3725
+ packages[fullName]?.version ||
3726
+ "",
3727
+ );
3541
3728
  } else {
3542
3729
  group = parts.length > 1 ? parts[0] : "";
3543
3730
  const tmpA = parts[parts.length - 1].split("@");
@@ -4004,7 +4191,7 @@ export async function parsePnpmLock(
4004
4191
  `About to fetch npm registry metadata for ${pkgList.length} packages in parsePnpmLock`,
4005
4192
  );
4006
4193
  }
4007
- pkgList = await getNpmMetadata(pkgList);
4194
+ pkgList = await getNpmMetadata(pkgList, undefined, { npmrcConfig });
4008
4195
  return {
4009
4196
  pkgList,
4010
4197
  dependenciesList,
@@ -4034,7 +4221,7 @@ export async function parseBowerJson(bowerJsonFile) {
4034
4221
  group: pkgIdentifier.scope || "",
4035
4222
  version: pkgData.version || "",
4036
4223
  description: pkgData.description || "",
4037
- license: pkgData.license || "",
4224
+ license: resolveNpmLicense(pkgData) || "",
4038
4225
  properties: [
4039
4226
  {
4040
4227
  name: "internal:SrcFile",