dependabot-npm_and_yarn 0.302.0 → 0.304.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,10 +1,12 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
5
  require "dependabot/npm_and_yarn/update_checker"
6
6
  require "dependabot/update_checkers/version_filters"
7
- require "dependabot/npm_and_yarn/update_checker/registry_finder"
7
+ require "dependabot/npm_and_yarn/package/registry_finder"
8
+ require "dependabot/npm_and_yarn/package/package_details_fetcher"
9
+ require "dependabot/package/package_latest_version_finder"
8
10
  require "dependabot/npm_and_yarn/version"
9
11
  require "dependabot/npm_and_yarn/requirement"
10
12
  require "dependabot/shared_helpers"
@@ -14,20 +16,460 @@ require "sorbet-runtime"
14
16
  module Dependabot
15
17
  module NpmAndYarn
16
18
  class UpdateChecker
17
- class LatestVersionFinder
19
+ class PackageLatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
18
20
  extend T::Sig
19
21
 
20
- def initialize(dependency:, credentials:, dependency_files:,
21
- ignored_versions:, security_advisories:,
22
- raise_on_ignored: false)
22
+ sig do
23
+ params(
24
+ dependency: Dependabot::Dependency,
25
+ dependency_files: T::Array[Dependabot::DependencyFile],
26
+ credentials: T::Array[Dependabot::Credential],
27
+ ignored_versions: T::Array[String],
28
+ security_advisories: T::Array[Dependabot::SecurityAdvisory],
29
+ raise_on_ignored: T::Boolean,
30
+ cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
31
+ ).void
32
+ end
33
+ def initialize(
34
+ dependency:,
35
+ dependency_files:,
36
+ credentials:,
37
+ ignored_versions:,
38
+ security_advisories:,
39
+ raise_on_ignored: false,
40
+ cooldown_options: nil
41
+ )
42
+ @package_fetcher = T.let(nil, T.nilable(Package::PackageDetailsFetcher))
43
+ super
44
+ end
45
+
46
+ sig { returns(Package::PackageDetailsFetcher) }
47
+ def package_fetcher
48
+ return @package_fetcher if @package_fetcher
49
+
50
+ @package_fetcher = Package::PackageDetailsFetcher.new(
51
+ dependency: dependency,
52
+ dependency_files: dependency_files,
53
+ credentials: credentials
54
+ )
55
+ @package_fetcher
56
+ end
57
+
58
+ sig { override.returns(T.nilable(Dependabot::Package::PackageDetails)) }
59
+ def package_details
60
+ return @package_details if @package_details
61
+
62
+ @package_details = package_fetcher.fetch
63
+ @package_details
64
+ end
65
+
66
+ sig do
67
+ returns(T.nilable(Dependabot::Version))
68
+ end
69
+ def latest_version_from_registry
70
+ fetch_latest_version(language_version: nil)
71
+ end
72
+
73
+ sig do
74
+ override.params(language_version: T.nilable(T.any(String, Dependabot::Version)))
75
+ .returns(T.nilable(Dependabot::Version))
76
+ end
77
+ def latest_version_with_no_unlock(language_version: nil)
78
+ with_custom_registry_rescue do
79
+ return unless valid_npm_details?
80
+ return version_from_dist_tags if specified_dist_tag_requirement?
81
+
82
+ super
83
+ end
84
+ end
85
+
86
+ sig do
87
+ params(language_version: T.nilable(T.any(String, Dependabot::Version)))
88
+ .returns(T.nilable(Dependabot::Version))
89
+ end
90
+ def lowest_security_fix_version(language_version: nil)
91
+ fetch_lowest_security_fix_version(language_version: language_version)
92
+ end
93
+
94
+ # This method is for latest_version_from_registry
95
+ sig do
96
+ params(language_version: T.nilable(T.any(String, Dependabot::Version)))
97
+ .returns(T.nilable(Dependabot::Version))
98
+ end
99
+ def fetch_latest_version(language_version: nil)
100
+ with_custom_registry_rescue do
101
+ return unless valid_npm_details?
102
+
103
+ tag_version = version_from_dist_tags
104
+ return tag_version if tag_version
105
+
106
+ return if specified_dist_tag_requirement?
107
+
108
+ super
109
+ end
110
+ end
111
+
112
+ sig do
113
+ override
114
+ .params(language_version: T.nilable(T.any(String, Dependabot::Version)))
115
+ .returns(T.nilable(Dependabot::Version))
116
+ end
117
+ def fetch_latest_version_with_no_unlock(language_version: nil)
118
+ with_custom_registry_rescue do
119
+ return unless valid_npm_details?
120
+ return version_from_dist_tags if specified_dist_tag_requirement?
121
+
122
+ super
123
+ end
124
+ end
125
+
126
+ sig { override.params(versions: T::Array[Dependabot::Version]).returns(T::Array[Dependabot::Version]) }
127
+ def apply_post_fetch_latest_versions_filter(versions)
128
+ original_count = versions.count
129
+ filtered_versions = lazy_filter_yanked_versions_by_min_max(versions, check_max: true)
130
+
131
+ # Log the filter if any versions were removed
132
+ if original_count > filtered_versions.count
133
+ Dependabot.logger.info(
134
+ "Filtered out #{original_count - filtered_versions.count} " \
135
+ "yanked (not found) versions after fetching latest versions"
136
+ )
137
+ end
138
+
139
+ filtered_versions
140
+ end
141
+
142
+ sig do
143
+ params(
144
+ versions: T::Array[Dependabot::Version],
145
+ check_max: T::Boolean
146
+ ).returns(T::Array[Dependabot::Version])
147
+ end
148
+ def lazy_filter_yanked_versions_by_min_max(versions, check_max: true)
149
+ # Sort the versions based on the check_max flag (max -> descending, min -> ascending)
150
+ sorted_versions = check_max ? versions.sort.reverse : versions.sort
151
+
152
+ filtered_versions = []
153
+
154
+ not_yanked = T.let(false, T::Boolean)
155
+
156
+ # Iterate through the sorted versions lazily, filtering out yanked versions
157
+ sorted_versions.each do |version|
158
+ next if !not_yanked && yanked_version?(version)
159
+
160
+ not_yanked = true
161
+
162
+ # Once we find a valid (non-yanked) version, add it to the filtered list
163
+ filtered_versions << version
164
+ break
165
+ end
166
+
167
+ filtered_versions
168
+ end
169
+
170
+ sig do
171
+ override
172
+ .params(language_version: T.nilable(T.any(String, Dependabot::Version)))
173
+ .returns(T.nilable(Dependabot::Version))
174
+ end
175
+ def fetch_lowest_security_fix_version(language_version:) # rubocop:disable Lint/UnusedMethodArgument
176
+ with_custom_registry_rescue do
177
+ return unless valid_npm_details?
178
+
179
+ secure_versions =
180
+ if specified_dist_tag_requirement?
181
+ [version_from_dist_tags].compact
182
+ else
183
+ possible_versions(filter_ignored: false)
184
+ end
185
+
186
+ secure_versions =
187
+ Dependabot::UpdateCheckers::VersionFilters
188
+ .filter_vulnerable_versions(
189
+ T.unsafe(secure_versions),
190
+ security_advisories
191
+ )
192
+ secure_versions = filter_ignored_versions(secure_versions)
193
+ secure_versions = filter_lower_versions(secure_versions)
194
+
195
+ # Apply lazy filtering for yanked versions (min or max logic)
196
+ secure_versions = lazy_filter_yanked_versions_by_min_max(secure_versions, check_max: false)
197
+
198
+ # Return the lowest non-yanked version
199
+ secure_versions.max
200
+ end
201
+ end
202
+
203
+ sig do
204
+ params(versions_array: T::Array[Dependabot::Version])
205
+ .returns(T::Array[Dependabot::Version])
206
+ end
207
+ def filter_prerelease_versions(versions_array)
208
+ filtered = versions_array.reject do |v|
209
+ v.prerelease? && !related_to_current_pre?(v)
210
+ end
211
+
212
+ if versions_array.count > filtered.count
213
+ Dependabot.logger.info(
214
+ "Filtered out #{versions_array.count - filtered.count} unrelated pre-release versions"
215
+ )
216
+ end
217
+
218
+ filtered
219
+ end
220
+
221
+ sig do
222
+ params(filter_ignored: T::Boolean)
223
+ .returns(T::Array[T::Array[T.untyped]])
224
+ end
225
+ def possible_versions_with_details(filter_ignored: true)
226
+ possible_releases(filter_ignored: filter_ignored).map { |r| [r.version, r.details] }
227
+ end
228
+
229
+ sig do
230
+ params(releases: T::Array[Dependabot::Package::PackageRelease])
231
+ .returns(T::Array[Dependabot::Package::PackageRelease])
232
+ end
233
+ def filter_releases(releases)
234
+ filtered =
235
+ releases
236
+ .reject do |release|
237
+ ignore_requirements.any? { |r| r.satisfied_by?(release.version) }
238
+ end
239
+ if @raise_on_ignored &&
240
+ filter_lower_releases(filtered).empty? &&
241
+ filter_lower_releases(releases).any?
242
+ raise Dependabot::AllVersionsIgnored
243
+ end
244
+
245
+ if releases.count > filtered.count
246
+ Dependabot.logger.info("Filtered out #{releases.count - filtered.count} ignored versions")
247
+ end
248
+ filtered
249
+ end
250
+
251
+ sig do
252
+ params(releases: T::Array[Dependabot::Package::PackageRelease])
253
+ .returns(T::Array[Dependabot::Package::PackageRelease])
254
+ end
255
+ def filter_lower_releases(releases)
256
+ return releases unless dependency.numeric_version
257
+
258
+ releases.select { |release| release.version > dependency.numeric_version }
259
+ end
260
+
261
+ sig do
262
+ params(filter_ignored: T::Boolean)
263
+ .returns(T::Array[Dependabot::Package::PackageRelease])
264
+ end
265
+ def possible_releases(filter_ignored: true)
266
+ releases = possible_previous_releases.reject(&:yanked?)
267
+
268
+ return filter_releases(releases) if filter_ignored
269
+
270
+ releases
271
+ end
272
+
273
+ sig do
274
+ params(filter_ignored: T::Boolean)
275
+ .returns(T::Array[Gem::Version])
276
+ end
277
+ def possible_versions(filter_ignored: true)
278
+ possible_releases(filter_ignored: filter_ignored).map(&:version)
279
+ end
280
+
281
+ sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
282
+ def possible_previous_releases
283
+ (package_details&.releases || [])
284
+ .reject do |r|
285
+ r.version.prerelease? && !related_to_current_pre?(T.unsafe(r.version))
286
+ end
287
+ .sort_by(&:version).reverse
288
+ end
289
+
290
+ sig { returns(T::Array[[Dependabot::Version, T::Hash[String, T.nilable(String)]]]) }
291
+ def possible_previous_versions_with_details
292
+ possible_previous_releases.map do |r|
293
+ [r.version, { "deprecated" => r.yanked? ? "yanked" : nil }]
294
+ end
295
+ end
296
+
297
+ sig { override.returns(T::Boolean) }
298
+ def cooldown_enabled?
299
+ Dependabot::Experiments.enabled?(:enable_cooldown_for_npm_and_yarn)
300
+ end
301
+
302
+ private
303
+
304
+ sig { params(_block: T.untyped).returns(T.nilable(Dependabot::Version)) }
305
+ def with_custom_registry_rescue(&_block)
306
+ yield
307
+ rescue Excon::Error::Socket, Excon::Error::Timeout, RegistryError
308
+ raise unless package_fetcher.custom_registry?
309
+
310
+ # Custom registries can be flaky. We don't want to make that
311
+ # our problem, so quietly return `nil` here.
312
+ nil
313
+ end
314
+
315
+ sig { returns(T::Boolean) }
316
+ def valid_npm_details?
317
+ !!package_details&.releases&.any?
318
+ end
319
+
320
+ sig { returns(T.nilable(Dependabot::Version)) }
321
+ def version_from_dist_tags # rubocop:disable Metrics/PerceivedComplexity
322
+ dist_tags = package_details&.dist_tags
323
+ return nil unless dist_tags
324
+
325
+ dist_tag_req = dependency.requirements
326
+ .find { |r| dist_tags.include?(r[:requirement]) }
327
+ &.fetch(:requirement)
328
+
329
+ releases = package_details&.releases
330
+
331
+ releases = filter_by_cooldown(releases) if releases
332
+
333
+ if dist_tag_req
334
+ release = find_dist_tag_release(dist_tag_req, releases)
335
+ return release.version if release && !release.yanked?
336
+ end
337
+
338
+ latest_release = find_dist_tag_release("latest", releases)
339
+
340
+ return nil unless latest_release
341
+
342
+ return latest_release.version if wants_latest_dist_tag?(latest_release.version) && !latest_release.yanked?
343
+
344
+ nil
345
+ end
346
+
347
+ sig do
348
+ params(
349
+ dist_tag: T.nilable(String),
350
+ releases: T.nilable(T::Array[Dependabot::Package::PackageRelease])
351
+ )
352
+ .returns(T.nilable(Dependabot::Package::PackageRelease))
353
+ end
354
+ def find_dist_tag_release(dist_tag, releases)
355
+ dist_tags = package_details&.dist_tags
356
+ return nil unless releases && dist_tags && dist_tag
357
+
358
+ dist_tag_version = dist_tags[dist_tag]
359
+
360
+ return nil unless dist_tag_version && !dist_tag_version.empty?
361
+
362
+ release = releases.find { |r| r.version == Version.new(dist_tag_version) }
363
+
364
+ release
365
+ end
366
+
367
+ sig { returns(T::Boolean) }
368
+ def specified_dist_tag_requirement?
369
+ dependency.requirements.any? do |req|
370
+ next false if req[:requirement].nil?
371
+ next false unless req[:requirement].match?(/^[A-Za-z]/)
372
+
373
+ !req[:requirement].match?(/^v\d/i)
374
+ end
375
+ end
376
+
377
+ sig do
378
+ params(version: Dependabot::Version)
379
+ .returns(T::Boolean)
380
+ end
381
+ def wants_latest_dist_tag?(version)
382
+ return false if related_to_current_pre?(version) ^ version.prerelease?
383
+ return false if current_version_greater_than?(version)
384
+ return false if current_requirement_greater_than?(version)
385
+ return false if ignore_requirements.any? { |r| r.satisfied_by?(version) }
386
+ return false if yanked_version?(version)
387
+
388
+ true
389
+ end
390
+
391
+ sig { params(version: Dependabot::Version).returns(T::Boolean) }
392
+ def current_requirement_greater_than?(version)
393
+ dependency.requirements.any? do |req|
394
+ next false unless req[:requirement]
395
+
396
+ req_version = req[:requirement].sub(/^\^|~|>=?/, "")
397
+ next false unless version_class.correct?(req_version)
398
+
399
+ version_class.new(req_version) > version
400
+ end
401
+ end
402
+
403
+ sig { params(version: Dependabot::Version).returns(T::Boolean) }
404
+ def related_to_current_pre?(version)
405
+ current_version = dependency.numeric_version
406
+ if current_version&.prerelease? &&
407
+ current_version.release == version.release
408
+ return true
409
+ end
410
+
411
+ dependency.requirements.any? do |req|
412
+ next unless req[:requirement]&.match?(/\d-[A-Za-z]/)
413
+
414
+ NpmAndYarn::Requirement
415
+ .requirements_array(req.fetch(:requirement))
416
+ .any? do |r|
417
+ r.requirements.any? { |a| a.last.release == version.release }
418
+ end
419
+ rescue Gem::Requirement::BadRequirementError
420
+ false
421
+ end
422
+ end
423
+
424
+ sig { params(version: Dependabot::Version).returns(T::Boolean) }
425
+ def current_version_greater_than?(version)
426
+ return false unless dependency.numeric_version
427
+
428
+ T.must(dependency.numeric_version) > version
429
+ end
430
+
431
+ sig { params(version: Dependabot::Version).returns(T::Boolean) }
432
+ def yanked_version?(version)
433
+ package_fetcher.yanked?(version)
434
+ end
435
+ end
436
+
437
+ class LatestVersionFinder # rubocop:disable Metrics/ClassLength
438
+ extend T::Sig
439
+
440
+ sig do
441
+ params(
442
+ dependency: Dependabot::Dependency,
443
+ dependency_files: T::Array[Dependabot::DependencyFile],
444
+ credentials: T::Array[Dependabot::Credential],
445
+ ignored_versions: T::Array[String],
446
+ security_advisories: T::Array[Dependabot::SecurityAdvisory],
447
+ raise_on_ignored: T::Boolean
448
+ ).void
449
+ end
450
+ def initialize(
451
+ dependency:,
452
+ dependency_files:,
453
+ credentials:,
454
+ ignored_versions:,
455
+ security_advisories:,
456
+ raise_on_ignored: false
457
+ )
23
458
  @dependency = dependency
