dependabot-bundler 0.382.0 → 0.383.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: 1b1389dea9ddec262718e238d70112ad3495b989bd2b857124974cba40a856c7
4
- data.tar.gz: 911a2a11704ad3334c805895940ae7411292e7188dd103f9a5c20c22ab27c668
3
+ metadata.gz: d1ba21c738892e21622391ab82718a15bc4fbd0ba49080dde90ed4fa917cfe61
4
+ data.tar.gz: 809f4495fbc61b082895ec1e411be55abe73f57655dcbfdaca818e5d83e36594
5
5
  SHA512:
6
- metadata.gz: bd55fd7d99cde9d0893c48ffd9690540c792737e5a0bb036aa1deda38f5b2cbf49230648cf1a2469cec65389c130fe9ade5b20f71d4854946923501a3057e240
7
- data.tar.gz: 746587193b68ef9037656d766226eac76f2a35cdb568b87f87ae02ec7fad95a9ace33c78f8ba3c0e97629b750af737bb3da2acbf9921c33eff6fbcd6a01f442b
6
+ metadata.gz: 30e9d815ca521d360711378caf668148ad4d75e62ccb42e851e9200fc309e08bf57c4cb2e841143c086e1087a061f90bfb19778c7d9e2e30ce1229eaf5335ee0
7
+ data.tar.gz: 3f13429240d46f3f0a942db18625b095bdceb4df72a0ea71b7e26779ed58ec1259927e604881d8420a756a493fe93d97671aef547091ca3d7161b3b5f2a7ea80
@@ -0,0 +1,32 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/endpoint_specification"
5
+
6
+ # Bundler 4 parses the metadata a registry's compact index (`/info/<gem>`)
7
+ # returns per gem version. Its guard is `next unless v`, but an empty array
8
+ # `[]` is truthy, so for an empty `checksum` it calls `Checksum.from_api(nil)`
9
+ # -> `nil.match?(...)` and raises `Bundler::GemspecError` ("There was an error
10
+ # parsing the metadata for the gem ..."), aborting the whole resolution.
11
+ #
12
+ # GitHub Packages serves this empty-checksum shape for some old gems (e.g.
13
+ # failbot 2.0.1), so one such gem blocks every update for the repo. Bundler 2
14
+ # didn't parse compact-index checksums, so this only surfaced once the updater
15
+ # moved to the Bundler 4 helper.
16
+ #
17
+ # Drop nil/empty metadata values before Bundler parses them. An empty checksum
18
+ # carries nothing to verify, so skipping it is safe; well-formed values (and
19
+ # genuinely malformed ones, which still raise) are untouched.
20
+ module BundlerEndpointSpecificationMetadataPatch
21
+ def parse_metadata(data)
22
+ if data.respond_to?(:reject)
23
+ data = data.reject do |_key, value|
24
+ value.nil? || (value.respond_to?(:empty?) && value.empty?)
25
+ end
26
+ end
27
+
28
+ super
29
+ end
30
+ end
31
+
32
+ Bundler::EndpointSpecification.prepend(BundlerEndpointSpecificationMetadataPatch)
data/helpers/v4/run.rb CHANGED
@@ -24,6 +24,7 @@ end
24
24
  require "definition_ruby_version_patch"
25
25
  require "definition_bundler_version_patch"
26
26
  require "git_source_patch"
27
+ require "endpoint_specification_metadata_patch"
27
28
 
28
29
  require "functions"
29
30
 
