dependabot-nuget 0.248.0 → 0.250.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.
@@ -1,6 +1,8 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  require "dependabot/update_checkers/base"
5
7
  require "dependabot/nuget/file_parser"
6
8
 
@@ -8,28 +10,47 @@ module Dependabot
8
10
  module Nuget
9
11
  class UpdateChecker < Dependabot::UpdateCheckers::Base
10
12
  class PropertyUpdater
13
+ extend T::Sig
14
+
11
15
  require_relative "version_finder"
12
16
  require_relative "requirements_updater"
13
17
  require_relative "dependency_finder"
14
18
 
19
+ sig do
20
+ params(
21
+ dependency: Dependabot::Dependency,
22
+ dependency_files: T::Array[Dependabot::DependencyFile],
23
+ credentials: T::Array[Dependabot::Credential],
24
+ target_version_details: T.nilable(T::Hash[Symbol, String]),
25
+ ignored_versions: T::Array[String],
26
+ repo_contents_path: T.nilable(String),
27
+ raise_on_ignored: T::Boolean
28
+ ).void
29
+ end
15
30
  def initialize(dependency:, dependency_files:, credentials:,
16
31
  target_version_details:, ignored_versions:,
17
- raise_on_ignored: false, repo_contents_path:)
32
+ repo_contents_path:, raise_on_ignored: false)
18
33
  @dependency = dependency
19
34
  @dependency_files = dependency_files
20
35
  @credentials = credentials
21
36
  @ignored_versions = ignored_versions
22
37
  @raise_on_ignored = raise_on_ignored
23
- @target_version = target_version_details&.fetch(:version)
24
- @source_details = target_version_details
25
- &.slice(:nuspec_url, :repo_url, :source_url)
38
+ @target_version = T.let(
39
+ target_version_details&.fetch(:version),
40
+ T.nilable(T.any(String, Dependabot::Nuget::Version))
41
+ )
42
+ @source_details = T.let(
43
+ target_version_details&.slice(:nuspec_url, :repo_url, :source_url),
44
+ T.nilable(T::Hash[Symbol, String])
45
+ )
26
46
  @repo_contents_path = repo_contents_path
27
47
  end
28
48
 
49
+ sig { returns(T::Boolean) }
29
50
  def update_possible?
30
51
  return false unless target_version
31
52
 
32
- @update_possible ||=
53
+ @update_possible ||= T.let(
33
54
  dependencies_using_property.all? do |dep|
34
55
  versions = VersionFinder.new(
35
56
  dependency: dep,
@@ -42,42 +63,73 @@ module Dependabot
42
63
  ).versions.map { |v| v.fetch(:version) }
43
64
 
44
65
  versions.include?(target_version) || versions.none?
45
- end
66
+ end,
67
+ T.nilable(T::Boolean)
68
+ )
46
69
  end
47
70
 
71
+ sig { returns(T::Array[Dependabot::Dependency]) }
48
72
  def updated_dependencies
49
73
  raise "Update not possible!" unless update_possible?
50
74
 