24
459
  @credentials = credentials
25
460
  @dependency_files = dependency_files
26
461
  @ignored_versions = ignored_versions
27
462
  @raise_on_ignored = raise_on_ignored
28
463
  @security_advisories = security_advisories
464
+
465
+ @possible_previous_versions_with_details = T.let(nil, T.nilable(T::Array[T::Array[T.untyped]]))
466
+ @yanked = T.let({}, T::Hash[Version, T.nilable(T::Boolean)])
467
+ @npm_details = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
468
+ @registry_finder = T.let(nil, T.nilable(Package::RegistryFinder))
469
+ @version_endpoint_working = T.let(nil, T.nilable(T::Boolean))
29
470
  end
30
471
 
472
+ sig { returns(T.nilable(Version)) }
31
473
  def latest_version_from_registry
32
474
  return unless valid_npm_details?
33
475
  return version_from_dist_tags if version_from_dist_tags
@@ -40,6 +482,7 @@ module Dependabot
40
482
  # our problem, so we quietly return `nil` here.
41
483
  end
42
484
 
485
+ sig { returns(T.nilable(Version)) }
43
486
  def latest_version_with_no_unlock
44
487
  return unless valid_npm_details?
45
488
  return version_from_dist_tags if specified_dist_tag_requirement?
@@ -52,6 +495,7 @@ module Dependabot
52
495
  # our problem, so we quietly return `nil` here.
