dependabot-nuget 0.289.0 → 0.291.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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/Directory.Packages.props +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/AnalyzeCommand.cs +7 -3
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +1 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +26 -1
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Discover.cs +2 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +0 -6
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +1 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +6 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +24 -9
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +0 -13
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/RequirementConverter.cs +17 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +44 -5
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/PackagesConfigDiscovery.cs +2 -2
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/ProjectDiscoveryResult.cs +2 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +19 -11
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +1 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ExperimentsManager.cs +3 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Advisory.cs +13 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/AllowedUpdate.cs +18 -1
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CommitOptions.cs +8 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Condition.cs +19 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyGroup.cs +8 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/GroupPullRequest.cs +9 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +13 -10
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PullRequest.cs +11 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/RequirementsUpdateStrategy.cs +15 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +24 -4
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/VersionConverter.cs +19 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +2 -1
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +3 -2
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +43 -18
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +13 -12
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +1 -1
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +2 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +40 -14
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +2 -2
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +45 -7
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProjectHelper.cs +2 -2
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +5 -2
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.DotNetToolsJson.cs +45 -1
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.GlobalJson.cs +35 -1
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +0 -4
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +41 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +1 -1
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +2 -1
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs +85 -0
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +7 -31
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +340 -0
  52. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs +18 -7
  53. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +24 -0
  54. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +0 -12
  55. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +84 -0
  56. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +66 -0
  57. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackageReference.cs +55 -0
  58. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +0 -6
  59. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +785 -755
  60. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +2 -2
  61. data/lib/dependabot/nuget/analysis/analysis_json_reader.rb +1 -1
  62. data/lib/dependabot/nuget/analysis/dependency_analysis.rb +3 -3
  63. data/lib/dependabot/nuget/discovery/dependency_details.rb +10 -3
  64. data/lib/dependabot/nuget/discovery/dependency_file_discovery.rb +8 -12
  65. data/lib/dependabot/nuget/discovery/discovery_json_reader.rb +214 -29
  66. data/lib/dependabot/nuget/discovery/project_discovery.rb +41 -8
  67. data/lib/dependabot/nuget/discovery/workspace_discovery.rb +14 -19
  68. data/lib/dependabot/nuget/file_fetcher.rb +3 -3
  69. data/lib/dependabot/nuget/file_parser.rb +92 -3
  70. data/lib/dependabot/nuget/file_updater.rb +13 -13
  71. data/lib/dependabot/nuget/language.rb +82 -0
  72. data/lib/dependabot/nuget/native_helpers.rb +37 -5
  73. data/lib/dependabot/nuget/package_manager.rb +51 -0
  74. data/lib/dependabot/nuget/update_checker/requirements_updater.rb +23 -27
  75. data/lib/dependabot/nuget/update_checker.rb +116 -190
  76. metadata +20 -29
  77. data/lib/dependabot/nuget/discovery/directory_packages_props_discovery.rb +0 -43
  78. data/lib/dependabot/nuget/http_response_helpers.rb +0 -19
  79. data/lib/dependabot/nuget/native_discovery/native_dependency_details.rb +0 -102
  80. data/lib/dependabot/nuget/native_discovery/native_dependency_file_discovery.rb +0 -122
  81. data/lib/dependabot/nuget/native_discovery/native_discovery_json_reader.rb +0 -277
  82. data/lib/dependabot/nuget/native_discovery/native_evaluation_details.rb +0 -63
  83. data/lib/dependabot/nuget/native_discovery/native_project_discovery.rb +0 -104
  84. data/lib/dependabot/nuget/native_discovery/native_property_details.rb +0 -43
  85. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +0 -61
  86. data/lib/dependabot/nuget/native_update_checker/native_requirements_updater.rb +0 -105
  87. data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +0 -214
  88. data/lib/dependabot/nuget/nuget_client.rb +0 -223
  89. data/lib/dependabot/nuget/update_checker/compatibility_checker.rb +0 -116
  90. data/lib/dependabot/nuget/update_checker/dependency_finder.rb +0 -297
  91. data/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +0 -221
  92. data/lib/dependabot/nuget/update_checker/nuspec_fetcher.rb +0 -110
  93. data/lib/dependabot/nuget/update_checker/property_updater.rb +0 -196
  94. data/lib/dependabot/nuget/update_checker/repository_finder.rb +0 -466
  95. data/lib/dependabot/nuget/update_checker/tfm_comparer.rb +0 -34
  96. data/lib/dependabot/nuget/update_checker/tfm_finder.rb +0 -30
  97. data/lib/dependabot/nuget/update_checker/version_finder.rb +0 -449