@@ -0,0 +1,49 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ require "native_spec_helper"
5
+
6
+ RSpec.describe BundlerEndpointSpecificationMetadataPatch do
7
+ let(:spec_fetcher) { instance_double(Bundler::Fetcher, uri: URI("https://example.com")) }
8
+
9
+ def build_spec(metadata)
10
+ Bundler::EndpointSpecification.new("failbot", "2.0.1", "ruby", spec_fetcher, [], metadata)
11
+ end
12
+
13
+ it "does not raise when the registry serves an empty checksum array" do
14
+ expect { build_spec([["checksum", []]]) }.not_to raise_error
15
+ end
16
+
17
+ it "leaves the checksum unset for an empty checksum array" do
18
+ spec = build_spec([["checksum", []]])
19
+ expect(spec.checksum).to be_nil
20
+ end
21
+
22
+ # The compact-index parser can emit a value-less entry (`["checksum"]`) rather
23
+ # than an empty array, depending on the RubyGems GemParser version. Both shapes
24
+ # reach this code, so both must be tolerated.
25
+ it "does not raise when the checksum entry has no value at all" do
26
+ expect { build_spec([["checksum"]]) }.not_to raise_error
27
+ end
28
+
29
+ it "ignores nil and blank metadata values" do
30
+ expect { build_spec([["checksum", nil], ["ruby", []]]) }.not_to raise_error
31
+ end
32
+
33
+ it "still parses a well-formed checksum" do
34
+ digest = "f" * 64
35
+ spec = build_spec([["checksum", [digest]]])
36
+ expect(spec.checksum).not_to be_nil
37
+ end
38
+
39
+ it "still parses ruby and rubygems requirements" do
40
+ spec = build_spec([["ruby", [">= 3.1"]], ["rubygems", [">= 3.0"]], ["checksum", []]])
41
+ expect(spec.required_ruby_version.to_s).to eq(">= 3.1")
42
+ expect(spec.required_rubygems_version.to_s).to eq(">= 3.0")
43
+ end
44
+
45
+ it "still raises on a genuinely malformed (non-empty) checksum" do
46
+ expect { build_spec([["checksum", ["not-a-valid-digest"]]]) }
47
+ .to raise_error(Bundler::GemspecError)
48
+ end
49
+ end
@@ -21,6 +21,7 @@ $LOAD_PATH.unshift(File.expand_path("../../spec_helpers", __dir__))
21
21
  require "definition_ruby_version_patch"
22
22
  require "definition_bundler_version_patch"
23
23
  require "git_source_patch"
24
+ require "endpoint_specification_metadata_patch"
24
25
 
25
26
  require "functions"
26
27
 
@@ -26,7 +26,9 @@ module Dependabot
26
26
  LOCKFILE_ENDING = /(?<ending>\s*(?:RUBY VERSION|BUNDLED WITH).*)/m
27
27
  GIT_DEPENDENCIES_SECTION = /GIT\n.*?\n\n(?!GIT)/m
28
28
  GIT_DEPENDENCY_DETAILS = /GIT\n.*?\n\n/m
29
- CHECKSUMS_SECTION = /(^CHECKSUMS\n)(?<entries>(?:^ .*\n)+)/m
29
+ # No `/m`: it would let the greedy `.*` in `entries` match newlines and
30
+ # swallow the trailing `BUNDLED WITH` section. `^` is line-anchored anyway.
31
+ CHECKSUMS_SECTION = /(^CHECKSUMS\n)(?<entries>(?:^ .*\n)+)/
30
32
  BUNDLED_WITH_VERSION_REGEX = /BUNDLED WITH\s+(?<version>\d+\.\d+\.\d+)/m
31
33
  BUNDLER_CHECKSUM_ENTRY_REGEX = /^ bundler \([^)]+\).*\n?$/
32
34
  MIN_BUNDLER_CHECKSUM_VERSION = Gem::Version.new("4.0.11")
@@ -226,6 +228,7 @@ module Dependabot
226
228
  def post_process_lockfile(lockfile_body)
227
229
  lockfile_body = reorder_git_dependencies(lockfile_body)
228
230
  lockfile_body = strip_new_bundler_checksum(lockfile_body)
231
+ lockfile_body = restore_bundler_checksum(lockfile_body)
229
232
  replace_lockfile_ending(lockfile_body)
230
233
  end
231
234
 
@@ -246,7 +249,9 @@ module Dependabot
246
249
  def should_strip_bundler_checksum?
247
250
  lockfile_content = T.must(lockfile).content
248
251
  return false unless lockfile_content&.include?("CHECKSUMS\n")
249
- return false if lockfile_content.match?(BUNDLER_CHECKSUM_ENTRY_REGEX)
252
+
253
+ checksums_section = lockfile_content.match(CHECKSUMS_SECTION)
254
+ return false if checksums_section && T.must(checksums_section[:entries]).match?(BUNDLER_CHECKSUM_ENTRY_REGEX)
250
255
 
