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.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/nuget/cache_manager.rb +22 -0
  3. data/lib/dependabot/nuget/file_fetcher/import_paths_finder.rb +15 -0
  4. data/lib/dependabot/nuget/file_fetcher.rb +61 -64
  5. data/lib/dependabot/nuget/file_parser/dotnet_tools_json_parser.rb +2 -1
  6. data/lib/dependabot/nuget/file_parser/global_json_parser.rb +2 -1
  7. data/lib/dependabot/nuget/file_parser/packages_config_parser.rb +22 -4
  8. data/lib/dependabot/nuget/file_parser/project_file_parser.rb +287 -15
  9. data/lib/dependabot/nuget/file_parser/property_value_finder.rb +24 -52
  10. data/lib/dependabot/nuget/file_parser.rb +4 -1
  11. data/lib/dependabot/nuget/file_updater.rb +123 -117
  12. data/lib/dependabot/nuget/native_helpers.rb +94 -0
  13. data/lib/dependabot/nuget/requirement.rb +5 -1
  14. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +85 -0
  15. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +228 -0
  16. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +119 -0
  17. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +83 -0
  18. data/lib/dependabot/nuget/update_checker/property_updater.rb +30 -3
  19. data/lib/dependabot/nuget/update_checker/repository_finder.rb +36 -10
  20. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +31 -0
  21. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +127 -0
  22. data/lib/dependabot/nuget/update_checker/version_finder.rb +47 -6
  23. data/lib/dependabot/nuget/update_checker.rb +42 -8
  24. data/lib/dependabot/nuget.rb +2 -0
  25. metadata +35 -9
  26. data/lib/dependabot/nuget/file_updater/packages_config_declaration_finder.rb +0 -70
  27. 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
- dependencies_using_property.map do |dep|
50
- Dependency.new(
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") == "SearchQueryService" }
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