bibliothecary 15.0.0 → 15.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0d3fafafd92ac4eac18f89f709733c655b0f05a803f9fcb2c1ef8b26690c584
4
- data.tar.gz: b89c725a5bbbcc8139a8ebac579f610a84839e3eaaf356dd04fe4d371294ad03
3
+ metadata.gz: 2c1e74940c87a46f8d7faee1b0cfc0abb6352d021c895c3be3ea06d6f491cdc5
4
+ data.tar.gz: abe103cdfcac6b56544fa983fe24e16f67441753a0cd1d8b348b864cc4d3f355
5
5
  SHA512:
6
- metadata.gz: 9b8caae9ff6a85ee12a1860f048e69c2910e7f48128a6621fc1c2d14ca587a79ca9478a14b5f092005edb02cc6ff00ee2819d6788a6a7f8fa95d5f10f1addf67
7
- data.tar.gz: 408d3891213550d27395f65587010ee0c4a49da47fb290e3805971dbe7f41b7fe2add33c3d3c4dbe646e084b6536e4a33c15928e8560d32ffaa8fde45332b14b
6
+ metadata.gz: 03baf459e89fd2761c74b8b1cce7d59f007c00699c95472bbaf91a1baeb9de358f649c13c87f7d00a6febc66481d33d9f876e78ac122087f9781441d79f5d6c3
7
+ data.tar.gz: 2688993b02d2f4f673b2a7c82050ae34e61f03f143494cf16030b74909ed92ab5eb91393c1609f8a6cc1d45bdda8dc12b73d1272c3b92173439d1b5e1dbd2fa7
data/CHANGELOG.md CHANGED
@@ -13,6 +13,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
13
13
 
14
14
  ### Removed
15
15
 
16
+ ## [15.1.0]
17
+
18
+ ### Added
19
+
20
+ - Add initial support for SPDX 3.0 JSON files
21
+
22
+ ### Changed
23
+
24
+ - Treat "cyclonedx.json" and "cyclonedx.xml" matchers as suffixes instead of filenames.
25
+ - Fix Nuget parser so it returns all target frameworks' dependencies from packages.lock.json instead of an arbitrary target framework's dependencies.
26
+
16
27
  ## [15.0.0]
17
28
 
18
29
  15.0.0 includes some breaking changes, so please see UPGRADING_TO_15_0_0.md to migrate your code.
@@ -63,7 +63,7 @@ module Bibliothecary
63
63
 
64
64
  def self.mapping
