bundler-sbom 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5d7fea7d39796fd36fec03e1e5b13695fdccb0cfc6926cfb5a8631d86f94f09
4
- data.tar.gz: fe3abc900c32f11af2b0c1f432c9b376042b48a9d8e69c293ccc76b7383a97b0
3
+ metadata.gz: f578e54cb4d73e62b18d305684a0ff770edb7cde70b31fdd8cb9eaa7b53e0885
4
+ data.tar.gz: 9e853789b6b01962185dde4a528f84c1e1f16f4432d4a9c5f57b16d5680e0489
5
5
  SHA512:
6
- metadata.gz: 8b79df0b6ab84ce586694fffcf9b9a25e4de2586f85b0430be60f93404df699675e5890936a4ea963f528ed7526b6ffb9e9afe591ee8c8b327952981457654a0
7
- data.tar.gz: dec35c52cf5cd36d1b0692a3eb6bb73431f2e07836226af366fd7a8d0c58c964c9577545bf10c01e5178333acbf75a299a63f205bbc3496c557835af5473496b
6
+ metadata.gz: 4bd4342d874517d03999ba5166fac776610ae3832d79eb620732de9cb7fa6e82614b61ba52a1dc89c79580d1985dad790c6f2ca4c641fd10afbc65ed32fe1b85
7
+ data.tar.gz: 9f30c4813af67c7882dcec7d132e8c324bc243d6f8fb19cef7a8571a9678fbbf0b92d15fd1a598f0afe6f73497faeea428bbbcd3d03838f3347380f4e44f722c
data/Gemfile CHANGED
@@ -9,4 +9,4 @@ group :development do
9
9
  gem "simplecov", require: false
10
10
  gem "rspec-its"
11
11
  gem "rspec-mocks"
12
- end
12
+ end
data/README.md CHANGED
@@ -14,24 +14,62 @@ $ bundler plugin install bundler-sbom
14
14
 
15
15
  ### Generate SBOM
16
16
 
17
- To generate an SBOM file in SPDX format from your project's Gemfile.lock:
17
+ To generate an SBOM file from your project's Gemfile.lock:
18
18
 
19
19
  ```
20
- $ bundle sbom dump
20
+ $ bundle sbom dump [options]
21
21
  ```
22
22
 
23
- This will create a `bom.json` file in your project directory.
23
+ Available options:
24
+ - `-f, --format FORMAT`: Output format (json or xml, default: json)
25
+ - `-s, --sbom FORMAT`: SBOM specification format (spdx or cyclonedx, default: spdx)
26
+
27
+ Generated files will be named according to the following pattern:
28
+ - SPDX format: `bom.json` or `bom.xml`
29
+ - CycloneDX format: `bom-cyclonedx.json` or `bom-cyclonedx.xml`
30
+
31
+ Examples:
32
+ ```
33
+ $ bundle sbom dump # Generates SPDX format in JSON (bom.json)
34
+ $ bundle sbom dump -f xml # Generates SPDX format in XML (bom.xml)
35
+ $ bundle sbom dump -s cyclonedx # Generates CycloneDX format in JSON (bom-cyclonedx.json)
36
+ $ bundle sbom dump -s cyclonedx -f xml # Generates CycloneDX format in XML (bom-cyclonedx.xml)
37
+ ```
24
38
 
25
39
  ### Analyze License Information
26
40
 
27
41
  To view a summary of licenses used in your project's dependencies:
28
42
 
29
43
  ```
30
- $ bundle sbom license
44
+ $ bundle sbom license [options]
31
45
  ```
32
46
 
47
+ Available options:
48
+ - `-f, --file PATH`: Input SBOM file path
49
+ - `-F, --format FORMAT`: Input format (json or xml)
50
+
51
+ If no options are specified, the command will automatically look for SBOM files in the following order:
52
+ 1. `bom.xml` (if format is xml)
53
+ 2. `bom-cyclonedx.json`
54
+ 3. `bom-cyclonedx.xml`
55
+ 4. `bom.json`
56
+
33
57
  This command will show:
34
58
  - A count of packages using each license
35
59
  - A detailed list of packages grouped by license
