dependabot-nuget 0.239.0 → 0.240.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: 2bf5bdfc4f365f5d04c30193e1b44e7a82ddb5816461148e4172cabc1a86bea3
4
- data.tar.gz: 0ca7483d0fdc3d3a7dc2af7c8f475e7b02a121d4a8a5c58f495ff857d32c3dad
3
+ metadata.gz: 7d8152c074be84d639b86fe67da074f558ca1509102a5a1e9e97c3411178a747
4
+ data.tar.gz: 6562552b5b89ddfc8c521aeb7a52cf17e2d918c230439f70281663d5e8e45811
5
5
  SHA512:
6
- metadata.gz: a0bd5448386d99ac0fa6a27ea6f5ec137885cd8a208762602d69284c1cdea6bb9e521a885b9a50cb42eb45b2de9ec760def87940c15023a910f7f9455a612fc9
7
- data.tar.gz: 4096cfc58f861ec2f4f5d7a4022fd06f001d0f4421130bf0708e877a93ce2e12e722259d60b29f8303375d2e3cdc5f595e396cb4273d5c1e03a34e23a3a154ec
6
+ metadata.gz: 6d91dec9cd6d5db739507bfc91d6454b6f21426a60a2086bd7b944922d4fa123886dbb1037a94b712471deeb89d175c1b0f10a2b84aeb7f6810f3aeddb92eecb
7
+ data.tar.gz: 7dbe0cacc3ffaa2d9290e09da5026a94432b8f45b839c6db49905b40636188c0201b61f6650aa1a01fa36fe590672b3e0359b8d4139d1bfe26d98dac344b494a
@@ -13,6 +13,8 @@ module Dependabot
13
13
  end
14
14
 
15
15
  def self.cache(name)
16
+ return {} if caching_disabled?
17
+
16
18
  @cache ||= {}
17
19
  @cache[name] ||= {}
18
20
  @cache[name]
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "dependabot/file_fetchers"
5
5
  require "dependabot/file_fetchers/base"
6
+ require "dependabot/nuget/cache_manager"
6
7
  require "set"
7
8
  require "sorbet-runtime"
8
9
 
@@ -23,7 +24,7 @@ module Dependabot
23
24
  return true if filenames.any? { |f| f.match?("^src$") }
24
25
  return true if filenames.any? { |f| f.end_with?(".proj") }
25
26
 
26
- filenames.any? { |name| name.match?(%r{^[^/]*\.[a-z]{2}proj$}) }
27
+ filenames.any? { |name| name.match?(/\.[a-z]{2}proj$/) }
27
28
  end
28
29
 
29
30
  def self.required_files_message
@@ -219,29 +220,32 @@ module Dependabot
219
220
  def nuget_config_files
220
221
  return @nuget_config_files if @nuget_config_files
221
222
 
222
- @nuget_config_files = []
223
- candidate_paths = [*project_files.map { |f| File.dirname(f.name) }, "."].uniq
224
- visited_directories = Set.new
225
- candidate_paths.each do |dir|
226
- search_in_directory_and_parents(dir, visited_directories)
227
- end
223
+ @nuget_config_files = [*project_files.map do |f|
224
+ named_file_up_tree_from_project_file(f, "nuget.config")
225
+ end].compact.uniq
228
226
  @nuget_config_files
229
227
  end
230
228
 
231
- def search_in_directory_and_parents(dir, visited_directories)
232
- loop do
233
- break if visited_directories.include?(dir)
234
-
235
- visited_directories << dir
236
- file = repo_contents(dir: dir)
237
- .find { |f| f.name.casecmp("nuget.config").zero? }
238
- if file
239
- file = fetch_file_from_host(File.join(dir, file.name))
240
- file&.tap { |f| f.support_file = true }
241
- @nuget_config_files << file
229
+ def named_file_up_tree_from_project_file(project_file, expected_file_name)
230
+ found_expected_file = nil
231
+ directory_path = Pathname.new(directory)
232
+ full_project_dir = Pathname.new(project_file.directory).join(project_file.name).dirname
233
+ full_project_dir.ascend.each do |base|
234
+ break if found_expected_file
235
+
236
+ candidate_file_path = Pathname.new(base).join(expected_file_name).cleanpath.to_path
237
+ candidate_directory = Pathname.new(File.dirname(candidate_file_path))
238
+ relative_candidate_directory = candidate_directory.relative_path_from(directory_path)
239
+ candidate_file = repo_contents(dir: relative_candidate_directory).find do |f|
240
+ f.name.casecmp?(expected_file_name)
241
+ end
242
+ if candidate_file
243
+ found_expected_file = fetch_file_from_host(File.join(relative_candidate_directory,
244
+ candidate_file.name))
242
245
  end