51
- @updated_dependencies ||= begin
52
- dependencies = {}
53
-
54
- dependencies_using_property.each do |dep|
55
- # Only keep one copy of each dependency, the one with the highest target version.
56
- visited_dependency = dependencies[dep.name.downcase]
57
- next unless visited_dependency.nil? || visited_dependency.numeric_version < target_version
58
-
59
- updated_dependency = Dependency.new(
60
- name: dep.name,
61
- version: target_version.to_s,
62
- requirements: updated_requirements(dep),
63
- previous_version: dep.version,
64
- previous_requirements: dep.requirements,
65
- package_manager: dep.package_manager
66
- )
67
- dependencies[updated_dependency.name.downcase] = updated_dependency
68
- # Add peer dependencies to the list of updated dependencies.
69
- process_updated_peer_dependencies(updated_dependency, dependencies)
70
- end
75
+ @updated_dependencies ||= T.let(
76
+ begin
77
+ dependencies = T.let({}, T::Hash[String, Dependabot::Dependency])
78
+
79
+ dependencies_using_property.each do |dep|
80
+ # Only keep one copy of each dependency, the one with the highest target version.
81
+ visited_dependency = dependencies[dep.name.downcase]
82
+ next unless visited_dependency.nil? || T.must(visited_dependency.numeric_version) < target_version
83
+
84
+ updated_dependency = Dependency.new(
85
+ name: dep.name,
86
+ version: target_version.to_s,
87
+ requirements: updated_requirements(dep),
88
+ previous_version: dep.version,
89
+ previous_requirements: dep.requirements,
90
+ package_manager: dep.package_manager
91
+ )
92
+ dependencies[updated_dependency.name.downcase] = updated_dependency
93
+ # Add peer dependencies to the list of updated dependencies.
94
+ process_updated_peer_dependencies(updated_dependency, dependencies)
95
+ end
71
96
 
72
- dependencies.map { |_, dependency| dependency }
73
- end
97
+ dependencies.map { |_, dependency| dependency }
98
+ end,
99
+ T.nilable(T::Array[Dependabot::Dependency])
100
+ )
74
101
  end
75
102
 
76
103
  private
77
104
 
78
- attr_reader :dependency, :dependency_files, :target_version,
79
- :source_details, :credentials, :ignored_versions, :repo_contents_path
105
+ sig { returns(Dependabot::Dependency) }
106
+ attr_reader :dependency
107
+
108
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
109
+ attr_reader :dependency_files
110
+
111
+ sig { returns(T.nilable(T.any(String, Dependabot::Nuget::Version))) }
112
+ attr_reader :target_version
80
113
 
114
+ sig { returns(T.nilable(T::Hash[Symbol, String])) }
115
+ attr_reader :source_details
116
+
117
+ sig { returns(T::Array[Dependabot::Credential]) }
118
+ attr_reader :credentials
119
+
120
+ sig { returns(T::Array[String]) }
121
+ attr_reader :ignored_versions
122
+
123
+ sig { returns(T.nilable(String)) }
124
+ attr_reader :repo_contents_path
125
+
126
+ sig do
127
+ params(
128
+ dependency: Dependabot::Dependency,
129
+ dependencies: T::Hash[String, Dependabot::Dependency]
130
+ )
131
+ .returns(T::Array[Dependabot::Dependency])
132
+ end
81
133
  def process_updated_peer_dependencies(dependency, dependencies)
82
134
  DependencyFinder.new(
83
135
  dependency: dependency,
@@ -87,36 +139,48 @@ module Dependabot
87
139
  ).updated_peer_dependencies.each do |peer_dependency|
88
140
  # Only keep one copy of each dependency, the one with the highest target version.
89
141
  visited_dependency = dependencies[peer_dependency.name.downcase]
90
- next unless visited_dependency.nil? || visited_dependency.numeric_version < peer_dependency.numeric_version
142
+ unless visited_dependency.nil? ||
143
+ T.must(visited_dependency.numeric_version) < peer_dependency.numeric_version
144
+ next
145
+ end
91
146
 
92
147
  dependencies[peer_dependency.name.downcase] = peer_dependency
93
148
  end
94
149
  end
95
150
 
151
+ sig { returns(T::Array[Dependabot::Dependency]) }
96
152
  def dependencies_using_property
97
153
  @dependencies_using_property ||=
98
- Nuget::FileParser.new(
99
- dependency_files: dependency_files,
100
- source: nil
101
- ).parse.select do |dep|
102
- dep.requirements.any? do |r|
103
- r.dig(:metadata, :property_name) == property_name
104
- end
105
- end
154
+ T.let(
155
+ Nuget::FileParser.new(
156
+ dependency_files: dependency_files,
157
+ source: nil
158
+ ).parse.select do |dep|
159
+ dep.requirements.any? do |r|
160
+ r.dig(:metadata, :property_name) == property_name
161
+ end
162
+ end,
163
+ T.nilable(T::Array[Dependabot::Dependency])
164
+ )
106
165
  end
