dependabot-nuget 0.237.0 → 0.239.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dependabot/nuget/cache_manager.rb +22 -0
- data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -0
- data/lib/dependabot/nuget/file_fetcher.rb +61 -64
- data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +2 -1
- data/lib/dependabot/nuget/file_parser/global_json_parser.rb +2 -1
- data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +22 -4
- data/lib/dependabot/nuget/file_parser/project_file_parser.rb +287 -15
- data/lib/dependabot/nuget/file_parser/property_value_finder.rb +24 -52
- data/lib/dependabot/nuget/file_parser.rb +4 -1
- data/lib/dependabot/nuget/file_updater.rb +123 -117
- data/lib/dependabot/nuget/native_helpers.rb +94 -0
- data/lib/dependabot/nuget/requirement.rb +5 -1
- data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +85 -0
- data/lib/dependabot/nuget/update_checker/dependency_finder.rb +228 -0
- data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +119 -0
- data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +83 -0
- data/lib/dependabot/nuget/update_checker/property_updater.rb +30 -3
- data/lib/dependabot/nuget/update_checker/repository_finder.rb +36 -10
- data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +31 -0
- data/lib/dependabot/nuget/update_checker/tfm_finder.rb +127 -0
- data/lib/dependabot/nuget/update_checker/version_finder.rb +47 -6
- data/lib/dependabot/nuget/update_checker.rb +42 -8
- data/lib/dependabot/nuget.rb +2 -0
- metadata +35 -9
- data/lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb +0 -70
- data/lib/dependabot/nuget/file_updater/project_file_declaration_finder.rb +0 -183
@@ -0,0 +1,228 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "nokogiri"
|
5
|
+
require "zip"
|
6
|
+
require "stringio"
|
7
|
+
require "dependabot/nuget/update_checker"
|
8
|
+
require "dependabot/nuget/version"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Nuget
|
12
|
+
class UpdateChecker
|
13
|
+
class DependencyFinder
|
14
|
+
require_relative "requirements_updater"
|
15
|
+
require_relative "nuspec_fetcher"
|
16
|
+
|
17
|
+
def self.transitive_dependencies_cache
|
18
|
+
CacheManager.cache("dependency_finder_transitive_dependencies")
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.updated_peer_dependencies_cache
|
22
|
+
CacheManager.cache("dependency_finder_updated_peer_dependencies")
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.fetch_dependencies_cache
|
26
|
+
CacheManager.cache("dependency_finder_fetch_dependencies")
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(dependency:, dependency_files:, credentials:)
|
30
|
+
@dependency = dependency
|
31
|
+
@dependency_files = dependency_files
|
32
|
+
@credentials = credentials
|
33
|
+
end
|
34
|
+
|
35
|
+
def transitive_dependencies
|
36
|
+
key = "#{dependency.name.downcase}::#{dependency.version}"
|
37
|
+
cache = DependencyFinder.transitive_dependencies_cache
|
38
|
+
|
39
|
+
cache[key] ||= fetch_transitive_dependencies(
|
40
|
+
@dependency.name,
|
41
|
+
@dependency.version
|
42
|
+
).map do |dependency_info|
|
43
|
+
package_name = dependency_info["packageName"]
|
44
|
+
target_version = dependency_info["version"]
|
45
|
+
|
46
|
+
Dependency.new(
|
47
|
+
name: package_name,
|
48
|
+
version: target_version.to_s,
|
49
|
+
requirements: [], # Empty requirements for transitive dependencies
|
50
|
+
package_manager: @dependency.package_manager
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
cache[key]
|
55
|
+
end
|
56
|
+
|
57
|
+
def updated_peer_dependencies
|
58
|
+
key = "#{dependency.name.downcase}::#{dependency.version}"
|
59
|
+
cache = DependencyFinder.updated_peer_dependencies_cache
|
60
|
+
|
61
|
+
cache[key] ||= fetch_transitive_dependencies(
|
62
|
+
@dependency.name,
|
63
|
+
@dependency.version
|
64
|
+
).filter_map do |dependency_info|
|
65
|
+
package_name = dependency_info["packageName"]
|
66
|
+
target_version = dependency_info["version"]
|
67
|
+
|
68
|
+
# Find the Dependency object for the peer dependency. We will not return
|
69
|
+
# dependencies that are not referenced from dependency files.
|
70
|
+
peer_dependency = top_level_dependencies.find { |d| d.name == package_name }
|
71
|
+
next unless peer_dependency
|
72
|
+
next unless target_version > peer_dependency.numeric_version
|
73
|
+
|
74
|
+
# Use version finder to determine the source details for the peer dependency.
|
75
|
+
target_version_details = version_finder(peer_dependency).versions.find do |v|
|
76
|
+
v.fetch(:version) == target_version
|
77
|
+
end
|
78
|
+
next unless target_version_details
|
79
|
+
|
80
|
+
Dependency.new(
|
81
|
+
name: peer_dependency.name,
|
82
|
+
version: target_version_details.fetch(:version).to_s,
|
83
|
+
requirements: updated_requirements(peer_dependency, target_version_details),
|
84
|
+
previous_version: peer_dependency.version,
|
85
|
+
previous_requirements: peer_dependency.requirements,
|
86
|
+
package_manager: peer_dependency.package_manager,
|
87
|
+
metadata: { information_only: true } # Instruct updater to not directly update this dependency
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
cache[key]
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
attr_reader :dependency, :dependency_files, :credentials
|
97
|
+
|
98
|
+
def updated_requirements(dep, target_version_details)
|
99
|
+
@updated_requirements ||= {}
|
100
|
+
@updated_requirements[dep.name] ||=
|
101
|
+
RequirementsUpdater.new(
|
102
|
+
requirements: dep.requirements,
|
103
|
+
latest_version: target_version_details.fetch(:version).to_s,
|
104
|
+
source_details: target_version_details
|
105
|
+
&.slice(:nuspec_url, :repo_url, :source_url)
|
106
|
+
).updated_requirements
|
107
|
+
end
|
108
|
+
|
109
|
+
def top_level_dependencies
|
110
|
+
@top_level_dependencies ||=
|
111
|
+
Nuget::FileParser.new(
|
112
|
+
dependency_files: dependency_files,
|
113
|
+
source: nil
|
114
|
+
).parse.select(&:top_level?)
|
115
|
+
end
|
116
|
+
|
117
|
+
def nuget_configs
|
118
|
+
@nuget_configs ||=
|
119
|
+
@dependency_files.select { |f| f.name.match?(/nuget\.config$/i) }
|
120
|
+
end
|
121
|
+
|
122
|
+
def dependency_urls
|
123
|
+
@dependency_urls ||=
|
124
|
+
UpdateChecker::RepositoryFinder.new(
|
125
|
+
dependency: @dependency,
|
126
|
+
credentials: @credentials,
|
127
|
+
config_files: nuget_configs
|
128
|
+
).dependency_urls
|
129
|
+
.select { |url| url.fetch(:repository_type) == "v3" }
|
130
|
+
end
|
131
|
+
|
132
|
+
def fetch_transitive_dependencies(package_id, package_version)
|
133
|
+
all_dependencies = {}
|
134
|
+
fetch_transitive_dependencies_impl(package_id, package_version, all_dependencies)
|
135
|
+
all_dependencies.map { |_, dependency_info| dependency_info }
|
136
|
+
end
|
137
|
+
|
138
|
+
def fetch_transitive_dependencies_impl(package_id, package_version, all_dependencies)
|
139
|
+
dependencies = fetch_dependencies(package_id, package_version)
|
140
|
+
return unless dependencies.any?
|
141
|
+
|
142
|
+
dependencies.each do |dependency|
|
143
|
+
next if dependency.nil?
|
144
|
+
|
145
|
+
dependency_id = dependency["packageName"]
|
146
|
+
dependency_version_range = dependency["versionRange"]
|
147
|
+
|
148
|
+
nuget_version_range_regex = /[\[(](\d+(\.\d+)*(-\w+(\.\d+)*)?)/
|
149
|
+
nuget_version_range_match_data = nuget_version_range_regex.match(dependency_version_range)
|
150
|
+
|
151
|
+
dependency_version = if nuget_version_range_match_data.nil?
|
152
|
+
dependency_version_range
|
153
|
+
else
|
154
|
+
nuget_version_range_match_data[1]
|
155
|
+
end
|
156
|
+
|
157
|
+
dependency["version"] = Version.new(dependency_version)
|
158
|
+
|
159
|
+
current_dependency = all_dependencies[dependency_id.downcase]
|
160
|
+
next unless current_dependency.nil? || current_dependency["version"] < dependency["version"]
|
161
|
+
|
162
|
+
all_dependencies[dependency_id.downcase] = dependency
|
163
|
+
fetch_transitive_dependencies_impl(dependency_id, dependency_version, all_dependencies)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def fetch_dependencies(package_id, package_version)
|
168
|
+
key = "#{package_id.downcase}::#{package_version}"
|
169
|
+
cache = DependencyFinder.fetch_dependencies_cache
|
170
|
+
|
171
|
+
cache[key] ||= begin
|
172
|
+
nuspec_xml = NuspecFetcher.fetch_nuspec(dependency_urls, package_id, package_version)
|
173
|
+
if nuspec_xml.nil?
|
174
|
+
[]
|
175
|
+
else
|
176
|
+
read_dependencies_from_nuspec(nuspec_xml)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
cache[key]
|
181
|
+
end
|
182
|
+
|
183
|
+
def read_dependencies_from_nuspec(nuspec_xml) # rubocop:disable Metrics/PerceivedComplexity
|
184
|
+
# we want to exclude development dependencies from the lookup
|
185
|
+
allowed_attributes = %w(all compile native runtime)
|
186
|
+
|
187
|
+
nuspec_xml_dependencies = nuspec_xml.xpath("//dependencies/child::node()/dependency").select do |dependency|
|
188
|
+
include_attr = dependency.attribute("include")
|
189
|
+
exclude_attr = dependency.attribute("exclude")
|
190
|
+
|
191
|
+
if include_attr.nil? && exclude_attr.nil?
|
192
|
+
true
|
193
|
+
elsif include_attr
|
194
|
+
include_values = include_attr.value.split(",").map(&:strip)
|
195
|
+
include_values.any? { |element1| allowed_attributes.any? { |element2| element1.casecmp?(element2) } }
|
196
|
+
else
|
197
|
+
exclude_values = exclude_attr.value.split(",").map(&:strip)
|
198
|
+
exclude_values.none? { |element1| allowed_attributes.any? { |element2| element1.casecmp?(element2) } }
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
dependency_list = []
|
203
|
+
nuspec_xml_dependencies.each do |dependency|
|
204
|
+
next unless dependency.attribute("version")
|
205
|
+
|
206
|
+
dependency_list << {
|
207
|
+
"packageName" => dependency.attribute("id").value,
|
208
|
+
"versionRange" => dependency.attribute("version").value
|
209
|
+
}
|
210
|
+
end
|
211
|
+
|
212
|
+
dependency_list
|
213
|
+
end
|
214
|
+
|
215
|
+
def version_finder(dep)
|
216
|
+
VersionFinder.new(
|
217
|
+
dependency: dep,
|
218
|
+
dependency_files: dependency_files,
|
219
|
+
credentials: credentials,
|
220
|
+
ignored_versions: [],
|
221
|
+
raise_on_ignored: false,
|
222
|
+
security_advisories: []
|
223
|
+
)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "nokogiri"
|
5
|
+
require "zip"
|
6
|
+
require "stringio"
|
7
|
+
|
8
|
+
module Dependabot
|
9
|
+
module Nuget
|
10
|
+
class NupkgFetcher
|
11
|
+
require_relative "repository_finder"
|
12
|
+
|
13
|
+
def self.fetch_nupkg_buffer(dependency_urls, package_id, package_version)
|
14
|
+
# check all repositories for the first one that has the nupkg
|
15
|
+
dependency_urls.reduce(nil) do |nupkg_buffer, repository_details|
|
16
|
+
nupkg_buffer || fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
|
21
|
+
return unless package_id && package_version && !package_version.empty?
|
22
|
+
|
23
|
+
feed_url = repository_details[:repository_url]
|
24
|
+
repository_type = repository_details[:repository_type]
|
25
|
+
|
26
|
+
azure_devops_match = try_match_azure_url(feed_url)
|
27
|
+
package_url = if azure_devops_match
|
28
|
+
get_azure_package_url(azure_devops_match, package_id, package_version)
|
29
|
+
elsif repository_type == "v2"
|
30
|
+
get_nuget_v2_package_url(feed_url, package_id, package_version)
|
31
|
+
elsif repository_type == "v3"
|
32
|
+
get_nuget_v3_package_url(repository_details, package_id, package_version)
|
33
|
+
else
|
34
|
+
raise Dependabot::DependencyFileNotResolvable, "Unexpected NuGet feed format: #{feed_url}"
|
35
|
+
end
|
36
|
+
|
37
|
+
package_url
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
|
41
|
+
package_url = fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
|
42
|
+
return unless package_url
|
43
|
+
|
44
|
+
auth_header = repository_details[:auth_header]
|
45
|
+
fetch_stream(package_url, auth_header)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.try_match_azure_url(feed_url)
|
49
|
+
# if url is azure devops
|
50
|
+
azure_devops_regexs = [
|
51
|
+
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json},
|
52
|
+
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)},
|
53
|
+
%r{https://(?<organization>[^\.\/]+)\.pkgs\.visualstudio\.com/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)}
|
54
|
+
]
|
55
|
+
regex = azure_devops_regexs.find { |reg| reg.match(feed_url) }
|
56
|
+
return unless regex
|
57
|
+
|
58
|
+
regex.match(feed_url)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.get_azure_package_url(azure_devops_match, package_id, package_version)
|
62
|
+
organization = azure_devops_match[:organization]
|
63
|
+
project = azure_devops_match[:project]
|
64
|
+
feed_id = azure_devops_match[:feedId]
|
65
|
+
|
66
|
+
if project.empty?
|
67
|
+
"https://pkgs.dev.azure.com/#{organization}/_apis/packaging/feeds/#{feed_id}/nuget/packages/#{package_id}/versions/#{package_version}/content?sourceProtocolVersion=nuget&api-version=7.0-preview"
|
68
|
+
else
|
69
|
+
"https://pkgs.dev.azure.com/#{organization}/#{project}/_apis/packaging/feeds/#{feed_id}/nuget/packages/#{package_id}/versions/#{package_version}/content?sourceProtocolVersion=nuget&api-version=7.0-preview"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
|
74
|
+
base_url = repository_details[:base_url].delete_suffix("/")
|
75
|
+
package_id_downcased = package_id.downcase
|
76
|
+
"#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.#{package_version}.nupkg"
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.get_nuget_v2_package_url(feed_url, package_id, package_version)
|
80
|
+
base_url = feed_url
|
81
|
+
base_url += "/" unless base_url.end_with?("/")
|
82
|
+
package_id_downcased = package_id.downcase
|
83
|
+
"#{base_url}/package/#{package_id_downcased}/#{package_version}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.fetch_stream(stream_url, auth_header, max_redirects = 5)
|
87
|
+
current_url = stream_url
|
88
|
+
current_redirects = 0
|
89
|
+
|
90
|
+
loop do
|
91
|
+
connection = Excon.new(current_url, persistent: true)
|
92
|
+
|
93
|
+
package_data = StringIO.new
|
94
|
+
response_block = lambda do |chunk, _remaining_bytes, _total_bytes|
|
95
|
+
package_data.write(chunk)
|
96
|
+
end
|
97
|
+
|
98
|
+
response = connection.request(
|
99
|
+
method: :get,
|
100
|
+
headers: auth_header,
|
101
|
+
response_block: response_block
|
102
|
+
)
|
103
|
+
|
104
|
+
if response.status == 303
|
105
|
+
current_redirects += 1
|
106
|
+
return nil if current_redirects > max_redirects
|
107
|
+
|
108
|
+
current_url = response.headers["Location"]
|
109
|
+
elsif response.status == 200
|
110
|
+
package_data.rewind
|
111
|
+
return package_data
|
112
|
+
else
|
113
|
+
return nil
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "nokogiri"
|
5
|
+
require "zip"
|
6
|
+
require "stringio"
|
7
|
+
|
8
|
+
module Dependabot
|
9
|
+
module Nuget
|
10
|
+
class NuspecFetcher
|
11
|
+
require_relative "nupkg_fetcher"
|
12
|
+
require_relative "repository_finder"
|
13
|
+
|
14
|
+
def self.fetch_nuspec(dependency_urls, package_id, package_version)
|
15
|
+
# check all repositories for the first one that has the nuspec
|
16
|
+
dependency_urls.reduce(nil) do |nuspec_xml, repository_details|
|
17
|
+
nuspec_xml || fetch_nuspec_from_repository(repository_details, package_id, package_version)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.fetch_nuspec_from_repository(repository_details, package_id, package_version)
|
22
|
+
return unless package_id && package_version && !package_version.empty?
|
23
|
+
|
24
|
+
feed_url = repository_details[:repository_url]
|
25
|
+
auth_header = repository_details[:auth_header]
|
26
|
+
|
27
|
+
nuspec_xml = nil
|
28
|
+
|
29
|
+
if azure_package_feed?(feed_url)
|
30
|
+
# this is an azure devops url we can extract the nuspec from the nupkg
|
31
|
+
package_data = NupkgFetcher.fetch_nupkg_buffer_from_repository(repository_details, package_id,
|
32
|
+
package_version)
|
33
|
+
return if package_data.nil?
|
34
|
+
|
35
|
+
nuspec_string = extract_nuspec(package_data, package_id)
|
36
|
+
nuspec_xml = Nokogiri::XML(nuspec_string)
|
37
|
+
else
|
38
|
+
# we can use the normal nuget apis to get the nuspec and list out the dependencies
|
39
|
+
base_url = feed_url.gsub("/index.json", "-flatcontainer")
|
40
|
+
package_id_downcased = package_id.downcase
|
41
|
+
nuspec_url = "#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.nuspec"
|
42
|
+
|
43
|
+
nuspec_response = Dependabot::RegistryClient.get(
|
44
|
+
url: nuspec_url,
|
45
|
+
headers: auth_header
|
46
|
+
)
|
47
|
+
|
48
|
+
return unless nuspec_response.status == 200
|
49
|
+
|
50
|
+
nuspec_response_body = remove_wrapping_zero_width_chars(nuspec_response.body)
|
51
|
+
nuspec_xml = Nokogiri::XML(nuspec_response_body)
|
52
|
+
end
|
53
|
+
|
54
|
+
nuspec_xml.remove_namespaces!
|
55
|
+
nuspec_xml
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.azure_package_feed?(feed_url)
|
59
|
+
# if url is azure devops
|
60
|
+
azure_devops_regexs = [
|
61
|
+
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json},
|
62
|
+
%r{https://pkgs\.dev\.azure\.com/(?<organization>[^/]+)/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)},
|
63
|
+
%r{https://(?<organization>[^\.\/]+)\.pkgs\.visualstudio\.com/_packaging/(?<feedId>[^/]+)/nuget/v3/index\.json(?<project>)}
|
64
|
+
]
|
65
|
+
azure_devops_regexs.any? { |reg| reg.match(feed_url) }
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.extract_nuspec(zip_stream, package_id)
|
69
|
+
Zip::File.open_buffer(zip_stream) do |zip|
|
70
|
+
nuspec_entry = zip.find { |entry| entry.name == "#{package_id}.nuspec" }
|
71
|
+
return nuspec_entry.get_input_stream.read if nuspec_entry
|
72
|
+
end
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.remove_wrapping_zero_width_chars(string)
|
77
|
+
string.force_encoding("UTF-8").encode
|
78
|
+
.gsub(/\A[\u200B-\u200D\uFEFF]/, "")
|
79
|
+
.gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -10,6 +10,7 @@ module Dependabot
|
|
10
10
|
class PropertyUpdater
|
11
11
|
require_relative "version_finder"
|
12
12
|
require_relative "requirements_updater"
|
13
|
+
require_relative "dependency_finder"
|
13
14
|
|
14
15
|
def initialize(dependency:, dependency_files:, credentials:,
|
15
16
|
target_version_details:, ignored_versions:,
|
@@ -45,9 +46,15 @@ module Dependabot
|
|
45
46
|
def updated_dependencies
|
46
47
|
raise "Update not possible!" unless update_possible?
|
47
48
|
|
48
|
-
@updated_dependencies ||=
|
49
|
-
|
50
|
-
|
49
|
+
@updated_dependencies ||= begin
|
50
|
+
dependencies = {}
|
51
|
+
|
52
|
+
dependencies_using_property.each do |dep|
|
53
|
+
# Only keep one copy of each dependency, the one with the highest target version.
|
54
|
+
visited_dependency = dependencies[dep.name.downcase]
|
55
|
+
next unless visited_dependency.nil? || visited_dependency.numeric_version < target_version
|
56
|
+
|
57
|
+
updated_dependency = Dependency.new(
|
51
58
|
name: dep.name,
|
52
59
|
version: target_version.to_s,
|
53
60
|
requirements: updated_requirements(dep),
|
@@ -55,7 +62,13 @@ module Dependabot
|
|
55
62
|
previous_requirements: dep.requirements,
|
56
63
|
package_manager: dep.package_manager
|
57
64
|
)
|
65
|
+
dependencies[updated_dependency.name.downcase] = updated_dependency
|
66
|
+
# Add peer dependencies to the list of updated dependencies.
|
67
|
+
process_updated_peer_dependencies(updated_dependency, dependencies)
|
58
68
|
end
|
69
|
+
|
70
|
+
dependencies.map { |_, dependency| dependency }
|
71
|
+
end
|
59
72
|
end
|
60
73
|
|
61
74
|
private
|
@@ -63,6 +76,20 @@ module Dependabot
|
|
63
76
|
attr_reader :dependency, :dependency_files, :target_version,
|
64
77
|
:source_details, :credentials, :ignored_versions
|
65
78
|
|
79
|
+
def process_updated_peer_dependencies(dependency, dependencies)
|
80
|
+
DependencyFinder.new(
|
81
|
+
dependency: dependency,
|
82
|
+
dependency_files: dependency_files,
|
83
|
+
credentials: credentials
|
84
|
+
).updated_peer_dependencies.each do |peer_dependency|
|
85
|
+
# Only keep one copy of each dependency, the one with the highest target version.
|
86
|
+
visited_dependency = dependencies[peer_dependency.name.downcase]
|
87
|
+
next unless visited_dependency.nil? || visited_dependency.numeric_version < peer_dependency.numeric_version
|
88
|
+
|
89
|
+
dependencies[peer_dependency.name.downcase] = peer_dependency
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
66
93
|
def dependencies_using_property
|
67
94
|
@dependencies_using_property ||=
|
68
95
|
Nuget::FileParser.new(
|
@@ -24,6 +24,19 @@ module Dependabot
|
|
24
24
|
find_dependency_urls
|
25
25
|
end
|
26
26
|
|
27
|
+
def self.get_default_repository_details(dependency_name)
|
28
|
+
{
|
29
|
+
base_url: "https://api.nuget.org/v3-flatcontainer/",
|
30
|
+
repository_url: DEFAULT_REPOSITORY_URL,
|
31
|
+
versions_url: "https://api.nuget.org/v3-flatcontainer/" \
|
32
|
+
"#{dependency_name.downcase}/index.json",
|
33
|
+
search_url: "https://azuresearch-usnc.nuget.org/query" \
|
34
|
+
"?q=#{dependency_name.downcase}&prerelease=true&semVerLevel=2.0.0",
|
35
|
+
auth_header: {},
|
36
|
+
repository_type: "v3"
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
27
40
|
private
|
28
41
|
|
29
42
|
attr_reader :dependency, :credentials, :config_files
|
@@ -48,9 +61,11 @@ module Dependabot
|
|
48
61
|
|
49
62
|
body = remove_wrapping_zero_width_chars(response.body)
|
50
63
|
base_url = base_url_from_v3_metadata(JSON.parse(body))
|
64
|
+
resolved_base_url = base_url || repo_details.fetch(:url).gsub("/index.json", "-flatcontainer")
|
51
65
|
search_url = search_url_from_v3_metadata(JSON.parse(body))
|
52
66
|
|
53
67
|
details = {
|
68
|
+
base_url: resolved_base_url,
|
54
69
|
repository_url: repo_details.fetch(:url),
|
55
70
|
auth_header: auth_header_for_token(repo_details.fetch(:token)),
|
56
71
|
repository_type: "v3"
|
@@ -85,9 +100,16 @@ module Dependabot
|
|
85
100
|
end
|
86
101
|
|
87
102
|
def search_url_from_v3_metadata(metadata)
|
103
|
+
# allowable values from here: https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource#versioning
|
104
|
+
allowed_search_types = %w(
|
105
|
+
SearchQueryService
|
106
|
+
SearchQueryService/3.0.0-beta
|
107
|
+
SearchQueryService/3.0.0-rc
|
108
|
+
SearchQueryService/3.5.0
|
109
|
+
)
|
88
110
|
metadata
|
89
111
|
.fetch("resources", [])
|
90
|
-
.find { |r| r.fetch("@type") ==
|
112
|
+
.find { |r| allowed_search_types.find { |s| r.fetch("@type") == s } }
|
91
113
|
&.fetch("@id")
|
92
114
|
end
|
93
115
|
|
@@ -101,6 +123,7 @@ module Dependabot
|
|
101
123
|
base_url ||= repo_details.fetch(:url)
|
102
124
|
|
103
125
|
{
|
126
|
+
base_url: base_url,
|
104
127
|
repository_url: base_url,
|
105
128
|
versions_url: File.join(
|
106
129
|
base_url,
|
@@ -157,6 +180,8 @@ module Dependabot
|
|
157
180
|
base_sources = [{ url: DEFAULT_REPOSITORY_URL, key: "nuget.org" }]
|
158
181
|
|
159
182
|
sources = []
|
183
|
+
|
184
|
+
# regular package sources
|
160
185
|
doc.css("configuration > packageSources").children.each do |node|
|
161
186
|
if node.name == "clear"
|
162
187
|
sources.clear
|
@@ -167,6 +192,15 @@ module Dependabot
|
|
167
192
|
sources << { url: url, key: key }
|
168
193
|
end
|
169
194
|
end
|
195
|
+
|
196
|
+
# signed package sources
|
197
|
+
# https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#trustedsigners-section
|
198
|
+
doc.xpath("/configuration/trustedSigners/repository").each do |node|
|
199
|
+
name = node.attribute("name")&.value&.strip
|
200
|
+
service_index = node.attribute("serviceIndex")&.value&.strip
|
201
|
+
sources << { url: service_index, key: name }
|
202
|
+
end
|
203
|
+
|
170
204
|
sources += base_sources # TODO: quirky overwrite behavior
|
171
205
|
disabled_sources = disabled_sources(doc)
|
172
206
|
sources.reject! do |s|
|
@@ -190,15 +224,7 @@ module Dependabot
|
|
190
224
|
# rubocop:enable Metrics/CyclomaticComplexity
|
191
225
|
|
192
226
|
def default_repository_details
|
193
|
-
|
194
|
-
repository_url: DEFAULT_REPOSITORY_URL,
|
195
|
-
versions_url: "https://api.nuget.org/v3-flatcontainer/" \
|
196
|
-
"#{dependency.name.downcase}/index.json",
|
197
|
-
search_url: "https://azuresearch-usnc.nuget.org/query" \
|
198
|
-
"?q=#{dependency.name.downcase}&prerelease=true&semVerLevel=2.0.0",
|
199
|
-
auth_header: {},
|
200
|
-
repository_type: "v3"
|
201
|
-
}
|
227
|
+
RepositoryFinder.get_default_repository_details(dependency.name)
|
202
228
|
end
|
203
229
|
|
204
230
|
# rubocop:disable Metrics/PerceivedComplexity
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "dependabot/nuget/version"
|
5
|
+
require "dependabot/nuget/requirement"
|
6
|
+
require "dependabot/nuget/native_helpers"
|
7
|
+
require "dependabot/nuget/update_checker"
|
8
|
+
require "dependabot/shared_helpers"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module Nuget
|
12
|
+
class UpdateChecker
|
13
|
+
class TfmComparer
|
14
|
+
def self.are_frameworks_compatible?(project_tfms, package_tfms)
|
15
|
+
return false if package_tfms.empty?
|
16
|
+
return false if project_tfms.empty?
|
17
|
+
|
18
|
+
key = "project_ftms:#{project_tfms.sort.join(',')}:package_tfms:#{package_tfms.sort.join(',')}".downcase
|
19
|
+
|
20
|
+
@cached_framework_check ||= {}
|
21
|
+
unless @cached_framework_check.key?(key)
|
22
|
+
@cached_framework_check[key] =
|
23
|
+
NativeHelpers.run_nuget_framework_check(project_tfms,
|
24
|
+
package_tfms)
|
25
|
+
end
|
26
|
+
@cached_framework_check[key]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|