dependabot-bundler 0.333.0 → 0.335.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 (25) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/v2/lib/functions/force_updater.rb +7 -2
  3. data/helpers/v2/lib/functions/version_resolver.rb +6 -2
  4. data/helpers/v2/spec/functions/conflicting_dependency_resolver_spec.rb +64 -52
  5. data/helpers/v2/spec/functions/dependency_source_spec.rb +14 -10
  6. data/helpers/v2/spec/functions/version_resolver_spec.rb +4 -2
  7. data/lib/dependabot/bundler/file_fetcher.rb +12 -6
  8. data/lib/dependabot/bundler/file_parser.rb +4 -2
  9. data/lib/dependabot/bundler/file_updater/gemspec_updater.rb +13 -6
  10. data/lib/dependabot/bundler/file_updater/git_pin_replacer.rb +27 -6
  11. data/lib/dependabot/bundler/file_updater/git_source_remover.rb +14 -2
  12. data/lib/dependabot/bundler/file_updater/lockfile_updater.rb +90 -26
  13. data/lib/dependabot/bundler/file_updater/requirement_replacer.rb +92 -17
  14. data/lib/dependabot/bundler/file_updater.rb +48 -19
  15. data/lib/dependabot/bundler/metadata_finder.rb +61 -30
  16. data/lib/dependabot/bundler/package/package_details_fetcher.rb +60 -2
  17. data/lib/dependabot/bundler/requirement.rb +3 -2
  18. data/lib/dependabot/bundler/update_checker/file_preparer.rb +81 -25
  19. data/lib/dependabot/bundler/update_checker/force_updater.rb +11 -5
  20. data/lib/dependabot/bundler/update_checker/latest_version_finder/dependency_source.rb +53 -15
  21. data/lib/dependabot/bundler/update_checker/latest_version_finder.rb +4 -2
  22. data/lib/dependabot/bundler/update_checker/requirements_updater.rb +92 -31
  23. data/lib/dependabot/bundler/update_checker/version_resolver.rb +14 -7
  24. data/lib/dependabot/bundler/update_checker.rb +14 -7
  25. metadata +12 -12
@@ -1,6 +1,8 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  require "dependabot/file_updaters"
5
7
  require "dependabot/file_updaters/base"
6
8
  require "dependabot/bundler/native_helpers"
@@ -10,10 +12,13 @@ require "dependabot/file_updaters/vendor_updater"
10
12
  module Dependabot
11
13
  module Bundler
12
14
  class FileUpdater < Dependabot::FileUpdaters::Base
15
+ extend T::Sig
16
+
13
17
  require_relative "file_updater/gemfile_updater"
14
18
  require_relative "file_updater/gemspec_updater"
15
19
  require_relative "file_updater/lockfile_updater"
16
20
 
21
+ sig { override.returns(T::Array[Regexp]) }
17
22
  def self.updated_files_regex
18
23
  [
19
24
  # Matches Gemfile, Gemfile.lock, gems.rb, gems.locked, .gemspec files, and anything in vendor directory
@@ -25,20 +30,21 @@ module Dependabot
25
30
 
26
31
  # rubocop:disable Metrics/PerceivedComplexity
27
32
  # rubocop:disable Metrics/AbcSize
33
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
28
34
  def updated_dependency_files
29
35
  updated_files = T.let([], T::Array[Dependabot::DependencyFile])
30
36
 
31
- if gemfile && file_changed?(gemfile)
37
+ if gemfile && file_changed?(T.must(gemfile))
32
38
  updated_files <<
33
39
  updated_file(
34
- file: gemfile,
35
- content: updated_gemfile_content(gemfile)
40
+ file: T.must(gemfile),
41
+ content: updated_gemfile_content(T.must(gemfile))
36
42
  )
37
43
  end
38
44
 
39
45
  if lockfile && dependencies.any?(&:appears_in_lockfile?)
40
46
  updated_files <<
41
- updated_file(file: lockfile, content: updated_lockfile_content)
47
+ updated_file(file: T.must(lockfile), content: updated_lockfile_content)
42
48
  end
43
49
 
44
50
  top_level_gemspecs.each do |file|
@@ -59,7 +65,7 @@ module Dependabot
59
65
 
60
66
  base_dir = T.must(updated_files.first).directory
61
67
  vendor_updater
62
- .updated_vendor_cache_files(base_directory: base_dir)
68
+ .updated_files(base_directory: base_dir)
63
69
  .each do |file|
64
70
  updated_files << file
65
71
  end
@@ -72,10 +78,9 @@ module Dependabot
72
78
  private
73
79
 
74
80
  # Dynamically fetch the vendor cache folder from bundler
81
+ sig { returns(T.nilable(String)) }
75
82
  def vendor_cache_dir
76
- return @vendor_cache_dir if defined?(@vendor_cache_dir)
77
-
78
- @vendor_cache_dir =
83
+ @vendor_cache_dir = T.let(
79
84
  NativeHelpers.run_bundler_subprocess(
80
85
  bundler_version: bundler_version,
81
86
  function: "vendor_cache_dir",
@@ -83,9 +88,12 @@ module Dependabot
83
88
  args: {
84
89
  dir: repo_contents_path
85
90
  }
86
- )
91
+ ),
92
+ T.nilable(String)
93
+ )
87
94
  end
