dependabot-nuget 0.237.0 → 0.239.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.
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