dependabot-maven 0.308.0 → 0.309.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 +4 -4
- data/lib/dependabot/maven/file_parser/property_value_finder.rb +7 -1
- data/lib/dependabot/maven/file_parser/repositories_finder.rb +64 -10
- data/lib/dependabot/maven/file_parser.rb +1 -1
- data/lib/dependabot/maven/file_updater/declaration_finder.rb +43 -11
- data/lib/dependabot/maven/file_updater/property_value_updater.rb +1 -2
- data/lib/dependabot/maven/file_updater.rb +80 -18
- data/lib/dependabot/maven/metadata_finder.rb +2 -0
- data/lib/dependabot/maven/package/package_details_fetcher.rb +485 -0
- data/lib/dependabot/maven/requirement.rb +32 -15
- data/lib/dependabot/maven/update_checker/property_updater.rb +79 -24
- data/lib/dependabot/maven/update_checker/version_finder.rb +103 -272
- data/lib/dependabot/maven/update_checker.rb +4 -2
- metadata +6 -5
@@ -0,0 +1,485 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "time"
|
5
|
+
require "excon"
|
6
|
+
require "nokogiri"
|
7
|
+
require "dependabot/registry_client"
|
8
|
+
require "dependabot/package/package_release"
|
9
|
+
require "dependabot/package/package_details"
|
10
|
+
require "dependabot/maven/file_parser/repositories_finder"
|
11
|
+
require "dependabot/maven/version"
|
12
|
+
require "dependabot/maven/requirement"
|
13
|
+
require "dependabot/maven/utils/auth_headers_finder"
|
14
|
+
require "sorbet-runtime"
|
15
|
+
|
16
|
+
module Dependabot
|
17
|
+
module Maven
|
18
|
+
module Package
|
19
|
+
class PackageDetailsFetcher
|
20
|
+
extend T::Sig
|
21
|
+
|
22
|
+
META_DATE_XML = T.let("maven-metadata.xml", String)
|
23
|
+
REPOSITORY_TYPE = T.let("maven_repository", String)
|
24
|
+
URL_KEY = T.let("url", String)
|
25
|
+
AUTH_HEADERS_KEY = T.let("auth_headers", String)
|
26
|
+
|
27
|
+
sig do
|
28
|
+
params(
|
29
|
+
dependency: Dependabot::Dependency,
|
30
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
31
|
+
credentials: T::Array[Dependabot::Credential]
|
32
|
+
).void
|
33
|
+
end
|
34
|
+
def initialize(dependency:, dependency_files:, credentials:) # rubocop:disable Metrics/AbcSize
|
35
|
+
@dependency = dependency
|
36
|
+
@dependency_files = dependency_files
|
37
|
+
@credentials = credentials
|
38
|
+
|
39
|
+
@forbidden_urls = T.let([], T::Array[String])
|
40
|
+
@pom_repository_details = T.let(nil, T.nilable(T::Array[T::Hash[String, T.untyped]]))
|
41
|
+
@dependency_metadata = T.let({}, T::Hash[T.untyped, Nokogiri::XML::Document])
|
42
|
+
@dependency_metadata_from_html = T.let({}, T::Hash[T.untyped, Nokogiri::HTML::Document])
|
43
|
+
@repository_finder = T.let(nil, T.nilable(Maven::FileParser::RepositoriesFinder))
|
44
|
+
@repositories = T.let(nil, T.nilable(T::Array[T::Hash[String, T.untyped]]))
|
45
|
+
@released_check = T.let({}, T::Hash[Dependabot::Version, T::Boolean])
|
46
|
+
@auth_headers_finder = T.let(nil, T.nilable(Utils::AuthHeadersFinder))
|
47
|
+
@dependency_parts = T.let(nil, T.nilable([String, String]))
|
48
|
+
@version_details = T.let(nil, T.nilable(T::Array[T::Hash[Symbol, T.untyped]]))
|
49
|
+
@package_details = T.let(nil, T.nilable(Dependabot::Package::PackageDetails))
|
50
|
+
end
|
51
|
+
|
52
|
+
sig { returns(Dependabot::Dependency) }
|
53
|
+
attr_reader :dependency
|
54
|
+
|
55
|
+
sig { returns(T::Array[T.untyped]) }
|
56
|
+
attr_reader :dependency_files
|
57
|
+
|
58
|
+
sig { returns(T::Array[T.untyped]) }
|
59
|
+
attr_reader :credentials
|
60
|
+
sig { returns(T::Array[T.untyped]) }
|
61
|
+
attr_reader :forbidden_urls
|
62
|
+
|
63
|
+
sig { returns(Dependabot::Package::PackageDetails) }
|
64
|
+
def fetch
|
65
|
+
return @package_details if @package_details
|
66
|
+
|
67
|
+
releases = versions.map do |version_details|
|
68
|
+
Dependabot::Package::PackageRelease.new(
|
69
|
+
version: version_details.fetch(:version),
|
70
|
+
released_at: version_details.fetch(:release_date, nil),
|
71
|
+
url: version_details.fetch(:source_url)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
@package_details = Dependabot::Package::PackageDetails.new(
|
76
|
+
dependency: dependency,
|
77
|
+
releases: releases
|
78
|
+
)
|
79
|
+
|
80
|
+
@package_details
|
81
|
+
end
|
82
|
+
|
83
|
+
sig { returns(T::Array[T.untyped]) }
|
84
|
+
def releases
|
85
|
+
fetch.releases
|
86
|
+
end
|
87
|
+
|
88
|
+
sig { params(version: Dependabot::Version).returns(T::Boolean) }
|
89
|
+
def released?(version)
|
90
|
+
released_check?(version)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
96
|
+
def versions
|
97
|
+
return @version_details if @version_details
|
98
|
+
|
99
|
+
@version_details = versions_details_from_xml
|
100
|
+
|
101
|
+
begin
|
102
|
+
versions_details_hash = versions_details_hash_from_html if @version_details.any?
|
103
|
+
|
104
|
+
if versions_details_hash
|
105
|
+
@version_details = @version_details.map do |version_details|
|
106
|
+
version = version_details[:version].to_s
|
107
|
+
version_details_hash = versions_details_hash[version]
|
108
|
+
|
109
|
+
next version_details unless version_details_hash
|
110
|
+
|
111
|
+
release_date = version_details_hash[:release_date]
|
112
|
+
|
113
|
+
next version_details unless release_date
|
114
|
+
|
115
|
+
version_details.merge(
|
116
|
+
release_date: version_details_hash[:release_date],
|
117
|
+
source_url: version_details[:source_url]
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
rescue StandardError => e
|
122
|
+
Dependabot.logger.error("Error fetching version details from HTML: #{e.message}")
|
123
|
+
end
|
124
|
+
|
125
|
+
@version_details = @version_details.sort_by { |details| details.fetch(:version) }
|
126
|
+
@version_details
|
127
|
+
end
|
128
|
+
|
129
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
130
|
+
def versions_details_from_xml
|
131
|
+
forbidden_urls.clear
|
132
|
+
version_details = repositories.flat_map do |repository_details|
|
133
|
+
url = repository_details.fetch(URL_KEY)
|
134
|
+
xml = dependency_metadata(repository_details)
|
135
|
+
next [] if xml.nil?
|
136
|
+
|
137
|
+
break extract_metadata_from_xml(xml, url)
|
138
|
+
end
|
139
|
+
|
140
|
+
raise PrivateSourceAuthenticationFailure, forbidden_urls.first if version_details.none? && forbidden_urls.any?
|
141
|
+
|
142
|
+
version_details
|
143
|
+
end
|
144
|
+
|
145
|
+
sig { returns(T::Hash[String, T::Hash[Symbol, T.untyped]]) }
|
146
|
+
def versions_details_hash_from_html
|
147
|
+
forbidden_urls.clear
|
148
|
+
|
149
|
+
# Iterate over repositories and fetch the first valid result
|
150
|
+
versions_detail_hash = T.let({}, T::Hash[String, T::Hash[Symbol, T.untyped]])
|
151
|
+
repositories.each do |repository_details|
|
152
|
+
html = dependency_metadata_from_html(repository_details)
|
153
|
+
|
154
|
+
# Skip if no HTML data is found
|
155
|
+
next if html.nil?
|
156
|
+
|
157
|
+
# Break and return result from the first valid HTML
|
158
|
+
versions_detail_hash = extract_version_details_from_html(html)
|
159
|
+
|
160
|
+
break if versions_detail_hash.any?
|
161
|
+
end
|
162
|
+
|
163
|
+
# If no version details were found, but there are forbidden URLs, raise an error
|
164
|
+
if versions_detail_hash.any? && forbidden_urls.any?
|
165
|
+
raise PrivateSourceAuthenticationFailure,
|
166
|
+
forbidden_urls.first
|
167
|
+
end
|
168
|
+
|
169
|
+
# Return the populated version details hash (may be empty if no valid repositories)
|
170
|
+
versions_detail_hash
|
171
|
+
end
|
172
|
+
|
173
|
+
sig { params(version: Dependabot::Version).returns(T::Boolean) }
|
174
|
+
def released_check?(version)
|
175
|
+
@released_check[version] ||=
|
176
|
+
repositories.any? do |repository_details|
|
177
|
+
url = repository_details.fetch(URL_KEY)
|
178
|
+
auth_headers = repository_details.fetch(AUTH_HEADERS_KEY)
|
179
|
+
response = Dependabot::RegistryClient.head(
|
180
|
+
url: dependency_files_url(url, version),
|
181
|
+
headers: auth_headers
|
182
|
+
)
|
183
|
+
|
184
|
+
response.status < 400
|
185
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout,
|
186
|
+
Excon::Error::TooManyRedirects
|
187
|
+
false
|
188
|
+
rescue URI::InvalidURIError => e
|
189
|
+
raise DependencyFileNotResolvable, e.message
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Extracts version details from the HTML document.
|
194
|
+
sig do
|
195
|
+
params(html_doc: Nokogiri::HTML::Document)
|
196
|
+
.returns(T::Hash[String, T::Hash[Symbol, T.untyped]])
|
197
|
+
end
|
198
|
+
def extract_version_details_from_html(html_doc)
|
199
|
+
versions_detail_hash = T.let({}, T::Hash[String, T::Hash[Symbol, T.untyped]])
|
200
|
+
|
201
|
+
html_doc.css("a[title]").each do |link|
|
202
|
+
version_string = link["title"]
|
203
|
+
version = version_string.gsub(%r{/$}, "") # Remove trailing slash
|
204
|
+
|
205
|
+
# Release date should be located after the version, and it is within the same <pre> block
|
206
|
+
raw_date_text = link.next.text.strip.split("\n").last.strip # Extract the last part of the text
|
207
|
+
|
208
|
+
# Parse the date and time properly (YYYY-MM-DD HH:MM)
|
209
|
+
release_date = begin
|
210
|
+
Time.parse(raw_date_text)
|
211
|
+
rescue StandardError
|
212
|
+
nil
|
213
|
+
end
|
214
|
+
|
215
|
+
next unless version && version_class.correct?(version)
|
216
|
+
|
217
|
+
versions_detail_hash[version] = {
|
218
|
+
release_date: release_date
|
219
|
+
}
|
220
|
+
end
|
221
|
+
versions_detail_hash
|
222
|
+
end
|
223
|
+
|
224
|
+
# Extracts version details from the XML document.
|
225
|
+
sig do
|
226
|
+
params(
|
227
|
+
xml: Nokogiri::XML::Document,
|
228
|
+
url: String
|
229
|
+
).returns(T::Array[T::Hash[Symbol, T.untyped]])
|
230
|
+
end
|
231
|
+
def extract_metadata_from_xml(xml, url)
|
232
|
+
xml.css("versions > version")
|
233
|
+
.select { |node| version_class.correct?(node.content) }
|
234
|
+
.map { |node| version_class.new(node.content) }
|
235
|
+
.map { |version| { version: version, source_url: url } }
|
236
|
+
end
|
237
|
+
|
238
|
+
sig { params(repository_details: T::Hash[String, T.untyped]).returns(T.nilable(Nokogiri::XML::Document)) }
|
239
|
+
def fetch_dependency_metadata(repository_details)
|
240
|
+
url = repository_details.fetch(URL_KEY)
|
241
|
+
auth_headers = repository_details.fetch(AUTH_HEADERS_KEY)
|
242
|
+
response = Dependabot::RegistryClient.get(
|
243
|
+
url: dependency_metadata_url(url),
|
244
|
+
headers: auth_headers
|
245
|
+
)
|
246
|
+
check_response(response, url)
|
247
|
+
return unless response.status < 400
|
248
|
+
|
249
|
+
Nokogiri::XML(response.body)
|
250
|
+
rescue URI::InvalidURIError
|
251
|
+
nil
|
252
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout,
|
253
|
+
Excon::Error::TooManyRedirects => e
|
254
|
+
handle_registry_error(url, e, response)
|
255
|
+
nil
|
256
|
+
end
|
257
|
+
|
258
|
+
sig { returns(T::Array[T::Hash[String, T.untyped]]) }
|
259
|
+
def repositories
|
260
|
+
return @repositories if @repositories
|
261
|
+
|
262
|
+
@repositories = credentials_repository_details
|
263
|
+
pom_repository_details.each do |repo|
|
264
|
+
@repositories << repo unless @repositories.any? do |r|
|
265
|
+
r[URL_KEY] == repo[URL_KEY]
|
266
|
+
end
|
267
|
+
end
|
268
|
+
@repositories
|
269
|
+
end
|
270
|
+
|
271
|
+
sig { params(repository_details: T::Hash[String, T.untyped]).returns(T.nilable(Nokogiri::XML::Document)) }
|
272
|
+
def dependency_metadata(repository_details)
|
273
|
+
repository_key = repository_details.hash
|
274
|
+
return @dependency_metadata[repository_key] if @dependency_metadata.key?(repository_key)
|
275
|
+
|
276
|
+
xml_document = fetch_dependency_metadata(repository_details)
|
277
|
+
|
278
|
+
@dependency_metadata[repository_key] ||= xml_document if xml_document
|
279
|
+
@dependency_metadata[repository_key]
|
280
|
+
end
|
281
|
+
|
282
|
+
sig { params(repository_details: T::Hash[String, T.untyped]).returns(T.nilable(Nokogiri::HTML::Document)) }
|
283
|
+
def dependency_metadata_from_html(repository_details)
|
284
|
+
repository_key = repository_details.hash
|
285
|
+
return @dependency_metadata_from_html[repository_key] if @dependency_metadata_from_html.key?(repository_key)
|
286
|
+
|
287
|
+
html_document = fetch_dependency_metadata_from_html(repository_details)
|
288
|
+
|
289
|
+
@dependency_metadata_from_html[repository_key] ||= html_document if html_document
|
290
|
+
@dependency_metadata_from_html[repository_key]
|
291
|
+
end
|
292
|
+
|
293
|
+
sig { params(response: Excon::Response, repository_url: String).void }
|
294
|
+
def check_response(response, repository_url)
|
295
|
+
return unless [401, 403].include?(response.status)
|
296
|
+
return if @forbidden_urls.include?(repository_url)
|
297
|
+
return if central_repo_urls.include?(repository_url)
|
298
|
+
|
299
|
+
@forbidden_urls << repository_url
|
300
|
+
end
|
301
|
+
|
302
|
+
sig do
|
303
|
+
params(
|
304
|
+
repository_details: T::Hash[String, T.untyped]
|
305
|
+
).returns(T.nilable(Nokogiri::HTML::Document))
|
306
|
+
end
|
307
|
+
def fetch_dependency_metadata_from_html(repository_details)
|
308
|
+
url = repository_details.fetch(URL_KEY)
|
309
|
+
auth_headers = repository_details.fetch(AUTH_HEADERS_KEY)
|
310
|
+
response = Dependabot::RegistryClient.get(
|
311
|
+
url: dependency_base_url(url),
|
312
|
+
headers: auth_headers
|
313
|
+
)
|
314
|
+
check_response(response, url)
|
315
|
+
return unless response.status < 400
|
316
|
+
|
317
|
+
Nokogiri::HTML(response.body)
|
318
|
+
rescue URI::InvalidURIError
|
319
|
+
nil
|
320
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout,
|
321
|
+
Excon::Error::TooManyRedirects => e
|
322
|
+
handle_registry_error(url, e, response)
|
323
|
+
nil
|
324
|
+
end
|
325
|
+
|
326
|
+
sig { returns(Maven::FileParser::RepositoriesFinder) }
|
327
|
+
def repository_finder
|
328
|
+
return @repository_finder if @repository_finder
|
329
|
+
|
330
|
+
@repository_finder =
|
331
|
+
Maven::FileParser::RepositoriesFinder.new(
|
332
|
+
pom_fetcher: Maven::FileParser::PomFetcher.new(dependency_files: dependency_files),
|
333
|
+
dependency_files: dependency_files,
|
334
|
+
credentials: credentials
|
335
|
+
)
|
336
|
+
@repository_finder
|
337
|
+
end
|
338
|
+
|
339
|
+
# Returns the repository details for the POM file.
|
340
|
+
# Example:
|
341
|
+
# repository_url: https://repo.maven.apache.org/maven2
|
342
|
+
# returns: [{ "url" => "https://repo.maven.apache.org/maven2", "auth_headers" => {} }]
|
343
|
+
sig { returns(T::Array[T::Hash[String, T.untyped]]) }
|
344
|
+
def pom_repository_details
|
345
|
+
return @pom_repository_details if @pom_repository_details
|
346
|
+
|
347
|
+
@pom_repository_details =
|
348
|
+
repository_finder
|
349
|
+
.repository_urls(pom: T.must(pom))
|
350
|
+
.map do |url|
|
351
|
+
{ URL_KEY => url, AUTH_HEADERS_KEY => {} }
|
352
|
+
end
|
353
|
+
@pom_repository_details
|
354
|
+
end
|
355
|
+
|
356
|
+
# Returns the POM file for the dependency, if it exists.
|
357
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
358
|
+
def pom
|
359
|
+
filename = dependency.requirements.first&.fetch(:file)
|
360
|
+
dependency_files.find { |f| f.name == filename }
|
361
|
+
end
|
362
|
+
|
363
|
+
# Constructs the URL for the dependency's metadata file (maven-metadata.xml).
|
364
|
+
#
|
365
|
+
# Example:
|
366
|
+
# repository_url: https://repo.maven.apache.org/maven2
|
367
|
+
# returns: https://repo.maven.apache.org/maven2/com/google/guava/guava/maven-metadata.xml
|
368
|
+
sig { params(repository_url: String).returns(String) }
|
369
|
+
def dependency_metadata_url(repository_url)
|
370
|
+
"#{dependency_base_url(repository_url)}/#{META_DATE_XML}"
|
371
|
+
end
|
372
|
+
|
373
|
+
# Constructs the URL for the dependency files, including version and artifact information.
|
374
|
+
#
|
375
|
+
# Example:
|
376
|
+
# repository_url: https://repo.maven.apache.org/maven2
|
377
|
+
# version: 23.6-jre
|
378
|
+
# artifact_id: guava
|
379
|
+
# group_id: com.google.guava
|
380
|
+
# classifier: nil
|
381
|
+
# type: jar
|
382
|
+
# returns: https://repo.maven.apache.org/maven2/com/google/guava/guava/23.6-jre/guava-23.6-jre.jar
|
383
|
+
# https://repo.maven.apache.org/maven2/com/google/guava/guava/23.7-jre/-23.7-jre.jar
|
384
|
+
sig { params(repository_url: String, version: Dependabot::Version).returns(String) }
|
385
|
+
def dependency_files_url(repository_url, version)
|
386
|
+
_, artifact_id = dependency_parts
|
387
|
+
base_url = dependency_base_url(repository_url)
|
388
|
+
type = dependency.requirements.first&.dig(:metadata, :packaging_type)
|
389
|
+
classifier = dependency.requirements.first&.dig(:metadata, :classifier)
|
390
|
+
actual_classifier = classifier.nil? ? "" : "-#{classifier}"
|
391
|
+
|
392
|
+
"#{base_url}/#{version.to_semver}/" \
|
393
|
+
"#{artifact_id}-#{version.to_semver}#{actual_classifier}.#{type}"
|
394
|
+
end
|
395
|
+
|
396
|
+
# # Constructs the full URL by combining the repository URL, group path, and artifact ID
|
397
|
+
#
|
398
|
+
# Example:
|
399
|
+
# repository_url: https://repo.maven.apache.org/maven2
|
400
|
+
# group_path: com/google/guava
|
401
|
+
# artifact_id: guava
|
402
|
+
# returns: https://repo.maven.apache.org/maven2/com/google/guava/guava
|
403
|
+
sig { params(repository_url: String).returns(String) }
|
404
|
+
def dependency_base_url(repository_url)
|
405
|
+
group_path, artifact_id = dependency_parts
|
406
|
+
|
407
|
+
"#{repository_url}/#{group_path}/#{artifact_id}"
|
408
|
+
end
|
409
|
+
|
410
|
+
# Splits the dependency name into its group path and artifact ID.
|
411
|
+
#
|
412
|
+
# Example:
|
413
|
+
# dependency.name: com.google.guava:guava
|
414
|
+
# returns: ["com/google/guava", "guava"]
|
415
|
+
sig { returns(T.nilable([String, String])) }
|
416
|
+
def dependency_parts
|
417
|
+
return @dependency_parts if @dependency_parts
|
418
|
+
|
419
|
+
group_id, artifact_id = dependency.name.split(":")
|
420
|
+
group_path = group_id&.tr(".", "/")
|
421
|
+
@dependency_parts = [T.must(group_path), T.must(artifact_id)]
|
422
|
+
@dependency_parts
|
423
|
+
end
|
424
|
+
|
425
|
+
sig { returns(T::Array[T.untyped]) }
|
426
|
+
def credentials_repository_details
|
427
|
+
credentials
|
428
|
+
.select { |cred| cred["type"] == REPOSITORY_TYPE && cred[URL_KEY] }
|
429
|
+
.map do |cred|
|
430
|
+
url_value = cred.fetch(URL_KEY).gsub(%r{/+$}, "")
|
431
|
+
{
|
432
|
+
URL_KEY => url_value,
|
433
|
+
AUTH_HEADERS_KEY => auth_headers(url_value)
|
434
|
+
}
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
sig { returns(T.class_of(Dependabot::Version)) }
|
439
|
+
def version_class
|
440
|
+
dependency.version_class
|
441
|
+
end
|
442
|
+
|
443
|
+
sig { returns(T::Array[String]) }
|
444
|
+
def central_repo_urls
|
445
|
+
central_url_without_protocol = repository_finder.central_repo_url.gsub(%r{^.*://}, "")
|
446
|
+
|
447
|
+
%w(http:// https://).map { |p| p + central_url_without_protocol }
|
448
|
+
end
|
449
|
+
|
450
|
+
sig { returns(Utils::AuthHeadersFinder) }
|
451
|
+
def auth_headers_finder
|
452
|
+
return @auth_headers_finder if @auth_headers_finder
|
453
|
+
|
454
|
+
@auth_headers_finder = Utils::AuthHeadersFinder.new(credentials)
|
455
|
+
@auth_headers_finder
|
456
|
+
end
|
457
|
+
|
458
|
+
sig { params(maven_repo_url: String).returns(T::Hash[String, String]) }
|
459
|
+
def auth_headers(maven_repo_url)
|
460
|
+
auth_headers_finder.auth_headers(maven_repo_url)
|
461
|
+
end
|
462
|
+
|
463
|
+
sig do
|
464
|
+
params(
|
465
|
+
url: String,
|
466
|
+
error: Excon::Error,
|
467
|
+
response: T.nilable(Excon::Response)
|
468
|
+
).void
|
469
|
+
end
|
470
|
+
def handle_registry_error(url, error, response)
|
471
|
+
return unless central_repo_urls.include?(url)
|
472
|
+
|
473
|
+
response_status = response&.status || 0
|
474
|
+
response_body = if response
|
475
|
+
"RegistryError: #{response.status} response status with body #{response.body}"
|
476
|
+
else
|
477
|
+
"RegistryError: #{error.message}"
|
478
|
+
end
|
479
|
+
|
480
|
+
raise RegistryError.new(response_status, response_body)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
# typed:
|
1
|
+
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
@@ -13,12 +13,13 @@ module Dependabot
|
|
13
13
|
extend T::Sig
|
14
14
|
|
15
15
|
quoted = OPS.keys.map { |k| Regexp.quote k }.join("|")
|
16
|
-
OR_SYNTAX = /(?<=\]|\))
|
17
|
-
PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Maven::Version::VERSION_PATTERN})\\s*".freeze
|
18
|
-
PATTERN = /\A#{PATTERN_RAW}\z
|
16
|
+
OR_SYNTAX = T.let(/(?<=\]|\)),/, Regexp)
|
17
|
+
PATTERN_RAW = T.let("\\s*(#{quoted})?\\s*(#{Maven::Version::VERSION_PATTERN})\\s*".freeze, String)
|
18
|
+
PATTERN = T.let(/\A#{PATTERN_RAW}\z/, Regexp)
|
19
19
|
# Like PATTERN, but the leading operator is required
|
20
|
-
RUBY_STYLE_PATTERN = /\A\s*(#{quoted})\s*(#{Maven::Version::VERSION_PATTERN})\s*\z
|
20
|
+
RUBY_STYLE_PATTERN = T.let(/\A\s*(#{quoted})\s*(#{Maven::Version::VERSION_PATTERN})\s*\z/, Regexp)
|
21
21
|
|
22
|
+
sig { params(obj: T.any(String, Gem::Version)).returns(T::Array[T.any(String, T.untyped)]) }
|
22
23
|
def self.parse(obj)
|
23
24
|
return ["=", Maven::Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
24
25
|
|
@@ -39,6 +40,7 @@ module Dependabot
|
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
43
|
+
sig { params(requirements: T.untyped).void }
|
42
44
|
def initialize(*requirements)
|
43
45
|
requirements = requirements.flatten.flat_map do |req_string|
|
44
46
|
convert_java_constraint_to_ruby_constraint(req_string)
|
@@ -47,6 +49,7 @@ module Dependabot
|
|
47
49
|
super(requirements)
|
48
50
|
end
|
49
51
|
|
52
|
+
sig { params(version: T.untyped).returns(T::Boolean) }
|
50
53
|
def satisfied_by?(version)
|
51
54
|
version = Maven::Version.new(version.to_s)
|
52
55
|
super
|
@@ -54,18 +57,25 @@ module Dependabot
|
|
54
57
|
|
55
58
|
private
|
56
59
|
|
60
|
+
sig { params(req_string: T.nilable(String)).returns(T::Array[String]) }
|
57
61
|
def self.split_java_requirement(req_string)
|
58
|
-
return [req_string] unless req_string
|
62
|
+
return [req_string || ""] unless req_string&.match?(OR_SYNTAX)
|
59
63
|
|
60
64
|
req_string.split(OR_SYNTAX).flat_map do |str|
|
61
65
|
next str if str.start_with?("(", "[")
|
62
66
|
|
63
67
|
exacts, *rest = str.split(/,(?=\[|\()/)
|
64
|
-
[*exacts.split(","), *rest]
|
68
|
+
[*T.must(exacts).split(","), *rest]
|
65
69
|
end
|
66
70
|
end
|
67
71
|
private_class_method :split_java_requirement
|
68
72
|
|
73
|
+
sig do
|
74
|
+
params(
|
75
|
+
req_string: T.nilable(String)
|
76
|
+
)
|
77
|
+
.returns(T.nilable(T.any(T::Array[String], T::Array[T.nilable(String)])))
|
78
|
+
end
|
69
79
|
def convert_java_constraint_to_ruby_constraint(req_string)
|
70
80
|
return unless req_string
|
71
81
|
|
@@ -86,26 +96,32 @@ module Dependabot
|
|
86
96
|
end
|
87
97
|
end
|
88
98
|
|
99
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
100
|
+
sig { params(req_string: String).returns(T::Array[T.nilable(String)]) }
|
89
101
|
def convert_java_range_to_ruby_range(req_string)
|
90
|
-
|
102
|
+
parts = req_string.split(",").map(&:strip)
|
103
|
+
lower_b = T.let(parts[0], T.nilable(String))
|
104
|
+
upper_b = T.let(parts[1], T.nilable(String))
|
91
105
|
|
92
106
|
lower_b =
|
93
|
-
if ["(", "["].include?(lower_b) then nil
|
94
|
-
elsif lower_b
|
95
|
-
|
107
|
+
if lower_b && ["(", "["].include?(lower_b) then nil
|
108
|
+
elsif lower_b&.start_with?("(") then "> #{lower_b.sub(/\(\s*/, '')}"
|
109
|
+
elsif lower_b
|
96
110
|
">= #{lower_b.sub(/\[\s*/, '').strip}"
|
97
111
|
end
|
98
112
|
|
99
113
|
upper_b =
|
100
|
-
if [")", "]"].include?(upper_b) then nil
|
101
|
-
elsif upper_b
|
102
|
-
|
114
|
+
if upper_b && [")", "]"].include?(upper_b) then nil
|
115
|
+
elsif upper_b&.end_with?(")") then "< #{upper_b.sub(/\s*\)/, '')}"
|
116
|
+
elsif upper_b
|
103
117
|
"<= #{upper_b.sub(/\s*\]/, '').strip}"
|
104
118
|
end
|
105
119
|
|
106
120
|
[lower_b, upper_b].compact
|
107
121
|
end
|
122
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
108
123
|
|
124
|
+
sig { params(req_string: T.nilable(String)).returns(T.nilable(String)) }
|
109
125
|
def convert_java_equals_req_to_ruby(req_string)
|
110
126
|
return convert_wildcard_req(req_string) if req_string&.end_with?("+")
|
111
127
|
|
@@ -115,8 +131,9 @@ module Dependabot
|
|
115
131
|
req_string.gsub(/[\[\]\(\)]/, "")
|
116
132
|
end
|
117
133
|
|
134
|
+
sig { params(req_string: T.nilable(String)).returns(String) }
|
118
135
|
def convert_wildcard_req(req_string)
|
119
|
-
version = req_string
|
136
|
+
version = req_string&.split("+")&.first
|
120
137
|
return ">= 0" if version.nil? || version.empty?
|
121
138
|
|
122
139
|
version += "0" if version.end_with?(".")
|