dependabot-nuget 0.289.0 → 0.291.0

Sign up to get free protection for your applications and to get access to all the features.
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