251
256
  bundled_with = lockfile_content.match(BUNDLED_WITH_VERSION_REGEX)&.[](:version)
252
257
  return false unless bundled_with
@@ -255,6 +260,37 @@ module Dependabot
255
260
  bundled_with_version >= Gem::Version.new("4.0.0") && bundled_with_version < MIN_BUNDLER_CHECKSUM_VERSION
256
261
  end
257
262
 
263
+ sig { params(lockfile_body: String).returns(String) }
264
+ def restore_bundler_checksum(lockfile_body)
265
+ original_entry = original_bundler_checksum_entry
266
+ return lockfile_body unless original_entry
267
+
268
+ checksums_section = lockfile_body.match(CHECKSUMS_SECTION)
269
+ return lockfile_body unless checksums_section
270
+
271
+ entries = T.must(checksums_section[:entries])
272
+ return lockfile_body unless entries.match?(BUNDLER_CHECKSUM_ENTRY_REGEX)
273
+
274
+ restored_entries = entries.lines.map do |line|
275
+ line.match?(BUNDLER_CHECKSUM_ENTRY_REGEX) ? original_entry : line
276
+ end.join
277
+
278
+ lockfile_body.sub(CHECKSUMS_SECTION) { "CHECKSUMS\n#{restored_entries}" }
279
+ end
280
+
281
+ sig { returns(T.nilable(String)) }
282
+ def original_bundler_checksum_entry
283
+ checksums_section = T.must(lockfile).content&.match(CHECKSUMS_SECTION)
284
+ return nil unless checksums_section
285
+
286
+ entry = T.must(checksums_section[:entries]).lines.find do |line|
287
+ line.match?(BUNDLER_CHECKSUM_ENTRY_REGEX)
288
+ end
289
+ return nil unless entry
290
+
291
+ "#{entry.chomp}\n"
292
+ end
293
+
258
294
  sig { params(lockfile_body: String).returns(String) }
259
295
  def reorder_git_dependencies(lockfile_body)
260
296
  new_section = lockfile_body.match(GIT_DEPENDENCIES_SECTION)&.to_s
@@ -4,6 +4,7 @@
4
4
  require "sorbet-runtime"
5
5
 
6
6
  require "dependabot/bundler/update_checker"
7
+ require "dependabot/dependency_requirement"
7
8
  require "dependabot/requirements_update_strategy"
8
9
 
9
10
  module Dependabot
@@ -25,7 +26,7 @@ module Dependabot
25
26
 
26
27
  sig do
27
28
  params(
28
- requirements: T::Array[T::Hash[Symbol, T.untyped]],
29
+ requirements: T::Array[Dependabot::DependencyRequirement],
29
30
  update_strategy: Dependabot::RequirementsUpdateStrategy,
30
31
  updated_source: T.nilable(T::Hash[Symbol, T.untyped]),
31
32
  latest_version: T.nilable(String),
@@ -39,7 +40,10 @@ module Dependabot
39
40
  latest_version:,
40
41
  latest_resolvable_version:
41
42
  )
42
- @requirements = requirements
43
+ @requirements = T.let(
44
+ requirements.map { |req| Dependabot::DependencyRequirement.create(req) },
45
+ T::Array[Dependabot::DependencyRequirement]
46
+ )
43
47
  @latest_version = T.let(
44
48
  (T.cast(Dependabot::Bundler::Version.new(latest_version), Dependabot::Bundler::Version) if latest_version),
45
49
  T.nilable(Dependabot::Bundler::Version)
@@ -57,7 +61,7 @@ module Dependabot
57
61
  )
58
62
  end
59
63
 
60
- sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
64
+ sig { returns(T::Array[Dependabot::DependencyRequirement]) }
61
65
  def updated_requirements
62
66
  return requirements if update_strategy.lockfile_only?
63
67
 
@@ -74,7 +78,7 @@ module Dependabot
74
78
 
75
79
  private
76
80
 
77
- sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
81
+ sig { returns(T::Array[Dependabot::DependencyRequirement]) }
78
82
  attr_reader :requirements
79
83
 
80
84
  sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
