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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b23c5beac181aafb8e92f670984121a88254da3a1804dbd7891e9e6bc274b84c
4
- data.tar.gz: c5007c5452b38eac6210eac2df08635c69e3ed7fb7410e8173e9964678902d9b
3
+ metadata.gz: 2182d20a6869566befc86859bd0260cbe23c3ce48b128c95c82ec5713af9f25b
4
+ data.tar.gz: 0c7a49f6debe6d304cc07854636139d2504a9bd74baa1c72ff08a072b2ddea7c
5
5
  SHA512:
6
- metadata.gz: f646e21b2dd5f869a1fc56a48ab0f04051d202b4efc6f98a4d3dd2efca28526ffae66068ee3d59fd2ad5e03ad243db060ddcc703df018cefb4c1f06369043887
7
- data.tar.gz: e5044b7cd9b6bb4f3a8273309e1d20a0ffb784aa5c5b92401f28e0a885cef9ac239ee2e4a51fdff6978425644529e705a835b8d58932bd45aae92e47d9dfc3e5
6
+ metadata.gz: ee6297977b3276e5d0661dfa1fc1400100a86dfacc0e916adef15ce504f9adb5707db9214c5ee017147202c8c2104333a9bf6c7f351fdd1ce8577e7b88d005ba
7
+ data.tar.gz: b71b17c588efbd34281a174f229657173f924cdba4194ebb8073a37b3e2b7e37cdeec7effa71da66807291f0d455f76d3da8e8ab2ff560b4e35cbdab60f6781a
@@ -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(pyproject_path, 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(dependency, pyproject_path)
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(entry, pyproject_path)
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
- if requirements_match?(T.must(captured_requirement), old_req)
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
- "#{Regexp.last_match(1)}#{new_req}#{Regexp.last_match(3)}"
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
- normalize = lambda do |req|
159
- req.split(",").map(&:strip).sort.join(",")
160
- end
177
+ normalized_requirement(req1) == normalized_requirement(req2)
178
+ end
161
179
 
162
- normalize.call(req1) == normalize.call(req2)
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
- command = "pyenv exec uv lock --upgrade-package #{T.must(dependency).name} #{options}"
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 "sorbet-runtime"
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
- LANGUAGE = "python"
11
-
12
- class Language < Dependabot::Ecosystem::VersionManager
13
- extend T::Sig
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: strict
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
- require "dependabot/logger"
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
- class LanguageVersionManager
11
- extend T::Sig
12
-
13
- sig { params(python_requirement_parser: T.untyped).void }
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 "sorbet-runtime"
4
+ require "dependabot/python/native_helpers"
5
5
 
6
6
  module Dependabot
7
7
  module Uv
8
- module NativeHelpers
9
- extend T::Sig
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/update_checkers/version_filters"
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 ||= Package::PackageDetailsFetcher.new(
30
- dependency: dependency,
31
- dependency_files: dependency_files,
32
- credentials: credentials
33
- ).fetch
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(dependency:, dependency_files:, credentials:, repo_contents_path: nil)
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
- nil
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
- return resolver.lowest_resolvable_security_fix_version if resolver_type == :requirements
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
  )