53
496
  end
54
497
 
498
+ sig { returns(T.nilable(Version)) }
55
499
  def lowest_security_fix_version
56
500
  return unless valid_npm_details?
57
501
 
@@ -62,8 +506,11 @@ module Dependabot
62
506
  possible_versions(filter_ignored: false)
63
507
  end
64
508
 
65
- secure_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(secure_versions,
66
- security_advisories)
509
+ secure_versions = Dependabot::UpdateCheckers::VersionFilters
510
+ .filter_vulnerable_versions(
511
+ secure_versions,
512
+ security_advisories
513
+ )
67
514
  secure_versions = filter_ignored_versions(secure_versions)
68
515
  secure_versions = filter_lower_versions(secure_versions)
69
516
 
@@ -74,15 +521,23 @@ module Dependabot
74
521
  # our problem, so we quietly return `nil` here.
75
522
  end
76
523
 
77
- def possible_previous_versions_with_details
78
- @possible_previous_versions_with_details ||= npm_details.fetch("versions", {})
79
- .transform_keys { |k| version_class.new(k) }
80
- .reject do |v, _|
81
- v.prerelease? && !related_to_current_pre?(v)
82
- end
83
- .sort_by(&:first).reverse
524
+ sig { returns(T::Array[T::Array[T.untyped]]) }
525
+ def possible_previous_versions_with_details # rubocop:disable Metrics/PerceivedComplexity
526
+ return @possible_previous_versions_with_details if @possible_previous_versions_with_details
527
+
528
+ @possible_previous_versions_with_details =
529
+ npm_details&.fetch("versions", {})
530
+ &.transform_keys { |k| version_class.new(k) }
531
+ &.reject do |v, _|
532
+ v.prerelease? && !related_to_current_pre?(v)
533
+ end&.sort_by(&:first)&.reverse
534
+ @possible_previous_versions_with_details
84
535
  end