@@ -96,9 +100,9 @@ module Dependabot
96
100
  raise "Unknown update strategy: #{update_strategy}"
97
101
  end
98
102
 
99
- sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
103
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
100
104
  def update_gemfile_requirement(req)
101
- req = req.merge(source: updated_source)
105
+ req = Dependabot::DependencyRequirement.create(req.merge(source: updated_source))
102
106
  return req unless latest_resolvable_version
103
107
 
104
108
  case update_strategy
@@ -110,14 +114,14 @@ module Dependabot
110
114
  end
111
115
  end
112
116
 
113
- sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
117
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
114
118
  def update_version_requirement_if_needed(req)
115
119
  return req if new_version_satisfies?(req)
116
120
 
117
121
  update_version_requirement(req)
118
122
  end
119
123
 
120
- sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
124
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
121
125
  def update_version_requirement(req)
122
126
  requirements =
123
127
  req[:requirement].split(",").map { |r| Gem::Requirement.new(r) }
@@ -131,10 +135,10 @@ module Dependabot
131
135
  update_gemfile_range(requirements).join(", ")
132
136
  end
133
137
 
134
- req.merge(requirement: new_requirement)
138
+ Dependabot::DependencyRequirement.create(req.merge(requirement: new_requirement))
135
139
  end
136
140
 
137
- sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Boolean) }
141
+ sig { params(req: Dependabot::DependencyRequirement).returns(T::Boolean) }
138
142
  def new_version_satisfies?(req)
139
143
  return false unless latest_resolvable_version
140
144
 
@@ -179,9 +183,9 @@ module Dependabot
179
183
  end
180
184
 
181
185
  # rubocop:disable Metrics/PerceivedComplexity
