dependabot-uv 0.359.0 → 0.360.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: 8bc8c19bc20841bbc8a91cdae65e58197a771c23668ac5907539369007671015
4
- data.tar.gz: 5cd07c0084c8d303ffa2354338b378ae0fd180d89a0970b52b37d66d9b8775c8
3
+ metadata.gz: 9ac9ccc655f8d74cd2b9ffc95c37a012bd74815ddc749117a241b1e200bfd4e7
4
+ data.tar.gz: a28f399323f96cd94c42b9095daa1e92bc411168eb7b35ef01a7931bddc5e459
5
5
  SHA512:
6
- metadata.gz: 4c5648e2680e9020e88e446ea53d96d27ebbe664ded8c689c1174c72c694ede6510bad040b2a27c3e831662d4e5edf2956cd6a11ab2c2aedfabc379c6c59b083
7
- data.tar.gz: d4e86f346d77bca9ebc40a729cab00822647880b4ffe2c18eadeb9a6be7e064b9b50aca14baf69e1c8cb13c0aec30598c5eb5ce1b7e1de084c779dfab242c150
6
+ metadata.gz: c434e3666e41e2f75cb4be56e13a9f005c8594a16e288405b0c0f234f95b4ce4a95a75d1abae351dfd5f28802ed856fdfccabecc564c94ea1bdadb29c8e4d23f
7
+ data.tar.gz: f520f88f03625f96260672f413dcb42400a0e468f6015aa9074dba07cdadda29e8961e0eb87dc85f327d1ad4ec2ec9acbf34fc745fbe4ff25298cb6d6d3b0b0d
@@ -321,7 +321,7 @@ module Dependabot
321
321
  params(
322
322
  dir: T.nilable(String),
323
323
  raise_errors: T::Boolean
324
- ).returns(T::Array[OpenStruct])
324
+ ).returns(T::Array[Dependabot::FileFetchers::RepositoryContent])
325
325
  end
326
326
  def repo_contents(dir: nil, raise_errors: true)
327
327
  @file_fetcher.send(:repo_contents, dir: dir, raise_errors: raise_errors)
@@ -272,11 +272,11 @@ module Dependabot
272
272
  @req_txt_and_in_files
273
273
  end
274
274
 
275
- sig { params(requirements_dir: OpenStruct).returns(T::Array[Dependabot::DependencyFile]) }
275
+ sig { params(requirements_dir: Dependabot::FileFetchers::RepositoryContent).returns(T::Array[Dependabot::DependencyFile]) }
276
276
  def req_files_for_dir(requirements_dir)
277
277
  dir = directory.gsub(%r{(^/|/$)}, "")
278
278
  relative_reqs_dir =