88
95
 
96
+ sig { returns(Dependabot::FileUpdaters::VendorUpdater) }
89
97
  def vendor_updater
90
98
  Dependabot::FileUpdaters::VendorUpdater.new(
91
99
  repo_contents_path: repo_contents_path,
@@ -93,6 +101,7 @@ module Dependabot
93
101
  )
94
102
  end
95
103
 
104
+ sig { override.void }
96
105
  def check_required_files
97
106
  file_names = dependency_files.map(&:name)
98
107
 
@@ -104,24 +113,32 @@ module Dependabot
104
113
  raise "A gemspec or Gemfile must be provided!"
105
114
  end
106
115
 
116
+ sig { params(updated_files: T::Array[Dependabot::DependencyFile]).void }
107
117
  def check_updated_files(updated_files)
108
118
  return if updated_files.reject { |f| dependency_files.include?(f) }.any?
109
119
 
110
120
  raise "No files have changed!"
111
121
  end
112
122
 
123
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
113
124
  def gemfile
114
- @gemfile ||= get_original_file("Gemfile") ||
115
- get_original_file("gems.rb")
125
+ @gemfile ||= T.let(
126
+ get_original_file("Gemfile") || get_original_file("gems.rb"),
127
+ T.nilable(Dependabot::DependencyFile)
128
+ )
116
129
  end
117
130
 
131
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
118
132
  def lockfile
119
- @lockfile ||= get_original_file("Gemfile.lock") ||
120
- get_original_file("gems.locked")
133
+ @lockfile ||= T.let(
134
+ get_original_file("Gemfile.lock") || get_original_file("gems.locked"),
135
+ T.nilable(Dependabot::DependencyFile)
136
+ )
121
137
  end
122
138
 
139
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
123
140
  def evaled_gemfiles
124
- @evaled_gemfiles ||=
141
+ @evaled_gemfiles ||= T.let(
125
142
  dependency_files
126
143
  .reject { |f| f.name.end_with?(".gemspec") }
127
144
  .reject { |f| f.name.end_with?(".specification") }
@@ -129,9 +146,12 @@ module Dependabot
129
146
  .reject { |f| f.name == "Gemfile" }
130
147
  .reject { |f| f.name == "gems.rb" }
131
148
  .reject { |f| f.name == "gems.locked" }
132
- .reject(&:support_file?)
149
+ .reject(&:support_file?),
150
+ T.nilable(T::Array[Dependabot::DependencyFile])
151
+ )
133
152
  end
134
153
 
154
+ sig { params(file: Dependabot::DependencyFile).returns(String) }
135
155
  def updated_gemfile_content(file)
136
156
  GemfileUpdater.new(
137
157
  dependencies: dependencies,
@@ -139,6 +159,7 @@ module Dependabot
139
159
  ).updated_gemfile_content
140
160
  end
141
161
 
162
+ sig { params(gemspec: Dependabot::DependencyFile).returns(String) }
142
163
  def updated_gemspec_content(gemspec)
143
164
  GemspecUpdater.new(
144
165
  dependencies: dependencies,
@@ -146,24 +167,32 @@ module Dependabot
146
167
  ).updated_gemspec_content
147
168
  end
148
169
 
170
+ sig { returns(String) }
149
171
  def updated_lockfile_content
150
- @updated_lockfile_content ||=
172
+ @updated_lockfile_content ||= T.let(
151
173
  LockfileUpdater.new(
152
174
  dependencies: dependencies,
153
175
  dependency_files: dependency_files,
154
176
  repo_contents_path: repo_contents_path,
155
177
  credentials: credentials,
156
178
  options: options
157
- ).updated_lockfile_content
179
+ ).updated_lockfile_content,
180
+ T.nilable(String)
181
+ )
158
182
  end
159
183
 
184
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
160
185
  def top_level_gemspecs