85
536
 
537
+ sig do
538
+ params(filter_ignored: T::Boolean)
539
+ .returns(T::Array[T::Array[T.untyped]])
540
+ end
86
541
  def possible_versions_with_details(filter_ignored: true)
87
542
  versions = possible_previous_versions_with_details
88
543
  .reject { |_, details| details["deprecated"] }
@@ -92,6 +547,10 @@ module Dependabot
92
547
  versions
93
548
  end
94
549
 
550
+ sig do
551
+ params(filter_ignored: T::Boolean)
552
+ .returns(T::Array[Version])
553
+ end
95
554
  def possible_versions(filter_ignored: true)
96
555
  possible_versions_with_details(filter_ignored: filter_ignored)
97
556
  .map(&:first)
@@ -99,12 +558,18 @@ module Dependabot
99
558
 
100
559
  private
101
560
 
561
+ sig { returns(Dependabot::Dependency) }
102
562
  attr_reader :dependency
563
+ sig { returns(T::Array[Dependabot::Credential]) }
103
564
  attr_reader :credentials
565
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
104
566
  attr_reader :dependency_files
567
+ sig { returns(T::Array[String]) }
105
568
  attr_reader :ignored_versions
569
+ sig { returns(T::Array[Dependabot::SecurityAdvisory]) }
106
570
  attr_reader :security_advisories
