dependabot-uv 0.299.1 → 0.301.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.
@@ -0,0 +1,392 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "toml-rb"
5
+ require "open3"
6
+ require "dependabot/dependency"
7
+ require "dependabot/shared_helpers"
8
+ require "dependabot/uv/language_version_manager"
9
+ require "dependabot/uv/version"
10
+ require "dependabot/uv/requirement"
11
+ require "dependabot/uv/file_parser/python_requirement_parser"
12
+ require "dependabot/uv/file_updater"
13
+ require "dependabot/uv/native_helpers"
14
+ require "dependabot/uv/name_normaliser"
15
+
16
+ module Dependabot
17
+ module Uv
18
+ class FileUpdater
19
+ class LockFileUpdater
20
+ require_relative "pyproject_preparer"
21
+
22
+ attr_reader :dependencies
23
+ attr_reader :dependency_files
24
+ attr_reader :credentials
25
+ attr_reader :index_urls
26
+
27
+ def initialize(dependencies:, dependency_files:, credentials:, index_urls: nil)
28
+ @dependencies = dependencies
29
+ @dependency_files = dependency_files
30
+ @credentials = credentials
31
+ @index_urls = index_urls
32
+ end
33
+
34
+ def updated_dependency_files
35
+ @updated_dependency_files ||= fetch_updated_dependency_files
36
+ end
37
+
38
+ private
39
+
40
+ def dependency
41
+ # For now, we'll only ever be updating a single dependency
42
+ dependencies.first
43
+ end
44
+
45
+ def fetch_updated_dependency_files
46
+ updated_files = []
47
+
48
+ if file_changed?(pyproject)
49
+ updated_files <<
50
+ updated_file(
51
+ file: pyproject,
52
+ content: updated_pyproject_content
53
+ )
54
+ end
55
+
56
+ if lockfile
57
+ # Use updated_lockfile_content which might raise if the lockfile doesn't change
58
+ new_content = updated_lockfile_content
59
+ raise "Expected lockfile to change!" if lockfile.content == new_content
60
+
61
+ updated_files << updated_file(file: lockfile, content: new_content)
62
+ end
63
+
64
+ updated_files
65
+ end
66
+
67
+ def updated_pyproject_content
68
+ content = pyproject.content
69
+ return content unless file_changed?(pyproject)
70
+
71
+ updated_content = content.dup
72
+
73
+ dependency.requirements.zip(dependency.previous_requirements).each do |new_r, old_r|
74
+ next unless new_r[:file] == pyproject.name && old_r[:file] == pyproject.name
75
+
76
+ updated_content = replace_dep(dependency, updated_content, new_r, old_r)
77
+ end
78
+
79
+ raise DependencyFileContentNotChanged, "Content did not change!" if content == updated_content
80
+
81
+ updated_content
82
+ end
83
+
84
+ def replace_dep(dep, content, new_r, old_r)
85
+ new_req = new_r[:requirement]
86
+ old_req = old_r[:requirement]
87
+
88
+ declaration_regex = declaration_regex(dep, old_r)
89
+ declaration_match = content.match(declaration_regex)
90
+ if declaration_match
91
+ declaration = declaration_match[:declaration]
92
+ new_declaration = declaration.sub(old_req, new_req)
93
+ content.sub(declaration, new_declaration)
94
+ else
95
+ content
96
+ end
97
+ end
98
+
99
+ def updated_lockfile_content
100
+ @updated_lockfile_content ||=
101
+ begin
102
+ original_content = lockfile.content
103
+ # Extract the original requires-python value to preserve it
104
+ original_requires_python = original_content
105
+ .match(/requires-python\s*=\s*["']([^"']+)["']/)&.captures&.first
106
+
107
+ # Use the original Python version requirement for the update if one exists
108
+ with_original_python_version(original_requires_python) do
109
+ new_lockfile = updated_lockfile_content_for(prepared_pyproject)
110
+
111
+ # Use direct string replacement to preserve the exact format
112
+ # Match the dependency section and update only the version
113
+ dependency_section_pattern = /
114
+ (\[\[package\]\]\s*\n
115
+ .*?name\s*=\s*["']#{Regexp.escape(dependency.name)}["']\s*\n
116
+ .*?)
117
+ (version\s*=\s*["'][^"']+["'])
118
+ (.*?)
119
+ (\[\[package\]\]|\z)
120
+ /xm
121
+
122
+ result = original_content.sub(dependency_section_pattern) do
123
+ section_start = Regexp.last_match(1)
124
+ version_line = "version = \"#{dependency.version}\""
125
+ section_end = Regexp.last_match(3)
126
+ next_section_or_end = Regexp.last_match(4)
127
+
128
+ "#{section_start}#{version_line}#{section_end}#{next_section_or_end}"
129
+ end
130
+
131
+ # If the content didn't change and we expect it to, something went wrong
132
+ if result == original_content
133
+ Dependabot.logger.warn("Package section not found for #{dependency.name}, falling back to raw update")
134
+ result = new_lockfile
135
+ end
136
+
137
+ # Restore the original requires-python if it exists
138
+ if original_requires_python
139
+ result = result.gsub(/requires-python\s*=\s*["'][^"']+["']/,
140
+ "requires-python = \"#{original_requires_python}\"")
141
+ end
142
+
143
+ result
144
+ end
145
+ end
146
+ end
147
+
148
+ # Helper method to temporarily override Python version during operations
149
+ def with_original_python_version(original_requires_python)
150
+ if original_requires_python
151
+ original_python_version = @original_python_version
152
+ @original_python_version = original_requires_python
153
+ result = yield
154
+ @original_python_version = original_python_version
155
+ result
156
+ else
157
+ yield
158
+ end
159
+ end
160
+
161
+ def prepared_pyproject
162
+ @prepared_pyproject ||=
163
+ begin
164
+ content = updated_pyproject_content
165
+ content = sanitize(content)
166
+ content = freeze_other_dependencies(content)
167
+ content = update_python_requirement(content)
168
+ content
169
+ end
170
+ end
171
+
172
+ def freeze_other_dependencies(pyproject_content)
173
+ PyprojectPreparer
174
+ .new(pyproject_content: pyproject_content, lockfile: lockfile)
175
+ .freeze_top_level_dependencies_except(dependencies)
176
+ end
177
+
178
+ def update_python_requirement(pyproject_content)
179
+ PyprojectPreparer
180
+ .new(pyproject_content: pyproject_content)
181
+ .update_python_requirement(language_version_manager.python_version)
182
+ end
183
+
184
+ def sanitize(pyproject_content)
185
+ PyprojectPreparer
186
+ .new(pyproject_content: pyproject_content)
187
+ .sanitize
188
+ end
189
+
190
+ def updated_lockfile_content_for(pyproject_content)
191
+ SharedHelpers.in_a_temporary_directory do
192
+ SharedHelpers.with_git_configured(credentials: credentials) do
193
+ write_temporary_dependency_files(pyproject_content)
194
+
195
+ # Install Python before writing .python-version to make sure we use a version that's available
196
+ language_version_manager.install_required_python
197
+
198
+ # Determine the Python version to use after installation
199
+ python_version = determine_python_version
200
+
201
+ # Now write the .python-version file with a version we know is installed
202
+ File.write(".python-version", python_version)
203
+
204
+ run_update_command
205
+
206
+ File.read("uv.lock")
207
+ end
208
+ end
209
+ end
210
+
211
+ def run_update_command
212
+ command = "pyenv exec uv lock --upgrade-package #{dependency.name}"
213
+ fingerprint = "pyenv exec uv lock --upgrade-package <dependency_name>"
214
+
215
+ run_command(command, fingerprint:)
216
+ end
217
+
218
+ def run_command(command, fingerprint: nil)
219
+ SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
220
+ end
221
+
222
+ def write_temporary_dependency_files(pyproject_content)
223
+ dependency_files.each do |file|
224
+ path = file.name
225
+ FileUtils.mkdir_p(Pathname.new(path).dirname)
226
+ File.write(path, file.content)
227
+ end
228
+
229
+ # Only write the .python-version file after the language version manager has
230
+ # installed the required Python version to ensure it's available
231
+ # Overwrite the pyproject with updated content
232
+ File.write("pyproject.toml", pyproject_content)
233
+ end
234
+
235
+ def determine_python_version
236
+ # Check available Python versions through pyenv
237
+ available_versions = nil
238
+ begin
239
+ available_versions = SharedHelpers.run_shell_command("pyenv versions --bare")
240
+ .split("\n")
241
+ .map(&:strip)
242
+ .reject(&:empty?)
243
+ rescue StandardError => e
244
+ Dependabot.logger.warn("Error checking available Python versions: #{e}")
245
+ end
246
+
247
+ # Try to find the closest match for our priority order
248
+ preferred_version = find_preferred_version(available_versions)
249
+
250
+ if preferred_version
251
+ # Just return the major.minor version string
252
+ preferred_version.match(/^(\d+\.\d+)/)[1]
253
+ else
254
+ # If all else fails, use "system" which should work with whatever Python is available
255
+ "system"
256
+ end
257
+ end
258
+
259
+ def find_preferred_version(available_versions)
260
+ return nil unless available_versions&.any?
261
+
262
+ # Try each strategy in order of preference
263
+ try_version_from_file(available_versions) ||
264
+ try_version_from_requires_python(available_versions) ||
265
+ try_highest_python3_version(available_versions)
266
+ end
267
+
268
+ def try_version_from_file(available_versions)
269
+ python_version_file = dependency_files.find { |f| f.name == ".python-version" }
270
+ return nil unless python_version_file && !python_version_file.content.strip.empty?
271
+
272
+ requested_version = python_version_file.content.strip
273
+ return requested_version if version_available?(available_versions, requested_version)
274
+
275
+ Dependabot.logger.info("Python version #{requested_version} from .python-version not available")
276
+ nil
277
+ end
278
+
279
+ def try_version_from_requires_python(available_versions)
280
+ return nil unless @original_python_version
281
+
282
+ version_match = @original_python_version.match(/(\d+\.\d+)/)
283
+ return nil unless version_match
284
+
285
+ requested_version = version_match[1]
286
+ return requested_version if version_available?(available_versions, requested_version)
287
+
288
+ Dependabot.logger.info("Python version #{requested_version} from requires-python not available")
289
+ nil
290
+ end
291
+
292
+ def try_highest_python3_version(available_versions)
293
+ python3_versions = available_versions
294
+ .select { |v| v.match(/^3\.\d+/) }
295
+ .sort_by { |v| Gem::Version.new(v.match(/^(\d+\.\d+)/)[1]) }
296
+ .reverse
297
+
298
+ python3_versions.first # returns nil if array is empty
299
+ end
300
+
301
+ def version_available?(available_versions, requested_version)
302
+ # Check if the exact version or a version with the same major.minor is available
303
+ available_versions.any? do |v|
304
+ v == requested_version || v.start_with?("#{requested_version}.")
305
+ end
306
+ end
307
+
308
+ def sanitize_env_name(url)
309
+ url.gsub(%r{^https?://}, "").gsub(/[^a-zA-Z0-9]/, "_").upcase
310
+ end
311
+
312
+ def declaration_regex(dep, old_req)
313
+ escaped_name = Regexp.escape(dep.name)
314
+ # Extract the requirement operator and version
315
+ operator = old_req.fetch(:requirement).match(/^(.+?)[0-9]/)&.captures&.first
316
+ # Escape special regex characters in the operator
317
+ escaped_operator = Regexp.escape(operator) if operator
318
+
319
+ # Match various formats of dependency declarations:
320
+ # 1. "dependency==1.0.0" (with quotes around the entire string)
321
+ # 2. dependency==1.0.0 (without quotes)
322
+ # The declaration should only include the package name, operator, and version
323
+ # without the enclosing quotes
324
+ /
325
+ ["']?(?<declaration>#{escaped_name}\s*#{escaped_operator}[\d\.\*]+)["']?
326
+ /x
327
+ end
328
+
329
+ def escape(name)
330
+ Regexp.escape(name).gsub("\\-", "[-_.]")
331
+ end
332
+
333
+ def file_changed?(file)
334
+ return false unless file
335
+
336
+ dependencies.any? do |dep|
337
+ dep.requirements.any? { |r| r[:file] == file.name } &&
338
+ requirement_changed?(file, dep)
339
+ end
340
+ end
341
+
342
+ def requirement_changed?(file, dependency)
343
+ changed_requirements =
344
+ dependency.requirements - dependency.previous_requirements
345
+
346
+ changed_requirements.any? { |f| f[:file] == file.name }
347
+ end
348
+
349
+ def updated_file(file:, content:)
350
+ updated_file = file.dup
351
+ updated_file.content = content
352
+ updated_file
353
+ end
354
+
355
+ def normalise(name)
356
+ NameNormaliser.normalise(name)
357
+ end
358
+
359
+ def python_requirement_parser
360
+ @python_requirement_parser ||=
361
+ FileParser::PythonRequirementParser.new(
362
+ dependency_files: dependency_files
363
+ )
364
+ end
365
+
366
+ def language_version_manager
367
+ @language_version_manager ||=
368
+ LanguageVersionManager.new(
369
+ python_requirement_parser: python_requirement_parser
370
+ )
371
+ end
372
+
373
+ def pyproject
374
+ @pyproject ||=
375
+ dependency_files.find { |f| f.name == "pyproject.toml" }
376
+ end
377
+
378
+ def lockfile
379
+ @lockfile ||= uv_lock
380
+ end
381
+
382
+ def python_helper_path
383
+ NativeHelpers.python_helper_path
384
+ end
385
+
386
+ def uv_lock
387
+ dependency_files.find { |f| f.name == "uv.lock" }
388
+ end
389
+ end
390
+ end
391
+ end
392
+ end
@@ -19,104 +19,124 @@ module Dependabot
19
19
  @lockfile = lockfile
20
20
  end
21
21
 
22
- # For hosted Dependabot token will be nil since the credentials aren't present.
23
- # This is for those running Dependabot themselves and for dry-run.
24
- def add_auth_env_vars(credentials)
25
- TomlRB.parse(@pyproject_content).dig("tool", "poetry", "source")&.each do |source|
26
- cred = credentials&.find { |c| c["index-url"] == source["url"] }
27
- next unless cred
28
-
29
- token = cred.fetch("token", nil)
30
- next unless token && token.count(":") == 1
31
-
32
- arr = token.split(":")
33
- # https://python-poetry.org/docs/configuration/#using-environment-variables
34
- name = source["name"]&.upcase&.gsub(/\W/, "_")
35
- ENV["POETRY_HTTP_BASIC_#{name}_USERNAME"] = arr[0]
36
- ENV["POETRY_HTTP_BASIC_#{name}_PASSWORD"] = arr[1]
22
+ def freeze_top_level_dependencies_except(dependencies_to_update)
23
+ return @pyproject_content unless lockfile
24
+
25
+ pyproject_object = TomlRB.parse(@pyproject_content)
26
+ deps_to_update_names = dependencies_to_update.map(&:name)
27
+
28
+ if pyproject_object["project"]&.key?("dependencies")
29
+ locked_deps = parsed_lockfile_dependencies || {}
30
+
31
+ pyproject_object["project"]["dependencies"] =
32
+ pyproject_object["project"]["dependencies"].map do |dep_string|
33
+ freeze_dependency(dep_string, deps_to_update_names, locked_deps)
34
+ end
37
35
  end
36
+
37
+ TomlRB.dump(pyproject_object)
38
38
  end
39
39
 
40
- def update_python_requirement(requirement)
40
+ def update_python_requirement(python_version)
41
+ return @pyproject_content unless python_version
42
+
41
43
  pyproject_object = TomlRB.parse(@pyproject_content)
42
- if (python_specification = pyproject_object.dig("tool", "poetry", "dependencies", "python"))
43
- python_req = Uv::Requirement.new(python_specification)
44
- unless python_req.satisfied_by?(requirement)
45
- pyproject_object["tool"]["poetry"]["dependencies"]["python"] = "~#{requirement}"
46
- end
44
+
45
+ if pyproject_object["project"]&.key?("requires-python")
46
+ pyproject_object["project"]["requires-python"] = ">=#{python_version}"
47
47
  end
48
+
48
49
  TomlRB.dump(pyproject_object)
49
50
  end
50
51
 
51
- def sanitize
52
- # {{ name }} syntax not allowed
53
- pyproject_content
54
- .gsub(/\{\{.*?\}\}/, "something")
55
- .gsub('#{', "{")
56
- end
52
+ def add_auth_env_vars(credentials)
53
+ return unless credentials
57
54
 
58
- # rubocop:disable Metrics/PerceivedComplexity
59
- # rubocop:disable Metrics/AbcSize
60
- def freeze_top_level_dependencies_except(dependencies)
61
- return pyproject_content unless lockfile
62
-
63
- pyproject_object = TomlRB.parse(pyproject_content)
64
- poetry_object = pyproject_object["tool"]["poetry"]
65
- excluded_names = dependencies.map(&:name) + ["python"]
66
-
67
- Dependabot::Uv::FileParser::PyprojectFilesParser::POETRY_DEPENDENCY_TYPES.each do |key|
68
- next unless poetry_object[key]
69
-
70
- source_types = %w(directory file url)
71
- poetry_object.fetch(key).each do |dep_name, _|
72
- next if excluded_names.include?(normalise(dep_name))
73
-
74
- locked_details = locked_details(dep_name)
75
-
76
- next unless (locked_version = locked_details&.fetch("version"))
77
-
78
- next if source_types.include?(locked_details&.dig("source", "type"))
79
-
80
- if locked_details&.dig("source", "type") == "git"
81
- poetry_object[key][dep_name] = {
82
- "git" => locked_details&.dig("source", "url"),
83
- "rev" => locked_details&.dig("source", "reference")
84
- }
85
- subdirectory = locked_details&.dig("source", "subdirectory")
86
- poetry_object[key][dep_name]["subdirectory"] = subdirectory if subdirectory
87
- elsif poetry_object[key][dep_name].is_a?(Hash)
88
- poetry_object[key][dep_name]["version"] = locked_version
89
- elsif poetry_object[key][dep_name].is_a?(Array)
90
- # if it has multiple-constraints, locking to a single version is
91
- # going to result in a bad lockfile, ignore
92
- next
93
- else
94
- poetry_object[key][dep_name] = locked_version
95
- end
96
- end
55
+ credentials.each do |credential|
56
+ next unless credential["type"] == "python_index"
57
+
58
+ token = credential["token"]
59
+ index_url = credential["index-url"]
60
+
61
+ next unless token && index_url
62
+
63
+ # Set environment variables for uv auth
64
+ ENV["UV_INDEX_URL_TOKEN_#{sanitize_env_name(index_url)}"] = token
65
+
66
+ # Also set pip-style credentials for compatibility
67
+ ENV["PIP_INDEX_URL"] ||= "https://#{token}@#{index_url.gsub(%r{^https?://}, '')}"
97
68
  end
69
+ end
98
70
 
99
- TomlRB.dump(pyproject_object)
71
+ def sanitize
72
+ # No special sanitization needed for UV files at this point
73
+ @pyproject_content
100
74
  end
101
- # rubocop:enable Metrics/AbcSize
102
- # rubocop:enable Metrics/PerceivedComplexity
103
75
 
104
76
  private
105
77
 
106
- attr_reader :pyproject_content
107
78
  attr_reader :lockfile
108
79
 
109
- def locked_details(dep_name)
110
- parsed_lockfile.fetch("package")
111
- .find { |d| d["name"] == normalise(dep_name) }
80
+ def parsed_lockfile
81
+ @parsed_lockfile ||= lockfile ? parse_lockfile(lockfile.content) : {}
112
82
  end
113
83
 
114
- def normalise(name)
115
- NameNormaliser.normalise(name)
84
+ def parse_lockfile(content)
85
+ TomlRB.parse(content)
86
+ rescue TomlRB::ParseError
87
+ {} # Return empty hash if parsing fails
116
88
  end
117
89
 
118
- def parsed_lockfile
119
- @parsed_lockfile ||= TomlRB.parse(lockfile.content)
90
+ def parsed_lockfile_dependencies
91
+ return {} unless lockfile
92
+
93
+ deps = {}
94
+ parsed = parsed_lockfile
95
+
96
+ # Handle UV lock format (version 1)
97
+ if parsed["version"] == 1 && parsed["package"].is_a?(Array)
98
+ parsed["package"].each do |pkg|
99
+ next unless pkg["name"] && pkg["version"]
100
+
101
+ deps[pkg["name"]] = { "version" => pkg["version"] }
102
+ end
103
+ # Handle traditional Poetry-style lock format
104
+ elsif parsed["dependencies"]
105
+ deps = parsed["dependencies"]
106
+ end
107
+
108
+ deps
109
+ end
110
+
111
+ def locked_version_for_dep(locked_deps, dep_name)
112
+ locked_deps.each do |name, details|
113
+ next unless Uv::FileParser.normalize_dependency_name(name) == dep_name
114
+ return details["version"] if details.is_a?(Hash) && details["version"]
115
+ end
116
+ nil
117
+ end
118
+
119
+ def sanitize_env_name(url)
120
+ url.gsub(%r{^https?://}, "").gsub(/[^a-zA-Z0-9]/, "_").upcase
121
+ end
122
+
123
+ def freeze_dependency(dep_string, deps_to_update_names, locked_deps)
124
+ package_name = dep_string.split(/[=>~<\[]/).first.strip
125
+ normalized_name = Uv::FileParser.normalize_dependency_name(package_name)
126
+
127
+ return dep_string if deps_to_update_names.include?(normalized_name)
128
+
129
+ version = locked_version_for_dep(locked_deps, normalized_name)
130
+ return dep_string unless version
131
+
132
+ if dep_string.include?("=") || dep_string.include?(">") ||
133
+ dep_string.include?("<") || dep_string.include?("~")
134
+ # Replace version constraint with exact version
135
+ dep_string.sub(/[=>~<\[].*$/, "==#{version}")
136
+ else
137
+ # Simple dependency, just append version
138
+ "#{dep_string}==#{version}"
139
+ end
120
140
  end
121
141
  end
122
142
  end
@@ -13,6 +13,7 @@ module Dependabot
13
13
  extend T::Sig
14
14
 
15
15
  require_relative "file_updater/compile_file_updater"
16
+ require_relative "file_updater/lock_file_updater"
16
17
  require_relative "file_updater/requirement_file_updater"
17
18
 
18
19
  sig { override.returns(T::Array[Regexp]) }
@@ -20,13 +21,15 @@ module Dependabot
20
21
  [
21
22
  /^.*\.txt$/, # Match any .txt files (e.g., requirements.txt) at any level
22
23
  /^.*\.in$/, # Match any .in files at any level
23
- /^.*pyproject\.toml$/ # Match pyproject.toml at any level
24
+ /^.*pyproject\.toml$/, # Match pyproject.toml at any level
25
+ /^.*uv\.lock$/ # Match uv.lock at any level
24
26
  ]
25
27
  end
26
28
 
27
29
  sig { override.returns(T::Array[DependencyFile]) }
28
30
  def updated_dependency_files
29
31
  updated_files = updated_pip_compile_based_files
32
+ updated_files += updated_uv_lock_files
30
33
 
31
34
  if updated_files.none? ||
32
35
  updated_files.sort_by(&:name) == dependency_files.sort_by(&:name)
@@ -65,6 +68,16 @@ module Dependabot
65
68
  ).updated_dependency_files
66
69
  end
67
70
 
71
+ sig { returns(T::Array[DependencyFile]) }
72
+ def updated_uv_lock_files
73
+ LockFileUpdater.new(
74
+ dependencies: dependencies,
75
+ dependency_files: dependency_files,
76
+ credentials: credentials,
77
+ index_urls: pip_compile_index_urls
78
+ ).updated_dependency_files
79
+ end
80
+
68
81
  sig { returns(T::Array[String]) }
69
82
  def pip_compile_index_urls
70
83
  if credentials.any?(&:replaces_base?)
@@ -3,16 +3,16 @@
3
3
 
4
4
  module Dependabot
5
5
  module Uv
6
- class PipCompileFileMatcher
6
+ class RequiremenstFileMatcher
7
7
  extend T::Sig
8
8
 
9
- sig { params(requirements_in_files: T::Array[Dependabot::Uv::Requirement]).void }
9
+ sig { params(requirements_in_files: T::Array[Requirement]).void }
10
10
  def initialize(requirements_in_files)
11
11
  @requirements_in_files = requirements_in_files
12
12
  end
13
13
 
14
- sig { params(file: Dependabot::DependencyFile).returns(T::Boolean) }
15
- def lockfile_for_pip_compile_file?(file)
14
+ sig { params(file: DependencyFile).returns(T::Boolean) }
15
+ def compiled_file?(file)
16
16
  return false unless requirements_in_files.any?
17
17
 
18
18
  name = file.name
@@ -26,7 +26,7 @@ module Dependabot
26
26
 
27
27
  private
28
28
 
29
- sig { returns(T::Array[Dependabot::Uv::Requirement]) }
29
+ sig { returns(T::Array[Requirement]) }
30
30
  attr_reader :requirements_in_files
31
31
 
32
32
  sig { params(filename: T.any(String, Symbol)).returns(String) }