161
186
  dependency_files
162
187
  .select { |file| file.name.end_with?(".gemspec") }
163
188
  end
164
189
 
190
+ sig { returns(String) }
165
191
  def bundler_version
166
- @bundler_version ||= Helpers.bundler_version(lockfile)
192
+ @bundler_version ||= T.let(
193
+ Helpers.bundler_version(lockfile),
194
+ T.nilable(String)
195
+ )
167
196
  end
168
197
  end
169
198
  end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
@@ -9,17 +9,36 @@ require "dependabot/registry_client"
9
9
  module Dependabot
10
10
  module Bundler
11
11
  class MetadataFinder < Dependabot::MetadataFinders::Base
12
- SOURCE_KEYS = %w(
13
- source_code_uri
14
- homepage_uri
15
- wiki_uri
16
- bug_tracker_uri
17
- documentation_uri
18
- changelog_uri
19
- mailing_list_uri
20
- download_uri
21
- ).freeze
12
+ extend T::Sig
13
+
14
+ SOURCE_KEYS = T.let(
15
+ %w(
16
+ source_code_uri
17
+ homepage_uri
18
+ wiki_uri
19
+ bug_tracker_uri
20
+ documentation_uri
21
+ changelog_uri
22
+ mailing_list_uri
23
+ download_uri
24
+ ).freeze,
25
+ T::Array[String]
26
+ )
27
+
28
+ sig do
29
+ params(
30
+ dependency: Dependabot::Dependency,
31
+ credentials: T::Array[Dependabot::Credential]
32
+ ).void
33
+ end
34
+ def initialize(dependency:, credentials:)
35
+ super
36
+ @rubygems_marshalled_gemspec_response = T.let(nil, T.nilable(String))
37
+ @rubygems_api_response = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
38
+ @base_url = T.let(nil, T.nilable(String))
39
+ end
22
40
 
41
+ sig { override.returns(T.nilable(String)) }
23
42
  def homepage_url
24
43
  return super unless %w(default rubygems).include?(new_source_type)
25
44
  return super unless rubygems_api_response["homepage_uri"]
@@ -29,6 +48,7 @@ module Dependabot
29
48
 
30
49
  private
31
50
 
51
+ sig { override.returns(T.nilable(Dependabot::Source)) }
32
52
  def look_up_source
33
53
  case new_source_type
34
54
  when "git" then find_source_from_git_url
@@ -37,6 +57,7 @@ module Dependabot
37
57
  end
38
58
  end
39
59
 
60
+ sig { override.returns(T.nilable(String)) }
40
61
  def suggested_changelog_url
41
62
  case new_source_type
42
63
  when "default"
@@ -50,10 +71,12 @@ module Dependabot
50
71
  end
51
72
  end
52
73
 
74
+ sig { returns(String) }
53
75
  def new_source_type
54
- dependency.source_type
76
+ T.must(dependency.source_type)
55
77
  end
56
78
 
79
+ sig { returns(T.nilable(Dependabot::Source)) }
57
80
  def find_source_from_rubygems
58
81
  api_source = find_source_from_rubygems_api_response
59
82
  return api_source if api_source || new_source_type == "default"
@@ -61,15 +84,16 @@ module Dependabot
61
84
  find_source_from_gemspec_download
62
85
  end
63
86
 
87
+ sig { returns(T.nilable(Dependabot::Source)) }
64
88
  def find_source_from_rubygems_api_response
65
- source_url = rubygems_api_response
66
- .values_at(*SOURCE_KEYS)
67
- .compact
68
- .find { |url| Source.from_url(url) }
89
+ api_response = rubygems_api_response
90
+ source_url = SOURCE_KEYS.filter_map { |key| api_response[key] }
91
+ .find { |url| Source.from_url(url) }
69
92
 
70
93
  Source.from_url(source_url)
71
94
  end
72
95
 
96
+ sig { returns(T.nilable(Dependabot::Source)) }
73
97
  def find_source_from_git_url
74
98
  info = dependency.requirements.filter_map { |r| r[:source] }.first
75
99
 
@@ -77,12 +101,13 @@ module Dependabot
77
101
  Source.from_url(url)
78
102
  end
79
103
 
104
+ sig { returns(T.nilable(Dependabot::Source)) }
80
105
  def find_source_from_gemspec_download
81
106
  github_urls = []
82
107
  return unless rubygems_marshalled_gemspec_response
83
108
 
