dependabot-gradle 0.312.0 → 0.313.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: 621540584e386591761665df154e0c16ff5335ffe212355fcb9075e5d8f9bfe2
4
- data.tar.gz: 55d0c4313b53bfe228973dcd660d6d0b719788a96531ab1bdec60e9ebe99bbe8
3
+ metadata.gz: 4226dd4d48a3901cf8ed98892067b901e1a9ab048e72596d939e9b7d4d416226
4
+ data.tar.gz: 102eabde5d58e0f40278382bfff3197cf31476ff37b53295be697033992fd1fd
5
5
  SHA512:
6
- metadata.gz: b29fc92a8d63fcff1c84491049698dd9b99a4003055f44d41ea82fa360d936ab0b8613252789638365520dff403a216b20b641bd0c6c63e961f09311069a3c82
7
- data.tar.gz: '08784e7dac80ac6f5f647d29b738bf9f013fc2a695ef340515a6260f11290b100212d96407249dfcf5960c5a20d14e9feaaeb41d6aa0775f693c7b34c802fd3c'
6
+ metadata.gz: e5de5020a56e21ceb8b6b6bbca4c755abea5ccabf012a82bbe22fd4bfb3586f4246b2262a5bae43869b21b7271f0c26d44130fbc49f69ae5d7039bf616494da0
7
+ data.tar.gz: ac6abd8571f5457ee58d7474eb4e79cf414a11ff9ee94adb57c8e8e27b610f792e6c4d9f21344d15129b8835b8c1dfb8d02fa056a19a07b531f53b9917463677
@@ -0,0 +1,368 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "nokogiri"
5
+ require "dependabot/shared_helpers"
6
+ require "dependabot/update_checkers/version_filters"
7
+ require "dependabot/gradle/file_parser/repositories_finder"
8
+ require "dependabot/gradle/update_checker"
9
+ require "dependabot/gradle/version"
10
+ require "dependabot/gradle/requirement"
11
+ require "dependabot/maven/utils/auth_headers_finder"
12
+ require "sorbet-runtime"
13
+ require "dependabot/gradle/metadata_finder"
14
+
15
+ module Dependabot
16
+ module Gradle
17
+ module Package
18
+ class PackageDetailsFetcher
19
+ extend T::Sig
20
+
21
+ CENTRAL_REPO_URL = "https://repo.maven.apache.org/maven2"
22
+ KOTLIN_PLUGIN_REPO_PREFIX = "org.jetbrains.kotlin"
23
+ TYPE_SUFFICES = %w(jre android java native_mt agp).freeze
24
+
25
+ sig do
26
+ params(
27
+ dependency: Dependabot::Dependency,
28
+ dependency_files: T::Array[Dependabot::DependencyFile],
29
+ credentials: T::Array[Dependabot::Credential],
30
+ forbidden_urls: T.nilable(T::Array[String])
31
+ ).void
32
+ end
33
+ def initialize(dependency:, dependency_files:, credentials:, forbidden_urls:)
34
+ @dependency = dependency
35
+ @dependency_files = dependency_files
36
+ @credentials = credentials
37
+ @forbidden_urls = forbidden_urls
38
+
39
+ @repositories = T.let(nil, T.nilable(T::Array[T::Hash[String, T.untyped]]))
40
+ @google_version_details = T.let(nil, T.nilable(T::Array[T::Hash[String, T.untyped]]))
41
+ @dependency_repository_details = T.let(nil, T.nilable(T::Array[T::Hash[String, T.untyped]]))
42
+ end
43
+
44
+ sig { returns(Dependabot::Dependency) }
45
+ attr_reader :dependency
46
+
47
+ sig { returns(T::Array[T.untyped]) }
48
+ attr_reader :dependency_files
49
+
50
+ sig { returns(T::Array[T.untyped]) }
51
+ attr_reader :credentials
52
+
53
+ sig { returns(T.nilable(T::Array[String])) }
54
+ attr_reader :forbidden_urls
55
+
56
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
57
+ sig do
58
+ returns(T::Array[T::Hash[String, T.untyped]])
59
+ end
60
+ def fetch_available_versions
61
+ release_date_info = T.let({}, T::Hash[String, T::Hash[Symbol, T.untyped]])
62
+ package_releases = T.let([], T::Array[T::Hash[String, T.untyped]])
63
+
64
+ version_details =
65
+ repositories.map do |repository_details|
66
+ url = repository_details.fetch("url")
67
+
68
+ next google_version_details if url == Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO
69
+
70
+ dependency_metadata(repository_details).css("versions > version")
71
+ .select { |node| version_class.correct?(node.content) }
72
+ .map { |node| version_class.new(node.content) }
73
+ .map do |version|
74
+ { version: version, source_url: url }
75
+ end
76
+ end.flatten.compact
77
+
78
+ version_details = version_details.sort_by { |details| details.fetch(:version) }
79
+ release_date_info = release_details
80
+
81
+ version_details.map do |info|
82
+ version = info[:version]&.to_s
83
+
84
+ package_releases << {
85
+ version: Gradle::Version.new(version),
86
+ released_at: release_date_info.none? ? nil : (release_date_info[version]&.fetch(:release_date) || nil),
87
+ source_url: info[:source_url]
88
+ }
89
+ end
90
+ if version_details.none? && T.must(forbidden_urls).any?
91
+ raise PrivateSourceAuthenticationFailure,
92
+ T.must(forbidden_urls).first
93
+ end
94
+ # version_details
95
+
96
+ package_releases
97
+ end
98
+ # rubocop:enable Metrics/AbcSize,Metrics/PerceivedComplexity
99
+
100
+ sig { returns(T::Hash[String, T::Hash[Symbol, T.untyped]]) }
101
+ def release_details
102
+ release_date_info = T.let({}, T::Hash[String, T::Hash[Symbol, T.untyped]])
103
+
104
+ begin
105
+ repositories.map do |repository_details|
106
+ url = repository_details.fetch("url")
107
+ next unless url == Gradle::FileParser::RepositoriesFinder::CENTRAL_REPO_URL
108
+
109
+ release_info_metadata(repository_details).css("a[title]").each do |link|
110
+ version_string = link["title"]
111
+ version = version_string.gsub(%r{/$}, "")
112
+ raw_date_text = link.next.text.strip.split("\n").last.strip
113
+
114
+ release_date = begin
115
+ Time.parse(raw_date_text)
116
+ rescue StandardError
117
+ nil
118
+ end
119
+
120
+ next unless version && version_class.correct?(version)
121
+
122
+ release_date_info[version] = {
123
+ release_date: release_date
124
+ }
125
+ end
126
+ end
127
+
128
+ release_date_info
129
+ rescue StandardError
130
+ Dependabot.logger.error("Failed to get release date")
131
+ {}
132
+ end
133
+ end
134
+
135
+ sig { returns(T::Array[T::Hash[String, T.untyped]]) }
136
+ def repositories
137
+ return @repositories if @repositories
138
+
139
+ details = if plugin?
140
+ T.must(plugin_repository_details) +
141
+ credentials_repository_details
142
+ else
143
+ dependency_repository_details +
144
+ credentials_repository_details
145
+ end
146
+
147
+ @repositories =
148
+ details.reject do |repo|
149
+ next if repo["auth_headers"]
150
+
151
+ # Reject this entry if an identical one with non-empty auth_headers exists
152
+ details.any? { |r| r["url"] == repo["url"] && r["auth_headers"] != {} }
153
+ end
154
+ end
155
+
156
+ sig { returns(T.any(T::Array[T::Hash[String, T.untyped]], NilClass)) }
157
+ def google_version_details
158
+ url = Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO
159
+ group_id, artifact_id = group_and_artifact_ids
160
+
161
+ dependency_metadata_url = "#{Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO}/" \
162
+ "#{T.must(group_id).tr('.', '/')}/" \
163
+ "group-index.xml"
164
+
165
+ @google_version_details ||=
166
+ begin
167
+ response = Dependabot::RegistryClient.get(url: dependency_metadata_url)
168
+ Nokogiri::XML(response.body)
169
+ end
170
+
171
+ xpath = "/#{group_id}/#{artifact_id}"
172
+ return unless @google_version_details.at_xpath(xpath)
173
+
174
+ @google_version_details.at_xpath(xpath)
175
+ .attributes.fetch("versions")
176
+ .value.split(",")
177
+ .select { |v| version_class.correct?(v) }
178
+ .map { |v| version_class.new(v) }
179
+ .map { |version| { version: version, source_url: url } }
180
+ rescue Nokogiri::XML::XPath::SyntaxError
181
+ nil
182
+ end
183
+
184
+ sig { params(repository_details: T::Hash[T.untyped, T.untyped]).returns(T.untyped) }
185
+ def dependency_metadata(repository_details)
186
+ @dependency_metadata ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped]))
187
+ @dependency_metadata[repository_details.hash] ||=
188
+ begin
189
+ response = Dependabot::RegistryClient.get(
190
+ url: dependency_metadata_url(repository_details.fetch("url")),
191
+ headers: repository_details.fetch("auth_headers")
192
+ )
193
+
194
+ check_response(response, repository_details.fetch("url"))
195
+ Nokogiri::XML(response.body)
196
+ rescue URI::InvalidURIError
197
+ Nokogiri::XML("")
198
+ rescue Excon::Error::Socket, Excon::Error::Timeout,
199
+ Excon::Error::TooManyRedirects
200
+ raise if central_repo_urls.include?(repository_details["url"])
201
+
202
+ Nokogiri::XML("")
203
+ end
204
+ end
205
+
206
+ sig { params(repository_details: T::Hash[T.untyped, T.untyped]).returns(T.untyped) }
207
+ def release_info_metadata(repository_details)
208
+ @release_info_metadata ||= T.let({}, T.nilable(T::Hash[T.untyped, T.untyped]))
209
+ @release_info_metadata[repository_details.hash] ||=
210
+ begin
211
+ response = Dependabot::RegistryClient.get(
212
+ url: dependency_metadata_url(repository_details.fetch("url")).gsub("maven-metadata.xml", ""),
213
+ headers: repository_details.fetch("auth_headers")
214
+ )
215
+
216
+ check_response(response, repository_details.fetch("url"))
217
+ Nokogiri::XML(response.body)
218
+ rescue URI::InvalidURIError
219
+ Nokogiri::XML("")
220
+ rescue Excon::Error::Socket, Excon::Error::Timeout,
221
+ Excon::Error::TooManyRedirects
222
+ raise if central_repo_urls.include?(repository_details["url"])
223
+
224
+ Nokogiri::XML("")
225
+ end
226
+ end
227
+
228
+ sig { returns(T.untyped) }
229
+ def repository_urls
230
+ plugin? ? plugin_repository_details : dependency_repository_details
231
+ end
232
+
233
+ sig { params(response: T.untyped, repository_url: T.untyped).returns(T.nilable(T::Array[T.untyped])) }
234
+ def check_response(response, repository_url)
235
+ return unless response.status == 401 || response.status == 403
236
+ return if T.must(@forbidden_urls).include?(repository_url)
237
+ return if central_repo_urls.include?(repository_url)
238
+
239
+ T.must(@forbidden_urls) << repository_url
240
+ end
241
+
242
+ sig { returns(T::Array[T.untyped]) }
243
+ def credentials_repository_details
244
+ credentials
245
+ .select { |cred| cred["type"] == "maven_repository" }
246
+ .map do |cred|
247
+ {
248
+ "url" => cred.fetch("url").gsub(%r{/+$}, ""),
249
+ "auth_headers" => auth_headers(cred.fetch("url").gsub(%r{/+$}, ""))
250
+ }
251
+ end
252
+ end
253
+
254
+ sig { returns(T::Array[T.untyped]) }
255
+ def dependency_repository_details
256
+ requirement_files =
257
+ dependency.requirements
258
+ .map { |r| r.fetch(:file) }
259
+ .map { |nm| dependency_files.find { |f| f.name == nm } }
260
+
261
+ @dependency_repository_details ||=
262
+ requirement_files.flat_map do |target_file|
263
+ Gradle::FileParser::RepositoriesFinder.new(
264
+ dependency_files: dependency_files,
265
+ target_dependency_file: target_file
266
+ ).repository_urls
267
+ .map do |url|
268
+ { "url" => url, "auth_headers" => {} }
269
+ end
270
+ end.uniq
271
+ end
272
+
273
+ sig { returns(T.nilable(T::Array[T::Hash[String, T.untyped]])) }
274
+ def plugin_repository_details
275
+ [{
276
+ "url" => Gradle::FileParser::RepositoriesFinder::GRADLE_PLUGINS_REPO,
277
+ "auth_headers" => {}
278
+ }] + dependency_repository_details
279
+ end
280
+
281
+ sig { params(comparison_version: T.untyped).returns(T::Boolean) }
282
+ def matches_dependency_version_type?(comparison_version)
283
+ return true unless dependency.version
284
+
285
+ current_type = T.must(dependency.version)
286
+ .gsub("native-mt", "native_mt")
287
+ .split(/[.\-]/)
288
+ .find do |type|
289
+ Dependabot::Gradle::UpdateChecker::VersionFinder::TYPE_SUFFICES.find { |s| type.include?(s) }
290
+ end
291
+
292
+ version_type = comparison_version.to_s
293
+ .gsub("native-mt", "native_mt")
294
+ .split(/[.\-]/)
295
+ .find do |type|
296
+ Dependabot::Gradle::UpdateChecker::VersionFinder::TYPE_SUFFICES.find { |s| type.include?(s) }
297
+ end
298
+
299
+ current_type == version_type
300
+ end
301
+
302
+ sig { returns(T::Array[T.untyped]) }
303
+ def pom
304
+ filename = T.must(dependency.requirements.first).fetch(:file)
305
+ dependency_files.find { |f| f.name == filename }
306
+ end
307
+
308
+ sig { params(repository_url: T.untyped).returns(String) }
309
+ def dependency_metadata_url(repository_url)
310
+ group_id, artifact_id = group_and_artifact_ids
311
+ group_id = "#{Dependabot::Gradle::MetadataFinder::KOTLIN_PLUGIN_REPO_PREFIX}.#{group_id}" if kotlin_plugin?
312
+
313
+ "#{repository_url}/" \
314
+ "#{T.must(group_id).tr('.', '/')}/" \
315
+ "#{artifact_id}/" \
316
+ "maven-metadata.xml"
317
+ end
318
+
319
+ sig { returns(T::Array[String]) }
320
+ def group_and_artifact_ids
321
+ if kotlin_plugin?
322
+ [dependency.name,
323
+ "#{Dependabot::Gradle::MetadataFinder::KOTLIN_PLUGIN_REPO_PREFIX}.#{dependency.name}.gradle.plugin"]
324
+ elsif plugin?
325
+ [dependency.name, "#{dependency.name}.gradle.plugin"]
326
+ else
327
+ dependency.name.split(":")
328
+ end
329
+ end
330
+
331
+ sig { returns(T::Boolean) }
332
+ def plugin?
333
+ dependency.requirements.any? { |r| r.fetch(:groups).include? "plugins" }
334
+ end
335
+
336
+ sig { returns(T.nilable(T::Boolean)) }
337
+ def kotlin_plugin?
338
+ plugin? && dependency.requirements.any? { |r| r.fetch(:groups).include? "kotlin" }
339
+ end
340
+
341
+ sig { returns(T::Array[String]) }
342
+ def central_repo_urls
343
+ central_url_without_protocol =
344
+ Gradle::FileParser::RepositoriesFinder::CENTRAL_REPO_URL
345
+ .gsub(%r{^.*://}, "")
346
+
347
+ %w(http:// https://).map { |p| p + central_url_without_protocol }
348
+ end
349
+
350
+ sig { returns(T.class_of(Dependabot::Version)) }
351
+ def version_class
352
+ dependency.version_class
353
+ end
354
+
355
+ sig { returns(Dependabot::Maven::Utils::AuthHeadersFinder) }
356
+ def auth_headers_finder
357
+ @auth_headers_finder ||= T.let(Dependabot::Maven::Utils::AuthHeadersFinder.new(credentials),
358
+ T.nilable(Dependabot::Maven::Utils::AuthHeadersFinder))
359
+ end
360
+
361
+ sig { params(maven_repo_url: String).returns(T::Hash[String, String]) }
362
+ def auth_headers(maven_repo_url)
363
+ auth_headers_finder.auth_headers(maven_repo_url)
364
+ end
365
+ end
366
+ end
367
+ end
368
+ end
@@ -1,4 +1,4 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "nokogiri"
@@ -10,43 +10,80 @@ require "dependabot/gradle/version"
10
10
  require "dependabot/gradle/requirement"
11
11
  require "dependabot/maven/utils/auth_headers_finder"
12
12
  require "sorbet-runtime"
13
+ require "dependabot/gradle/package/package_details_fetcher"
14
+ require "dependabot/package/package_latest_version_finder"
13
15
 
14
16
  module Dependabot
15
17
  module Gradle
16
18
  class UpdateChecker
17
- class VersionFinder
19
+ class VersionFinder < Dependabot::Package::PackageLatestVersionFinder
18
20
  extend T::Sig
19
21
 
20
22
  KOTLIN_PLUGIN_REPO_PREFIX = "org.jetbrains.kotlin"
21
23
  TYPE_SUFFICES = %w(jre android java native_mt agp).freeze
22
24
 
25
+ sig do
26
+ params(dependency: Dependabot::Dependency, dependency_files: T::Array[Dependabot::DependencyFile],
27
+ credentials: T::Array[Dependabot::Credential], ignored_versions: T::Array[String],
28
+ security_advisories: T::Array[Dependabot::SecurityAdvisory], raise_on_ignored: T::Boolean,
29
+ cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)).void
30
+ end
23
31
  def initialize(dependency:, dependency_files:, credentials:,
24
- ignored_versions:, raise_on_ignored: false,
25
- security_advisories:)
32
+ ignored_versions:,
33
+ security_advisories:, raise_on_ignored: false, cooldown_options: nil)
34
+ @security_advisories = security_advisories
26
35
  @dependency = dependency
27
36
  @dependency_files = dependency_files
28
37
  @credentials = credentials
29
- @ignored_versions = ignored_versions
30
38
  @raise_on_ignored = raise_on_ignored
31
- @security_advisories = security_advisories
32
- @forbidden_urls = []
39
+ @forbidden_urls = T.let([], T::Array[T.untyped])
40
+ @ignored_versions = ignored_versions
41
+
42
+ super(
43
+ dependency: dependency,
44
+ dependency_files: dependency_files,
45
+ credentials: credentials,
46
+ ignored_versions: ignored_versions,
47
+ security_advisories: security_advisories,
48
+ cooldown_options: cooldown_options,
49
+ raise_on_ignored: raise_on_ignored,
50
+ options: {}
51
+ )
33
52
  end
34
53
 
54
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
35
55
  def latest_version_details
36
- possible_versions = versions
56
+ possible_versions = package_release(versions)
37
57
 
38
- possible_versions = filter_prereleases(possible_versions)
58
+ possible_versions = filter_prerelease_versions(possible_versions)
39
59
  possible_versions = filter_date_based_versions(possible_versions)
40
60
  possible_versions = filter_version_types(possible_versions)
41
61
  possible_versions = filter_ignored_versions(possible_versions)
42
62
 
43
- possible_versions.last
63
+ possible_versions = filter_cooldown_versions(possible_versions)
64
+
65
+ return unless possible_versions.any?
66
+
67
+ version_max = possible_versions.max_by(&:version)&.version
68
+
69
+ url = possible_versions.select do |v| # rubocop:disable Performance/Detect
70
+ v.version.to_s == version_max.to_s
71
+ end.last&.url
72
+
73
+ { version: version_max,
74
+ source_url: url }
44
75
  end
45
76
 
77
+ sig { override.returns(T::Boolean) }
78
+ def cooldown_enabled?
79
+ Dependabot::Experiments.enabled?(:enable_cooldown_for_gradle)
80
+ end
81
+
82
+ sig { returns(T.nilable(T::Hash[T.untyped, T.untyped])) }
46
83
  def lowest_security_fix_version_details
47
- possible_versions = versions
84
+ possible_versions = package_release(versions)
48
85
 
49
- possible_versions = filter_prereleases(possible_versions)
86
+ possible_versions = filter_prerelease_versions(possible_versions)
50
87
  possible_versions = filter_date_based_versions(possible_versions)
51
88
  possible_versions = filter_version_types(possible_versions)
52
89
  possible_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(possible_versions,
@@ -54,51 +91,53 @@ module Dependabot
54
91
  possible_versions = filter_ignored_versions(possible_versions)
55
92
  possible_versions = filter_lower_versions(possible_versions)
56
93
 
57
- possible_versions.first
58
- end
94
+ return unless possible_versions.any?
59
95
 
60
- def versions
61
- version_details =
62
- repositories.map do |repository_details|
63
- url = repository_details.fetch("url")
64
- next google_version_details if url == Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO
96
+ version_min = possible_versions.min_by(&:version)&.version
65
97
 
66
- dependency_metadata(repository_details).css("versions > version")
67
- .select { |node| version_class.correct?(node.content) }
68
- .map { |node| version_class.new(node.content) }
69
- .map { |version| { version: version, source_url: url } }
70
- end.flatten.compact
98
+ url = possible_versions.select do |v| # rubocop:disable Performance/Detect
99
+ v.version.to_s == version_min.to_s
100
+ end.last&.url
71
101
 
72
- raise PrivateSourceAuthenticationFailure, forbidden_urls.first if version_details.none? && forbidden_urls.any?
102
+ { version: version_min,
103
+ source_url: url }
104
+ end
73
105
 
74
- version_details.sort_by { |details| details.fetch(:version) }
106
+ sig { returns(T.any(T::Array[T::Hash[String, T.untyped]], T::Array[T::Hash[Symbol, T.untyped]])) }
107
+ def versions
108
+ Package::PackageDetailsFetcher.new(
109
+ dependency: dependency,
110
+ dependency_files: dependency_files,
111
+ credentials: credentials,
112
+ forbidden_urls: forbidden_urls
113
+ ).fetch_available_versions
75
114
  end
76
115
 
77
116
  private
78
117
 
118
+ sig { returns(Dependabot::Dependency) }
79
119
  attr_reader :dependency
120
+
121
+ sig { returns(T::Array[T.untyped]) }
80
122
  attr_reader :dependency_files
123
+
124
+ sig { returns(T::Array[T.untyped]) }
81
125
  attr_reader :credentials
82
- attr_reader :ignored_versions
126
+
127
+ sig { returns(T.nilable(T::Array[String])) }
83
128
  attr_reader :forbidden_urls
84
- attr_reader :security_advisories
85
129
 
86
- sig { params(possible_versions: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
87
- def filter_prereleases(possible_versions)
88
- return possible_versions if wants_prerelease?
130
+ sig { returns(T::Array[String]) }
131
+ attr_reader :ignored_versions
89
132
 
90
- filtered = possible_versions.reject { |v| v.fetch(:version).prerelease? }
91
- if possible_versions.count > filtered.count
92
- Dependabot.logger.info("Filtered out #{possible_versions.count - filtered.count} pre-release versions")
93
- end
94
- filtered
95
- end
133
+ sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
134
+ attr_reader :security_advisories
96
135
 
97
136
  sig { params(possible_versions: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
98
137
  def filter_date_based_versions(possible_versions)
99
138
  return possible_versions if wants_date_based_version?
100
139
 
101
- filtered = possible_versions.reject { |v| v.fetch(:version) > version_class.new(1900) }
140
+ filtered = possible_versions.reject { |release| release.version > version_class.new(1900) }
102
141
  if possible_versions.count > filtered.count
103
142
  Dependabot.logger.info("Filtered out #{possible_versions.count - filtered.count} date-based versions")
104
143
  end
@@ -107,15 +146,39 @@ module Dependabot
107
146
 
108
147
  sig { params(possible_versions: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
109
148
  def filter_version_types(possible_versions)
110
- filtered = possible_versions.select { |v| matches_dependency_version_type?(v.fetch(:version)) }
149
+ filtered = possible_versions.select { |release| matches_dependency_version_type?(release.version) }
111
150
  if possible_versions.count > filtered.count
112
151
  diff = possible_versions.count - filtered.count
113
- classifier = dependency.version.split(/[.\-]/).last
152
+ classifier = T.must(dependency.version).split(/[.\-]/).last
114
153
  Dependabot.logger.info("Filtered out #{diff} non-#{classifier} classifier versions")
115
154
  end
116
155
  filtered
117
156
  end
118
157
 
158
+ sig { params(version: T::Array[T.untyped]).returns(T::Array[Dependabot::Package::PackageRelease]) }
159
+ def package_release(version)
160
+ package_releases = []
161
+
162
+ version.map do |info|
163
+ package_releases << Dependabot::Package::PackageRelease.new(
164
+ version: info[:version],
165
+ released_at: info[:released_at],
166
+ url: info[:source_url]
167
+ )
168
+ end
169
+ package_releases
170
+ end
171
+
172
+ sig { returns(Package::PackageDetailsFetcher) }
173
+ def package_details_fetcher
174
+ @package_details_fetcher ||= T.let(Package::PackageDetailsFetcher.new(
175
+ dependency: dependency,
176
+ dependency_files: dependency_files,
177
+ credentials: credentials,
178
+ forbidden_urls: []
179
+ ), T.nilable(Dependabot::Gradle::Package::PackageDetailsFetcher))
180
+ end
181
+
119
182
  sig { params(possible_versions: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
120
183
  def filter_ignored_versions(possible_versions)
121
184
  filtered = possible_versions
@@ -124,7 +187,7 @@ module Dependabot
124
187
  ignore_requirements = Gradle::Requirement.requirements_array(req)
125
188
  filtered =
126
189
  filtered
127
- .reject { |v| ignore_requirements.any? { |r| r.satisfied_by?(v.fetch(:version)) } }
190
+ .reject { |v| ignore_requirements.any? { |r| r.satisfied_by?(v.version) } }
128
191
  end
129
192
 
130
193
  if @raise_on_ignored && filter_lower_versions(filtered).empty? &&
@@ -140,149 +203,82 @@ module Dependabot
140
203
  filtered
141
204
  end
142
205
 
143
- sig { params(possible_versions: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
144
- def filter_lower_versions(possible_versions)
145
- return possible_versions unless dependency.numeric_version
146
-
147
- possible_versions.select do |v|
148
- v.fetch(:version) > dependency.numeric_version
149
- end
206
+ sig do
207
+ params(releases: T::Array[Dependabot::Package::PackageRelease])
208
+ .returns(T::Array[Dependabot::Package::PackageRelease])
150
209
  end
210
+ def filter_cooldown_versions(releases)
211
+ return releases unless cooldown_enabled?
151
212
 
152
- def wants_prerelease?
153
- return false unless dependency.numeric_version
213
+ Dependabot.logger.info("Initializing cooldown filter")
154
214
 
155
- dependency.numeric_version.prerelease?
156
- end
215
+ sorted_releases = releases.sort_by(&:version).reverse
157
216
 
158
- def wants_date_based_version?
159
- return false unless dependency.numeric_version
160
-
161
- dependency.numeric_version >= version_class.new(100)
162
- end
217
+ filtered_versions = []
218
+ cooldown_filtered_versions = 0
163
219
 
164
- def google_version_details
165
- url = Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO
166
- group_id, artifact_id = group_and_artifact_ids
220
+ # Iterate through the sorted versions lazily, filtering out cooldown versions
221
+ sorted_releases.each do |release|
222
+ if in_cooldown_period?(release)
223
+ Dependabot.logger.info("Filtered out (cooldown) : #{release}")
224
+ cooldown_filtered_versions += 1
167
225
 
168
- dependency_metadata_url = "#{Gradle::FileParser::RepositoriesFinder::GOOGLE_MAVEN_REPO}/" \
169
- "#{group_id.tr('.', '/')}/" \
170
- "group-index.xml"
171
-
172
- @google_version_details ||=
173
- begin
174
- response = Dependabot::RegistryClient.get(url: dependency_metadata_url)
175
- Nokogiri::XML(response.body)
226
+ next
176
227
  end
177
228
 
178
- xpath = "/#{group_id}/#{artifact_id}"
179
- return unless @google_version_details.at_xpath(xpath)
180
-
181
- @google_version_details.at_xpath(xpath)
182
- .attributes.fetch("versions")
183
- .value.split(",")
184
- .select { |v| version_class.correct?(v) }
185
- .map { |v| version_class.new(v) }
186
- .map { |version| { version: version, source_url: url } }
187
- rescue Nokogiri::XML::XPath::SyntaxError
188
- nil
189
- end
190
-
191
- def dependency_metadata(repository_details)
192
- @dependency_metadata ||= {}
193
- @dependency_metadata[repository_details.hash] ||=
194
- begin
195
- response = Dependabot::RegistryClient.get(
196
- url: dependency_metadata_url(repository_details.fetch("url")),
197
- headers: repository_details.fetch("auth_headers")
198
- )
199
- check_response(response, repository_details.fetch("url"))
200
- Nokogiri::XML(response.body)
201
- rescue URI::InvalidURIError
202
- Nokogiri::XML("")
203
- rescue Excon::Error::Socket, Excon::Error::Timeout,
204
- Excon::Error::TooManyRedirects
205
- raise if central_repo_urls.include?(repository_details["url"])
206
-
207
- Nokogiri::XML("")
208
- end
209
- end
229
+ filtered_versions << release
230
+ break
231
+ end
210
232
 
211
- def repository_urls
212
- plugin? ? plugin_repository_details : dependency_repository_details
233
+ Dependabot.logger.info("Filtered out #{cooldown_filtered_versions} version(s) due to cooldown")
234
+ filtered_versions
213
235
  end
214
236
 
215
- def check_response(response, repository_url)
216
- return unless response.status == 401 || response.status == 403
217
- return if @forbidden_urls.include?(repository_url)
218
- return if central_repo_urls.include?(repository_url)
219
-
220
- @forbidden_urls << repository_url
221
- end
237
+ sig { params(release: Dependabot::Package::PackageRelease).returns(T::Boolean) }
238
+ def in_cooldown_period?(release)
239
+ unless release.released_at
240
+ Dependabot.logger.info("Release date not available for version #{release.version}")
241
+ return false
242
+ end
222
243
 
223
- def repositories
224
- return @repositories if @repositories
244
+ current_version = version_class.correct?(dependency.version) ? version_class.new(dependency.version) : nil
245
+ days = cooldown_days_for(current_version, release.version)
225
246
 
226
- details = if plugin?
227
- plugin_repository_details +
228
- credentials_repository_details
229
- else
230
- dependency_repository_details +
231
- credentials_repository_details
232
- end
247
+ # Calculate the number of seconds passed since the release
248
+ passed_seconds = Time.now.to_i - release.released_at.to_i
249
+ passed_days = passed_seconds / DAY_IN_SECONDS
233
250
 
234
- @repositories =
235
- details.reject do |repo|
236
- next if repo["auth_headers"]
251
+ if passed_days < days
252
+ Dependabot.logger.info("Version #{release.version}, Release date: #{release.released_at}." \
253
+ " Days since release: #{passed_days} (cooldown days: #{days})")
254
+ end
237
255
 
238
- # Reject this entry if an identical one with non-empty auth_headers exists
239
- details.any? { |r| r["url"] == repo["url"] && r["auth_headers"] != {} }
240
- end
256
+ # Check if the release is within the cooldown period
257
+ passed_seconds < days * DAY_IN_SECONDS
241
258
  end
242
259
 
243
- def credentials_repository_details
244
- credentials
245
- .select { |cred| cred["type"] == "maven_repository" }
246
- .map do |cred|
247
- {
248
- "url" => cred.fetch("url").gsub(%r{/+$}, ""),
249
- "auth_headers" => auth_headers(cred.fetch("url").gsub(%r{/+$}, ""))
250
- }
251
- end
252
- end
260
+ sig { returns(T::Boolean) }
261
+ def wants_prerelease?
262
+ return false unless dependency.numeric_version
253
263
 
254
- def dependency_repository_details
255
- requirement_files =
256
- dependency.requirements
257
- .map { |r| r.fetch(:file) }
258
- .map { |nm| dependency_files.find { |f| f.name == nm } }
259
-
260
- @dependency_repository_details ||=
261
- requirement_files.flat_map do |target_file|
262
- Gradle::FileParser::RepositoriesFinder.new(
263
- dependency_files: dependency_files,
264
- target_dependency_file: target_file
265
- ).repository_urls
266
- .map do |url|
267
- { "url" => url, "auth_headers" => {} }
268
- end
269
- end.uniq
264
+ T.must(dependency.numeric_version).prerelease?
270
265
  end
271
266
 
272
- def plugin_repository_details
273
- [{
274
- "url" => Gradle::FileParser::RepositoriesFinder::GRADLE_PLUGINS_REPO,
275
- "auth_headers" => {}
276
- }] + dependency_repository_details
267
+ sig { returns(T::Boolean) }
268
+ def wants_date_based_version?
269
+ return false unless dependency.numeric_version
270
+
271
+ T.must(dependency.numeric_version) >= version_class.new(100)
277
272
  end
278
273
 
274
+ sig { params(comparison_version: T.untyped).returns(T::Boolean) }
279
275
  def matches_dependency_version_type?(comparison_version)
280
276
  return true unless dependency.version
281
277
 
282
- current_type = dependency.version
283
- .gsub("native-mt", "native_mt")
284
- .split(/[.\-]/)
285
- .find do |type|
278
+ current_type = T.must(dependency.version)
279
+ .gsub("native-mt", "native_mt")
280
+ .split(/[.\-]/)
281
+ .find do |type|
286
282
  TYPE_SUFFICES.find { |s| type.include?(s) }
287
283
  end
288
284
 
@@ -296,58 +292,12 @@ module Dependabot
296
292
  current_type == version_type
297
293
  end
298
294
 
299
- def pom
300
- filename = dependency.requirements.first.fetch(:file)
301
- dependency_files.find { |f| f.name == filename }
302
- end
303
-
304
- def dependency_metadata_url(repository_url)
305
- group_id, artifact_id = group_and_artifact_ids
306
- group_id = "#{KOTLIN_PLUGIN_REPO_PREFIX}.#{group_id}" if kotlin_plugin?
307
-
308
- "#{repository_url}/" \
309
- "#{group_id.tr('.', '/')}/" \
310
- "#{artifact_id}/" \
311
- "maven-metadata.xml"
312
- end
313
-
314
- def group_and_artifact_ids
315
- if kotlin_plugin?
316
- [dependency.name, "#{KOTLIN_PLUGIN_REPO_PREFIX}.#{dependency.name}.gradle.plugin"]
317
- elsif plugin?
318
- [dependency.name, "#{dependency.name}.gradle.plugin"]
319
- else
320
- dependency.name.split(":")
321
- end
322
- end
323
-
324
- def plugin?
325
- dependency.requirements.any? { |r| r.fetch(:groups).include? "plugins" }
326
- end
327
-
328
- def kotlin_plugin?
329
- plugin? && dependency.requirements.any? { |r| r.fetch(:groups).include? "kotlin" }
330
- end
331
-
332
- def central_repo_urls
333
- central_url_without_protocol =
334
- Gradle::FileParser::RepositoriesFinder::CENTRAL_REPO_URL
335
- .gsub(%r{^.*://}, "")
336
-
337
- %w(http:// https://).map { |p| p + central_url_without_protocol }
338
- end
339
-
295
+ sig { returns(T.class_of(Dependabot::Version)) }
340
296
  def version_class
341
297
  dependency.version_class
342
298
  end
343
-
344
- def auth_headers_finder
345
- @auth_headers_finder ||= Dependabot::Maven::Utils::AuthHeadersFinder.new(credentials)
346
- end
347
-
348
- def auth_headers(maven_repo_url)
349
- auth_headers_finder.auth_headers(maven_repo_url)
350
- end
299
+ sig { override.returns(T.nilable(Dependabot::Package::PackageDetails)) }
300
+ def package_details; end
351
301
  end
352
302
  end
353
303
  end
@@ -123,6 +123,7 @@ module Dependabot
123
123
  credentials: credentials,
124
124
  ignored_versions: ignored_versions,
125
125
  raise_on_ignored: raise_on_ignored,
126
+ cooldown_options: update_cooldown,
126
127
  security_advisories: security_advisories
127
128
  )
128
129
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-gradle
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.312.0
4
+ version: 0.313.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-09 00:00:00.000000000 Z
10
+ date: 2025-05-15 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dependabot-common
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.312.0
18
+ version: 0.313.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.312.0
25
+ version: 0.313.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dependabot-maven
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.312.0
32
+ version: 0.313.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.312.0
39
+ version: 0.313.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -266,6 +266,7 @@ files:
266
266
  - lib/dependabot/gradle/file_updater/property_value_updater.rb
267
267
  - lib/dependabot/gradle/language.rb
268
268
  - lib/dependabot/gradle/metadata_finder.rb
269
+ - lib/dependabot/gradle/package/package_details_fetcher.rb
269
270
  - lib/dependabot/gradle/package_manager.rb
270
271
  - lib/dependabot/gradle/requirement.rb
271
272
  - lib/dependabot/gradle/update_checker.rb
@@ -278,7 +279,7 @@ licenses:
278
279
  - MIT
279
280
  metadata:
280
281
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
281
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.312.0
282
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.313.0
282
283
  rdoc_options: []
283
284
  require_paths:
284
285
  - lib