@@ -1,116 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "sorbet-runtime"
5
-
6
- require "dependabot/update_checkers/base"
7
-
8
- module Dependabot
9
- module Nuget
10
- class CompatibilityChecker
11
- extend T::Sig
12
-
13
- require_relative "nuspec_fetcher"
14
- require_relative "nupkg_fetcher"
15
- require_relative "tfm_finder"
16
- require_relative "tfm_comparer"
17
-
18
- sig do
19
- params(
20
- dependency_urls: T::Array[T::Hash[Symbol, String]],
21
- dependency: Dependabot::Dependency
22
- ).void
23
- end
24
- def initialize(dependency_urls:, dependency:)
25
- @dependency_urls = dependency_urls
26
- @dependency = dependency
27
- end
28
-
29
- sig { params(version: String).returns(T::Boolean) }
30
- def compatible?(version)
31
- nuspec_xml = NuspecFetcher.fetch_nuspec(dependency_urls, dependency.name, version)
32
- return false unless nuspec_xml
33
-
34
- # development dependencies are packages such as analyzers which need to be compatible with the compiler not the
35
- # project itself, but some packages that report themselves as development dependencies still contain target
36
- # framework dependencies and should be checked for compatibility through the regular means
37
- return true if pure_development_dependency?(nuspec_xml)
38
-
39
- package_tfms = parse_package_tfms(nuspec_xml)
40
- package_tfms = fetch_package_tfms(version) if package_tfms.empty?
41
- # nil is a special return value that indicates that the package is likely a development dependency
42
- return true if package_tfms.nil?
43
- return false if package_tfms.empty?
44
-
45
- return false if project_tfms.nil? || project_tfms&.empty?
46
-
47
- TfmComparer.are_frameworks_compatible?(T.must(project_tfms), package_tfms)
48
- end
49
-
50
- private
51
-
52
- sig { returns(T::Array[T::Hash[Symbol, String]]) }
53
- attr_reader :dependency_urls
54
-
55
- sig { returns(Dependabot::Dependency) }
56
- attr_reader :dependency
57
-
58
- sig { params(nuspec_xml: Nokogiri::XML::Document).returns(T::Boolean) }
59
- def pure_development_dependency?(nuspec_xml)
60
- contents = nuspec_xml.at_xpath("package/metadata/developmentDependency")&.content&.strip
61
- return false unless contents # no `developmentDependency` element
62
-
63
- self_reports_as_development_dependency = contents.casecmp?("true")
64
- return false unless self_reports_as_development_dependency
65
-
66
- # even though a package self-reports as a development dependency, it might not be if it has dependency groups
67
- # with a target framework
68
- dependency_groups_with_target_framework =
69
- nuspec_xml.at_xpath("/package/metadata/dependencies/group[@targetFramework]")
70
- dependency_groups_with_target_framework.to_a.empty?
71
- end
72
-
73
- sig { params(nuspec_xml: Nokogiri::XML::Document).returns(T::Array[String]) }
74
- def parse_package_tfms(nuspec_xml)
75
- nuspec_xml.xpath("//dependencies/group").filter_map { |group| group.attribute("targetFramework") }
76
- end
77
-
78
- sig { returns(T.nilable(T::Array[String])) }
79
- def project_tfms
80
- @project_tfms ||= T.let(TfmFinder.frameworks(dependency), T.nilable(T::Array[String]))
81
- end
82
-
83
- sig { params(dependency_version: String).returns(T.nilable(T::Array[String])) }
84
- def fetch_package_tfms(dependency_version)
85
- cache = CacheManager.cache("compatibility_checker_tfms_cache")
86
- key = "#{dependency.name}::#{dependency_version}"
87
-
88
- cache[key] ||= begin
89
- nupkg_buffer = NupkgFetcher.fetch_nupkg_buffer(dependency_urls, dependency.name, dependency_version)
90
- return [] unless nupkg_buffer
91
-
92
- # Parse tfms from the folders beneath the lib folder
93
- folder_name = "lib/"
94
- tfms = Set.new
95
- Zip::File.open_buffer(nupkg_buffer) do |zip|
96
- lib_file_entries = zip.select { |entry| entry.name.start_with?(folder_name) }
97
- # If there is no lib folder in this package, assume it is a development dependency
98
- return nil if lib_file_entries.empty?
99
-
100
- lib_file_entries.each do |entry|
101
- _, tfm = entry.name.split("/").first(2)
102
-
103
- # some zip compressors create empty directory entries (in this case `lib/`) which can cause the string
104
- # split to return `nil`, so we have to explicitly guard against that
105
- tfms << tfm if tfm
106
- end
107
- end
108
-
109
- tfms.to_a
110
- end
111
-
112
- cache[key]
113
- end
114
- end
115
- end
116
- end
@@ -1,297 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "nokogiri"
5
- require "sorbet-runtime"
6
- require "stringio"
7
- require "zip"
8
-
9
- require "dependabot/update_checkers/base"
10
- require "dependabot/nuget/version"
11
-
12
- module Dependabot
13
- module Nuget
14
- class UpdateChecker < Dependabot::UpdateCheckers::Base
15
- class DependencyFinder
16
- extend T::Sig
17
-
18
- require_relative "requirements_updater"
19
- require_relative "nuspec_fetcher"
20
-
21
- sig { returns(T::Hash[String, T.untyped]) }
22
- def self.transitive_dependencies_cache
23
- CacheManager.cache("dependency_finder_transitive_dependencies")
24
- end
25
-
26
- sig { returns(T::Hash[String, T.untyped]) }
27
- def self.updated_peer_dependencies_cache
28
- CacheManager.cache("dependency_finder_updated_peer_dependencies")
29
- end
30
-
31
- sig { returns(T::Hash[String, T.untyped]) }
32
- def self.fetch_dependencies_cache
33
- CacheManager.cache("dependency_finder_fetch_dependencies")
34
- end
35
-
36
- sig do
37
- params(
38
- dependency: Dependabot::Dependency,
39
- dependency_files: T::Array[Dependabot::DependencyFile],
40
- ignored_versions: T::Array[String],
41
- credentials: T::Array[Dependabot::Credential],
42
- repo_contents_path: T.nilable(String)
43
- ).void
44
- end
45
- def initialize(dependency:, dependency_files:, ignored_versions:, credentials:, repo_contents_path:)
46
- @dependency = dependency
47
- @dependency_files = dependency_files
48
- @ignored_versions = ignored_versions
49
- @credentials = credentials
50
- @repo_contents_path = repo_contents_path
51
- end
52
-
53
- sig { returns(T::Array[Dependabot::Dependency]) }
54
- def transitive_dependencies
55
- key = "#{dependency.name.downcase}::#{dependency.version}"
56
- cache = DependencyFinder.transitive_dependencies_cache
57
-
58
- unless cache[key]
59
- begin
60
- # first do a quick sanity check on the version string; if it can't be parsed, an exception will be raised
61
- _ = Version.new(dependency.version)
62
-
63
- cache[key] = fetch_transitive_dependencies(
64
- @dependency.name,
65
- T.must(@dependency.version)
66
- ).map do |dependency_info|
67
- package_name = dependency_info["packageName"]
68
- target_version = dependency_info["version"]
69
-
70
- Dependency.new(
71
- name: package_name,
72
- version: target_version.to_s,
73
- requirements: [], # Empty requirements for transitive dependencies
74
- package_manager: @dependency.package_manager
75
- )
76
- end
77
- rescue StandardError
78
- # if anything happened above, there are no meaningful dependencies that can be derived
79
- cache[key] = []
80
- end
81
- end
82
-
83
- cache[key]
84
- end
85
-
86
- sig { returns(T::Array[Dependabot::Dependency]) }
87
- def updated_peer_dependencies
88
- key = "#{dependency.name.downcase}::#{dependency.version}"
89
- cache = DependencyFinder.updated_peer_dependencies_cache
90
-
91
- cache[key] ||= fetch_transitive_dependencies(
92
- @dependency.name,
93
- T.must(@dependency.version)
94
- ).filter_map do |dependency_info|
95
- package_name = dependency_info["packageName"]
96
- target_version = dependency_info["version"]
97
-
98
- # Find the Dependency object for the peer dependency. We will not return
99
- # dependencies that are not referenced from dependency files.
100
- peer_dependency = top_level_dependencies.find { |d| d.name == package_name }
101
- next unless peer_dependency
102
- next unless target_version > peer_dependency.numeric_version
103
-
104
- # Use version finder to determine the source details for the peer dependency.
105
- target_version_details = version_finder(peer_dependency).versions.find do |v|
106
- v.fetch(:version) == target_version
107
- end
108
- next unless target_version_details
109
-
110
- Dependency.new(
111
- name: peer_dependency.name,
112
- version: target_version_details.fetch(:version).to_s,
113
- requirements: updated_requirements(peer_dependency, target_version_details),
114
- previous_version: peer_dependency.version,
115
- previous_requirements: peer_dependency.requirements,
116
- package_manager: peer_dependency.package_manager,
117
- metadata: { information_only: true } # Instruct updater to not directly update this dependency
118
- )
119
- end
120
-
121
- cache[key]
122
- end
123
-
124
- private
125
-
126
- sig { returns(Dependabot::Dependency) }
127
- attr_reader :dependency
128
-
129
- sig { returns(T::Array[Dependabot::DependencyFile]) }
130
- attr_reader :dependency_files
131
-
132
- sig { returns(T::Array[String]) }
133
- attr_reader :ignored_versions
134
-
135
- sig { returns(T::Array[Dependabot::Credential]) }
136
- attr_reader :credentials
137
-
138
- sig { returns(T.nilable(String)) }
139
- attr_reader :repo_contents_path
140
-
141
- sig do
142
- params(
143
- dep: Dependabot::Dependency,
144
- target_version_details: T::Hash[Symbol, T.untyped]
145
- )
146
- .returns(T::Array[T::Hash[String, T.untyped]])
147
- end
148
- def updated_requirements(dep, target_version_details)
149
- @updated_requirements ||= T.let({}, T.nilable(T::Hash[String, T.untyped]))
150
- @updated_requirements[dep.name] ||=
151
- RequirementsUpdater.new(
152
- requirements: dep.requirements,
153
- latest_version: target_version_details.fetch(:version).to_s,
154
- source_details: target_version_details.slice(:nuspec_url, :repo_url, :source_url)
155
- ).updated_requirements
156
- end
157
-
158
- sig { returns(T::Array[Dependabot::Dependency]) }
159
- def top_level_dependencies
160
- @top_level_dependencies ||=
161
- T.let(
162
- Nuget::FileParser.new(
163
- dependency_files: dependency_files,
164
- repo_contents_path: repo_contents_path,
165
- source: nil
166
- ).parse.select(&:top_level?),
167
- T.nilable(T::Array[Dependabot::Dependency])
168
- )
169
- end
170
-
171
- sig { returns(T::Array[Dependabot::DependencyFile]) }
172
- def nuget_configs
173
- @nuget_configs ||=
174
- T.let(
175
- @dependency_files.select { |f| f.name.match?(/nuget\.config$/i) },
176
- T.nilable(T::Array[Dependabot::DependencyFile])
177
- )
178
- end
179
-
180
- sig { returns(T::Array[T::Hash[Symbol, String]]) }
181
- def dependency_urls
182
- @dependency_urls ||=
183
- T.let(
184
- RepositoryFinder.new(
185
- dependency: @dependency,
186
- credentials: @credentials,
187
- config_files: nuget_configs
188
- )
189
- .dependency_urls
190
- .select { |url| url.fetch(:repository_type) == "v3" },
191
- T.nilable(T::Array[T::Hash[Symbol, String]])
192
- )
193
- end
194
-
195
- sig { params(package_id: String, package_version: String).returns(T::Array[T::Hash[String, T.untyped]]) }
196
- def fetch_transitive_dependencies(package_id, package_version)
197
- all_dependencies = {}
198
- fetch_transitive_dependencies_impl(package_id, package_version, all_dependencies)
199
- all_dependencies.map { |_, dependency_info| dependency_info }
200
- end
201
-
202
- sig { params(package_id: String, package_version: String, all_dependencies: T::Hash[String, T.untyped]).void }
203
- def fetch_transitive_dependencies_impl(package_id, package_version, all_dependencies)
204
- dependencies = fetch_dependencies(package_id, package_version)
205
- return unless dependencies.any?
206
-
207
- dependencies.each do |dependency|
208
- next if dependency.nil?
209
-
210
- dependency_id = dependency["packageName"]
211
- dependency_version_range = dependency["versionRange"]
212
-
213
- nuget_version_range_regex = /[\[(](\d+(\.\d+)*(-\w+(\.\d+)*)?)/
214
- nuget_version_range_match_data = nuget_version_range_regex.match(dependency_version_range)
215
-
216
- dependency_version = if nuget_version_range_match_data.nil?
217
- dependency_version_range
218
- else
219
- nuget_version_range_match_data[1]
220
- end
221
-
222
- dependency["version"] = Version.new(dependency_version)
223
-
224
- current_dependency = all_dependencies[dependency_id.downcase]
225
- next unless current_dependency.nil? || current_dependency["version"] < dependency["version"]
226
-
227
- all_dependencies[dependency_id.downcase] = dependency
228
- fetch_transitive_dependencies_impl(dependency_id, dependency_version, all_dependencies)
229
- end
230
- end
231
-
232
- sig { params(package_id: String, package_version: String).returns(T::Array[T::Hash[String, T.untyped]]) }
233
- def fetch_dependencies(package_id, package_version)
234
- key = "#{package_id.downcase}::#{package_version}"
235
- cache = DependencyFinder.fetch_dependencies_cache
236
-
237
- cache[key] ||= begin
238
- nuspec_xml = NuspecFetcher.fetch_nuspec(dependency_urls, package_id, package_version)
239
- if nuspec_xml.nil?
240
- []
241
- else
242
- read_dependencies_from_nuspec(nuspec_xml)
243
- end
244
- end
245
-
246
- cache[key]
247
- end
248
-
249
- sig { params(nuspec_xml: Nokogiri::XML::Document).returns(T::Array[T::Hash[String, String]]) }
250
- def read_dependencies_from_nuspec(nuspec_xml) # rubocop:disable Metrics/PerceivedComplexity
251
- # we want to exclude development dependencies from the lookup
252
- allowed_attributes = %w(all compile native runtime)
253
-
254
- nuspec_xml_dependencies = nuspec_xml.xpath("//dependencies/child::node()/dependency").select do |dependency|
255
- include_attr = dependency.attribute("include")
256
- exclude_attr = dependency.attribute("exclude")
257
-
258
- if include_attr.nil? && exclude_attr.nil?
259
- true
260
- elsif include_attr
261
- include_values = include_attr.value.split(",").map(&:strip)
262
- include_values.any? { |element1| allowed_attributes.any? { |element2| element1.casecmp?(element2) } }
263
- else
264
- exclude_values = exclude_attr.value.split(",").map(&:strip)
265
- exclude_values.none? { |element1| allowed_attributes.any? { |element2| element1.casecmp?(element2) } }
266
- end
267
- end
268
-
269
- dependency_list = []
270
- nuspec_xml_dependencies.each do |dependency|
271
- next unless dependency.attribute("version")
272
-
273
- dependency_list << {
274
- "packageName" => dependency.attribute("id").value,
275
- "versionRange" => dependency.attribute("version").value
276
- }
277
- end
278
-
279
- dependency_list
280
- end
281
-
282
- sig { params(dep: Dependabot::Dependency).returns(Dependabot::Nuget::UpdateChecker::VersionFinder) }
283
- def version_finder(dep)
284
- VersionFinder.new(
285
- dependency: dep,
286
- dependency_files: dependency_files,
287
- credentials: credentials,
288
- ignored_versions: ignored_versions,
289
- raise_on_ignored: false,
290
- security_advisories: [],
291
- repo_contents_path: repo_contents_path
292
- )
293
- end
294
- end
295
- end
296
- end
297
- end
@@ -1,221 +0,0 @@
1
- # typed: strict
2
- # frozen_string_literal: true
3
-
4
- require "nokogiri"
5
- require "stringio"
6
- require "sorbet-runtime"
7
- require "zip"
8
-
9
- require "dependabot/nuget/http_response_helpers"
10
-
11
- module Dependabot
12
- module Nuget
13
- class NupkgFetcher
14
- extend T::Sig
15
-
16
- require_relative "repository_finder"
17
-
18
- sig do
19
- params(
20
- dependency_urls: T::Array[T::Hash[Symbol, String]],
21
- package_id: String,
22
- package_version: String
23
- )
24
- .returns(T.nilable(String))
25
- end
26
- def self.fetch_nupkg_buffer(dependency_urls, package_id, package_version)
27
- # check all repositories for the first one that has the nupkg
28
- dependency_urls.reduce(T.let(nil, T.nilable(String))) do |nupkg_buffer, repository_details|
29
- nupkg_buffer || fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
30
- end
31
- end
32
-
33
- sig do
34
- params(
35
- repository_details: T::Hash[Symbol, T.untyped],
36
- package_id: T.nilable(String),
37
- package_version: T.nilable(String)
38
- )
39
- .returns(T.nilable(String))
40
- end
41
- def self.fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
42
- return unless package_id && package_version && !package_version.empty?
43
-
44
- feed_url = repository_details[:repository_url]
45
- repository_type = repository_details[:repository_type]
46
-
47
- package_url = if repository_type == "v2"
48
- get_nuget_v2_package_url(repository_details, package_id, package_version)
49
- elsif repository_type == "v3"
50
- get_nuget_v3_package_url(repository_details, package_id, package_version)
51
- else
52
- raise Dependabot::DependencyFileNotResolvable, "Unexpected NuGet feed format: #{feed_url}"
53
- end
54
-
55
- package_url
56
- end
57
-
58
- sig do
59
- params(
60
- repository_details: T::Hash[Symbol, T.untyped],
61
- package_id: String,
62
- package_version: String
63
- )
64
- .returns(T.nilable(String))
65
- end
66
- def self.fetch_nupkg_buffer_from_repository(repository_details, package_id, package_version)
67
- package_url = fetch_nupkg_url_from_repository(repository_details, package_id, package_version)
68
- return unless package_url
69
-
70
- auth_header = repository_details[:auth_header]
71
- fetch_stream(package_url, auth_header)
72
- end
73
-
74
- sig do
75
- params(
76
- repository_details: T::Hash[Symbol, T.untyped],
77
- package_id: String,
78
- package_version: String
79
- )
80
- .returns(T.nilable(String))
81
- end
82
- def self.get_nuget_v3_package_url(repository_details, package_id, package_version)
83
- base_url = repository_details[:base_url]
84
- unless base_url
85
- return get_nuget_v3_package_url_from_search(repository_details, package_id,
86
- package_version)
87
- end
88
-
89
- base_url = base_url.delete_suffix("/")
90
- package_id_downcased = package_id.downcase
91
- "#{base_url}/#{package_id_downcased}/#{package_version}/#{package_id_downcased}.#{package_version}.nupkg"
92
- end
93
-
94
- # rubocop:disable Metrics/CyclomaticComplexity
95
- # rubocop:disable Metrics/PerceivedComplexity
96
- sig do
97
- params(
98
- repository_details: T::Hash[Symbol, T.untyped],
99
- package_id: String,
100
- package_version: String
101
- )
102
- .returns(T.nilable(String))
103
- end
104
- def self.get_nuget_v3_package_url_from_search(repository_details, package_id, package_version)
105
- search_url = repository_details[:search_url]
106
- return nil unless search_url
107
-
108
- # get search result
109
- search_result_response = fetch_url(search_url, repository_details)
110
- return nil unless search_result_response&.status == 200
111
-
112
- search_response_body = HttpResponseHelpers.remove_wrapping_zero_width_chars(T.must(search_result_response).body)
113
- search_results = JSON.parse(search_response_body)
114
-
115
- # find matching package and version
116
- package_search_result = search_results&.[]("data")&.find { |d| package_id.casecmp?(d&.[]("id")) }
117
- version_search_result = package_search_result&.[]("versions")&.find do |v|
118
- package_version.casecmp?(v&.[]("version"))
119
- end
120
- registration_leaf_url = version_search_result&.[]("@id")
121
- return nil unless registration_leaf_url
122
-
123
- registration_leaf_response = fetch_url(registration_leaf_url, repository_details)
124
- return nil unless registration_leaf_response
125
- return nil unless registration_leaf_response.status == 200
126
-
127
- registration_leaf_response_body =
128
- HttpResponseHelpers.remove_wrapping_zero_width_chars(registration_leaf_response.body)
129
- registration_leaf = JSON.parse(registration_leaf_response_body)
130
-
131
- # finally, get the .nupkg url
132
- registration_leaf&.[]("packageContent")
133
- end
134
- # rubocop:enable Metrics/PerceivedComplexity
135
- # rubocop:enable Metrics/CyclomaticComplexity
136
-
137
- sig do
138
- params(
139
- repository_details: T::Hash[Symbol, T.untyped],
140
- package_id: String,
141
- package_version: String
142
- )
143
- .returns(T.nilable(String))
144
- end
145
- def self.get_nuget_v2_package_url(repository_details, package_id, package_version)
146
- # get package XML
147
- base_url = repository_details[:base_url].delete_suffix("/")
148
- package_url = "#{base_url}/Packages(Id='#{package_id}',Version='#{package_version}')"
149
- response = fetch_url(package_url, repository_details)
150
- return nil unless response&.status == 200
151
-
152
- # find relevant element
153
- doc = Nokogiri::XML(T.must(response).body)
154
- doc.remove_namespaces!
155
-
156
- content_element = doc.xpath("/entry/content")
157
- nupkg_url = content_element&.attribute("src")&.value
158
- nupkg_url
159
- end
160
-
161
- sig do
162
- params(
163
- stream_url: String,
164
- auth_header: T::Hash[String, String],
165
- max_redirects: Integer
166
- )
167
- .returns(T.nilable(String))
168
- end
169
- def self.fetch_stream(stream_url, auth_header, max_redirects = 5)
170
- current_url = stream_url
171
- current_redirects = 0
172
-
173
- loop do
174
- # Directly download the stream without any additional settings _except_ for `omit_default_port: true` which
175
- # is necessary to not break the URL signing that some NuGet feeds use.
176
- response = Excon.get(
177
- current_url,
178
- headers: auth_header,
179
- omit_default_port: true
180
- )
181
-
182
- # redirect the HTTP response as appropriate based on documentation here:
183
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections
184
- case response.status
185
- when 200
186
- return response.body
187
- when 301, 302, 303, 307, 308
188
- current_redirects += 1
189
- return nil if current_redirects > max_redirects
190
-
191
- current_url = T.must(response.headers["Location"])
192
- else
193
- return nil
194
- end
195
- end
196
- end
197
-
198
- sig do
199
- params(
200
- url: String,
201
- repository_details: T::Hash[Symbol, T.untyped]
202
- )
203
- .returns(T.nilable(Excon::Response))
204
- end
205
- def self.fetch_url(url, repository_details)
206
- fetch_url_with_auth(url, repository_details.fetch(:auth_header))
207
- end
208
-
209
- sig { params(url: String, auth_header: T::Hash[T.any(String, Symbol), T.untyped]).returns(Excon::Response) }
210
- def self.fetch_url_with_auth(url, auth_header)
211
- cache = CacheManager.cache("nupkg_fetcher_cache")
212
- cache[url] ||= Dependabot::RegistryClient.get(
213
- url: url,
214
- headers: auth_header
215
- )
216
-
217
- cache[url]
218
- end
219
- end
220
- end
221
- end