dependabot-uv 0.300.0 → 0.301.1
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/uv/file_fetcher.rb +51 -21
- data/lib/dependabot/uv/file_parser.rb +39 -1
- data/lib/dependabot/uv/file_updater/lock_file_updater.rb +392 -0
- data/lib/dependabot/uv/file_updater/pyproject_preparer.rb +97 -77
- data/lib/dependabot/uv/file_updater.rb +14 -1
- data/lib/dependabot/uv/package/package_details_fetcher.rb +488 -0
- data/lib/dependabot/uv/{update_checker/index_finder.rb → package/package_registry_finder.rb} +12 -6
- data/lib/dependabot/uv/update_checker/latest_version_finder.rb +15 -269
- data/lib/dependabot/uv/update_checker/lock_file_resolver.rb +48 -0
- data/lib/dependabot/uv/update_checker/pip_version_resolver.rb +7 -5
- data/lib/dependabot/uv/update_checker.rb +14 -1
- metadata +9 -6
@@ -0,0 +1,488 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "json"
|
5
|
+
require "time"
|
6
|
+
require "cgi"
|
7
|
+
require "excon"
|
8
|
+
require "nokogiri"
|
9
|
+
require "sorbet-runtime"
|
10
|
+
require "dependabot/registry_client"
|
11
|
+
require "dependabot/uv/name_normaliser"
|
12
|
+
require "dependabot/package/package_release"
|
13
|
+
require "dependabot/package/package_details"
|
14
|
+
require "dependabot/uv/package/package_registry_finder"
|
15
|
+
|
16
|
+
# Stores metadata for a package, including all its available versions
|
17
|
+
module Dependabot
|
18
|
+
module Uv
|
19
|
+
module Package
|
20
|
+
CREDENTIALS_USERNAME = "username"
|
21
|
+
CREDENTIALS_PASSWORD = "password"
|
22
|
+
|
23
|
+
APPLICATION_JSON = "application/json"
|
24
|
+
APPLICATION_TEXT = "text/html"
|
25
|
+
CPYTHON = "cpython"
|
26
|
+
PYTHON = "python"
|
27
|
+
UNKNOWN = "unknown"
|
28
|
+
|
29
|
+
MAIN_PYPI_INDEXES = %w(
|
30
|
+
https://pypi.python.org/simple/
|
31
|
+
https://pypi.org/simple/
|
32
|
+
).freeze
|
33
|
+
VERSION_REGEX = /[0-9]+(?:\.[A-Za-z0-9\-_]+)*/
|
34
|
+
|
35
|
+
class PackageDetailsFetcher
|
36
|
+
extend T::Sig
|
37
|
+
|
38
|
+
sig do
|
39
|
+
params(
|
40
|
+
dependency: Dependabot::Dependency,
|
41
|
+
dependency_files: T::Array[Dependabot::DependencyFile],
|
42
|
+
credentials: T::Array[Dependabot::Credential]
|
43
|
+
).void
|
44
|
+
end
|
45
|
+
def initialize(
|
46
|
+
dependency:,
|
47
|
+
dependency_files:,
|
48
|
+
credentials:
|
49
|
+
)
|
50
|
+
@dependency = dependency
|
51
|
+
@dependency_files = dependency_files
|
52
|
+
@credentials = credentials
|
53
|
+
|
54
|
+
@registry_urls = T.let(nil, T.nilable(T::Array[String]))
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { returns(Dependabot::Dependency) }
|
58
|
+
attr_reader :dependency
|
59
|
+
|
60
|
+
sig { returns(T::Array[T.untyped]) }
|
61
|
+
attr_reader :dependency_files
|
62
|
+
|
63
|
+
sig { returns(T::Array[T.untyped]) }
|
64
|
+
attr_reader :credentials
|
65
|
+
|
66
|
+
sig { returns(Dependabot::Package::PackageDetails) }
|
67
|
+
def fetch
|
68
|
+
package_releases = registry_urls
|
69
|
+
.select { |index_url| validate_index(index_url) } # Ensure only valid URLs
|
70
|
+
.flat_map do |index_url|
|
71
|
+
fetch_from_registry(index_url) || [] # Ensure it always returns an array
|
72
|
+
rescue Excon::Error::Timeout, Excon::Error::Socket
|
73
|
+
raise if MAIN_PYPI_INDEXES.include?(index_url)
|
74
|
+
|
75
|
+
raise PrivateSourceTimedOut, sanitized_url(index_url)
|
76
|
+
rescue URI::InvalidURIError
|
77
|
+
raise DependencyFileNotResolvable, "Invalid URL: #{sanitized_url(index_url)}"
|
78
|
+
end
|
79
|
+
|
80
|
+
Dependabot::Package::PackageDetails.new(
|
81
|
+
dependency: dependency,
|
82
|
+
releases: package_releases.reverse.uniq(&:version)
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
sig do
|
89
|
+
params(index_url: String)
|
90
|
+
.returns(T.nilable(T::Array[Dependabot::Package::PackageRelease]))
|
91
|
+
end
|
92
|
+
def fetch_from_registry(index_url)
|
93
|
+
if Dependabot::Experiments.enabled?(:enable_cooldown_for_uv)
|
94
|
+
metadata = fetch_from_json_registry(index_url)
|
95
|
+
|
96
|
+
return metadata if metadata&.any?
|
97
|
+
|
98
|
+
Dependabot.logger.warn("No valid versions found via JSON API. Falling back to HTML.")
|
99
|
+
end
|
100
|
+
fetch_from_html_registry(index_url)
|
101
|
+
rescue StandardError => e
|
102
|
+
Dependabot.logger.warn("Unexpected error in JSON fetch: #{e.message}. Falling back to HTML.")
|
103
|
+
fetch_from_html_registry(index_url)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Example JSON Response Format:
|
107
|
+
#
|
108
|
+
# {
|
109
|
+
# "info": {
|
110
|
+
# "name": "requests",
|
111
|
+
# "summary": "Python HTTP for Humans.",
|
112
|
+
# "author": "Kenneth Reitz",
|
113
|
+
# "license": "Apache-2.0"
|
114
|
+
# },
|
115
|
+
# "releases": {
|
116
|
+
# "2.32.3": [
|
117
|
+
# {
|
118
|
+
# "filename": "requests-2.32.3-py3-none-any.whl",
|
119
|
+
# "version": "2.32.3",
|
120
|
+
# "requires_python": ">=3.8",
|
121
|
+
# "yanked": false,
|
122
|
+
# "url": "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl"
|
123
|
+
# },
|
124
|
+
# {
|
125
|
+
# "filename": "requests-2.32.3.tar.gz",
|
126
|
+
# "version": "2.32.3",
|
127
|
+
# "requires_python": ">=3.8",
|
128
|
+
# "yanked": false,
|
129
|
+
# "url": "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz"
|
130
|
+
# }
|
131
|
+
# ],
|
132
|
+
# "2.27.0": [
|
133
|
+
# {
|
134
|
+
# "filename": "requests-2.27.0-py2.py3-none-any.whl",
|
135
|
+
# "version": "2.27.0",
|
136
|
+
# "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*",
|
137
|
+
# "yanked": false,
|
138
|
+
# "url": "https://files.pythonhosted.org/packages/47/01/f420e7add78110940639a958e5af0e3f8e07a8a8b62049bac55ee117aa91/requests-2.27.0-py2.py3-none-any.whl"
|
139
|
+
# },
|
140
|
+
# {
|
141
|
+
# "filename": "requests-2.27.0.tar.gz",
|
142
|
+
# "version": "2.27.0",
|
143
|
+
# "requires_python": ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*",
|
144
|
+
# "yanked": false,
|
145
|
+
# "url": "https://files.pythonhosted.org/packages/c0/e3/826e27b942352a74b656e8f58b4dc7ed9495ce2d4eeb498181167c615303/requests-2.27.0.tar.gz"
|
146
|
+
# }
|
147
|
+
# ]
|
148
|
+
# }
|
149
|
+
# }
|
150
|
+
sig do
|
151
|
+
params(index_url: String)
|
152
|
+
.returns(T.nilable(T::Array[Dependabot::Package::PackageRelease]))
|
153
|
+
end
|
154
|
+
def fetch_from_json_registry(index_url)
|
155
|
+
json_url = index_url.sub(%r{/simple/?$}i, "/pypi/")
|
156
|
+
|
157
|
+
Dependabot.logger.info(
|
158
|
+
"Fetching release information from json registry at #{sanitized_url(json_url)} for #{dependency.name}"
|
159
|
+
)
|
160
|
+
|
161
|
+
response = registry_json_response_for_dependency(json_url)
|
162
|
+
|
163
|
+
return nil unless response.status == 200
|
164
|
+
|
165
|
+
begin
|
166
|
+
data = JSON.parse(response.body)
|
167
|
+
|
168
|
+
version_releases = data["releases"]
|
169
|
+
|
170
|
+
releases = format_version_releases(version_releases)
|
171
|
+
|
172
|
+
releases.sort_by(&:version).reverse
|
173
|
+
rescue JSON::ParserError
|
174
|
+
Dependabot.logger.warn("JSON parsing error for #{json_url}. Falling back to HTML.")
|
175
|
+
nil
|
176
|
+
rescue StandardError => e
|
177
|
+
Dependabot.logger.warn("Unexpected error while fetching JSON data: #{e.message}.")
|
178
|
+
nil
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# This URL points to the Simple Index API for the "requests" package on PyPI.
|
183
|
+
# It provides an HTML listing of available package versions following PEP 503 (Simple Repository API).
|
184
|
+
# The information found here is useful for dependency resolution and package version retrieval.
|
185
|
+
#
|
186
|
+
# ✅ Information available in the Simple Index:
|
187
|
+
# - A list of package versions as anchor (`<a>`) elements.
|
188
|
+
# - URLs to distribution files (e.g., `.tar.gz`, `.whl`).
|
189
|
+
# - The `data-requires-python` attribute (if present) specifying the required Python version.
|
190
|
+
# - An optional `data-yanked` attribute indicating a yanked (withdrawn) version.
|
191
|
+
#
|
192
|
+
# ❌ Information NOT available in the Simple Index:
|
193
|
+
# - Release timestamps (upload time).
|
194
|
+
# - File digests (hashes like SHA256, MD5).
|
195
|
+
# - Package metadata such as description, author, or dependencies.
|
196
|
+
# - Download statistics.
|
197
|
+
# - Package type (`sdist` or `bdist_wheel`).
|
198
|
+
#
|
199
|
+
# To obtain full package metadata, use the PyPI JSON API:
|
200
|
+
# - JSON API: https://pypi.org/pypi/requests/json
|
201
|
+
#
|
202
|
+
# More details: https://www.python.org/dev/peps/pep-0503/
|
203
|
+
sig { params(index_url: String).returns(T::Array[Dependabot::Package::PackageRelease]) }
|
204
|
+
def fetch_from_html_registry(index_url)
|
205
|
+
Dependabot.logger.info(
|
206
|
+
"Fetching release information from html registry at #{sanitized_url(index_url)} for #{dependency.name}"
|
207
|
+
)
|
208
|
+
index_response = registry_response_for_dependency(index_url)
|
209
|
+
if index_response.status == 401 || index_response.status == 403
|
210
|
+
registry_index_response = registry_index_response(index_url)
|
211
|
+
|
212
|
+
if registry_index_response.status == 401 || registry_index_response.status == 403
|
213
|
+
raise PrivateSourceAuthenticationFailure, sanitized_url(index_url)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
version_releases = extract_release_details_json_from_html(index_response.body)
|
218
|
+
releases = format_version_releases(version_releases)
|
219
|
+
|
220
|
+
releases.sort_by(&:version).reverse
|
221
|
+
end
|
222
|
+
|
223
|
+
sig do
|
224
|
+
params(html_body: String)
|
225
|
+
.returns(T::Hash[String, T::Array[T::Hash[String, T.untyped]]]) # Returns JSON-like format
|
226
|
+
end
|
227
|
+
def extract_release_details_json_from_html(html_body)
|
228
|
+
doc = Nokogiri::HTML(html_body)
|
229
|
+
|
230
|
+
releases = {}
|
231
|
+
|
232
|
+
doc.css("a").each do |a_tag|
|
233
|
+
details = version_details_from_link(a_tag.to_s)
|
234
|
+
if details && details["version"]
|
235
|
+
releases[details["version"]] ||= []
|
236
|
+
releases[details["version"]] << details
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
releases
|
241
|
+
end
|
242
|
+
|
243
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
244
|
+
sig do
|
245
|
+
params(link: T.nilable(String))
|
246
|
+
.returns(T.nilable(T::Hash[String, T.untyped]))
|
247
|
+
end
|
248
|
+
def version_details_from_link(link)
|
249
|
+
return unless link
|
250
|
+
|
251
|
+
doc = Nokogiri::XML(link)
|
252
|
+
filename = doc.at_css("a")&.content
|
253
|
+
url = doc.at_css("a")&.attributes&.fetch("href", nil)&.value
|
254
|
+
|
255
|
+
return unless filename&.match?(name_regex) || url&.match?(name_regex)
|
256
|
+
|
257
|
+
version = get_version_from_filename(filename)
|
258
|
+
return unless version_class.correct?(version)
|
259
|
+
|
260
|
+
{
|
261
|
+
"version" => version,
|
262
|
+
"requires_python" => requires_python_from_link(link),
|
263
|
+
"yanked" => link.include?("data-yanked"),
|
264
|
+
"url" => link
|
265
|
+
}
|
266
|
+
end
|
267
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
268
|
+
|
269
|
+
sig do
|
270
|
+
params(
|
271
|
+
releases_json: T.nilable(T::Hash[String, T::Array[T::Hash[String, T.untyped]]])
|
272
|
+
)
|
273
|
+
.returns(T::Array[Dependabot::Package::PackageRelease])
|
274
|
+
end
|
275
|
+
def format_version_releases(releases_json)
|
276
|
+
return [] unless releases_json
|
277
|
+
|
278
|
+
releases_json.each_with_object([]) do |(version, release_data_array), versions|
|
279
|
+
release_data = release_data_array.last
|
280
|
+
|
281
|
+
next unless release_data
|
282
|
+
|
283
|
+
release = format_version_release(version, release_data)
|
284
|
+
|
285
|
+
next unless release
|
286
|
+
|
287
|
+
versions << release
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
sig do
|
292
|
+
params(
|
293
|
+
version: String,
|
294
|
+
release_data: T::Hash[String, T.untyped]
|
295
|
+
)
|
296
|
+
.returns(T.nilable(Dependabot::Package::PackageRelease))
|
297
|
+
end
|
298
|
+
def format_version_release(version, release_data)
|
299
|
+
upload_time = release_data["upload_time"]
|
300
|
+
released_at = Time.parse(upload_time) if upload_time
|
301
|
+
yanked = release_data["yanked"] || false
|
302
|
+
yanked_reason = release_data["yanked_reason"]
|
303
|
+
downloads = release_data["downloads"] || -1
|
304
|
+
url = release_data["url"]
|
305
|
+
package_type = release_data["packagetype"]
|
306
|
+
language = package_language(
|
307
|
+
python_version: release_data["python_version"],
|
308
|
+
requires_python: release_data["requires_python"]
|
309
|
+
)
|
310
|
+
|
311
|
+
release = Dependabot::Package::PackageRelease.new(
|
312
|
+
version: Dependabot::Uv::Version.new(version),
|
313
|
+
released_at: released_at,
|
314
|
+
yanked: yanked,
|
315
|
+
yanked_reason: yanked_reason,
|
316
|
+
downloads: downloads,
|
317
|
+
url: url,
|
318
|
+
package_type: package_type,
|
319
|
+
language: language
|
320
|
+
)
|
321
|
+
release
|
322
|
+
end
|
323
|
+
|
324
|
+
sig do
|
325
|
+
params(
|
326
|
+
python_version: T.nilable(String),
|
327
|
+
requires_python: T.nilable(String)
|
328
|
+
)
|
329
|
+
.returns(T.nilable(Dependabot::Package::PackageLanguage))
|
330
|
+
end
|
331
|
+
def package_language(python_version:, requires_python:)
|
332
|
+
# Extract language name and version
|
333
|
+
language_name, language_version = convert_language_version(python_version)
|
334
|
+
|
335
|
+
# Extract language requirement
|
336
|
+
language_requirement = build_python_requirement(requires_python)
|
337
|
+
|
338
|
+
return nil unless language_version || language_requirement
|
339
|
+
|
340
|
+
# Return a Language object with all details
|
341
|
+
Dependabot::Package::PackageLanguage.new(
|
342
|
+
name: language_name,
|
343
|
+
version: language_version,
|
344
|
+
requirement: language_requirement
|
345
|
+
)
|
346
|
+
end
|
347
|
+
|
348
|
+
sig { params(version: T.nilable(String)).returns([String, T.nilable(Dependabot::Version)]) }
|
349
|
+
def convert_language_version(version)
|
350
|
+
return ["python", nil] if version.nil? || version == "source"
|
351
|
+
|
352
|
+
# Extract numeric parts dynamically (e.g., "cp37" -> "3.7", "py38" -> "3.8")
|
353
|
+
extracted_version = version.scan(/\d+/).join(".")
|
354
|
+
|
355
|
+
# Detect the language implementation
|
356
|
+
language_name = if version.start_with?("cp")
|
357
|
+
"cpython" # CPython implementation
|
358
|
+
elsif version.start_with?("py")
|
359
|
+
"python" # General Python compatibility
|
360
|
+
else
|
361
|
+
"unknown" # Fallback for unknown cases
|
362
|
+
end
|
363
|
+
|
364
|
+
# Ensure extracted version is valid before converting
|
365
|
+
language_version =
|
366
|
+
extracted_version.match?(/^\d+(\.\d+)*$/) ? Dependabot::Version.new(extracted_version) : nil
|
367
|
+
|
368
|
+
Dependabot.logger.warn("Skipping invalid language_version: #{version.inspect}") if language_version.nil?
|
369
|
+
|
370
|
+
[language_name, language_version]
|
371
|
+
end
|
372
|
+
|
373
|
+
sig { returns(T::Array[String]) }
|
374
|
+
def registry_urls
|
375
|
+
@registry_urls ||=
|
376
|
+
Package::PackageRegistryFinder.new(
|
377
|
+
dependency_files: dependency_files,
|
378
|
+
credentials: credentials,
|
379
|
+
dependency: dependency
|
380
|
+
).registry_urls
|
381
|
+
end
|
382
|
+
|
383
|
+
sig { returns(String) }
|
384
|
+
def normalised_name
|
385
|
+
NameNormaliser.normalise(dependency.name)
|
386
|
+
end
|
387
|
+
|
388
|
+
sig { params(json_url: String).returns(Excon::Response) }
|
389
|
+
def registry_json_response_for_dependency(json_url)
|
390
|
+
url = "#{json_url.chomp('/')}/#{@dependency.name}/json"
|
391
|
+
Dependabot::RegistryClient.get(
|
392
|
+
url: url,
|
393
|
+
headers: { "Accept" => APPLICATION_JSON }
|
394
|
+
)
|
395
|
+
end
|
396
|
+
|
397
|
+
sig { params(index_url: String).returns(Excon::Response) }
|
398
|
+
def registry_response_for_dependency(index_url)
|
399
|
+
Dependabot::RegistryClient.get(
|
400
|
+
url: index_url + normalised_name + "/",
|
401
|
+
headers: { "Accept" => APPLICATION_TEXT }
|
402
|
+
)
|
403
|
+
end
|
404
|
+
|
405
|
+
sig { params(index_url: String).returns(Excon::Response) }
|
406
|
+
def registry_index_response(index_url)
|
407
|
+
Dependabot::RegistryClient.get(
|
408
|
+
url: index_url,
|
409
|
+
headers: { "Accept" => APPLICATION_TEXT }
|
410
|
+
)
|
411
|
+
end
|
412
|
+
|
413
|
+
sig { params(filename: String).returns(T.nilable(String)) }
|
414
|
+
def get_version_from_filename(filename)
|
415
|
+
filename
|
416
|
+
.gsub(/#{name_regex}-/i, "")
|
417
|
+
.split(/-|\.tar\.|\.zip|\.whl/)
|
418
|
+
.first
|
419
|
+
end
|
420
|
+
|
421
|
+
sig do
|
422
|
+
params(req_string: T.nilable(String))
|
423
|
+
.returns(T.nilable(Dependabot::Requirement))
|
424
|
+
end
|
425
|
+
def build_python_requirement(req_string)
|
426
|
+
return nil unless req_string
|
427
|
+
|
428
|
+
requirement_class.new(CGI.unescapeHTML(req_string))
|
429
|
+
rescue Gem::Requirement::BadRequirementError
|
430
|
+
nil
|
431
|
+
end
|
432
|
+
|
433
|
+
sig { params(link: String).returns(T.nilable(String)) }
|
434
|
+
def requires_python_from_link(link)
|
435
|
+
raw_value = Nokogiri::XML(link)
|
436
|
+
.at_css("a")
|
437
|
+
&.attribute("data-requires-python")
|
438
|
+
&.content
|
439
|
+
|
440
|
+
return nil unless raw_value
|
441
|
+
|
442
|
+
CGI.unescapeHTML(raw_value) # Decodes HTML entities like >=3 → >=3
|
443
|
+
end
|
444
|
+
|
445
|
+
sig { returns(T.class_of(Dependabot::Version)) }
|
446
|
+
def version_class
|
447
|
+
dependency.version_class
|
448
|
+
end
|
449
|
+
|
450
|
+
sig { returns(T.class_of(Dependabot::Requirement)) }
|
451
|
+
def requirement_class
|
452
|
+
dependency.requirement_class
|
453
|
+
end
|
454
|
+
|
455
|
+
sig { params(index_url: String).returns(T::Hash[String, String]) }
|
456
|
+
def auth_headers_for(index_url)
|
457
|
+
credential = @credentials.find { |cred| cred["index-url"] == index_url }
|
458
|
+
return {} unless credential
|
459
|
+
|
460
|
+
{ "Authorization" => "Basic #{Base64.strict_encode64(
|
461
|
+
"#{credential[CREDENTIALS_USERNAME]}:#{credential[CREDENTIALS_PASSWORD]}"
|
462
|
+
)}" }
|
463
|
+
end
|
464
|
+
|
465
|
+
sig { returns(Regexp) }
|
466
|
+
def name_regex
|
467
|
+
parts = normalised_name.split(/[\s_.-]/).map { |n| Regexp.quote(n) }
|
468
|
+
/#{parts.join("[\s_.-]")}/i
|
469
|
+
end
|
470
|
+
|
471
|
+
sig { params(index_url: T.nilable(String)).returns(T::Boolean) }
|
472
|
+
def validate_index(index_url)
|
473
|
+
return false unless index_url
|
474
|
+
|
475
|
+
return true if index_url.match?(URI::DEFAULT_PARSER.regexp[:ABS_URI])
|
476
|
+
|
477
|
+
raise Dependabot::DependencyFileNotResolvable,
|
478
|
+
"Invalid URL: #{sanitized_url(index_url)}"
|
479
|
+
end
|
480
|
+
|
481
|
+
sig { params(index_url: String).returns(String) }
|
482
|
+
def sanitized_url(index_url)
|
483
|
+
index_url.sub(%r{//([^/@]+)@}, "//redacted@")
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
data/lib/dependabot/uv/{update_checker/index_finder.rb → package/package_registry_finder.rb}
RENAMED
@@ -7,8 +7,9 @@ require "dependabot/errors"
|
|
7
7
|
|
8
8
|
module Dependabot
|
9
9
|
module Uv
|
10
|
-
|
11
|
-
class
|
10
|
+
module Package
|
11
|
+
class PackageRegistryFinder
|
12
|
+
extend T::Sig
|
12
13
|
PYPI_BASE_URL = "https://pypi.org/simple/"
|
13
14
|
ENVIRONMENT_VARIABLE_REGEX = /\$\{.+\}/
|
14
15
|
|
@@ -18,7 +19,8 @@ module Dependabot
|
|
18
19
|
@dependency = dependency
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
+
sig { returns(T::Array[String]) }
|
23
|
+
def registry_urls
|
22
24
|
extra_index_urls =
|
23
25
|
config_variable_index_urls[:extra] +
|
24
26
|
pipfile_index_urls[:extra] +
|
@@ -140,7 +142,7 @@ module Dependabot
|
|
140
142
|
end
|
141
143
|
|
142
144
|
def config_variable_index_urls
|
143
|
-
urls = { main: nil, extra: [] }
|
145
|
+
urls = { main: T.let(nil, T.nilable(String)), extra: [] }
|
144
146
|
|
145
147
|
index_url_creds = credentials
|
146
148
|
.select { |cred| cred["type"] == "python_index" }
|
@@ -158,7 +160,7 @@ module Dependabot
|
|
158
160
|
end
|
159
161
|
|
160
162
|
def clean_check_and_remove_environment_variables(url)
|
161
|
-
url = url.strip.
|
163
|
+
url = url.strip.sub(%r{/+$}, "") + "/"
|
162
164
|
|
163
165
|
return authed_base_url(url) unless url.match?(ENVIRONMENT_VARIABLE_REGEX)
|
164
166
|
|
@@ -190,7 +192,11 @@ module Dependabot
|
|
190
192
|
cred = credential_for(base_url)
|
191
193
|
return base_url unless cred
|
192
194
|
|
193
|
-
AuthedUrlBuilder.authed_url(credential: cred)
|
195
|
+
builder = AuthedUrlBuilder.authed_url(credential: cred)
|
196
|
+
|
197
|
+
return base_url unless builder
|
198
|
+
|
199
|
+
builder.gsub(%r{/*$}, "") + "/"
|
194
200
|
end
|
195
201
|
|
196
202
|
def credential_for(url)
|