107
571
 
572
+ sig { returns(T::Boolean) }
108
573
  def valid_npm_details?
109
574
  !npm_details&.fetch("dist-tags", nil).nil?
110
575
  end
@@ -145,8 +610,13 @@ module Dependabot
145
610
  .select { |version, _| version > dependency.numeric_version }
146
611
  end
147
612
 
613
+ sig { returns(T.nilable(Version)) }
148
614
  def version_from_dist_tags
149
- dist_tags = npm_details["dist-tags"].keys
615
+ details = npm_details
616
+
617
+ return nil unless details
618
+
619
+ dist_tags = details["dist-tags"].keys
150
620
 
151
621
  # Check if a dist tag was specified as a requirement. If it was, and
152
622
  # it exists, use it.
@@ -156,22 +626,23 @@ module Dependabot
156
626
 
157
627
  if dist_tag_req
158
628
  tag_vers =
159
- version_class.new(npm_details["dist-tags"][dist_tag_req])
629
+ version_class.new(details["dist-tags"][dist_tag_req])
160
630
  return tag_vers unless yanked?(tag_vers)
161
631
  end
162
632
 
163
633
  # Use the latest dist tag unless there's a reason not to
164
- return nil unless npm_details["dist-tags"]["latest"]
634
+ return nil unless details["dist-tags"]["latest"]
165
635
 