84
- rubygems_marshalled_gemspec_response.gsub("\x06;", "\n")
85
- .scan(Source::SOURCE_REGEX) do
109
+ T.must(rubygems_marshalled_gemspec_response).gsub("\x06;", "\n")
110
+ .scan(Source::SOURCE_REGEX) do
86
111
  github_urls << Regexp.last_match.to_s
87
112
  end
88
113
 
@@ -95,12 +120,13 @@ module Dependabot
95
120
  Source.from_url(source_url)
96
121
  end
97
122
 
123
+ sig { returns(T.nilable(String)) }
98
124
  def changelog_url_from_gemspec_download
99
125
  github_urls = []
100
126
  return unless rubygems_marshalled_gemspec_response
101
127
 
102
- rubygems_marshalled_gemspec_response.gsub("\x06;", "\n")
103
- .scan(Dependabot::Source::SOURCE_REGEX) do
128
+ T.must(rubygems_marshalled_gemspec_response).gsub("\x06;", "\n")
129
+ .scan(Dependabot::Source::SOURCE_REGEX) do
104
130
  github_urls << (Regexp.last_match.to_s +
105
131
  T.must(T.must(Regexp.last_match).post_match.split("\n").first))
106
132
  end
@@ -115,8 +141,9 @@ module Dependabot
115
141
 
116
142
  # NOTE: This response MUST NOT be unmarshalled
117
143
  # (as calling Marshal.load is unsafe)
144
+ sig { returns(T.nilable(String)) }
118
145
  def rubygems_marshalled_gemspec_response
119
- return @rubygems_marshalled_gemspec_response if defined?(@rubygems_marshalled_gemspec_response)
146
+ return @rubygems_marshalled_gemspec_response if @rubygems_marshalled_gemspec_response
120
147
 
121
148
  gemspec_uri =
122
149
  "#{registry_url}quick/Marshal.4.8/" \
@@ -136,8 +163,9 @@ module Dependabot
136
163
  @rubygems_marshalled_gemspec_response = nil
137
164
  end
138
165
 
166
+ sig { returns(T::Hash[String, T.untyped]) }
139
167
  def rubygems_api_response
140
- return @rubygems_api_response if defined?(@rubygems_api_response)
168
+ return @rubygems_api_response if @rubygems_api_response
141
169
 
142
170
  response =
143
171
  Dependabot::RegistryClient.get(
@@ -155,16 +183,18 @@ module Dependabot
155
183
  @rubygems_api_response = {}
156
184
  end
157
185
 
186
+ sig { params(listing: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]) }
158
187
  def append_slash_to_source_code_uri(listing)
159
188
  # We have to do this so that `Source.from_url(...)` doesn't prune the
160
189
  # last line off of the directory.
161
- return listing unless listing&.fetch("source_code_uri", nil)
162
- return listing if listing.fetch("source_code_uri").end_with?("/")
190
+ return listing unless listing.fetch("source_code_uri", nil)
191
+ return listing if T.cast(listing.fetch("source_code_uri"), String).end_with?("/")
163
192
 
164
- listing["source_code_uri"] = listing["source_code_uri"] + "/"
193
+ listing["source_code_uri"] = T.cast(listing["source_code_uri"], String) + "/"
165
194
  listing
166
195
  end
167
196
 
197
+ sig { params(response_body: String).returns(String) }
168
198
  def augment_private_response_if_appropriate(response_body)
169
199
  return response_body if new_source_type == "default"
170
200
 
@@ -173,10 +203,8 @@ module Dependabot
173
203
 
174
204
  digest = parsed_body.values_at("version", "authors", "info").hash
175
205
 
176
- source_url = parsed_body
177
- .values_at(*SOURCE_KEYS)
178
- .compact
179
- .find { |url| Source.from_url(url) }
206
+ source_url = SOURCE_KEYS.filter_map { |key| parsed_body[key] }
207
+ .find { |url| Source.from_url(url) }
180
208
  return response_body if source_url
181
209
 
182
210
  rubygems_response =
@@ -190,6 +218,7 @@ module Dependabot
190
218
  response_body
191
219
  end
192
220
 
221
+ sig { returns(String) }
193
222
  def registry_url
194
223
  return base_url if new_source_type == "default"
195
224
 
@@ -197,8 +226,9 @@ module Dependabot
197
226
  info[:url] || info.fetch("url")
198
227
  end
199
228
 
229
+ sig { returns(String) }
200
230
  def base_url
201
- return @base_url if defined?(@base_url)
231
+ return @base_url if @base_url
202
232
 
203
233
  credential = credentials.find do |cred|
204
234
  cred["type"] == "rubygems_server" && cred.replaces_base?