182
- sig { params(req: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
186
+ sig { params(req: Dependabot::DependencyRequirement).returns(Dependabot::DependencyRequirement) }
183
187
  def update_gemspec_requirement(req)
184
- req = req.merge(source: updated_source) if req.fetch(:source)
188
+ req = Dependabot::DependencyRequirement.create(req.merge(source: updated_source)) if req.fetch(:source)
185
189
  return req unless latest_version && latest_resolvable_version
186
190
 
187
191
  requirements =
@@ -202,9 +206,9 @@ module Dependabot
202
206
  end
203
207
 
204
208
  updated_requirements = binding_requirements(updated_requirements)
205
- req.merge(requirement: updated_requirements.join(", "))
209
+ Dependabot::DependencyRequirement.create(req.merge(requirement: updated_requirements.join(", ")))
206
210
  rescue UnfixableRequirement
207
- req.merge(requirement: :unfixable)
211
+ Dependabot::DependencyRequirement.create(req.merge(requirement: :unfixable))
208
212
  end
209
213
  # rubocop:enable Metrics/PerceivedComplexity
210
214
 
@@ -38,6 +38,12 @@ module Dependabot
38
38
  GIT_REGEX = /reset --hard [^\s]*` in directory (?<path>[^\s]*)/
39
39
  GIT_REF_REGEX = /not exist in the repository (?<path>[^\s]*)\./
40
40
  PATH_REGEX = /The path `(?<path>.*)` does not exist/
41
+ # Raised by Bundler when a registry's compact index (`/info/<gem>`) returns
42
+ # gem metadata it can't parse (e.g. an illformed `ruby:`/`rubygems:`
43
+ # requirement). This means the *registry* served bad data, not that the
44
+ # user's dependency files are broken.
45
+ REGISTRY_METADATA_ERROR_REGEX =
46
+ /error parsing the metadata for the gem (?<gem>\S+) \((?<version>[^)]+)\)/
41
47
 
42
48
  module BundlerErrorPatterns
43
49
  MISSING_AUTH_REGEX = /bundle config set --global (?<source>.*) username:password/
@@ -118,7 +124,7 @@ module Dependabot
118
124
  case error.error_class
119
125
  when "Bundler::Dsl::DSLError", "Bundler::GemspecError"
120
126
  # We couldn't evaluate the Gemfile, let alone resolve it
121
- raise Dependabot::DependencyFileNotEvaluatable, msg
127
+ raise registry_metadata_error(error) || Dependabot::DependencyFileNotEvaluatable.new(msg)
122
128
  when "Bundler::Source::Git::MissingGitRevisionError"
123
129
  match_data = error.message.match(GIT_REF_REGEX)
124
130
  gem_name = T.must(T.must(match_data).named_captures["path"])
@@ -196,6 +202,31 @@ module Dependabot
196
202
  # rubocop:enable Metrics/AbcSize
197
203
  # rubocop:enable Metrics/MethodLength
198
204
 
205
+ # When Bundler fails to parse gem metadata served by a registry's compact
206
+ # index, surface it as a private source error (the registry returned bad
207
+ # data) rather than blaming the user's dependency files. Returns nil when
208
+ # the error isn't a registry metadata error so the caller can fall back.
209
+ sig do
210
+ params(error: Dependabot::SharedHelpers::HelperSubprocessFailed)
211
+ .returns(T.nilable(Dependabot::PrivateSourceBadResponse))
212
+ end
213
+ def registry_metadata_error(error)
214
+ match = error.message.match(REGISTRY_METADATA_ERROR_REGEX)
215
+ return nil unless match
216
+
217
+ source = private_registry_source
218
+ return nil unless source
219
+
220
+ detail = "Invalid gem metadata returned for #{match[:gem]} (#{match[:version]}) " \
221
+ "by the source: #{source}"
222
+ Dependabot::PrivateSourceBadResponse.new(source, detail)
223
+ end
224
+
225
+ sig { returns(T.nilable(String)) }
226
+ def private_registry_source
227
+ private_registry_credentials.filter_map { |cred| cred["host"] }.first
228
+ end
229
+
199
230
  sig { returns(T::Array[T::Hash[String, T.untyped]]) }
200
231
  def inaccessible_git_dependencies
201
232
  in_a_native_bundler_context(error_handling: false) do |tmp_dir|
@@ -80,15 +80,13 @@ module Dependabot
80
80
  latest_version_for_req_updater = latest_version_details&.fetch(:version)&.to_s
81
81
  latest_resolvable_version_for_req_updater = preferred_resolvable_version_details&.fetch(:version)&.to_s
82
82
 
83
- wrap_requirements(
84
- RequirementsUpdater.new(
85
- requirements: dependency.requirements,
86
- update_strategy: T.must(requirements_update_strategy),
87
- updated_source: updated_source,
88
- latest_version: latest_version_for_req_updater,
89
- latest_resolvable_version: latest_resolvable_version_for_req_updater
90
- ).updated_requirements
91
- )
83
+ RequirementsUpdater.new(
84
+ requirements: dependency.requirements,
85
+ update_strategy: T.must(requirements_update_strategy),
86
+ updated_source: updated_source,
87
+ latest_version: latest_version_for_req_updater,
88
+ latest_resolvable_version: latest_resolvable_version_for_req_updater
89
+ ).updated_requirements
92
90
  end
93
91
 
94
92
  sig { returns(T::Boolean) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.382.0
4
+ version: 0.383.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.382.0
18
+ version: 0.383.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.382.0
25
+ version: 0.383.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: parallel
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -294,8 +294,10 @@ files:
294
294
  - helpers/v4/lib/functions/version_resolver.rb
295
295
  - helpers/v4/monkey_patches/definition_bundler_version_patch.rb
296
296
  - helpers/v4/monkey_patches/definition_ruby_version_patch.rb
297
+ - helpers/v4/monkey_patches/endpoint_specification_metadata_patch.rb
297
298
  - helpers/v4/monkey_patches/git_source_patch.rb
298
299
  - helpers/v4/run.rb
300
+ - helpers/v4/spec/bundler_endpoint_specification_metadata_patch_spec.rb
299
301
  - helpers/v4/spec/bundler_version_constraint_spec.rb
300
302
  - helpers/v4/spec/functions/conflicting_dependency_resolver_spec.rb
301
303
  - helpers/v4/spec/functions/dependency_source_spec.rb
@@ -349,7 +351,7 @@ licenses:
349
351
  - MIT
350
352
  metadata:
351
353
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
352
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.382.0
354
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.383.0
353
355
  rdoc_options: []
354
356
  require_paths:
355
357
  - lib