166
- latest = version_class.new(npm_details["dist-tags"]["latest"])
636
+ latest = version_class.new(details["dist-tags"]["latest"])
167
637
 
168
638
  wants_latest_dist_tag?(latest) ? latest : nil
169
639
  end
170
640
 
641
+ sig { params(version: Version).returns(T::Boolean) }
171
642
  def related_to_current_pre?(version)
172
643
  current_version = dependency.numeric_version
173
644
  if current_version&.prerelease? &&
174
- current_version&.release == version.release
645
+ current_version.release == version.release
175
646
  return true
176
647
  end
177
648
 
@@ -188,6 +659,7 @@ module Dependabot
188
659
  end
189
660
  end
190
661
 
662
+ sig { returns(T::Boolean) }
191
663
  def specified_dist_tag_requirement?
192
664
  dependency.requirements.any? do |req|
193
665
  next false if req[:requirement].nil?
@@ -197,6 +669,7 @@ module Dependabot
197
669
  end
198
670
  end
199
671
 
672
+ sig { params(latest_version: Version).returns(T::Boolean) }
200
673
  def wants_latest_dist_tag?(latest_version)
201
674
  ver = latest_version
202
675
  return false if related_to_current_pre?(ver) ^ ver.prerelease?
@@ -208,12 +681,14 @@ module Dependabot
208
681
  true