243
- dir = File.dirname(dir)
244
246
  end
247
+
248
+ found_expected_file
245
249
  end
246
250
 
247
251
  def global_json
@@ -280,27 +284,34 @@ module Dependabot
280
284
  end
281
285
 
282
286
  def fetch_imported_property_files(file:, previously_fetched_files:)
283
- paths =
284
- ImportPathsFinder.new(project_file: file).import_paths +
285
- ImportPathsFinder.new(project_file: file).project_reference_paths +
286
- ImportPathsFinder.new(project_file: file).project_file_paths
287
-
288
- paths.flat_map do |path|
289
- next if previously_fetched_files.map(&:name).include?(path)
290
- next if file.name == path
291
- next if path.include?("$(")
292
-
293
- fetched_file = fetch_file_from_host(path)
294
- grandchild_property_files = fetch_imported_property_files(
295
- file: fetched_file,
296
- previously_fetched_files: previously_fetched_files + [file]
297
- )
298
- [fetched_file, *grandchild_property_files]
299
- rescue Dependabot::DependencyFileNotFound
300
- # Don't worry about missing files too much for now (at least
301
- # until we start resolving properties)
302
- nil
303
- end.compact
287
+ file_id = file.directory + "/" + file.name
288
+ @fetched_files ||= {}
289
+ if @fetched_files[file_id]
290
+ @fetched_files[file_id]
291
+ else
292
+ paths =
293
+ ImportPathsFinder.new(project_file: file).import_paths +
294
+ ImportPathsFinder.new(project_file: file).project_reference_paths +
295
+ ImportPathsFinder.new(project_file: file).project_file_paths
296
+
297
+ paths.flat_map do |path|
298
+ next if previously_fetched_files.map(&:name).include?(path)
299
+ next if file.name == path
300
+ next if path.include?("$(")
301
+
302
+ fetched_file = fetch_file_from_host(path)
303
+ grandchild_property_files = fetch_imported_property_files(
304
+ file: fetched_file,
305
+ previously_fetched_files: previously_fetched_files + [file]
306
+ )
307
+ @fetched_files[file_id] = [fetched_file, *grandchild_property_files]
308
+ @fetched_files[file_id]
309
+ rescue Dependabot::DependencyFileNotFound
310
+ # Don't worry about missing files too much for now (at least
311
+ # until we start resolving properties)
312
+ nil
313
+ end.compact
314
+ end
304
315
  end
305
316
  end
306
317
  end
@@ -26,16 +26,10 @@ module Dependabot
26
26
  end
27
27
 
28
28
  def dependency_set
29
- return parse_dependencies if CacheManager.caching_disabled?
30
-
31
29
  key = "#{packages_config.name.downcase}::#{packages_config.content.hash}"
32
30
  cache = PackagesConfigParser.dependency_set_cache
33
31
 
34
32
  cache[key] ||= parse_dependencies
35
-
36
- dependency_set = Dependabot::FileParsers::Base::DependencySet.new
37
- dependency_set += cache[key]
38
- dependency_set
39
33
  end
40
34
 
41
35
  private
@@ -7,13 +7,13 @@ require "dependabot/dependency"
7
7
  require "dependabot/nuget/file_parser"
8
8
  require "dependabot/nuget/update_checker"
9
9
  require "dependabot/nuget/cache_manager"
10
+ require "dependabot/nuget/nuget_client"
10
11
 
11
12
  # For details on how dotnet handles version constraints, see:
12
13
  # https://docs.microsoft.com/en-us/nuget/reference/package-versioning
13
14
  module Dependabot
14
15
  module Nuget
15
16
  class FileParser