65
65
  {
66
- match_filename("cyclonedx.json") => {
66
+ match_extension("cyclonedx.json") => {
67
67
  kind: "lockfile",
68
68
  parser: :parse_cyclonedx_json,
69
69
  ungroupable: true,
@@ -73,7 +73,7 @@ module Bibliothecary
73
73
  parser: :parse_cyclonedx_json,
74
74
  ungroupable: true,
75
75
  },
76
- match_filename("cyclonedx.xml") => {
76
+ match_extension("cyclonedx.xml") => {
77
77
  kind: "lockfile",
78
78
  parser: :parse_cyclonedx_xml,
79
79
  ungroupable: true,
@@ -120,9 +120,19 @@ module Bibliothecary
120
120
  end
121
121
 
122
122
  def self.parse_spdx_json_file_contents(file_contents, source = nil)
123
- entries = Set.new
124
123
  manifest = JSON.parse(file_contents)
125
124
 
125
+ if manifest.key?("spdxVersion")
126
+ parse_spdx_2_json_file_contents(manifest, source)
127
+ elsif manifest.key?("@context")
128
+ parse_spdx_3_json_file_contents(manifest, source)
129
+ else
130
+ Set.new
131
+ end
132
+ end
133
+
134
+ def self.parse_spdx_2_json_file_contents(manifest, source)
135
+ entries = Set.new
126
136
  manifest["packages"]&.each do |package|
127
137
  spdx_name = package["name"]
128
138
  spdx_version = package["versionInfo"]
@@ -139,6 +149,47 @@ module Bibliothecary
139
149
  spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version,
140
150
  source: source, purl_type: purl_type)
141
151
  end
152
+ entries
153
+ end
154
+
155
+ def self.parse_spdx_3_json_file_contents(manifest, source)
156
+ entries = Set.new
157
+ manifest.fetch("@graph", [])
158
+ .select do |element|
159
+ types = Array(element["type"] || element["@type"])
160
+ # "software_Package" is the common one, but these may differ and include namespaces
161
+ types.any? { |t| t.to_s.match?(/Package$/i) }
162
+ end
163
+ .each do |element|
164
+ spdx_name = element["name"]
165
+ spdx_version = element["software_packageVersion"] || element["packageVersion"] || element["versionInfo"]
166
+
167
+ purl_string = element["externalIdentifier"]&.find do |ext_id|
168
+ ext_id_type = ext_id["externalIdentifierType"] || ext_id["identifierType"]
169
+ ext_id_type&.match?(/purl/i)
170
+ end&.dig("identifier")
171
+
172
+ purl_string ||= element.fetch("externalRef", [])
173
+ .map { |ref| ref.fetch("locator", nil) }
174
+ .compact
175
+ .map(&:first)
176
+ .find { |locator| locator.start_with?("pkg:") }
177
+
178
+ purl_string ||= element.fetch("software_packageUrl", nil)
179
+
180
+ next unless purl_string
181
+
182
+ purl = PackageURL.parse(purl_string)
183
+ purl_type = purl&.type
184
+ # Use the mapped purl->bibliothecary platform, or else fall back to original platform itself.
185
+ platform = PurlUtil::PURL_TYPE_MAPPING.fetch(purl_type, purl_type)
186
+ purl_name = PurlUtil.full_name(purl)
187
+ purl_version = purl&.version
188
+
189
+ add_entry(entries: entries, platform: platform, purl_name: purl_name,
190
+ spdx_name: spdx_name, purl_version: purl_version, spdx_version: spdx_version,
191
+ source: source, purl_type: purl_type)
192
+ end
142
193
 
143
194
  entries
144
195
  end
@@ -64,32 +64,32 @@ module Bibliothecary
64
64
  def self.parse_packages_lock_json(file_contents, options: {})
65
65
  manifest = JSON.parse file_contents
66
66
 
67
- frameworks = {}
68
- manifest.fetch("dependencies", []).each do |framework, deps|
69
- frameworks[framework] = deps
70
- .reject { |_name, details| details["type"] == "Project" } # Projects do not have versions
71
- .map do |name, details|
72
- Dependency.new(
73
- name: name,
74
- # 'resolved' has been set in all examples so far
75
- # so fallback to requested is pure paranoia
76
- requirement: details.fetch("resolved", details.fetch("requested", "*")),
77
- type: "runtime",
78
- source: options.fetch(:filename, nil),
79
- platform: platform_name
80
- )
81
- end
82
- end
83
-
84
- unless frameworks.empty?
85
- # we should really return multiple manifests, but bibliothecary doesn't
86
- # do that yet so at least pick deterministically.
67
+ # NOTE: here we are merging dependencies from potentially >1 target framework. The versions
68
+ # across target frameworks in package.lock.json are not guaranteed to be the same, so e.g. a
69
+ # single packages.lock.json may include both "Newtonsoft.Json@13.01" and "Newtonsoft.Json@12.0.3". This
70
+ # should be more comprehensive than just returning one arbitrary framework's deps, but we're losing the
71
+ # fidelity of which target framework a given dependency was used in. Someday, bibliothecary could
72
+ # include a new Dependency field like "architecture" or "framework" and add the metadata for each dep there.
73
+ dependencies = manifest
74
+ .fetch("dependencies", [])
75
+ .flat_map do |_framework, framework_dependencies|
76
+ framework_dependencies
77
+ .reject { |_name, details| details["type"] == "Project" } # Projects do not have versions
78
+ .map do |name, details|
79
+ Dependency.new(
80
+ name: name,
81
+ # 'resolved' has been set in all examples so far
82
+ # so fallback to requested is pure paranoia
83
+ requirement: details.fetch("resolved", details.fetch("requested", "*")),
84
+ type: "runtime",
85
+ source: options.fetch(:filename, nil),
86
+ platform: platform_name
87
+ )
88
+ end
89
+ end
90
+ .uniq
87
91
 
88
- # Note, frameworks can be empty, so remove empty ones and then return the last sorted item if any
89
- frameworks.delete_if { |_k, v| v.empty? }
90
- return ParserResult.new(dependencies: frameworks[frameworks.keys.max]) unless frameworks.empty?
91
- end
92
- ParserResult.new(dependencies: [])
92
+ ParserResult.new(dependencies: dependencies)
93
93
  end
94
94
 
95
95
  def self.parse_packages_config(file_contents, options: {})
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bibliothecary
4
- VERSION = "15.0.0"
4
+ VERSION = "15.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bibliothecary
3
3
  version: !ruby/object:Gem::Version
4
- version: 15.0.0
4
+ version: 15.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-12-03 00:00:00.000000000 Z
11
+ date: 2025-12-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: commander