209
682
  end
210
683
 
684
+ sig { params(version: Version).returns(T::Boolean) }
211
685
  def current_version_greater_than?(version)
212
686
  return false unless dependency.numeric_version
213
687
 
214
- dependency.numeric_version > version
688
+ T.must(dependency.numeric_version) > version
215
689
  end
216
690
 
691
+ sig { params(version: Version).returns(T::Boolean) }
217
692
  def current_requirement_greater_than?(version)
218
693
  dependency.requirements.any? do |req|
219
694
  next false unless req[:requirement]
@@ -225,9 +700,9 @@ module Dependabot
225
700
  end
226
701
  end
227
702
 
703
+ sig { params(version: Version).returns(T::Boolean) }
228
704
  def yanked?(version)
229
- @yanked ||= {}
230
- return @yanked[version] if @yanked.key?(version)
705
+ return @yanked[version] || false if @yanked.key?(version)
231
706
 
232
707
  @yanked[version] =
233
708
  begin
@@ -257,12 +732,15 @@ module Dependabot
257
732
  # Give the benefit of the doubt if the registry is playing up
258
733
  false
259
734
  end
735
+
736
+ @yanked[version] || false
260
737
  end
261
738
 
739
+ sig { returns(T.nilable(T::Boolean)) }
262
740
  def version_endpoint_working?
263
741
  return true if dependency_registry == "registry.npmjs.org"
264
742
 
265
- return @version_endpoint_working if defined?(@version_endpoint_working)
743
+ return @version_endpoint_working if @version_endpoint_working
266
744
 
267
745
  @version_endpoint_working =
268
746
  begin
@@ -274,17 +752,22 @@ module Dependabot
274
752
  # Give the benefit of the doubt if the registry is playing up
275
753
  true
276
754
  end
755
+ @version_endpoint_working
277
756
  end
278
757
 
758
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
279
759
  def npm_details
280
- return @npm_details if defined?(@npm_details)
760
+ return @npm_details if @npm_details
281
761
 
282
762
  @npm_details = fetch_npm_details
283
763
  end
284
764
 
765
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
285
766
  def fetch_npm_details
286
767
  npm_response = fetch_npm_response
287
768
 
769
+ return nil unless npm_response
770
+
288
771
  check_npm_response(npm_response)
289
772
  JSON.parse(npm_response.body)
290
773
  rescue JSON::ParserError,
@@ -298,6 +781,7 @@ module Dependabot
298
781
  end
299
782
  end
300
783
 
784
+ sig { returns(T.nilable(Excon::Response)) }
301
785
  def fetch_npm_response