16
- # rubocop:disable Metrics/ClassLength
17
17
  class ProjectFileParser
18
18
  require "dependabot/file_parsers/base/dependency_set"
19
19
  require_relative "property_value_finder"
@@ -50,16 +50,10 @@ module Dependabot
50
50
  end
51
51
 
52
52
  def dependency_set(project_file:)
53
- return parse_dependencies(project_file) if CacheManager.caching_disabled?
54
-
55
53
  key = "#{project_file.name.downcase}::#{project_file.content.hash}"
56
54
  cache = ProjectFileParser.dependency_set_cache
57
55
 
58
56
  cache[key] ||= parse_dependencies(project_file)
59
-
60
- dependency_set = Dependabot::FileParsers::Base::DependencySet.new
61
- dependency_set += cache[key]
62
- dependency_set
63
57
  end
64
58
 
65
59
  def target_frameworks(project_file:)
@@ -78,6 +72,10 @@ module Dependabot
78
72
  ["net#{value[1..-1].delete('.')}"]
79
73
  end
80
74
 
75
+ def nuget_configs
76
+ dependency_files.select { |f| f.name.match?(%r{(^|/)nuget\.config$}i) }
77
+ end
78
+
81
79
  private
82
80
 
83
81
  attr_reader :dependency_files, :credentials
@@ -281,7 +279,6 @@ module Dependabot
281
279
  end
282
280
 
283
281
  def dependency_has_search_results?(dependency)
284
- nuget_configs = dependency_files.select { |f| f.name.casecmp?("nuget.config") }
285
282
  dependency_urls = UpdateChecker::RepositoryFinder.new(
286
283
  dependency: dependency,
287
284
  credentials: credentials,
@@ -307,16 +304,9 @@ module Dependabot
307
304
  end
308
305
 
309
306
  def dependency_url_has_matching_result_v3?(dependency_name, dependency_url)
310
- url = dependency_url.fetch(:search_url)
311
- auth_header = dependency_url.fetch(:auth_header)
312
- response = execute_search_for_dependency_url(url, auth_header)
313
- return false unless response.status == 200
314
-
315
- body = JSON.parse(response.body)
316
- data = body["data"]
317
- return false unless data.length.positive?
307
+ versions = NugetClient.get_package_versions_v3(dependency_name, dependency_url)
318
308
 
319
- data.any? { |result| result["id"].casecmp?(dependency_name) }
309
+ versions != nil
320
310
  end
321
311
 
322
312
  def dependency_url_has_matching_result_v2?(dependency_name, dependency_url)
@@ -490,10 +480,6 @@ module Dependabot
490
480
  end
491
481
  end
492
482
 
493
- def nuget_configs
494
- dependency_files.select { |f| f.name.match?(/nuget\.config$/i) }
495
- end
496
-
497
483
  def global_json
498
484
  dependency_files.find { |f| f.name.casecmp("global.json").zero? }
499
485
  end
@@ -502,7 +488,6 @@ module Dependabot
502
488
  dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json").zero? }
503
489
  end
504
490
  end
505
- # rubocop:enable Metrics/ClassLength
506
491
  end
507
492
  end
508
493
  end
@@ -74,7 +74,7 @@ module Dependabot
74
74
  end
75
75
 
76
76
  def project_files
77
- projfile = /\.[a-z]{2}proj$/
77
+ projfile = /\.([a-z]{2})?proj$/
78
78
  packageprops = /[Dd]irectory.[Pp]ackages.props/
79
79
 
80
80
  dependency_files.select do |df|
@@ -62,7 +62,9 @@ module Dependabot
62
62
 
63
63
  next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? }
64
64
 
65
- NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?)
65
+ NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path,
66
+ dependency: dependency, is_transitive: !dependency.top_level?,
67
+ credentials: credentials)
66
68
  update_ran = true
67
69
  end
68
70
 
@@ -77,7 +79,9 @@ module Dependabot
77
79
  project_file = project_files.first
78
80
  proj_path = dependency_file_path(project_file)
79
81
 
80
- NativeHelpers.run_nuget_updater_tool(repo_contents_path, proj_path, dependency, !dependency.top_level?)
82
+ NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path,
83
+ dependency: dependency, is_transitive: !dependency.top_level?,
84
+ credentials: credentials)
81
85
  return true