279
- T.unsafe(requirements_dir).path.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "")
279
+ requirements_dir.path&.gsub(%r{^/?#{Regexp.escape(dir)}/?}, "")
280
280
 
281
281
  fetch_requirement_files_from_path(relative_reqs_dir)
282
282
  end
@@ -327,7 +327,7 @@ module Dependabot
327
327
 
328
328
  sig do
329
329
  params(
330
- contents: T::Array[OpenStruct],
330
+ contents: T::Array[Dependabot::FileFetchers::RepositoryContent],
331
331
  base_path: T.nilable(T.any(Pathname, String))
332
332
  ).returns(T::Array[Dependabot::DependencyFile])
333
333
  end
@@ -0,0 +1,205 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/errors"
5
+ require "dependabot/utils"
6
+ require "dependabot/uv/file_updater"
7
+
8
+ module Dependabot
9
+ module Uv
10
+ class FileUpdater < Dependabot::FileUpdaters::Base
11
+ class LockFileErrorHandler
12
+ extend T::Sig
13
+
14
+ UV_UNRESOLVABLE_REGEX = T.let(/× No solution found when resolving dependencies.*[\s\S]*$/, Regexp)
15
+ UV_BUILD_FAILED_REGEX = T.let(/× Failed to build.*[\s\S]*$/, Regexp)
16
+ RESOLUTION_IMPOSSIBLE_ERROR = T.let("ResolutionImpossible", String)
17
+
18
+ GIT_DEPENDENCY_UNREACHABLE_REGEX = T.let(%r{git clone.*(?<url>https?://[^\s]+)}, Regexp)
19
+ GIT_REFERENCE_NOT_FOUND_REGEX = T.let(
20
+ /Did not find branch or tag '(?<tag>[^\n"']+)'/m,
21
+ Regexp
22
+ )
23
+ PYTHON_VERSION_ERROR_REGEX = T.let(
24
+ /Requires-Python|requires-python|python_requires|Python version/i,
25
+ Regexp
26
+ )
27
+ AUTH_ERROR_REGEX = T.let(
28
+ /401|403|authentication|unauthorized|forbidden|HTTP status code: 40[13]/i,
29
+ Regexp
30
+ )
31
+ TIMEOUT_ERROR_REGEX = T.let(
32
+ /timed?\s*out|connection.*reset|read timeout|connect timeout/i,
33
+ Regexp
34
+ )
35
+ NETWORK_ERROR_REGEX = T.let(
36
+ /ConnectionError|NetworkError|SSLError|certificate verify failed/i,
37
+ Regexp
38
+ )
39
+ PACKAGE_NOT_FOUND_REGEX = T.let(
40
+ /No matching distribution found|package.*not found|No versions found/i,
41
+ Regexp
42
+ )
43
+ UV_REQUIRED_VERSION_REGEX = T.let(
44
+ /Required uv version `(?<required>[^`]+)` does not match the running version `(?<running>[^`]+)`/,
45
+ Regexp
46
+ )
47
+
48
+ # Maximum number of lines to include in cleaned error messages.
49
+ # This limit ensures error messages remain readable while providing enough
50
+ # context for debugging. Most uv error messages convey the key information
51
+ # within the first few lines.
52
+ MAX_ERROR_LINES = T.let(10, Integer)
53
+
54
+ sig { params(error: SharedHelpers::HelperSubprocessFailed).returns(T.noreturn) }
55
+ def handle_uv_error(error)
56
+ message = error.message
57
+
58
+ handle_required_version_errors(message)
59
+ handle_resolution_errors(message)
60
+ handle_git_errors(message)
61
+ handle_authentication_errors(message)
62
+ handle_network_errors(message)
63
+ handle_python_version_errors(message)
64
+ handle_resource_errors(message)
65
+ handle_package_not_found_errors(message)
66
+
67
+ raise error
68
+ end
69
+
70
+ private
71
+
72
+ sig { params(message: String).void }
73
+ def handle_required_version_errors(message)
74
+ return unless (version_match = message.match(UV_REQUIRED_VERSION_REGEX))
75
+
76
+ raise Dependabot::ToolVersionNotSupported.new(
77
+ "uv",
78
+ T.must(version_match[:required]),
79
+ T.must(version_match[:running])
80
+ )
81
+ end
82
+
83
+ sig { params(message: String).void }
84
+ def handle_resolution_errors(message)
85
+ return unless message.include?("No solution found when resolving dependencies") ||
86
+ message.include?("Failed to build") ||
87
+ message.include?(RESOLUTION_IMPOSSIBLE_ERROR)
88
+
89
+ match_unresolvable = message.scan(UV_UNRESOLVABLE_REGEX).last
90
+ match_build_failed = message.scan(UV_BUILD_FAILED_REGEX).last
91
+
92
+ if match_unresolvable
93
+ formatted_error = extract_match_string(match_unresolvable) || message
94
+ conflicting_deps = extract_conflicting_dependencies(formatted_error)
95
+ raise Dependabot::UpdateNotPossible, conflicting_deps if conflicting_deps.any?
96
+
97
+ raise Dependabot::DependencyFileNotResolvable, formatted_error
98
+ end
99
+
100
+ formatted_error = extract_match_string(match_build_failed) || message
101
+ raise Dependabot::DependencyFileNotResolvable, formatted_error
102
+ end
103
+
104
+ sig { params(error_message: String).returns(T::Array[String]) }
105
+ def extract_conflicting_dependencies(error_message)
106
+ # Extract conflicting dependency names from the error message
107
+ # Pattern: "Because <pkg>==<ver> depends on <dep>>=<ver> and your project depends on <dep>==<ver>"
108
+ normalized_message = error_message.gsub(/\s+/, " ")
109
+ conflict_pattern = /Because (\S+)==\S+ depends on (\S+)[><=!]+\S+ and your project depends on \2==\S+/
110
+
111
+ match = normalized_message.match(conflict_pattern)
112
+ return [] unless match
113
+
114
+ [T.must(match[1]), T.must(match[2])].uniq
115
+ end
116
+
117
+ sig { params(message: String).void }
118
+ def handle_git_errors(message)
119
+ if (match = message.match(GIT_REFERENCE_NOT_FOUND_REGEX))
120
+ tag = match.named_captures.fetch("tag")
121
+ raise Dependabot::GitDependencyReferenceNotFound, "(unknown package at #{tag})"
122
+ end
123
+
124
+ return unless (match = message.match(GIT_DEPENDENCY_UNREACHABLE_REGEX))
125
+
126
+ url = match.named_captures.fetch("url")
127
+ raise Dependabot::GitDependenciesNotReachable, T.must(url)
128
+ end
129
+
130
+ sig { params(message: String).void }
131
+ def handle_authentication_errors(message)
132
+ return unless message.match?(AUTH_ERROR_REGEX)
133
+
134
+ source = extract_source_from_message(message)
135
+ raise Dependabot::PrivateSourceAuthenticationFailure, source
136
+ end
137
+
138
+ sig { params(message: String).void }
139
+ def handle_network_errors(message)
140
+ if message.match?(TIMEOUT_ERROR_REGEX)
141
+ source = extract_source_from_message(message)
142
+ raise Dependabot::PrivateSourceTimedOut, source
143
+ end
144
+
145
+ return unless message.match?(NETWORK_ERROR_REGEX)
146
+
147
+ source = extract_source_from_message(message)
148
+ if message.include?("certificate verify failed") || message.include?("SSLError")
149
+ raise Dependabot::PrivateSourceCertificateFailure, source
150
+ end
151
+
152
+ raise Dependabot::DependencyFileNotResolvable,
153
+ "Network error while resolving dependencies: #{clean_error_message(message)}"
154
+ end
155
+
156
+ sig { params(message: String).void }
157
+ def handle_python_version_errors(message)
158
+ return unless message.match?(PYTHON_VERSION_ERROR_REGEX)
159
+
160
+ raise Dependabot::DependencyFileNotResolvable,
161
+ "Python version incompatibility: #{clean_error_message(message)}"
162
+ end
163
+
164
+ sig { params(message: String).void }
165
+ def handle_resource_errors(message)
166
+ raise Dependabot::OutOfDisk if message.include?("[Errno 28] No space left on device")
167
+ raise Dependabot::OutOfMemory if message.include?("MemoryError")
168
+ end
169
+
170
+ sig { params(message: String).void }
171
+ def handle_package_not_found_errors(message)
172
+ return unless message.match?(PACKAGE_NOT_FOUND_REGEX)
173
+
174
+ raise Dependabot::DependencyFileNotResolvable, clean_error_message(message)
175
+ end
176
+
177
+ sig { params(match: T.untyped).returns(T.nilable(String)) }
178
+ def extract_match_string(match)
179
+ return nil unless match
180
+
181
+ match.is_a?(Array) ? match.join : match.to_s
182
+ end
183
+
184
+ sig { params(message: String).returns(String) }
185
+ def extract_source_from_message(message)
186
+ urls = URI.extract(message, %w(http https))
187
+ return T.must(urls.first).gsub(%r{/$}, "") if urls.any?
188
+
189
+ "private source"
190
+ end
191
+
192
+ sig { params(message: String).returns(String) }
193
+ def clean_error_message(message)
194
+ message
195
+ .gsub(/#{Regexp.escape(Utils::BUMP_TMP_DIR_PATH)}[^\s]*/o, "")
196
+ .lines
197
+ .reject { |line| line.strip.empty? }
198
+ .first(MAX_ERROR_LINES)
199
+ .join
200
+ .strip
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -23,12 +23,17 @@ module Dependabot
23
23
 
24
24
  require_relative "pyproject_preparer"
25
25
  require_relative "version_config_parser"
26
+ require_relative "lock_file_error_handler"
26
27
 
27
28
  REQUIRED_FILES = %w(pyproject.toml uv.lock).freeze # At least one of these files should be present
28
29
 
29
30
  UV_UNRESOLVABLE_REGEX = T.let(/× No solution found when resolving dependencies.*[\s\S]*$/, Regexp)
30
31
  RESOLUTION_IMPOSSIBLE_ERROR = T.let("ResolutionImpossible", String)
31
32
  UV_BUILD_FAILED_REGEX = T.let(/× Failed to build.*[\s\S]*$/, Regexp)
33
+ UV_REQUIRED_VERSION_REGEX = T.let(
34
+ /Required uv version `(?<required>[^`]+)` does not match the running version `(?<running>[^`]+)`/,
35
+ Regexp
36
+ )
32
37
 
33
38
  sig { returns(T::Array[Dependency]) }
34
39
  attr_reader :dependencies
@@ -262,25 +267,7 @@ module Dependabot
262
267
  end
263
268
  end
264
269
  rescue SharedHelpers::HelperSubprocessFailed => e
265
- handle_uv_error(e)
266
- end
267
-
268
- sig do
269
- params(
270
- error: SharedHelpers::HelperSubprocessFailed
271
- )
272
- .returns(T.noreturn)
273
- end
274
- def handle_uv_error(error)
275
- error_message = error.message
276
-
277
- if resolution_error?(error_message)
278
- handle_resolution_error(error_message)
279
- elsif error_message.include?(RESOLUTION_IMPOSSIBLE_ERROR)
280
- raise Dependabot::DependencyFileNotResolvable, error_message
281
- else
282
- raise error
283
- end
270
+ error_handler.handle_uv_error(e)
284
271
  end
285
272
 
286
273
  sig { params(error_message: String).returns(T::Boolean) }
@@ -317,8 +304,12 @@ module Dependabot
317
304
  match = normalized_message.match(conflict_pattern)
318
305
  return [] unless match
319
306
 
320
- # Return both the package being updated and the blocking dependency
321
- [match[1], match[2]].compact
307
+ [T.must(match[1]), T.must(match[2])].uniq
308
+ end
309
+
310
+ sig { returns(LockFileErrorHandler) }
311
+ def error_handler
312
+ @error_handler ||= T.let(LockFileErrorHandler.new, T.nilable(LockFileErrorHandler))
322
313
  end
323
314
 
324
315
  sig { returns(T.nilable(String)) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-uv
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.359.0
4
+ version: 0.360.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.359.0
18
+ version: 0.360.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.359.0
25
+ version: 0.360.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: dependabot-python
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 0.359.0
32
+ version: 0.360.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 0.359.0
39
+ version: 0.360.0
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: debug
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -270,6 +270,7 @@ files:
270
270
  - lib/dependabot/uv/file_parser/python_requirement_parser.rb
271
271
  - lib/dependabot/uv/file_updater.rb
272
272
  - lib/dependabot/uv/file_updater/compile_file_updater.rb
273
+ - lib/dependabot/uv/file_updater/lock_file_error_handler.rb
273
274
  - lib/dependabot/uv/file_updater/lock_file_updater.rb
274
275
  - lib/dependabot/uv/file_updater/pyproject_preparer.rb
275
276
  - lib/dependabot/uv/file_updater/requirement_file_updater.rb
@@ -298,7 +299,7 @@ licenses:
298
299
  - MIT
299
300
  metadata:
300
301
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
301
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.359.0
302
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.360.0
302
303
  rdoc_options: []
303
304
  require_paths:
304
305
  - lib