36
60
 
37
- Note: The `license` command requires that you've already generated the SBOM using `bundle sbom dump`.
61
+ Note: The `license` command requires that you've already generated the SBOM using `bundle sbom dump`.
62
+
63
+ ## Supported SBOM Formats
64
+
65
+ ### SPDX
66
+ [SPDX (Software Package Data Exchange)](https://spdx.dev/) is a standard format for communicating software bill of material information, including components, licenses, copyrights, and security references.
67
+
68
+ ### CycloneDX
69
+ [CycloneDX](https://cyclonedx.org/) is a lightweight SBOM specification designed for use in application security contexts and supply chain component analysis.
70
+
71
+ ## References
72
+
73
+ - [SPDX Specification](https://spdx.github.io/spdx-spec/)
74
+ - [CycloneDX Specification](https://cyclonedx.org/specification/overview/)
75
+ - [About Software Bill of Materials (SBOM)](https://www.cisa.gov/sbom)
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require "rspec/core/rake_task"
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task default: :spec
@@ -26,24 +26,24 @@ module Bundler
26
26
 
27
27
  # Generate SBOM based on specified format
28
28
  sbom = Bundler::Sbom::Generator.generate_sbom(sbom_format)
29
-
29
+
30
30
  # Determine file extension based on output format
31
- ext = format == "json" ? "json" : "xml"
32
-
31
+ ext = (format == "json") ? "json" : "xml"
32
+
33
33
  # Determine filename prefix based on SBOM format
34
- prefix = sbom_format == "spdx" ? "bom" : "bom-cyclonedx"
34
+ prefix = (sbom_format == "spdx") ? "bom" : "bom-cyclonedx"
35
35
  output_file = "#{prefix}.#{ext}"
36
-
36
+
37
37
  if format == "json"
38
38
  File.write(output_file, JSON.pretty_generate(sbom))
39
39
  else # xml
40
40
  xml_content = Bundler::Sbom::Generator.convert_to_xml(sbom)
41
41
  File.write(output_file, xml_content)
42
42
  end
43
-
43
+
44
44
  Bundler.ui.info("Generated #{sbom_format.upcase} SBOM at #{output_file}")
45
45
  end
46
-
46
+
47
47
  desc "license", "Display license report from SBOM file"
48
48
  method_option :file, type: :string, desc: "Input SBOM file path", aliases: "-f"
49
49
  method_option :format, type: :string, desc: "Input format: json or xml", aliases: "-F"
@@ -59,19 +59,19 @@ module Bundler
59
59
 
60
60
  # Determine input file based on format or find default files
61
61
  if input_file.nil?
62
- if format == "xml" || (format.nil? && File.exist?("bom.xml"))
63
- input_file = "bom.xml"
62
+ input_file = if format == "xml" || (format.nil? && File.exist?("bom.xml"))
63
+ "bom.xml"
64
64
  elsif File.exist?("bom-cyclonedx.json")
65
- input_file = "bom-cyclonedx.json"
65
+ "bom-cyclonedx.json"
66
66
  elsif File.exist?("bom-cyclonedx.xml")
67
- input_file = "bom-cyclonedx.xml"
67
+ "bom-cyclonedx.xml"
68
68
  else
69
- input_file = "bom.json"
69
+ "bom.json"
70
70
  end
71
71
  end
72
72
 
73
73
  unless File.exist?(input_file)
74
- file_type = File.extname(input_file) == ".xml" ? "xml" : "json"
74
+ file_type = (File.extname(input_file) == ".xml") ? "xml" : "json"
75
75
  sbom_type = input_file.include?("cyclonedx") ? "cyclonedx" : "spdx"
76
76
  Bundler.ui.error("Error: #{input_file} not found. Run 'bundle sbom dump --format=#{file_type} --sbom=#{sbom_type}' first.")
77
77
  exit 1
@@ -79,18 +79,18 @@ module Bundler
79
79
 
80
80
  begin
81
81
  content = File.read(input_file)
82
-
82
+
83
83
  sbom = if format == "xml" || (!format && File.extname(input_file) == ".xml")
84
84
  Bundler::Sbom::Generator.parse_xml(content)
85
85
  else
86
86
  JSON.parse(content)
87
87
  end
88
-
88
+
89
89
  Bundler::Sbom::Reporter.display_license_report(sbom)
90
90
  rescue JSON::ParserError
91
91
  Bundler.ui.error("Error: #{input_file} is not a valid JSON file")
92
92
  exit 1
93
- rescue StandardError => e
93
+ rescue => e
94
94
  Bundler.ui.error("Error processing #{input_file}: #{e.message}")
95
95
  exit 1
96
96
  end
@@ -102,4 +102,4 @@ module Bundler
102
102
  end
103
103
  end
104
104
  end
105
- end
105
+ end
@@ -31,7 +31,12 @@ module Bundler
31
31
  "components" => []
32
32
  }
33
33
 
34
+ # Deduplicate specs by name and version
35
+ seen_gems = Set.new
34
36
  lockfile.specs.each do |spec|
37
+ gem_key = "#{spec.name}:#{spec.version}"
38
+ next if seen_gems.include?(gem_key)
39
+ seen_gems.add(gem_key)
35
40
  begin
36
41
  gemspec = Gem::Specification.find_by_name(spec.name, spec.version)
37
42
  licenses = []
@@ -56,7 +61,7 @@ module Bundler
56
61
  }
57
62
 
58
63
  unless licenses.empty?
59
- component["licenses"] = licenses.map { |license| { "license" => { "id" => license } } }
64
+ component["licenses"] = licenses.map { |license| {"license" => {"id" => license}} }
60
65
  end
61
66
 
62
67
  sbom["components"] << component
@@ -68,72 +73,72 @@ module Bundler
68
73
  def self.to_xml(sbom)
69
74
  doc = REXML::Document.new
70
75
  doc << REXML::XMLDecl.new("1.0", "UTF-8")
71
-
76
+
72
77
  # Root element
73
78
  root = REXML::Element.new("bom")
74
79
  root.add_namespace("http://cyclonedx.org/schema/bom/1.4")
75
80
  root.add_attributes({
76
81
  "serialNumber" => sbom["serialNumber"],
77
- "version" => sbom["version"].to_s,
82
+ "version" => sbom["version"].to_s
78
83
  })
79
84
  doc.add_element(root)
80
-
85
+
81
86
  # Metadata
82
87
  metadata = REXML::Element.new("metadata")
83
88
  root.add_element(metadata)
84
-
89
+
85
90
  add_element(metadata, "timestamp", sbom["metadata"]["timestamp"])
86
-
91
+
87
92
  # Tools
88
93
  tools = REXML::Element.new("tools")
89
94
  metadata.add_element(tools)
90
-
95
+
91
96
  sbom["metadata"]["tools"].each do |tool_data|
92
97
  tool = REXML::Element.new("tool")
93
98
  tools.add_element(tool)
94
-
99
+
95
100
  add_element(tool, "vendor", tool_data["vendor"])
96
101
  add_element(tool, "name", tool_data["name"])
97
102
  add_element(tool, "version", tool_data["version"].to_s)
98
103
  end
99
-
104
+
100
105
  # Component (root project)
101
106
  component = REXML::Element.new("component")
102
107
  component.add_attribute("type", sbom["metadata"]["component"]["type"])
103
108
  metadata.add_element(component)
104
-
109
+
105
110
  add_element(component, "name", sbom["metadata"]["component"]["name"])
106
111
  add_element(component, "version", sbom["metadata"]["component"]["version"])
107
-
112
+
108
113
  # Components
109
114
  components = REXML::Element.new("components")
110
115
  root.add_element(components)
111
-
116
+
112
117
  sbom["components"].each do |comp_data|
113
118
  comp = REXML::Element.new("component")
114
119
  comp.add_attribute("type", comp_data["type"])
115
120
  components.add_element(comp)
116
-
121
+
117
122
  add_element(comp, "name", comp_data["name"])
118
123
  add_element(comp, "version", comp_data["version"])
119
124
  add_element(comp, "purl", comp_data["purl"])
120
-
125
+
121
126
  # Licenses
122
127
  if comp_data["licenses"] && !comp_data["licenses"].empty?
123
128
  licenses = REXML::Element.new("licenses")
124
129
  comp.add_element(licenses)
125
-
130
+
126
131
  comp_data["licenses"].each do |license_data|
127
132
  license = REXML::Element.new("license")
128
133
  licenses.add_element(license)
129
-
134
+
130
135
  if license_data["license"]["id"]
131
136
  add_element(license, "id", license_data["license"]["id"])
132
137
  end
133
138
  end
134
139
  end
135
140
  end
136
-
141
+
137
142
  formatter = REXML::Formatters::Pretty.new(2)
138
143
  formatter.compact = true
139
144
  output = ""
@@ -143,7 +148,7 @@ module Bundler
143
148
 
144
149
  def self.parse_xml(doc)
145
150
  root = doc.root
146
-
151
+
147
152
  sbom = {
148
153
  "bomFormat" => "CycloneDX",
149
154
  "specVersion" => "1.4",
@@ -160,7 +165,7 @@ module Bundler
160
165
  },
161
166
  "components" => []
162
167
  }
163
-
168
+
164
169
  # Collect tools
165
170
  REXML::XPath.each(root, "metadata/tools/tool") do |tool|
166
171
  tool_data = {
@@ -170,7 +175,7 @@ module Bundler
170
175
  }
171
176
  sbom["metadata"]["tools"] << tool_data
172
177
  end
173
-
178
+
174
179
  # Collect components
175
180
  REXML::XPath.each(root, "components/component") do |comp|
176
181
  component = {
@@ -179,26 +184,26 @@ module Bundler
179
184
  "version" => get_element_text(comp, "version"),
180
185
  "purl" => get_element_text(comp, "purl")
181
186
  }
182
-
187
+
183
188
  # Collect licenses
184
189
  licenses = []
185
190
  REXML::XPath.each(comp, "licenses/license") do |license|
186
191
  license_id = get_element_text(license, "id")
187
- licenses << { "license" => { "id" => license_id } } if license_id
192
+ licenses << {"license" => {"id" => license_id}} if license_id
188
193
  end
189
-
194
+
190
195
  component["licenses"] = licenses unless licenses.empty?
191
196
  sbom["components"] << component
192
197
  end
193
198
 
194
199
  # Convert CycloneDX format to SPDX-like format for compatibility with Reporter
195
- converted_sbom = {
200
+ {
196
201
  "packages" => sbom["components"].map do |comp|
197
202
  license_string = if comp["licenses"]
198
- comp["licenses"].map { |l| l["license"]["id"] }.join(", ")
199
- else
200
- "NOASSERTION"
201
- end
203
+ comp["licenses"].map { |l| l["license"]["id"] }.join(", ")
204
+ else
205
+ "NOASSERTION"
206
+ end
202
207
  {
203
208
  "name" => comp["name"],
204
209
  "versionInfo" => comp["version"],
@@ -206,18 +211,16 @@ module Bundler
206
211
  }
207
212
  end
208
213
  }
209
-
210
- converted_sbom
211
214
  end
212
215
 
213
216
  def self.to_report_format(sbom)
214
217
  {
215
218
  "packages" => sbom["components"].map do |comp|
216
219
  license_string = if comp["licenses"]
217
- comp["licenses"].map { |l| l["license"]["id"] }.join(", ")
218
- else
219
- "NOASSERTION"
220
- end
220
+ comp["licenses"].map { |l| l["license"]["id"] }.join(", ")
221
+ else
222
+ "NOASSERTION"
223
+ end
221
224
  {
222
225
  "name" => comp["name"],
223
226
  "versionInfo" => comp["version"],
@@ -228,17 +231,17 @@ module Bundler
228
231
  end
229
232
 
230
233
  private
231
-
234
+
232
235
  def self.add_element(parent, name, value)
233
236
  element = REXML::Element.new(name)
234
237
  element.text = value
235
238
  parent.add_element(element)
236
239
  end
237
-
240
+
238
241
  def self.get_element_text(element, xpath)
239
242
  result = REXML::XPath.first(element, xpath)
240
243
  result ? result.text : nil
241
244
  end
242
245
  end
243
246
  end
244
- end
247
+ end
@@ -39,7 +39,7 @@ module Bundler
39
39
  def self.parse_xml(xml_content)
40
40
  doc = REXML::Document.new(xml_content)
41
41
  root = doc.root
42
-
42
+
43
43
  # Determine if it's CycloneDX or SPDX
44
44
  if root.name == "bom" && root.namespace.include?("cyclonedx.org")
45
45
  CycloneDX.parse_xml(doc)
@@ -49,4 +49,4 @@ module Bundler
49
49
  end
50
50
  end
51
51
  end
52
- end
52
+ end
@@ -8,7 +8,7 @@ module Bundler
8
8
  else
9
9
  SPDX.to_report_format(sbom)
10
10
  end
11
-
11
+
12
12
  display_report(sbom)
13
13
  end
14
14
 
@@ -16,7 +16,7 @@ module Bundler
16
16
 
17
17
  def self.sbom_format(sbom)
18
18
  return :cyclonedx if sbom["bomFormat"] == "CycloneDX"
19
- return :spdx
19
+ :spdx
20
20
  end
21
21
 
22
22
  def self.display_report(sbom)
@@ -60,4 +60,4 @@ module Bundler
60
60
  end
61
61
  end
62
62
  end
63
- end
63
+ end
@@ -6,7 +6,7 @@ module Bundler
6
6
  module Sbom
7
7
  class SPDX
8
8
  def self.generate(lockfile, document_name)
9
- spdx_id = SecureRandom.uuid
9
+ spdx_id = generate_spdx_id
10
10
  sbom = {
11
11
  "SPDXID" => "SPDXRef-DOCUMENT",
12
12
  "spdxVersion" => "SPDX-2.3",
@@ -21,7 +21,12 @@ module Bundler
21
21
  "packages" => []
22
22
  }
23
23
 
24
+ # Deduplicate specs by name and version
25
+ seen_gems = Set.new
24
26
  lockfile.specs.each do |spec|
27
+ gem_key = "#{spec.name}:#{spec.version}"
28
+ next if seen_gems.include?(gem_key)
29
+ seen_gems.add(gem_key)
25
30
  begin
26
31
  gemspec = Gem::Specification.find_by_name(spec.name, spec.version)
27
32
  licenses = []
@@ -67,39 +72,39 @@ module Bundler
67
72
  def self.to_xml(sbom)
68
73
  doc = REXML::Document.new
69
74
  doc << REXML::XMLDecl.new("1.0", "UTF-8")
70
-
75
+
71
76
  # Root element
72
77
  root = REXML::Element.new("SpdxDocument")
73
78
  root.add_namespace("https://spdx.org/spdxdocs/")
74
79
  doc.add_element(root)
75
-
80
+
76
81
  # Document info
77
82
  add_element(root, "SPDXID", sbom["SPDXID"])
78
83
  add_element(root, "spdxVersion", sbom["spdxVersion"])
79
84
  add_element(root, "name", sbom["name"])
80
85
  add_element(root, "dataLicense", sbom["dataLicense"])
81
86
  add_element(root, "documentNamespace", sbom["documentNamespace"])
82
-
87
+
83
88
  # Creation info
84
89
  creation_info = REXML::Element.new("creationInfo")
85
90
  root.add_element(creation_info)
86
91
  add_element(creation_info, "created", sbom["creationInfo"]["created"])
87
92
  add_element(creation_info, "licenseListVersion", sbom["creationInfo"]["licenseListVersion"])
88
-
93
+
89
94
  sbom["creationInfo"]["creators"].each do |creator|
90
95
  add_element(creation_info, "creator", creator)
91
96
  end
92
-
97
+
93
98
  # Describes
94
99
  sbom["documentDescribes"].each do |describes|
95
100
  add_element(root, "documentDescribes", describes)
96
101
  end
97
-
102
+
98
103
  # Packages
99
104
  sbom["packages"].each do |pkg|
100
105
  package = REXML::Element.new("package")
101
106
  root.add_element(package)
102
-
107
+
103
108
  add_element(package, "SPDXID", pkg["SPDXID"])
104
109
  add_element(package, "name", pkg["name"])
105
110
  add_element(package, "versionInfo", pkg["versionInfo"])
@@ -109,20 +114,20 @@ module Bundler
109
114
  add_element(package, "licenseDeclared", pkg["licenseDeclared"])
110
115
  add_element(package, "copyrightText", pkg["copyrightText"])
111
116
  add_element(package, "supplier", pkg["supplier"])
112
-
117
+
113
118
  # External references
114
119
  if pkg["externalRefs"]
115
120
  pkg["externalRefs"].each do |ref|
116
121
  ext_ref = REXML::Element.new("externalRef")
117
122
  package.add_element(ext_ref)
118
-
123
+
119
124
  add_element(ext_ref, "referenceCategory", ref["referenceCategory"])
120
125
  add_element(ext_ref, "referenceType", ref["referenceType"])
121
126
  add_element(ext_ref, "referenceLocator", ref["referenceLocator"])
122
127
  end
123
128
  end
124
129
  end
125
-
130
+
126
131
  formatter = REXML::Formatters::Pretty.new(2)
127
132
  formatter.compact = true
128
133
  output = ""
@@ -132,7 +137,7 @@ module Bundler
132
137
 
133
138
  def self.parse_xml(doc)
134
139
  root = doc.root
135
-
140
+
136
141
  sbom = {
137
142
  "SPDXID" => get_element_text(root, "SPDXID"),
138
143
  "spdxVersion" => get_element_text(root, "spdxVersion"),
@@ -147,17 +152,17 @@ module Bundler
147
152
  "packages" => [],
148
153
  "documentDescribes" => []
149
154
  }
150
-
155
+
151
156
  # Collect creators
152
157
  REXML::XPath.each(root, "creationInfo/creator") do |creator|
153
158
  sbom["creationInfo"]["creators"] << creator.text
154
159
  end
155
-
160
+
156
161
  # Collect documentDescribes
157
162
  REXML::XPath.each(root, "documentDescribes") do |describes|
158
163
  sbom["documentDescribes"] << describes.text
159
164
  end
160
-
165
+
161
166
  # Collect packages
162
167
  REXML::XPath.each(root, "package") do |pkg_element|
163
168
  package = {
@@ -172,7 +177,7 @@ module Bundler
172
177
  "supplier" => get_element_text(pkg_element, "supplier"),
173
178
  "externalRefs" => []
174
179
  }
175
-
180
+
176
181
  # Collect external references
177
182
  REXML::XPath.each(pkg_element, "externalRef") do |ref_element|
178
183
  ref = {
@@ -182,10 +187,10 @@ module Bundler
182
187
  }
183
188
  package["externalRefs"] << ref
184
189
  end
185
-
190
+
186
191
  sbom["packages"] << package
187
192
  end
188
-
193
+
189
194
  sbom
190
195
  end
191
196
 
@@ -203,18 +208,20 @@ module Bundler
203
208
  }
204
209
  end
205
210
 
206
- private
207
-
211
+ def self.generate_spdx_id
212
+ SecureRandom.uuid
213
+ end
214
+
208
215
  def self.add_element(parent, name, value)
209
216
  element = REXML::Element.new(name)
210
217
  element.text = value
211
218
  parent.add_element(element)
212
219
  end
213
-
220
+
214
221
  def self.get_element_text(element, xpath)
215
222
  result = REXML::XPath.first(element, xpath)
216
223
  result ? result.text : nil
217
224
  end
218
225
  end
219
226
  end
220
- end
227
+ end
@@ -1,5 +1,5 @@
1
1
  module Bundler
2
2
  module Sbom
3
- VERSION = "0.1.7"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler-sbom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SHIBATA Hiroshi
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-03-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bundler
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
78
  - !ruby/object:Gem::Version
79
79
  version: '0'
80
80
  requirements: []
81
- rubygems_version: 3.6.2
81
+ rubygems_version: 4.0.3
82
82
  specification_version: 4
83
83
  summary: Generate SPDX SBOM(Software Bill of Materials) files with Bundler
84
84
  test_files: []