@@ -207,6 +237,7 @@ module Dependabot
207
237
  @base_url = "https://#{host}#{'/' unless host&.end_with?('/')}"
208
238
  end
209
239
 
240
+ sig { returns(T::Hash[String, String]) }
210
241
  def registry_auth_headers
211
242
  return {} unless new_source_type == "rubygems"
212
243
 
@@ -143,7 +143,9 @@ module Dependabot
143
143
  rescue URI::InvalidURIError
144
144
  raise "Invalid registry URL: #{registry_url}"
145
145
  end
146
- return package_details([]) if parsed_url.host == "rubygems.pkg.github.com"
146
+
147
+ # Handle GitHub Package Registry
148
+ return github_packages_versions(registry_url) if parsed_url.host == "rubygems.pkg.github.com"
147
149
 
148
150
  response = registry_json_response_for_dependency(registry_url)
149
151
 
@@ -193,6 +195,61 @@ module Dependabot
193
195
  )
194
196
  end
195
197
 
198
+ sig { params(registry_url: String).returns(Dependabot::Package::PackageDetails) }
199
+ def github_packages_versions(registry_url)
200
+ # Extract org name from URL like "https://rubygems.pkg.github.com/dsp-testing/"
201
+ org_name = registry_url.split("/").last
202
+
203
+ # GitHub Packages API endpoint for RubyGems packages
204
+ api_url = "https://api.github.com/orgs/#{org_name}/packages/rubygems/#{dependency.name}/versions"
205
+
206
+ response = Dependabot::RegistryClient.get(
207
+ url: api_url,
208
+ headers: {
209
+ "Accept" => "application/vnd.github.v3+json",
210
+ "Authorization" => "Bearer #{github_token}"
211
+ }
212
+ )
213
+
214
+ unless response.status == 200
215
+ error_details = "Status: #{response.status}"
216
+ error_details += " (Package not found in GitHub Registry)" if response.status == 404
217
+ error_message = "Failed to fetch versions for '#{dependency.name}' from GitHub Packages. #{error_details}"
218
+ Dependabot.logger.info(error_message)
219
+ return package_details([])
220
+ end
221
+
222
+ begin
223
+ versions_data = JSON.parse(response.body)
224
+ package_releases = versions_data.map do |version_info|
225
+ # GitHub Packages API returns different structure than RubyGems
226
+ version_number = version_info["name"] # GitHub uses "name" for version
227
+ created_at = version_info["created_at"]
228
+
229
+ package_release(
230
+ version: version_number,
231
+ released_at: Time.parse(created_at),
232
+ downloads: 0, # GitHub Packages doesn't provide download counts
233
+ url: "#{registry_url}/gems/#{dependency.name}-#{version_number}.gem",
234
+ ruby_version: nil # GitHub Packages API doesn't provide ruby version requirements
235
+ )
236
+ end
237
+
238
+ package_details(package_releases)
239
+ rescue JSON::ParserError => e
240
+ Dependabot.logger.info("Failed to parse GitHub Packages response: #{e.message}")
241
+ package_details([])
242
+ end
243
+ end
244
+
245
+ sig { returns(T.nilable(String)) }
246
+ def github_token
247
+ github_credential = credentials.find do |cred|
248
+ cred["type"] == "rubygems_server" && cred["host"] == "rubygems.pkg.github.com"
249
+ end
250
+ github_credential&.fetch("token", nil)
251
+ end
252
+
196
253
  sig { params(req_string: String).returns(Requirement) }
197
254
  def language_requirement(req_string)
198
255
  Requirement.new(req_string)
@@ -222,7 +279,8 @@ module Dependabot
222
279
  Dependabot::Package::PackageDetails.new(
223
280
  dependency: dependency,
224
281
  releases: releases.reverse.uniq(&:version)
225
- ), T.nilable(Dependabot::Package::PackageDetails)
282
+ ),
283
+ T.nilable(Dependabot::Package::PackageDetails)
226
284
  )
227
285
  end
228
286
 
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "sorbet-runtime"
@@ -26,9 +26,10 @@ module Dependabot
26
26
 
27
27
  # Patches Gem::Requirement to make it accept requirement strings like
28
28
  # "~> 4.2.5, >= 4.2.5.1" without first needing to split them.
29
+ sig { params(requirements: T.nilable(T.any(String, T::Array[String]))).void }
29
30
  def initialize(*requirements)
30
31
  requirements = requirements.flatten.flat_map do |req_string|
31
- req_string.split(",").map(&:strip)
32
+ T.must(req_string).split(",").map(&:strip)
32
33
  end
33
34
 
34
35
  super(requirements)