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
@@ -19,104 +19,124 @@ module Dependabot
|
|
19
19
|
@lockfile = lockfile
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
TomlRB.parse(@pyproject_content)
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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(
|
40
|
+
def update_python_requirement(python_version)
|
41
|
+
return @pyproject_content unless python_version
|
42
|
+
|
41
43
|
pyproject_object = TomlRB.parse(@pyproject_content)
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
52
|
-
|
53
|
-
pyproject_content
|
54
|
-
.gsub(/\{\{.*?\}\}/, "something")
|
55
|
-
.gsub('#{', "{")
|
56
|
-
end
|
52
|
+
def add_auth_env_vars(credentials)
|
53
|
+
return unless credentials
|
57
54
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
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
|
110
|
-
parsed_lockfile.
|
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
|
115
|
-
|
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
|
119
|
-
|
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
|
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?)
|