302
786
  response = Dependabot::RegistryClient.get(
303
787
  url: dependency_url,
@@ -307,7 +791,7 @@ module Dependabot
307
791
  return response unless registry_auth_headers["Authorization"]
308
792
 
309
793
  auth = registry_auth_headers["Authorization"]
310
- return response unless auth.start_with?("Basic")
794
+ return response unless auth&.start_with?("Basic")
311
795
 
312
796
  decoded_token = Base64.decode64(auth.gsub("Basic ", ""))
313
797
  return unless decoded_token.include?(":")
@@ -324,6 +808,7 @@ module Dependabot
324
808
  raise DependencyFileNotResolvable, e.message
325
809
  end
326
810
 
811
+ sig { params(npm_response: Excon::Response).void }
327
812
  def check_npm_response(npm_response)
328
813
  return if git_dependency?
329
814
 
@@ -357,6 +842,7 @@ module Dependabot
357
842
  raise RegistryError.new(status, msg)
358
843
  end
359
844
 
845
+ sig { params(error: Exception).void }
360
846
  def raise_npm_details_error(error)
361
847
  raise if dependency_registry == "registry.npmjs.org"
362
848
  raise unless error.is_a?(Excon::Error::Timeout)
@@ -364,6 +850,7 @@ module Dependabot
364
850
  raise PrivateSourceTimedOut, dependency_registry
365
851
  end
366
852
 
853
+ sig { params(npm_response: Excon::Response).returns(T::Boolean) }
367
854
  def private_dependency_not_reachable?(npm_response)
368
855
  return true if npm_response.body.start_with?(/user ".*?" is not a /)
369
856
  return false unless [401, 402, 403, 404].include?(npm_response.status)
@@ -381,6 +868,7 @@ module Dependabot
381
868
  true
382
869
  end
383
870
 
871
+ sig { params(npm_response: Excon::Response).returns(T::Boolean) }
384
872
  def private_dependency_server_error?(npm_response)
385
873
  if [500, 501, 502, 503].include?(npm_response.status)
386
874
  Dependabot.logger.warn("#{dependency_registry} returned code #{npm_response.status} with " \
@@ -390,6 +878,7 @@ module Dependabot
390
878
  false
391
879
  end
392
880
 
881
+ sig { params(npm_response: Excon::Response).returns(T::Boolean) }
393
882
  def response_invalid_json?(npm_response)
394
883
  result = JSON.parse(npm_response.body)
395
884
  result.is_a?(Hash) || result.is_a?(Array)
@@ -398,53 +887,66 @@ module Dependabot
398
887
  true
399
888
  end
400
889
 
890
+ sig { returns(String) }
401
891
  def dependency_url
402
892
  registry_finder.dependency_url
403
893
  end
404
894
 
895
+ sig { returns(String) }
405
896
  def dependency_registry
406
897
  registry_finder.registry
407
898
  end
408
899
 
900
+ sig { returns(T::Hash[String, String]) }
409
901
  def registry_auth_headers
410
902
  registry_finder.auth_headers
411
903
  end
412
904
 
905
+ sig { returns(Package::RegistryFinder) }
413
906
  def registry_finder
414
- @registry_finder ||= RegistryFinder.new(
907
+ return @registry_finder if @registry_finder
908
+
909
+ @registry_finder = Package::RegistryFinder.new(
415
910
  dependency: dependency,
416
911
  credentials: credentials,
417
912
  npmrc_file: npmrc_file,
418
913
  yarnrc_file: yarnrc_file,
419
914
  yarnrc_yml_file: yarnrc_yml_file
420
915
  )
916
+ @registry_finder
421
917
  end
422
918
 
919
+ sig { returns(T::Array[Dependabot::Requirement]) }
423
920
  def ignore_requirements
424
921
  ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
425
922
  end
426
923
 
924
+ sig { returns(T.class_of(Version)) }
427
925
  def version_class
428
- dependency.version_class
926
+ Dependabot::NpmAndYarn::Version
429
927
  end
430
928
 
929
+ sig { returns(T.class_of(Requirement)) }
431
930
  def requirement_class
432
- dependency.requirement_class
931
+ Dependabot::NpmAndYarn::Requirement
433
932
  end
434
933
 
934
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
435
935
  def npmrc_file
436
936
  dependency_files.find { |f| f.name.end_with?(".npmrc") }
437
937
  end
438
938
 
939
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
439
940
  def yarnrc_file
440
941
  dependency_files.find { |f| f.name.end_with?(".yarnrc") }
441
942
  end
442
943
 
944
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
443
945
  def yarnrc_yml_file
444
946
  dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }
445
947
  end
446
948
 
447
- # TODO: Remove need for me
949
+ sig { returns(T::Boolean) }
448
950
  def git_dependency?
449
951
  # ignored_version/raise_on_ignored are irrelevant.
450
952
  GitCommitChecker.new(