107
166
 
167
+ sig { returns(String) }
108
168
  def property_name
109
- @property_name ||= dependency.requirements
110
- .find { |r| r.dig(:metadata, :property_name) }
111
- &.dig(:metadata, :property_name)
169
+ @property_name ||= T.let(
170
+ dependency.requirements
171
+ .find { |r| r.dig(:metadata, :property_name) }
172
+ &.dig(:metadata, :property_name),
173
+ T.nilable(String)
174
+ )
112
175
 
113
176
  raise "No requirement with a property name!" unless @property_name
114
177
 
115
178
  @property_name
116
179
  end
117
180
 
181
+ sig { params(dep: Dependabot::Dependency).returns(T::Array[T::Hash[Symbol, T.untyped]]) }
118
182
  def updated_requirements(dep)
119
- @updated_requirements ||= {}
183
+ @updated_requirements ||= T.let({}, T.nilable(T::Hash[String, T::Array[T::Hash[Symbol, T.untyped]]]))
120
184
  @updated_requirements[dep.name] ||=
121
185
  RequirementsUpdater.new(
122
186
  requirements: dep.requirements,
@@ -1,8 +1,10 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
5
  require "nokogiri"
6
+ require "sorbet-runtime"
7
+
6
8
  require "dependabot/errors"
7
9
  require "dependabot/update_checkers/base"
8
10
  require "dependabot/registry_client"
@@ -12,23 +14,34 @@ require "dependabot/nuget/http_response_helpers"
12
14
  module Dependabot
13
15
  module Nuget
14
16
  class RepositoryFinder
17
+ extend T::Sig
18
+
15
19
  DEFAULT_REPOSITORY_URL = "https://api.nuget.org/v3/index.json"
16
20
  DEFAULT_REPOSITORY_API_KEY = "nuget.org"
17
21
 
22
+ sig do
23
+ params(
24
+ dependency: Dependabot::Dependency,
25
+ credentials: T::Array[Dependabot::Credential],
26
+ config_files: T::Array[Dependabot::DependencyFile]
27
+ ).void
28
+ end
18
29
  def initialize(dependency:, credentials:, config_files: [])
19
30
  @dependency = dependency
20
31
  @credentials = credentials
21
32
  @config_files = config_files
22
33
  end
23
34
 
35
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
24
36
  def dependency_urls
25
37
  find_dependency_urls
26
38
  end
27
39
 
40
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
28
41
  def known_repositories
29
42
  return @known_repositories if @known_repositories
30
43
 
31
- @known_repositories = []
44
+ @known_repositories ||= T.let([], T.nilable(T::Array[T::Hash[Symbol, String]]))
32
45
  @known_repositories += credential_repositories
33
46
  @known_repositories += config_file_repositories
34
47
 
@@ -40,6 +53,7 @@ module Dependabot
40
53
  @known_repositories.uniq
41
54
  end
42
55
 
56
+ sig { params(dependency_name: String).returns(T::Hash[Symbol, T.untyped]) }
43
57
  def self.get_default_repository_details(dependency_name)
44
58
  {
45
59
  base_url: "https://api.nuget.org/v3-flatcontainer/",
@@ -56,21 +70,33 @@ module Dependabot
56
70
 
57
71
  private
58
72
 
59
- attr_reader :dependency, :credentials, :config_files
73
+ sig { returns(Dependabot::Dependency) }
74
+ attr_reader :dependency
75
+
76
+ sig { returns(T::Array[Dependabot::Credential]) }
77
+ attr_reader :credentials
78
+
79
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
80
+ attr_reader :config_files
60
81
 
82
+ sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
61
83
  def find_dependency_urls
62
84
  @find_dependency_urls ||=
63
- known_repositories.flat_map do |details|
64
- if details.fetch(:url) == DEFAULT_REPOSITORY_URL
65
- # Save a request for the default URL, since we already know how
66
- # it addresses packages
67
- next default_repository_details
68
- end
85
+ T.let(
86
+ known_repositories.flat_map do |details|
87
+ if details.fetch(:url) == DEFAULT_REPOSITORY_URL
88
+ # Save a request for the default URL, since we already know how
89
+ # it addresses packages
90
+ next default_repository_details
91
+ end
69
92
 
70
- build_url_for_details(details)
71
- end.compact.uniq
93
+ build_url_for_details(details)
94
+ end.compact.uniq,
95
+ T.nilable(T::Array[T::Hash[Symbol, T.untyped]])
96
+ )
72
97
  end
73
98
 
99
+ sig { params(repo_details: T::Hash[Symbol, T.untyped]).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
74
100
  def build_url_for_details(repo_details)
75
101
  url = repo_details.fetch(:url)
76
102
  url_obj = URI.parse(url)
@@ -86,6 +112,7 @@ module Dependabot
86
112
  details
87
113
  end
88
114
 
115
+ sig { params(repo_details: T::Hash[Symbol, T.untyped]).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
89
116
  def build_url_for_details_remote(repo_details)
90
117
  response = get_repo_metadata(repo_details)
91
118
  check_repo_response(response, repo_details)
@@ -118,11 +145,12 @@ module Dependabot
118
145
 
119
146
  details
120
147
  rescue JSON::ParserError
121
- build_v2_url(response, repo_details)
148
+ build_v2_url(T.must(response), repo_details)
122
149
  rescue Excon::Error::Timeout, Excon::Error::Socket
123
150
  handle_timeout(repo_metadata_url: repo_details.fetch(:url))
124
151
  end
125
152
 
153
+ sig { params(repo_details: T::Hash[Symbol, T.untyped]).returns(Excon::Response) }
126
154
  def get_repo_metadata(repo_details)
127
155
  url = repo_details.fetch(:url)
128
156
  cache = CacheManager.cache("repo_finder_metadatacache")
@@ -138,6 +166,7 @@ module Dependabot
138
166
  end
139
167
  end
140
168
 
169
+ sig { params(metadata: T::Hash[String, T::Array[T::Hash[String, T.untyped]]]).returns(T.nilable(String)) }
141
170
  def base_url_from_v3_metadata(metadata)
142
171
  metadata
143
172
  .fetch("resources", [])
@@ -145,6 +174,7 @@ module Dependabot
145
174
  &.fetch("@id")
146
175
  end
147
176
 
177
+ sig { params(metadata: T::Hash[String, T::Array[T::Hash[String, T.untyped]]]).returns(T.nilable(String)) }
148
178
  def registration_url_from_v3_metadata(metadata)
149
179
  allowed_registration_types = %w(
150
180
  RegistrationsBaseUrl
@@ -159,6 +189,7 @@ module Dependabot
159
189
  &.fetch("@id")
160
190
  end
161
191
 
192
+ sig { params(metadata: T::Hash[String, T::Array[T::Hash[String, T.untyped]]]).returns(T.nilable(String)) }
162
193
  def search_url_from_v3_metadata(metadata)
163
194
  # allowable values from here: https://learn.microsoft.com/en-us/nuget/api/search-query-service-resource#versioning
164
195
  allowed_search_types = %w(
@@ -173,6 +204,13 @@ module Dependabot
173
204
  &.fetch("@id")
174
205
  end
175
206
 
207
+ sig do
208
+ params(
209
+ response: Excon::Response,
210
+ repo_details: T::Hash[Symbol, T.untyped]
211
+ )
212
+ .returns(T::Hash[Symbol, T.untyped])
213
+ end
176
214
  def build_v2_url(response, repo_details)
177
215
  doc = Nokogiri::XML(response.body)
178
216
 
@@ -194,6 +232,7 @@ module Dependabot
194
232
  }
195
233
  end
196
234
 
235
+ sig { params(response: Excon::Response, details: T::Hash[Symbol, T.untyped]).void }
197
236
  def check_repo_response(response, details)
198
237
  return unless [401, 402, 403].include?(response.status)
199
238
  raise if details.fetch(:url) == DEFAULT_REPOSITORY_URL
@@ -201,19 +240,25 @@ module Dependabot
201
240
  raise PrivateSourceAuthenticationFailure, details.fetch(:url)
202
241
  end
203
242
 
243
+ sig { params(repo_metadata_url: String).returns(T.noreturn) }
204
244
  def handle_timeout(repo_metadata_url:)
205
245
  raise if repo_metadata_url == DEFAULT_REPOSITORY_URL
206
246
 
207
247
  raise PrivateSourceTimedOut, repo_metadata_url
208
248
  end
209
249
 
250
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
210
251
  def credential_repositories
211
252
  @credential_repositories ||=
212
- credentials
213
- .select { |cred| cred["type"] == "nuget_feed" && cred["url"] }
214
- .map { |c| { url: c.fetch("url"), token: c["token"] } }
253
+ T.let(
254
+ credentials
255
+ .select { |cred| cred["type"] == "nuget_feed" && cred["url"] }
256
+ .map { |c| { url: c.fetch("url"), token: c["token"] } },
257
+ T.nilable(T::Array[T::Hash[Symbol, String]])
258
+ )
215
259
  end
216
260
 
261
+ sig { returns(T::Array[T::Hash[Symbol, String]]) }
217
262
  def config_file_repositories
218
263
  config_files.flat_map { |file| repos_from_config_file(file) }
219
264
  end
@@ -222,13 +267,14 @@ module Dependabot
222
267
  # rubocop:disable Metrics/PerceivedComplexity
223
268
  # rubocop:disable Metrics/MethodLength
224
269
  # rubocop:disable Metrics/AbcSize
270
+ sig { params(config_file: Dependabot::DependencyFile).returns(T::Array[T::Hash[Symbol, String]]) }
225
271
  def repos_from_config_file(config_file)
226
272
  doc = Nokogiri::XML(config_file.content)
227
273
  doc.remove_namespaces!
228
274
  # analogous to having a root config with the default repository
229
275
  base_sources = [{ url: DEFAULT_REPOSITORY_URL, key: "nuget.org" }]
230
276
 
231
- sources = []
277
+ sources = T.let([], T::Array[T::Hash[Symbol, String]])
232
278
 
233
279
  # regular package sources
234
280
  doc.css("configuration > packageSources").children.each do |node|
@@ -269,6 +315,23 @@ module Dependabot
269
315
  known_urls.include?(s.fetch(:url))
270
316
  end
271
317
 
318
+ # filter out based on packageSourceMapping
319
+ package_mapping_elements = doc.xpath("/configuration/packageSourceMapping/packageSource/package[@pattern]")
320
+ matching_package_elements = package_mapping_elements.select do |package_element|
321
+ pattern = package_element.attribute("pattern").value
322
+
323
+ # reusing this function for a case insensitive GLOB pattern patch (e.g., "Microsoft.Azure.*")
324
+ File.fnmatch(pattern, @dependency.name, File::FNM_CASEFOLD)
325
+ end
326
+ longest_matching_package_element = matching_package_elements.max_by do |package_element|
327
+ package_element.attribute("pattern").value.length
328
+ end
329
+ matching_key = longest_matching_package_element&.parent&.attribute("key")&.value
330
+ if matching_key
331
+ # found a matching source, only keep that one
332
+ sources.select! { |s| s.fetch(:key) == matching_key }
333
+ end
334
+
272
335
  add_config_file_credentials(sources: sources, doc: doc)
273
336
  sources.each { |details| details.delete(:key) }
274
337
 
@@ -279,11 +342,13 @@ module Dependabot
279
342
  # rubocop:enable Metrics/PerceivedComplexity
280
343
  # rubocop:enable Metrics/CyclomaticComplexity
281
344
 
345
+ sig { returns(T::Hash[Symbol, T.untyped]) }
282
346
  def default_repository_details
283
347
  RepositoryFinder.get_default_repository_details(dependency.name)
284
348
  end
285
349
 
286
350
  # rubocop:disable Metrics/PerceivedComplexity
351
+ sig { params(doc: Nokogiri::XML::Document).returns(T::Array[String]) }
287
352
  def disabled_sources(doc)
288
353
  doc.css("configuration > disabledPackageSources > add").filter_map do |node|
289
354
  value = node.attribute("value")&.value ||
@@ -298,6 +363,13 @@ module Dependabot
298
363
  # rubocop:enable Metrics/PerceivedComplexity
299
364
 
300
365
  # rubocop:disable Metrics/PerceivedComplexity
366
+ sig do
367
+ params(
368
+ sources: T::Array[T::Hash[Symbol, T.nilable(String)]],
369
+ doc: Nokogiri::XML::Document
370
+ )
371
+ .void
372
+ end
301
373
  def add_config_file_credentials(sources:, doc:)
302
374
  sources.each do |source_details|
303
375
  key = source_details.fetch(:key)
@@ -329,11 +401,10 @@ module Dependabot
329
401
  # Any non-ascii characters in the tag with cause a syntax error
330
402
  next source_details[:token] = nil
331
403
  end
332
-
333
- sources
334
404
  end
335
405
  # rubocop:enable Metrics/PerceivedComplexity
336
406
 
407
+ sig { params(string: String).returns(String) }
337
408
  def expand_windows_style_environment_variables(string)
338
409
  # NuGet.Config files can have Windows-style environment variables that need to be replaced
339
410
  # https://learn.microsoft.com/en-us/nuget/reference/nuget-config-file#using-environment-variables
@@ -352,6 +423,7 @@ module Dependabot
352
423
  end
353
424
  end
354
425
 
426
+ sig { params(token: T.nilable(String)).returns(T::Hash[String, String]) }
355
427
  def auth_header_for_token(token)
356
428
  return {} unless token
357
429
 
@@ -1,6 +1,8 @@
1
- # typed: true
1
+ # typed: strong
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  require "dependabot/update_checkers/base"
5
7
  require "dependabot/nuget/version"
6
8
  require "dependabot/nuget/requirement"
@@ -10,19 +12,22 @@ require "dependabot/shared_helpers"
10
12
  module Dependabot
11
13
  module Nuget
12
14
  class TfmComparer
15
+ extend T::Sig
16
+
17
+ sig { params(project_tfms: T::Array[String], package_tfms: T::Array[String]).returns(T::Boolean) }
13
18
  def self.are_frameworks_compatible?(project_tfms, package_tfms)
14
19
  return false if package_tfms.empty?
15
20
  return false if project_tfms.empty?
16
21
 
17
22
  key = "project_ftms:#{project_tfms.sort.join(',')}:package_tfms:#{package_tfms.sort.join(',')}".downcase
18
23
 
19
- @cached_framework_check ||= {}
24
+ @cached_framework_check ||= T.let({}, T.nilable(T::Hash[String, T::Boolean]))
20
25
  unless @cached_framework_check.key?(key)
21
26
  @cached_framework_check[key] =
22
27
  NativeHelpers.run_nuget_framework_check(project_tfms,
23
28
  package_tfms)
24
29
  end
25
- @cached_framework_check[key]
30
+ T.must(@cached_framework_check[key])
26
31
  end
27
32
  end
28
33
  end