dependabot-uv 0.350.0 → 0.352.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/helpers/lib/parser.py +13 -5
- data/lib/dependabot/uv/file_updater/lock_file_updater.rb +37 -10
- data/lib/dependabot/uv/language.rb +6 -76
- data/lib/dependabot/uv/language_version_manager.rb +6 -131
- data/lib/dependabot/uv/native_helpers.rb +4 -30
- data/lib/dependabot/uv/package.rb +27 -0
- data/lib/dependabot/uv/requirement_suffix_helper.rb +29 -0
- data/lib/dependabot/uv/update_checker/latest_version_finder.rb +10 -16
- data/lib/dependabot/uv/update_checker/lock_file_resolver.rb +41 -3
- data/lib/dependabot/uv/update_checker.rb +9 -2
- metadata +8 -8
- data/lib/dependabot/uv/package/package_details_fetcher.rb +0 -486
- data/lib/dependabot/uv/package/package_registry_finder.rb +0 -288
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2182d20a6869566befc86859bd0260cbe23c3ce48b128c95c82ec5713af9f25b
|
|
4
|
+
data.tar.gz: 0c7a49f6debe6d304cc07854636139d2504a9bd74baa1c72ff08a072b2ddea7c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee6297977b3276e5d0661dfa1fc1400100a86dfacc0e916adef15ce504f9adb5707db9214c5ee017147202c8c2104333a9bf6c7f351fdd1ce8577e7b88d005ba
|
|
7
|
+
data.tar.gz: b71b17c588efbd34281a174f229657173f924cdba4194ebb8073a37b3e2b7e37cdeec7effa71da66807291f0d455f76d3da8e8ab2ff560b4e35cbdab60f6781a
|
data/helpers/lib/parser.py
CHANGED
|
@@ -32,7 +32,7 @@ def parse_pep621_pep735_dependencies(pyproject_path):
|
|
|
32
32
|
next(iter(specifier_set)).operator in {"==", "==="}):
|
|
33
33
|
return next(iter(specifier_set)).version
|
|
34
34
|
|
|
35
|
-
def parse_requirement(entry, pyproject_path):
|
|
35
|
+
def parse_requirement(entry, pyproject_path, requirement_type=None):
|
|
36
36
|
try:
|
|
37
37
|
req = Requirement(entry)
|
|
38
38
|
except InvalidRequirement as e:
|
|
@@ -46,14 +46,19 @@ def parse_pep621_pep735_dependencies(pyproject_path):
|
|
|
46
46
|
"file": pyproject_path,
|
|
47
47
|
"requirement": str(req.specifier),
|
|
48
48
|
"extras": sorted(list(req.extras)),
|
|
49
|
+
"requirement_type": requirement_type,
|
|
49
50
|
}
|
|
50
51
|
return data
|
|
51
52
|
|
|
52
|
-
def parse_toml_section_pep621_dependencies(
|
|
53
|
+
def parse_toml_section_pep621_dependencies(
|
|
54
|
+
pyproject_path, dependencies, requirement_type=None
|
|
55
|
+
):
|
|
53
56
|
requirement_packages = []
|
|
54
57
|
|
|
55
58
|
for dependency in dependencies:
|
|
56
|
-
parsed_dependency = parse_requirement(
|
|
59
|
+
parsed_dependency = parse_requirement(
|
|
60
|
+
dependency, pyproject_path, requirement_type
|
|
61
|
+
)
|
|
57
62
|
requirement_packages.append(parsed_dependency)
|
|
58
63
|
|
|
59
64
|
return requirement_packages
|
|
@@ -75,7 +80,9 @@ def parse_pep621_pep735_dependencies(pyproject_path):
|
|
|
75
80
|
for entry in dependencies:
|
|
76
81
|
# Handle direct requirement
|
|
77
82
|
if isinstance(entry, str):
|
|
78
|
-
parsed_dependency = parse_requirement(
|
|
83
|
+
parsed_dependency = parse_requirement(
|
|
84
|
+
entry, pyproject_path, group_name
|
|
85
|
+
)
|
|
79
86
|
requirement_packages.append(parsed_dependency)
|
|
80
87
|
# Handle include-group directive
|
|
81
88
|
elif isinstance(entry, dict) and "include-group" in entry:
|
|
@@ -128,7 +135,8 @@ def parse_pep621_pep735_dependencies(pyproject_path):
|
|
|
128
135
|
if 'requires' in build_system_section:
|
|
129
136
|
build_system_dependencies = parse_toml_section_pep621_dependencies(
|
|
130
137
|
pyproject_path,
|
|
131
|
-
build_system_section['requires']
|
|
138
|
+
build_system_section['requires'],
|
|
139
|
+
"build-system"
|
|
132
140
|
)
|
|
133
141
|
dependencies.extend(build_system_dependencies)
|
|
134
142
|
|
|
@@ -12,10 +12,12 @@ require "dependabot/uv/file_parser/python_requirement_parser"
|
|
|
12
12
|
require "dependabot/uv/file_updater"
|
|
13
13
|
require "dependabot/uv/native_helpers"
|
|
14
14
|
require "dependabot/uv/name_normaliser"
|
|
15
|
+
require "dependabot/uv/requirement_suffix_helper"
|
|
15
16
|
|
|
16
17
|
module Dependabot
|
|
17
18
|
module Uv
|
|
18
19
|
class FileUpdater
|
|
20
|
+
# rubocop:disable Metrics/ClassLength
|
|
19
21
|
class LockFileUpdater
|
|
20
22
|
extend T::Sig
|
|
21
23
|
|
|
@@ -73,6 +75,16 @@ module Dependabot
|
|
|
73
75
|
T.must(dependencies.first)
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
sig { returns(T::Boolean) }
|
|
79
|
+
def build_system_only_dependency?
|
|
80
|
+
return false unless dependency
|
|
81
|
+
|
|
82
|
+
groups = T.must(dependency).requirements.flat_map { |req| req[:groups] || [] }.compact.uniq
|
|
83
|
+
return false if groups.empty?
|
|
84
|
+
|
|
85
|
+
groups.all?("build-system")
|
|
86
|
+
end
|
|
87
|
+
|
|
76
88
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
|
77
89
|
def fetch_updated_dependency_files
|
|
78
90
|
return [] unless create_or_update_lock_file?
|
|
@@ -87,9 +99,10 @@ module Dependabot
|
|
|
87
99
|
)
|
|
88
100
|
end
|
|
89
101
|
|
|
90
|
-
if lockfile
|
|
102
|
+
if lockfile && !build_system_only_dependency?
|
|
91
103
|
# Use updated_lockfile_content which might raise if the lockfile doesn't change
|
|
92
104
|
new_content = updated_lockfile_content
|
|
105
|
+
|
|
93
106
|
raise "Expected lockfile to change!" if T.must(lockfile).content == new_content
|
|
94
107
|
|
|
95
108
|
updated_files << updated_file(file: T.must(lockfile), content: new_content)
|
|
@@ -136,17 +149,23 @@ module Dependabot
|
|
|
136
149
|
updated_content = content.gsub(regex) do
|
|
137
150
|
captured_requirement = Regexp.last_match(2)
|
|
138
151
|
|
|
139
|
-
|
|
152
|
+
requirement_body, suffix = RequirementSuffixHelper.split(T.must(captured_requirement))
|
|
153
|
+
|
|
154
|
+
next Regexp.last_match(0) unless old_req
|
|
155
|
+
|
|
156
|
+
if requirements_match?(T.must(requirement_body), old_req)
|
|
140
157
|
replaced = true
|
|
141
|
-
"#{Regexp.last_match(1)}#{new_req}#{Regexp.last_match(3)}"
|
|
158
|
+
"#{Regexp.last_match(1)}#{new_req}#{suffix}#{Regexp.last_match(3)}"
|
|
142
159
|
else
|
|
143
160
|
Regexp.last_match(0)
|
|
144
161
|
end
|
|
145
162
|
end
|
|
146
|
-
|
|
147
163
|
unless replaced
|
|
148
164
|
updated_content = content.sub(regex) do
|
|
149
|
-
|
|
165
|
+
captured_requirement = Regexp.last_match(2)
|
|
166
|
+
_, suffix = RequirementSuffixHelper.split(T.must(captured_requirement))
|
|
167
|
+
|
|
168
|
+
"#{Regexp.last_match(1)}#{new_req}#{suffix}#{Regexp.last_match(3)}"
|
|
150
169
|
end
|
|
151
170
|
end
|
|
152
171
|
|
|
@@ -155,11 +174,12 @@ module Dependabot
|
|
|
155
174
|
|
|
156
175
|
sig { params(req1: String, req2: String).returns(T::Boolean) }
|
|
157
176
|
def requirements_match?(req1, req2)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
end
|
|
177
|
+
normalized_requirement(req1) == normalized_requirement(req2)
|
|
178
|
+
end
|
|
161
179
|
|
|
162
|
-
|
|
180
|
+
sig { params(req: String).returns(String) }
|
|
181
|
+
def normalized_requirement(req)
|
|
182
|
+
req.split(",").map(&:strip).sort.join(",")
|
|
163
183
|
end
|
|
164
184
|
|
|
165
185
|
sig { returns(String) }
|
|
@@ -285,7 +305,13 @@ module Dependabot
|
|
|
285
305
|
options_fingerprint = lock_options_fingerprint(options)
|
|
286
306
|
|
|
287
307
|
# Use pyenv exec to ensure we're using the correct Python environment
|
|
288
|
-
|
|
308
|
+
# Include the target version to respect ignore conditions and avoid upgrading
|
|
309
|
+
# to the absolute latest version (which may be blocked by ignore rules)
|
|
310
|
+
dep_name = T.must(dependency).name
|
|
311
|
+
dep_version = T.must(dependency).version
|
|
312
|
+
package_spec = dep_version ? "#{dep_name}==#{dep_version}" : dep_name
|
|
313
|
+
|
|
314
|
+
command = "pyenv exec uv lock --upgrade-package #{package_spec} #{options}"
|
|
289
315
|
fingerprint = "pyenv exec uv lock --upgrade-package <dependency_name> #{options_fingerprint}"
|
|
290
316
|
|
|
291
317
|
run_command(command, fingerprint:)
|
|
@@ -453,6 +479,7 @@ module Dependabot
|
|
|
453
479
|
T.must(dependency).requirements.select { _1[:file].end_with?(*REQUIRED_FILES) }.any?
|
|
454
480
|
end
|
|
455
481
|
end
|
|
482
|
+
# rubocop:enable Metrics/ClassLength
|
|
456
483
|
end
|
|
457
484
|
end
|
|
458
485
|
end
|
|
@@ -1,84 +1,14 @@
|
|
|
1
1
|
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "
|
|
5
|
-
require "dependabot/uv/version"
|
|
6
|
-
require "dependabot/ecosystem"
|
|
4
|
+
require "dependabot/python/language"
|
|
7
5
|
|
|
8
6
|
module Dependabot
|
|
9
7
|
module Uv
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# This list must match the versions specified at the top of `uv/Dockerfile`
|
|
16
|
-
# ARG PY_3_13=3.13.2
|
|
17
|
-
# When updating this list, also update python/lib/dependabot/python/language.rb
|
|
18
|
-
PRE_INSTALLED_PYTHON_VERSIONS_RAW = %w(
|
|
19
|
-
3.14.0
|
|
20
|
-
3.13.9
|
|
21
|
-
3.12.12
|
|
22
|
-
3.11.14
|
|
23
|
-
3.10.19
|
|
24
|
-
3.9.24
|
|
25
|
-
).freeze
|
|
26
|
-
|
|
27
|
-
PRE_INSTALLED_PYTHON_VERSIONS = T.let(
|
|
28
|
-
PRE_INSTALLED_PYTHON_VERSIONS_RAW.map do |v|
|
|
29
|
-
Version.new(v)
|
|
30
|
-
end.sort,
|
|
31
|
-
T::Array[Version]
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
PRE_INSTALLED_VERSIONS_MAP = T.let(
|
|
35
|
-
PRE_INSTALLED_PYTHON_VERSIONS.to_h do |v|
|
|
36
|
-
[Version.new(T.must(v.segments[0..1]).join(".")), v]
|
|
37
|
-
end,
|
|
38
|
-
T::Hash[Version, Version]
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
PRE_INSTALLED_HIGHEST_VERSION = T.let(T.must(PRE_INSTALLED_PYTHON_VERSIONS.max), Version)
|
|
42
|
-
|
|
43
|
-
SUPPORTED_VERSIONS = T.let(
|
|
44
|
-
PRE_INSTALLED_PYTHON_VERSIONS.map do |v|
|
|
45
|
-
Version.new(T.must(v.segments[0..1]&.join(".")))
|
|
46
|
-
end,
|
|
47
|
-
T::Array[Version]
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
NON_SUPPORTED_HIGHEST_VERSION = "3.8"
|
|
51
|
-
|
|
52
|
-
DEPRECATED_VERSIONS = T.let([Version.new(NON_SUPPORTED_HIGHEST_VERSION)].freeze, T::Array[Dependabot::Version])
|
|
53
|
-
|
|
54
|
-
sig do
|
|
55
|
-
params(
|
|
56
|
-
detected_version: T.nilable(String),
|
|
57
|
-
raw_version: T.nilable(String),
|
|
58
|
-
requirement: T.nilable(Requirement)
|
|
59
|
-
).void
|
|
60
|
-
end
|
|
61
|
-
def initialize(detected_version:, raw_version: nil, requirement: nil)
|
|
62
|
-
super(
|
|
63
|
-
name: LANGUAGE,
|
|
64
|
-
detected_version: detected_version ? major_minor_version(detected_version) : nil,
|
|
65
|
-
version: raw_version ? Version.new(raw_version) : nil,
|
|
66
|
-
deprecated_versions: DEPRECATED_VERSIONS,
|
|
67
|
-
supported_versions: SUPPORTED_VERSIONS,
|
|
68
|
-
requirement: requirement,
|
|
69
|
-
)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
sig { params(version: String).returns(T.nilable(Version)) }
|
|
75
|
-
def major_minor_version(version)
|
|
76
|
-
return nil if version.empty?
|
|
77
|
-
|
|
78
|
-
major_minor = T.let(T.must(Version.new(version).segments[0..1]&.join(".")), String)
|
|
79
|
-
|
|
80
|
-
Version.new(major_minor)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
8
|
+
# Both uv and Python ecosystems use the same Python language versions.
|
|
9
|
+
# The Python version list is maintained in python/lib/dependabot/python/language.rb
|
|
10
|
+
# and shared via this alias to avoid dual-maintenance.
|
|
11
|
+
LANGUAGE = Python::LANGUAGE
|
|
12
|
+
Language = Python::Language
|
|
83
13
|
end
|
|
84
14
|
end
|
|
@@ -1,138 +1,13 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "dependabot/
|
|
5
|
-
require "dependabot/uv/version"
|
|
6
|
-
require "sorbet-runtime"
|
|
4
|
+
require "dependabot/python/language_version_manager"
|
|
7
5
|
|
|
8
6
|
module Dependabot
|
|
9
7
|
module Uv
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def initialize(python_requirement_parser:)
|
|
15
|
-
@python_requirement_parser = python_requirement_parser
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
sig { returns(T.nilable(String)) }
|
|
19
|
-
def install_required_python
|
|
20
|
-
# The leading space is important in the version check
|
|
21
|
-
return if SharedHelpers.run_shell_command("pyenv versions").include?(" #{python_major_minor}.")
|
|
22
|
-
|
|
23
|
-
SharedHelpers.run_shell_command(
|
|
24
|
-
"tar -axf /usr/local/.pyenv/versions/#{python_version}.tar.zst -C /usr/local/.pyenv/versions"
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
sig { returns(String) }
|
|
29
|
-
def installed_version
|
|
30
|
-
# Use `pyenv exec` to query the active Python version
|
|
31
|
-
output, _status = SharedHelpers.run_shell_command("pyenv exec python --version")
|
|
32
|
-
version = output.strip.split.last # Extract the version number (e.g., "3.13.1")
|
|
33
|
-
|
|
34
|
-
T.must(version)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
sig { returns(T.untyped) }
|
|
38
|
-
def python_major_minor
|
|
39
|
-
@python_major_minor ||= T.let(T.must(Version.new(python_version).segments[0..1]).join("."), T.untyped)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
sig { returns(String) }
|
|
43
|
-
def python_version
|
|
44
|
-
@python_version ||= T.let(python_version_from_supported_versions, T.nilable(String))
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
sig { returns(String) }
|
|
48
|
-
def python_requirement_string
|
|
49
|
-
if user_specified_python_version
|
|
50
|
-
if user_specified_python_version.start_with?(/\d/)
|
|
51
|
-
parts = user_specified_python_version.split(".")
|
|
52
|
-
parts.fill("*", (parts.length)..2).join(".")
|
|
53
|
-
else
|
|
54
|
-
user_specified_python_version
|
|
55
|
-
end
|
|
56
|
-
else
|
|
57
|
-
python_version_matching_imputed_requirements || Language::PRE_INSTALLED_HIGHEST_VERSION.to_s
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
sig { params(requirement_string: T.nilable(String)).returns(T.nilable(String)) }
|
|
62
|
-
def normalize_python_exact_version(requirement_string)
|
|
63
|
-
return requirement_string if requirement_string.nil? || requirement_string.strip.empty?
|
|
64
|
-
|
|
65
|
-
requirement_string = requirement_string.strip
|
|
66
|
-
|
|
67
|
-
# If the requirement already has a wildcard, return nil
|
|
68
|
-
return nil if requirement_string == "*"
|
|
69
|
-
|
|
70
|
-
# If the requirement is not an exact version such as not X.Y.Z, =X.Y.Z, ==X.Y.Z, ===X.Y.Z
|
|
71
|
-
# then return the requirement as is
|
|
72
|
-
return requirement_string unless requirement_string.match?(/^=?={0,2}\s*\d+\.\d+(\.\d+)?(-[a-z0-9.-]+)?$/i)
|
|
73
|
-
|
|
74
|
-
parts = requirement_string.gsub(/^=+/, "").split(".")
|
|
75
|
-
|
|
76
|
-
case parts.length
|
|
77
|
-
when 1 # Only major version (X)
|
|
78
|
-
">= #{parts[0]}.0.0 < #{parts[0].to_i + 1}.0.0" # Ensure only major version range
|
|
79
|
-
when 2 # Major.Minor (X.Y)
|
|
80
|
-
">= #{parts[0]}.#{parts[1]}.0 < #{parts[0].to_i}.#{parts[1].to_i + 1}.0" # Ensure only minor version range
|
|
81
|
-
when 3 # Major.Minor.Patch (X.Y.Z)
|
|
82
|
-
">= #{parts[0]}.#{parts[1]}.0 < #{parts[0].to_i}.#{parts[1].to_i + 1}.0" # Convert to >= X.Y.0
|
|
83
|
-
else
|
|
84
|
-
requirement_string
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
sig { returns(String) }
|
|
89
|
-
def python_version_from_supported_versions
|
|
90
|
-
requirement_string = python_requirement_string
|
|
91
|
-
|
|
92
|
-
# If the requirement string isn't already a range (eg ">3.10"), coerce it to "major.minor.*".
|
|
93
|
-
# The patch version is ignored because a non-matching patch version is unlikely to affect resolution.
|
|
94
|
-
requirement_string = requirement_string.gsub(/\.\d+$/, ".*") if /^\d/.match?(requirement_string)
|
|
95
|
-
|
|
96
|
-
requirement_string = normalize_python_exact_version(requirement_string)
|
|
97
|
-
|
|
98
|
-
if requirement_string.nil? || requirement_string.strip.empty?
|
|
99
|
-
return Language::PRE_INSTALLED_HIGHEST_VERSION.to_s
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
# Try to match one of our pre-installed Python versions
|
|
103
|
-
requirement = T.must(Requirement.requirements_array(requirement_string).first)
|
|
104
|
-
version = Language::PRE_INSTALLED_PYTHON_VERSIONS.find { |v| requirement.satisfied_by?(v) }
|
|
105
|
-
return version.to_s if version
|
|
106
|
-
|
|
107
|
-
# Otherwise we have to raise an error
|
|
108
|
-
supported_versions = Language::SUPPORTED_VERSIONS.map { |v| "#{v}.*" }.join(", ")
|
|
109
|
-
raise ToolVersionNotSupported.new("Python", python_requirement_string, supported_versions)
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
sig { returns(T.untyped) }
|
|
113
|
-
def user_specified_python_version
|
|
114
|
-
@python_requirement_parser.user_specified_requirements.first
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
sig { returns(T.nilable(String)) }
|
|
118
|
-
def python_version_matching_imputed_requirements
|
|
119
|
-
compiled_file_python_requirement_markers =
|
|
120
|
-
@python_requirement_parser.imputed_requirements.map do |r|
|
|
121
|
-
Requirement.new(r)
|
|
122
|
-
end
|
|
123
|
-
python_version_matching(compiled_file_python_requirement_markers)
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
sig { params(requirements: T.untyped).returns(T.nilable(String)) }
|
|
127
|
-
def python_version_matching(requirements)
|
|
128
|
-
Language::PRE_INSTALLED_PYTHON_VERSIONS.find do |version|
|
|
129
|
-
requirements.all? do |req|
|
|
130
|
-
next req.any? { |r| r.satisfied_by?(version) } if req.is_a?(Array)
|
|
131
|
-
|
|
132
|
-
req.satisfied_by?(version)
|
|
133
|
-
end
|
|
134
|
-
end.to_s
|
|
135
|
-
end
|
|
136
|
-
end
|
|
8
|
+
# Uv and Python ecosystems share the same Python version management logic.
|
|
9
|
+
# This alias ensures uv benefits from improvements in Python's implementation,
|
|
10
|
+
# including bug fixes like the guard clause in python_version_matching_imputed_requirements.
|
|
11
|
+
LanguageVersionManager = Python::LanguageVersionManager
|
|
137
12
|
end
|
|
138
13
|
end
|
|
@@ -1,38 +1,12 @@
|
|
|
1
1
|
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "
|
|
4
|
+
require "dependabot/python/native_helpers"
|
|
5
5
|
|
|
6
6
|
module Dependabot
|
|
7
7
|
module Uv
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
sig { returns(String) }
|
|
12
|
-
def self.python_helper_path
|
|
13
|
-
clean_path(File.join(python_helpers_dir, "run.py"))
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
sig { returns(String) }
|
|
17
|
-
def self.python_requirements_path
|
|
18
|
-
clean_path(File.join(python_helpers_dir, "requirements.txt"))
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
sig { returns(String) }
|
|
22
|
-
def self.python_helpers_dir
|
|
23
|
-
File.join(native_helpers_root, "python")
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
sig { returns(String) }
|
|
27
|
-
def self.native_helpers_root
|
|
28
|
-
default_path = File.join(__dir__, "../../../..")
|
|
29
|
-
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
sig { params(path: T.nilable(String)).returns(String) }
|
|
33
|
-
def self.clean_path(path)
|
|
34
|
-
Pathname.new(path).cleanpath.to_path
|
|
35
|
-
end
|
|
36
|
-
end
|
|
8
|
+
# Uv and Python ecosystems share the same native Python helpers.
|
|
9
|
+
# Both point to the same helpers/python directory.
|
|
10
|
+
NativeHelpers = Python::NativeHelpers
|
|
37
11
|
end
|
|
38
12
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/python/package/package_registry_finder"
|
|
6
|
+
require "dependabot/python/package/package_details_fetcher"
|
|
7
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
module Uv
|
|
10
|
+
# UV uses the same Python package registry handling (PyPI)
|
|
11
|
+
module Package
|
|
12
|
+
# Re-export constants from Python::Package for backward compatibility
|
|
13
|
+
CREDENTIALS_USERNAME = Python::Package::CREDENTIALS_USERNAME
|
|
14
|
+
CREDENTIALS_PASSWORD = Python::Package::CREDENTIALS_PASSWORD
|
|
15
|
+
APPLICATION_JSON = Python::Package::APPLICATION_JSON
|
|
16
|
+
APPLICATION_TEXT = Python::Package::APPLICATION_TEXT
|
|
17
|
+
CPYTHON = Python::Package::CPYTHON
|
|
18
|
+
PYTHON = Python::Package::PYTHON
|
|
19
|
+
UNKNOWN = Python::Package::UNKNOWN
|
|
20
|
+
MAIN_PYPI_INDEXES = Python::Package::MAIN_PYPI_INDEXES
|
|
21
|
+
VERSION_REGEX = Python::Package::VERSION_REGEX
|
|
22
|
+
|
|
23
|
+
PackageRegistryFinder = Dependabot::Python::Package::PackageRegistryFinder
|
|
24
|
+
PackageDetailsFetcher = Dependabot::Python::Package::PackageDetailsFetcher
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
module Uv
|
|
8
|
+
module RequirementSuffixHelper
|
|
9
|
+
extend T::Sig
|
|
10
|
+
|
|
11
|
+
REQUIREMENT_SUFFIX_REGEX = T.let(
|
|
12
|
+
Regexp.new(
|
|
13
|
+
"\\A(?<requirement>.*?)(?<suffix>\\s*(?:;|#).*)?\\z",
|
|
14
|
+
Regexp::MULTILINE
|
|
15
|
+
).freeze,
|
|
16
|
+
Regexp
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
sig { params(segment: String).returns(T::Array[String]) }
|
|
20
|
+
def self.split(segment)
|
|
21
|
+
match = REQUIREMENT_SUFFIX_REGEX.match(segment)
|
|
22
|
+
requirement = match ? match[:requirement] : segment
|
|
23
|
+
suffix = match&.[](:suffix) || ""
|
|
24
|
+
|
|
25
|
+
[T.must(requirement).strip, suffix]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require "cgi"
|
|
5
|
-
require "excon"
|
|
6
|
-
require "nokogiri"
|
|
7
4
|
require "sorbet-runtime"
|
|
8
|
-
|
|
9
|
-
require "dependabot/dependency"
|
|
10
5
|
require "dependabot/uv/update_checker"
|
|
11
|
-
require "dependabot/
|
|
12
|
-
require "dependabot/registry_client"
|
|
13
|
-
require "dependabot/uv/authed_url_builder"
|
|
14
|
-
require "dependabot/uv/name_normaliser"
|
|
15
|
-
require "dependabot/uv/package/package_registry_finder"
|
|
16
|
-
require "dependabot/uv/package/package_details_fetcher"
|
|
6
|
+
require "dependabot/uv/package"
|
|
17
7
|
require "dependabot/package/package_latest_version_finder"
|
|
18
8
|
|
|
19
9
|
module Dependabot
|
|
20
10
|
module Uv
|
|
21
11
|
class UpdateChecker
|
|
12
|
+
# UV uses the same PyPI registry for package lookups as Python
|
|
22
13
|
class LatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
|
|
23
14
|
extend T::Sig
|
|
24
15
|
|
|
@@ -26,11 +17,14 @@ module Dependabot
|
|
|
26
17
|
override.returns(T.nilable(Dependabot::Package::PackageDetails))
|
|
27
18
|
end
|
|
28
19
|
def package_details
|
|
29
|
-
@package_details ||=
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
20
|
+
@package_details ||= T.let(
|
|
21
|
+
Package::PackageDetailsFetcher.new(
|
|
22
|
+
dependency: dependency,
|
|
23
|
+
dependency_files: dependency_files,
|
|
24
|
+
credentials: credentials
|
|
25
|
+
).fetch,
|
|
26
|
+
T.nilable(Dependabot::Package::PackageDetails)
|
|
27
|
+
)
|
|
34
28
|
end
|
|
35
29
|
|
|
36
30
|
sig { override.returns(T::Boolean) }
|
|
@@ -6,6 +6,7 @@ require "sorbet-runtime"
|
|
|
6
6
|
require "dependabot/uv/version"
|
|
7
7
|
require "dependabot/uv/requirement"
|
|
8
8
|
require "dependabot/uv/update_checker"
|
|
9
|
+
require "dependabot/uv/update_checker/latest_version_finder"
|
|
9
10
|
|
|
10
11
|
module Dependabot
|
|
11
12
|
module Uv
|
|
@@ -18,14 +19,25 @@ module Dependabot
|
|
|
18
19
|
dependency: Dependabot::Dependency,
|
|
19
20
|
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
20
21
|
credentials: T::Array[Dependabot::Credential],
|
|
21
|
-
repo_contents_path: T.nilable(String)
|
|
22
|
+
repo_contents_path: T.nilable(String),
|
|
23
|
+
security_advisories: T::Array[Dependabot::SecurityAdvisory],
|
|
24
|
+
ignored_versions: T::Array[String]
|
|
22
25
|
).void
|
|
23
26
|
end
|
|
24
|
-
def initialize(
|
|
27
|
+
def initialize(
|
|
28
|
+
dependency:,
|
|
29
|
+
dependency_files:,
|
|
30
|
+
credentials:,
|
|
31
|
+
repo_contents_path: nil,
|
|
32
|
+
security_advisories: [],
|
|
33
|
+
ignored_versions: []
|
|
34
|
+
)
|
|
25
35
|
@dependency = dependency
|
|
26
36
|
@dependency_files = dependency_files
|
|
27
37
|
@credentials = credentials
|
|
28
38
|
@repo_contents_path = repo_contents_path
|
|
39
|
+
@security_advisories = security_advisories
|
|
40
|
+
@ignored_versions = ignored_versions
|
|
29
41
|
end
|
|
30
42
|
|
|
31
43
|
sig { params(requirement: T.nilable(String)).returns(T.nilable(Dependabot::Uv::Version)) }
|
|
@@ -50,7 +62,12 @@ module Dependabot
|
|
|
50
62
|
|
|
51
63
|
sig { returns(T.nilable(Dependabot::Uv::Version)) }
|
|
52
64
|
def lowest_resolvable_security_fix_version
|
|
53
|
-
|
|
65
|
+
# Delegate to LatestVersionFinder which handles security advisory filtering
|
|
66
|
+
fix_version = latest_version_finder.lowest_security_fix_version
|
|
67
|
+
return nil if fix_version.nil?
|
|
68
|
+
|
|
69
|
+
# Return the fix version cast to Uv::Version
|
|
70
|
+
Uv::Version.new(fix_version.to_s)
|
|
54
71
|
end
|
|
55
72
|
|
|
56
73
|
private
|
|
@@ -66,6 +83,27 @@ module Dependabot
|
|
|
66
83
|
|
|
67
84
|
sig { returns(T.nilable(String)) }
|
|
68
85
|
attr_reader :repo_contents_path
|
|
86
|
+
|
|
87
|
+
sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
|
|
88
|
+
attr_reader :security_advisories
|
|
89
|
+
|
|
90
|
+
sig { returns(T::Array[String]) }
|
|
91
|
+
attr_reader :ignored_versions
|
|
92
|
+
|
|
93
|
+
sig { returns(LatestVersionFinder) }
|
|
94
|
+
def latest_version_finder
|
|
95
|
+
@latest_version_finder ||= T.let(
|
|
96
|
+
LatestVersionFinder.new(
|
|
97
|
+
dependency: dependency,
|
|
98
|
+
dependency_files: dependency_files,
|
|
99
|
+
credentials: credentials,
|
|
100
|
+
ignored_versions: ignored_versions,
|
|
101
|
+
security_advisories: security_advisories,
|
|
102
|
+
raise_on_ignored: false
|
|
103
|
+
),
|
|
104
|
+
T.nilable(LatestVersionFinder)
|
|
105
|
+
)
|
|
106
|
+
end
|
|
69
107
|
end
|
|
70
108
|
end
|
|
71
109
|
end
|
|
@@ -127,7 +127,12 @@ module Dependabot
|
|
|
127
127
|
fix_version = lowest_security_fix_version
|
|
128
128
|
return latest_resolvable_version if fix_version.nil?
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
# For requirements and lock_file resolver types, delegate to the resolver
|
|
131
|
+
if resolver_type == :requirements || resolver_type == :lock_file
|
|
132
|
+
resolved_fix = resolver.lowest_resolvable_security_fix_version
|
|
133
|
+
# If no security fix version is found, fall back to latest_resolvable_version
|
|
134
|
+
return resolved_fix || latest_resolvable_version
|
|
135
|
+
end
|
|
131
136
|
|
|
132
137
|
resolver.resolvable?(version: fix_version) ? fix_version : nil
|
|
133
138
|
end
|
|
@@ -216,7 +221,9 @@ module Dependabot
|
|
|
216
221
|
dependency: dependency,
|
|
217
222
|
dependency_files: dependency_files,
|
|
218
223
|
credentials: credentials,
|
|
219
|
-
repo_contents_path: repo_contents_path
|
|
224
|
+
repo_contents_path: repo_contents_path,
|
|
225
|
+
security_advisories: security_advisories,
|
|
226
|
+
ignored_versions: ignored_versions
|
|
220
227
|
),
|
|
221
228
|
T.nilable(LockFileResolver)
|
|
222
229
|
)
|