82
86
  end
83
87
 
@@ -1,6 +1,8 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "nuget_config_credential_helpers"
5
+
4
6
  module Dependabot
5
7
  module Nuget
6
8
  module NativeHelpers
@@ -46,7 +48,7 @@ module Dependabot
46
48
  end
47
49
 
48
50
  # rubocop:disable Metrics/MethodLength
49
- def self.run_nuget_updater_tool(repo_root, proj_path, dependency, is_transitive)
51
+ def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency:, is_transitive:, credentials:)
50
52
  exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli")
51
53
  command = [
52
54
  exe_path,
@@ -84,9 +86,10 @@ module Dependabot
84
86
 
85
87
  puts "running NuGet updater:\n" + command
86
88
 
87
- output = SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
88
-
89
- puts output
89
+ NuGetConfigCredentialHelpers.patch_nuget_config_for_action(credentials) do
90
+ output = SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
91
+ puts output
92
+ end
90
93
  end
91
94
  # rubocop:enable Metrics/MethodLength
92
95
  end
@@ -0,0 +1,99 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/nuget/cache_manager"
5
+ require "dependabot/nuget/update_checker/repository_finder"
6
+
7
+ module Dependabot
8
+ module Nuget
9
+ class NugetClient
10
+ def self.get_package_versions_v3(dependency_name, repository_details)
11
+ # Use the registration URL if possible because it is fast and correct
12
+ if repository_details[:registration_url]
13
+ get_versions_from_registration_v3(repository_details)
14
+ # use the search API if not because it is slow but correct
15
+ elsif repository_details[:search_url]
16
+ get_versions_from_search_url_v3(repository_details, dependency_name)
17
+ # Otherwise, use the versions URL (fast but wrong because it includes unlisted versions)
18
+ elsif repository_details[:versions_url]
19
+ get_versions_from_versions_url_v3(repository_details)
20
+ end
21
+ end
22
+
23
+ private_class_method def self.get_versions_from_versions_url_v3(repository_details)
24
+ body = execute_search_for_dependency_url(repository_details[:versions_url], repository_details)
25
+ body&.fetch("versions")
26
+ end
27
+
28
+ private_class_method def self.get_versions_from_registration_v3(repository_details)
29
+ url = repository_details[:registration_url]
30
+ body = execute_search_for_dependency_url(url, repository_details)
31
+
32
+ return unless body
33
+
34
+ pages = body.fetch("items")
35
+ versions = Set.new
36
+ pages.each do |page|
37
+ items = page["items"]
38
+ if items
39
+ # inlined entries
40
+ items.each do |item|
41
+ catalog_entry = item["catalogEntry"]
42
+ if catalog_entry["listed"] == true
43
+ vers = catalog_entry["version"]
44
+ versions << vers
45
+ end
46
+ end
47
+ else
48
+ # paged entries
49
+ page_url = page["@id"]
50
+ page_body = execute_search_for_dependency_url(page_url, repository_details)
51
+ items = page_body.fetch("items")
52
+ items.each do |item|
53
+ catalog_entry = item.fetch("catalogEntry")
54
+ versions << catalog_entry.fetch("version") if catalog_entry["listed"] == true
55
+ end
56
+ end
57
+ end
58
+
59
+ versions
60
+ end
61
+
62
+ private_class_method def self.get_versions_from_search_url_v3(repository_details, dependency_name)
63
+ search_url = repository_details[:search_url]
64
+ body = execute_search_for_dependency_url(search_url, repository_details)
65
+
66
+ body&.fetch("data")
67
+ &.find { |d| d.fetch("id").casecmp(dependency_name.downcase).zero? }
68
+ &.fetch("versions")
69
+ &.map { |d| d.fetch("version") }
70
+ end
71
+
72
+ private_class_method def self.execute_search_for_dependency_url(url, repository_details)
73
+ cache = CacheManager.cache("dependency_url_search_cache")
74
+ cache[url] ||= Dependabot::RegistryClient.get(
75
+ url: url,
76
+ headers: repository_details[:auth_header]
77
+ )
78
+
79
+ response = cache[url]
80
+
81
+ return unless response.status == 200
82
+
83
+ body = remove_wrapping_zero_width_chars(response.body)
84
+ JSON.parse(body)
85
+ rescue Excon::Error::Timeout, Excon::Error::Socket
86
+ repo_url = repository_details[:repository_url]
87
+ raise if repo_url == Dependabot::Nuget::UpdateChecker::RepositoryFinder::DEFAULT_REPOSITORY_URL
88
+
89
+ raise PrivateSourceTimedOut, repo_url
90
+ end
91
+
92
+ private_class_method def self.remove_wrapping_zero_width_chars(string)
93
+ string.force_encoding("UTF-8").encode
94
+ .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
95
+ .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,71 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Nuget
6
+ module NuGetConfigCredentialHelpers
7
+ def self.user_nuget_config_path
8
+ home_directory = Dir.home
9
+ File.join(home_directory, ".nuget", "NuGet", "NuGet.Config")
10
+ end
11
+
12
+ def self.temporary_nuget_config_path
13
+ user_nuget_config_path + "_ORIGINAL"
14
+ end
15
+
16
+ def self.add_credentials_to_nuget_config(credentials)
17
+ return unless File.exist?(user_nuget_config_path)
18
+
19
+ nuget_credentials = credentials.select { |cred| cred["type"] == "nuget_feed" }
20
+ return if nuget_credentials.empty?
21
+
22
+ File.rename(user_nuget_config_path, temporary_nuget_config_path)
23
+
24
+ package_sources = []
25
+ package_source_credentials = []
26
+ nuget_credentials.each_with_index do |c, i|
27
+ source_name = "nuget_source_#{i + 1}"
28
+ package_sources << " <add key=\"#{source_name}\" value=\"#{c['url']}\" />"
29
+ next unless c["token"]
30
+
31
+ package_source_credentials << " <#{source_name}>"
32
+ package_source_credentials << " <add key=\"Username\" value=\"user\" />"
33
+ package_source_credentials << " <add key=\"ClearTextPassword\" value=\"#{c['token']}\" />"
34
+ package_source_credentials << " </#{source_name}>"
35
+ end
36
+
37
+ nuget_config = <<~NUGET_XML
38
+ <?xml version="1.0" encoding="utf-8"?>
39
+ <configuration>
40
+ <packageSources>
41
+ #{package_sources.join("\n")}
42
+ </packageSources>
43
+ <packageSourceCredentials>
44
+ #{package_source_credentials.join("\n")}
45
+ </packageSourceCredentials>
46
+ </configuration>
47
+ NUGET_XML
48
+ File.write(user_nuget_config_path, nuget_config)
49
+ end
50
+
51
+ def self.restore_user_nuget_config
52
+ return unless File.exist?(temporary_nuget_config_path)
53
+
54
+ File.delete(user_nuget_config_path)
55
+ File.rename(temporary_nuget_config_path, user_nuget_config_path)
56
+ end
57
+
58
+ # rubocop:disable Lint/SuppressedException
59
+ def self.patch_nuget_config_for_action(credentials, &_block)
60
+ add_credentials_to_nuget_config(credentials)
61
+ begin
62
+ yield
63
+ rescue StandardError
64
+ ensure
65
+ restore_user_nuget_config
66
+ end
67
+ end
68
+ # rubocop:enable Lint/SuppressedException
69
+ end
70
+ end
71
+ end
@@ -22,7 +22,7 @@ module Dependabot
22
22
 
23
23
  return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
24
24
 
25
- [matches[1] || "=", Nuget::Version.new(matches[2])]
25
+ [matches[1] || "=", Nuget::Version.new(T.must(matches[2]))]
26
26
  end
27
27
 
28
28
  # For consistency with other languages, we define a requirements array.
@@ -1,11 +1,11 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
- require "dependabot/nuget/update_checker"
4
+ require "dependabot/update_checkers/base"
5
5
 
6
6
  module Dependabot
7
7
  module Nuget
8
- class UpdateChecker
8
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
9
9
  class CompatibilityChecker
10
10
  require_relative "nuspec_fetcher"
11
11
  require_relative "nupkg_fetcher"
@@ -4,12 +4,12 @@
4
4
  require "nokogiri"
5
5
  require "zip"
6
6
  require "stringio"
7
- require "dependabot/nuget/update_checker"
7
+ require "dependabot/update_checkers/base"
8
8
  require "dependabot/nuget/version"
9
9
 
10
10
  module Dependabot
11
11
  module Nuget
12
- class UpdateChecker
12
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
13
13
  class DependencyFinder
14
14
  require_relative "requirements_updater"
15
15
  require_relative "nuspec_fetcher"
@@ -23,10 +23,7 @@ module Dependabot
23
23
  feed_url = repository_details[:repository_url]
24
24
  repository_type = repository_details[:repository_type]
25
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"
26
+ package_url = if repository_type == "v2"
30
27
  get_nuget_v2_package_url(feed_url, package_id, package_version)
31
28
  elsif repository_type == "v3"
32
29
  get_nuget_v3_package_url(repository_details, package_id, package_version)
@@ -45,31 +42,6 @@ module Dependabot
45
42
  fetch_stream(package_url, auth_header)
46
43
  end
47
44
 
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
45
  def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
74
46
  base_url = repository_details[:base_url].delete_suffix("/")
75
47
  package_id_downcased = package_id.downcase
@@ -1,12 +1,12 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "dependabot/update_checkers/base"
4
5
  require "dependabot/nuget/file_parser"
5
- require "dependabot/nuget/update_checker"
6
6
 
7
7
  module Dependabot
8
8
  module Nuget
9
- class UpdateChecker
9
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
10
10
  class PropertyUpdater
11
11
  require_relative "version_finder"
12
12
  require_relative "requirements_updater"
@@ -4,12 +4,13 @@
4
4
  require "excon"
5
5
  require "nokogiri"
6
6
  require "dependabot/errors"
7
- require "dependabot/nuget/update_checker"
7
+ require "dependabot/update_checkers/base"
8
8
  require "dependabot/registry_client"
9
+ require "dependabot/nuget/cache_manager"
9
10
 
10
11
  module Dependabot
11
12
  module Nuget
12
- class UpdateChecker
13
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
13
14
  class RepositoryFinder
14
15
  DEFAULT_REPOSITORY_URL = "https://api.nuget.org/v3/index.json"
15
16
  DEFAULT_REPOSITORY_API_KEY = "nuget.org"
@@ -27,6 +28,7 @@ module Dependabot
27
28
  def self.get_default_repository_details(dependency_name)
28
29
  {
29
30
  base_url: "https://api.nuget.org/v3-flatcontainer/",
31
+ registration_url: "https://api.nuget.org/v3/registration5-gz-semver2/#{dependency_name.downcase}/index.json",
30
32
  repository_url: DEFAULT_REPOSITORY_URL,
31
33
  versions_url: "https://api.nuget.org/v3-flatcontainer/" \
32
34
  "#{dependency_name.downcase}/index.json",
@@ -60,9 +62,11 @@ module Dependabot
60
62
  return unless response.status == 200
61
63
 
62
64
  body = remove_wrapping_zero_width_chars(response.body)
63
- base_url = base_url_from_v3_metadata(JSON.parse(body))
65
+ parsed_json = JSON.parse(body)
66
+ base_url = base_url_from_v3_metadata(parsed_json)
64
67
  resolved_base_url = base_url || repo_details.fetch(:url).gsub("/index.json", "-flatcontainer")
65
- search_url = search_url_from_v3_metadata(JSON.parse(body))
68
+ search_url = search_url_from_v3_metadata(parsed_json)
69
+ registration_url = registration_url_from_v3_metadata(parsed_json)
66
70
 
67
71
  details = {
68
72
  base_url: resolved_base_url,
@@ -78,6 +82,11 @@ module Dependabot
78
82
  details[:search_url] =
79
83
  search_url + "?q=#{dependency.name.downcase}&prerelease=true&semVerLevel=2.0.0"
80
84
  end
85
+
86
+ if registration_url
87
+ details[:registration_url] = File.join(registration_url, dependency.name.downcase, "index.json")
88
+ end
89
+
81
90
  details
82
91
  rescue JSON::ParserError
83
92
  build_v2_url(response, repo_details)
@@ -86,10 +95,18 @@ module Dependabot
86
95
  end
87
96
 
88
97
  def get_repo_metadata(repo_details)
89
- Dependabot::RegistryClient.get(
90
- url: repo_details.fetch(:url),
91
- headers: auth_header_for_token(repo_details.fetch(:token))
92
- )
98
+ url = repo_details.fetch(:url)
99
+ cache = CacheManager.cache("repo_finder_metadatacache")
100
+ if cache[url]
101
+ cache[url]
102
+ else
103
+ result = Dependabot::RegistryClient.get(
104
+ url: url,
105
+ headers: auth_header_for_token(repo_details.fetch(:token))
106
+ )
107
+ cache[url] = result
108
+ result
109
+ end
93
110
  end
94
111
 
95
112
  def base_url_from_v3_metadata(metadata)
@@ -99,6 +116,20 @@ module Dependabot
99
116
  &.fetch("@id")
100
117
  end
101
118
 
119
+ def registration_url_from_v3_metadata(metadata)
120
+ allowed_registration_types = %w(
121
+ RegistrationsBaseUrl
122
+ RegistrationsBaseUrl/3.0.0-beta
123
+ RegistrationsBaseUrl/3.0.0-rc
124
+ RegistrationsBaseUrl/3.4.0
125
+ RegistrationsBaseUrl/3.6.0
126
+ )
127
+ metadata
128
+ .fetch("resources", [])
129
+ .find { |r| allowed_registration_types.find { |s| r.fetch("@type") == s } }
130
+ &.fetch("@id")
131
+ end
132
+
102
133
  def search_url_from_v3_metadata(metadata)
103
134
  # allowable values from here: https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource#versioning
104
135
  allowed_search_types = %w(
@@ -6,12 +6,12 @@
6
6
  # https://docs.microsoft.com/en-us/nuget/reference/package-versioning #
7
7
  #######################################################################
8
8
 
9
- require "dependabot/nuget/update_checker"
9
+ require "dependabot/update_checkers/base"
10
10
  require "dependabot/nuget/version"
11
11
 
12
12
  module Dependabot
13
13
  module Nuget
14
- class UpdateChecker
14
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
15
15
  class RequirementsUpdater
16
16
  def initialize(requirements:, latest_version:, source_details:)
17
17
  @requirements = requirements
@@ -1,15 +1,15 @@
1
1
  # typed: true
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "dependabot/update_checkers/base"
4
5
  require "dependabot/nuget/version"
5
6
  require "dependabot/nuget/requirement"
6
7
  require "dependabot/nuget/native_helpers"
7
- require "dependabot/nuget/update_checker"
8
8
  require "dependabot/shared_helpers"
9
9
 
10
10
  module Dependabot
11
11
  module Nuget
12
- class UpdateChecker
12
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
13
13
  class TfmComparer
14
14
  def self.are_frameworks_compatible?(project_tfms, package_tfms)
15
15
  return false if package_tfms.empty?
@@ -4,15 +4,15 @@
4
4
  require "excon"
5
5
  require "nokogiri"
6
6
 
7
+ require "dependabot/update_checkers/base"
7
8
  require "dependabot/nuget/version"
8
9
  require "dependabot/nuget/requirement"
9
10
  require "dependabot/nuget/native_helpers"
10
- require "dependabot/nuget/update_checker"
11
11
  require "dependabot/shared_helpers"
12
12
 
13
13
  module Dependabot
14
14
  module Nuget
15
- class UpdateChecker
15
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
16
16
  class TfmFinder
17
17
  require "dependabot/nuget/file_parser/packages_config_parser"
18
18
  require "dependabot/nuget/file_parser/project_file_parser"
@@ -3,12 +3,13 @@
3
3
 
4
4
  require "dependabot/nuget/version"
5
5
  require "dependabot/nuget/requirement"
6
+ require "dependabot/update_checkers/base"
6
7
  require "dependabot/update_checkers/version_filters"
7
- require "dependabot/nuget/update_checker"
8
+ require "dependabot/nuget/nuget_client"
8
9
 
9
10
  module Dependabot
10
11
  module Nuget
11
- class UpdateChecker
12
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
12
13
  class VersionFinder
13
14
  require_relative "compatibility_checker"
14
15
  require_relative "repository_finder"
@@ -294,40 +295,7 @@ module Dependabot
294
295
  end
295
296
 
296
297
  def versions_for_v3_repository(repository_details)
297
- # If we have a search URL that returns results we use it
298
- # (since it will exclude unlisted versions)
299
- if repository_details[:search_url]
300
- fetch_versions_from_search_url(repository_details)
301
- # Otherwise, use the versions URL
302
- elsif repository_details[:versions_url]
303
- response = Dependabot::RegistryClient.get(
304
- url: repository_details[:versions_url],
305
- headers: repository_details[:auth_header]
306
- )
307
- return unless response.status == 200
308
-
309
- body = remove_wrapping_zero_width_chars(response.body)
310
- JSON.parse(body).fetch("versions")
311
- end
312
- end
313
-
314
- def fetch_versions_from_search_url(repository_details)
315
- response = Dependabot::RegistryClient.get(
316
- url: repository_details[:search_url],
317
- headers: repository_details[:auth_header]
318
- )
319
- return unless response.status == 200
320
-
321
- body = remove_wrapping_zero_width_chars(response.body)
322
- JSON.parse(body).fetch("data")
323
- .find { |d| d.fetch("id").casecmp(sanitized_name).zero? }
324
- &.fetch("versions")
325
- &.map { |d| d.fetch("version") }
326
- rescue Excon::Error::Timeout, Excon::Error::Socket
327
- repo_url = repository_details[:repository_url]
328
- raise if repo_url == RepositoryFinder::DEFAULT_REPOSITORY_URL
329
-
330
- raise PrivateSourceTimedOut, repo_url
298
+ NugetClient.get_package_versions_v3(dependency.name, repository_details)
331
299
  end
332
300
 
333
301
  def dependency_urls
@@ -356,12 +324,6 @@ module Dependabot
356
324
  dependency.requirement_class
357
325
  end
358
326
 
359
- def remove_wrapping_zero_width_chars(string)
360
- string.force_encoding("UTF-8").encode
361
- .gsub(/\A[\u200B-\u200D\uFEFF]/, "")
362
- .gsub(/[\u200B-\u200D\uFEFF]\Z/, "")
363
- end
364
-
365
327
  def excon_options
366
328
  # For large JSON files we sometimes need a little longer than for
367
329
  # other languages. For example, see:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.239.0
4
+ version: 0.240.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-12-28 00:00:00.000000000 Z
11
+ date: 2024-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.239.0
19
+ version: 0.240.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.239.0
26
+ version: 0.240.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rubyzip
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -226,6 +226,20 @@ dependencies:
226
226
  - - "~>"
227
227
  - !ruby/object:Gem::Version
228
228
  version: '3.18'
229
+ - !ruby/object:Gem::Dependency
230
+ name: webrick
231
+ requirement: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - ">="
234
+ - !ruby/object:Gem::Version
235
+ version: '1.7'
236
+ type: :development
237
+ prerelease: false
238
+ version_requirements: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '1.7'
229
243
  description: Dependabot-Nuget provides support for bumping .NET (NuGet) packages via
230
244
  Dependabot. If you want support for multiple package managers, you probably want
231
245
  the meta-gem dependabot-omnibus.
@@ -249,6 +263,8 @@ files:
249
263
  - lib/dependabot/nuget/file_updater/property_value_updater.rb
250
264
  - lib/dependabot/nuget/metadata_finder.rb
251
265
  - lib/dependabot/nuget/native_helpers.rb
266
+ - lib/dependabot/nuget/nuget_client.rb
267
+ - lib/dependabot/nuget/nuget_config_credential_helpers.rb
252
268
  - lib/dependabot/nuget/requirement.rb
253
269
  - lib/dependabot/nuget/update_checker.rb
254
270
  - lib/dependabot/nuget/update_checker/compatibility_checker.rb
@@ -267,7 +283,7 @@ licenses:
267
283
  - Nonstandard
268
284
  metadata:
269
285
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
270
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.239.0
286
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.240.0
271
287
  post_install_message:
272
288
  rdoc_options: []
273
289
  require_paths: