dependabot-npm_and_yarn 0.303.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.
- checksums.yaml +4 -4
- data/lib/dependabot/npm_and_yarn/package/package_details_fetcher.rb +161 -36
- data/lib/dependabot/npm_and_yarn/package/registry_finder.rb +122 -33
- data/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb +531 -29
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +270 -61
- data/lib/dependabot/npm_and_yarn/update_checker.rb +134 -21
- metadata +5 -5
@@ -1,10 +1,12 @@
|
|
1
|
-
# typed:
|
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
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
|
19
|
+
class PackageLatestVersionFinder < Dependabot::Package::PackageLatestVersionFinder
|
18
20
|
extend T::Sig
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
66
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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
|
-
|
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(
|
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
|
634
|
+
return nil unless details["dist-tags"]["latest"]
|
165
635
|
|
166
|
-
latest = version_class.new(
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
926
|
+
Dependabot::NpmAndYarn::Version
|
429
927
|
end
|
430
928
|
|
929
|
+
sig { returns(T.class_of(Requirement)) }
|
431
930
|
def requirement_class
|
432
|
-
|
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
|
-
|
949
|
+
sig { returns(T::Boolean) }
|
448
950
|
def git_dependency?
|
449
951
|
# ignored_version/raise_on_ignored are irrelevant.
|
450
952
|
GitCommitChecker.new(
|