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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09eb2aa26710f8625ed217129433fea3e65bf1a57e20af246b5fcb0e0042545f'
4
- data.tar.gz: c7043f64922f0014183a33435f85106e753ff348a9b6ae0feab26774d5e13444
3
+ metadata.gz: 03ceb27ff40ce2354675588a932c77c0aba9957d7ada32b82c73edcd4566c470
4
+ data.tar.gz: 9670f9f1102fe62f0b2d838aa359300040f486b93a181f768bb96869f514b15e
5
5
  SHA512:
6
- metadata.gz: 0a9d15e4ebbc11d8ea146eedb4b1ef9cf5ab5ecae3d68e0fe4102427df39abb910cb328ba5cb73497117264f5e36fc768d2bad686f7c69e122c565035bb971ba
7
- data.tar.gz: 70b005db601e0871f213eb0ced05b2932796e3673201cf5b4d2f1d9a2682b7c9bbe5b314997efe6515fb441a75ede6d7ac21d969fef130666f68c3f21bfc370b
6
+ metadata.gz: c1891b008fe1683848ee2cbc5e0d2b28276e88e6cf728a5c5233bbf1f13b6dfc1c01ea48c575297e26a9ce9b0e89dbe3f98bb85055fcb4f17c6c31583363c4cb
7
+ data.tar.gz: e4e953398df4515cc1f81a894a4d24173e3a453f482ee3b33cc12eedd76bf7160131704ddf25f16bc9bff6ee27da15d813905c10dc93505879d46211aaf37b57
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/npm_and_yarn/helpers"
5
- require "dependabot/npm_and_yarn/update_checker/registry_finder"
5
+ require "dependabot/npm_and_yarn/package/registry_finder"
6
6
  require "dependabot/npm_and_yarn/registry_parser"
7
7
  require "dependabot/shared_helpers"
8
8
 
@@ -10,7 +10,7 @@ require "dependabot/npm_and_yarn/file_parser"
10
10
  require "dependabot/npm_and_yarn/file_updater"
11
11
  require "dependabot/npm_and_yarn/helpers"
12
12
  require "dependabot/npm_and_yarn/native_helpers"
13
- require "dependabot/npm_and_yarn/update_checker/registry_finder"
13
+ require "dependabot/npm_and_yarn/package/registry_finder"
14
14
  require "dependabot/shared_helpers"
15
15
 
16
16
  # rubocop:disable Metrics/ClassLength
@@ -669,7 +669,7 @@ module Dependabot
669
669
 
670
670
  raise_resolvability_error(error_message) unless missing_dep
671
671
 
672
- reg = NpmAndYarn::UpdateChecker::RegistryFinder.new(
672
+ reg = Package::RegistryFinder.new(
673
673
  dependency: missing_dep,
674
674
  credentials: credentials,
675
675
  npmrc_file: dependency_files. find { |f| f.name.end_with?(".npmrc") },
@@ -677,7 +677,7 @@ module Dependabot
677
677
  yarnrc_yml_file: dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }
678
678
  ).registry
679
679
 
680
- return if UpdateChecker::RegistryFinder.central_registry?(reg) && !package_name.start_with?("@")
680
+ return if Package::RegistryFinder.central_registry?(reg) && !package_name.start_with?("@")
681
681
 
682
682
  raise Dependabot::PrivateSourceAuthenticationFailure, reg
683
683
  end
@@ -10,7 +10,7 @@ module Dependabot
10
10
  class FileUpdater < Dependabot::FileUpdaters::Base
11
11
  # Build a .npmrc file from the lockfile content, credentials, and any
12
12
  # committed .npmrc
13
- # We should refactor this to use UpdateChecker::RegistryFinder
13
+ # We should refactor this to use Package::RegistryFinder
14
14
  class NpmrcBuilder
15
15
  extend T::Sig
16
16
 
@@ -176,7 +176,7 @@ module Dependabot
176
176
 
177
177
  if dependencies.any?
178
178
  @dependency_urls = dependencies.map do |dependency|
179
- UpdateChecker::RegistryFinder.new(
179
+ Package::RegistryFinder.new(
180
180
  dependency: dependency,
181
181
  credentials: credentials,
182
182
  npmrc_file: npmrc_file,
@@ -249,7 +249,7 @@ module Dependabot
249
249
  yarnrc_file&.content
250
250
  &.lines
251
251
  &.find { |line| line.match?(/^\s*registry\s/) }
252
- &.match(NpmAndYarn::UpdateChecker::RegistryFinder::YARN_GLOBAL_REGISTRY_REGEX)
252
+ &.match(Package::RegistryFinder::YARN_GLOBAL_REGISTRY_REGEX)
253
253
  &.named_captures&.fetch("registry")
254
254
 
255
255
  return "registry = #{yarnrc_global_registry}\n" if yarnrc_global_registry
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/npm_and_yarn/helpers"
5
- require "dependabot/npm_and_yarn/update_checker/registry_finder"
5
+ require "dependabot/npm_and_yarn/package/registry_finder"
6
6
  require "dependabot/npm_and_yarn/registry_parser"
7
7
  require "dependabot/shared_helpers"
8
8
 
@@ -309,7 +309,7 @@ module Dependabot
309
309
  .find { |dep| dep.name == package_name }
310
310
  raise DependencyNotFound, package_name unless missing_dep
311
311
 
312
- reg = NpmAndYarn::UpdateChecker::RegistryFinder.new(
312
+ reg = Package::RegistryFinder.new(
313
313
  dependency: missing_dep,
314
314
  credentials: credentials,
315
315
  npmrc_file: npmrc_file
@@ -2,7 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "dependabot/npm_and_yarn/helpers"
5
- require "dependabot/npm_and_yarn/update_checker/registry_finder"
5
+ require "dependabot/npm_and_yarn/package/registry_finder"
6
6
  require "dependabot/npm_and_yarn/registry_parser"
7
7
  require "dependabot/shared_helpers"
8
8
 
@@ -7,7 +7,7 @@ require "dependabot/npm_and_yarn"
7
7
  require "dependabot/npm_and_yarn/file_updater"
8
8
  require "dependabot/npm_and_yarn/file_parser"
9
9
  require "dependabot/npm_and_yarn/helpers"
10
- require "dependabot/npm_and_yarn/update_checker/registry_finder"
10
+ require "dependabot/npm_and_yarn/package/registry_finder"
11
11
  require "dependabot/npm_and_yarn/native_helpers"
12
12
  require "dependabot/shared_helpers"
13
13
  require "dependabot/errors"
@@ -429,7 +429,7 @@ module Dependabot
429
429
 
430
430
  error_handler.raise_resolvability_error(error_message, yarn_lock) unless missing_dep
431
431
 
432
- reg = NpmAndYarn::UpdateChecker::RegistryFinder.new(
432
+ reg = Package::RegistryFinder.new(
433
433
  dependency: missing_dep,
434
434
  credentials: credentials,
435
435
  npmrc_file: npmrc_file,
@@ -437,7 +437,7 @@ module Dependabot
437
437
  yarnrc_yml_file: yarnrc_yml_file
438
438
  ).registry
439
439
 
440
- return if UpdateChecker::RegistryFinder.central_registry?(reg) && !package_name.start_with?("@")
440
+ return if Package::RegistryFinder.central_registry?(reg) && !package_name.start_with?("@")
441
441
 
442
442
  raise PrivateSourceAuthenticationFailure, reg
443
443
  end
@@ -489,7 +489,7 @@ module Dependabot
489
489
  def yarnrc_specifies_private_reg?
490
490
  return false unless yarnrc_file
491
491
 
492
- regex = UpdateChecker::RegistryFinder::YARN_GLOBAL_REGISTRY_REGEX
492
+ regex = Package::RegistryFinder::YARN_GLOBAL_REGISTRY_REGEX
493
493
  yarnrc_global_registry =
494
494
  yarnrc_file.content
495
495
  .lines.find { |line| line.match?(regex) }
@@ -499,7 +499,7 @@ module Dependabot
499
499
 
500
500
  return false unless yarnrc_global_registry
501
501
 
502
- UpdateChecker::RegistryFinder::CENTRAL_REGISTRIES.any? do |r|
502
+ Package::RegistryFinder::CENTRAL_REGISTRIES.any? do |r|
503
503
  r.include?(T.must(URI(yarnrc_global_registry).host))
504
504
  end
505
505
  end
@@ -7,7 +7,7 @@ require "time"
7
7
  require "dependabot/metadata_finders"
8
8
  require "dependabot/metadata_finders/base"
9
9
  require "dependabot/registry_client"
10
- require "dependabot/npm_and_yarn/update_checker/registry_finder"
10
+ require "dependabot/npm_and_yarn/package/registry_finder"
11
11
  require "dependabot/npm_and_yarn/version"
12
12
 
13
13
  module Dependabot
@@ -95,7 +95,7 @@ module Dependabot
95
95
  def new_source
96
96
  sources = dependency.requirements
97
97
  .map { |r| r.fetch(:source) }.uniq.compact
98
- .sort_by { |source| UpdateChecker::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 }
98
+ .sort_by { |source| Package::RegistryFinder.central_registry?(source[:url]) ? 1 : 0 }
99
99
 
100
100
  sources.first
101
101
  end
@@ -0,0 +1,440 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "json"
5
+ require "excon"
6
+ require "time"
7
+ require "dependabot/package/package_release"
8
+ require "dependabot/package/package_details"
9
+ require "dependabot/npm_and_yarn/package/registry_finder"
10
+
11
+ module Dependabot
12
+ module NpmAndYarn
13
+ module Package
14
+ class PackageDetailsFetcher
15
+ extend T::Sig
16
+
17
+ GLOBAL_REGISTRY = "registry.npmjs.org"
18
+ NPM_OFFICIAL_WEBSITE = "https://www.npmjs.com"
19
+
20
+ API_AUTHORIZATION_KEY = "Authorization"
21
+ API_AUTHORIZATION_VALUE_BASIC_PREFIX = "Basic"
22
+ API_RESPONSE_STATUS_SUCCESS_PREFIX = "2"
23
+
24
+ RELEASE_TIME_KEY = "time"
25
+ RELEASE_VERSIONS_KEY = "versions"
26
+ RELEASE_DIST_TAGS_KEY = "dist-tags"
27
+ RELEASE_DIST_TAGS_LATEST_KEY = "latest"
28
+ RELEASE_ENGINES_KEY = "engines"
29
+ RELEASE_LANGUAGE_KEY = "node"
30
+ RELEASE_DEPRECATION_KEY = "deprecated"
31
+ RELEASE_REPOSITORY_KEY = "repository"
32
+ RELEASE_PACKAGE_TYPE_KEY = "type"
33
+ RELEASE_PACKAGE_TYPE_GIT = "git"
34
+ RELEASE_PACKAGE_TYPE_NPM = "npm"
35
+
36
+ REGISTRY_FILE_NPMRC = ".npmrc"
37
+ REGISTRY_FILE_YARNRC = ".yarnrc"
38
+ REGISTRY_FILE_YARNRC_YML = ".yarnrc.yml"
39
+
40
+ sig do
41
+ params(
42
+ dependency: Dependabot::Dependency,
43
+ dependency_files: T::Array[Dependabot::DependencyFile],
44
+ credentials: T::Array[Dependabot::Credential]
45
+ ).void
46
+ end
47
+ def initialize(
48
+ dependency:,
49
+ dependency_files:,
50
+ credentials:
51
+ )
52
+ @dependency = T.let(dependency, Dependabot::Dependency)
53
+ @dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
54
+ @credentials = T.let(credentials, T::Array[Dependabot::Credential])
55
+
56
+ @npm_details = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
57
+ @dist_tags = T.let(nil, T.nilable(T::Hash[String, String]))
58
+ @registry_finder = T.let(nil, T.nilable(Package::RegistryFinder))
59
+ @version_endpoint_working = T.let(nil, T.nilable(T::Boolean))
60
+ @yanked = T.let({}, T::Hash[Gem::Version, T.nilable(T::Boolean)])
61
+ end
62
+
63
+ sig { returns(Dependabot::Dependency) }
64
+ attr_reader :dependency
65
+
66
+ sig { returns(T::Array[Dependabot::Credential]) }
67
+ attr_reader :credentials
68
+
69
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
70
+ attr_reader :dependency_files
71
+
72
+ sig { returns(T.nilable(Dependabot::Package::PackageDetails)) }
73
+ def fetch
74
+ package_data = npm_details
75
+ Dependabot::Package::PackageDetails.new(
76
+ dependency: @dependency,
77
+ releases: package_data ? parse_versions(package_data) : [],
78
+ dist_tags: dist_tags
79
+ )
80
+ end
81
+
82
+ sig { returns(T::Boolean) }
83
+ def valid_npm_details?
84
+ !dist_tags.nil?
85
+ end
86
+
87
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
88
+ def npm_details
89
+ @npm_details ||= fetch_npm_details
90
+ end
91
+
92
+ sig { returns(T::Boolean) }
93
+ def custom_registry?
94
+ registry_finder.custom_registry?
95
+ end
96
+
97
+ sig { returns(String) }
98
+ def dependency_url
99
+ registry_finder.dependency_url
100
+ end
101
+
102
+ sig { params(version: Gem::Version).returns(T::Boolean) }
103
+ def yanked?(version)
104
+ return @yanked[version] || false if @yanked.key?(version)
105
+
106
+ @yanked[version] =
107
+ begin
108
+ if dependency_registry == GLOBAL_REGISTRY
109
+ status = Dependabot::RegistryClient.head(
110
+ url: registry_finder.tarball_url(version),
111
+ headers: registry_auth_headers
112
+ ).status
113
+ else
114
+ status = Dependabot::RegistryClient.get(
115
+ url: dependency_url + "/#{version}",
116
+ headers: registry_auth_headers
117
+ ).status
118
+
119
+ if status == 404
120
+ # Some registries don't handle escaped package names properly
121
+ status = Dependabot::RegistryClient.get(
122
+ url: dependency_url.gsub("%2F", "/") + "/#{version}",
123
+ headers: registry_auth_headers
124
+ ).status
125
+ end
126
+ end
127
+
128
+ version_not_found = status == 404
129
+ version_not_found && version_endpoint_working?
130
+ rescue Excon::Error::Timeout, Excon::Error::Socket
131
+ # Give the benefit of the doubt if the registry is playing up
132
+ false
133
+ end
134
+
135
+ @yanked[version] || false
136
+ end
137
+
138
+ private
139
+
140
+ sig { returns(T.nilable(T::Boolean)) }
141
+ def version_endpoint_working?
142
+ return true if dependency_registry == GLOBAL_REGISTRY
143
+
144
+ return @version_endpoint_working if @version_endpoint_working
145
+
146
+ @version_endpoint_working =
147
+ begin
148
+ Dependabot::RegistryClient.get(
149
+ url: dependency_url + "/#{RELEASE_DIST_TAGS_LATEST_KEY}",
150
+ headers: registry_auth_headers
151
+ ).status < 400
152
+ rescue Excon::Error::Timeout, Excon::Error::Socket
153
+ # Give the benefit of the doubt if the registry is playing up
154
+ true
155
+ end
156
+ @version_endpoint_working
157
+ end
158
+
159
+ sig do
160
+ params(
161
+ npm_data: T::Hash[String, T.untyped]
162
+ ).returns(T::Array[Dependabot::Package::PackageRelease])
163
+ end
164
+ def parse_versions(npm_data)
165
+ time_data = fetch_value_from_hash(npm_data, RELEASE_TIME_KEY) || {}
166
+ versions_data = fetch_value_from_hash(npm_data, RELEASE_VERSIONS_KEY) || {}
167
+
168
+ dist_tags = fetch_value_from_hash(npm_data, RELEASE_DIST_TAGS_KEY)
169
+ latest_version = fetch_value_from_hash(dist_tags, RELEASE_DIST_TAGS_LATEST_KEY)
170
+
171
+ versions_data.filter_map do |version, details|
172
+ next unless Dependabot::NpmAndYarn::Version.correct?(version)
173
+
174
+ package_type = infer_package_type(details)
175
+
176
+ deprecated = fetch_value_from_hash(details, RELEASE_DEPRECATION_KEY)
177
+
178
+ Dependabot::Package::PackageRelease.new(
179
+ version: Version.new(version),
180
+ released_at: time_data[version] ? Time.parse(time_data[version]) : nil,
181
+ yanked: deprecated ? true : false,
182
+ yanked_reason: deprecated.is_a?(String) ? deprecated : nil,
183
+ downloads: nil,
184
+ latest: latest_version.to_s == version,
185
+ url: package_version_url(version),
186
+ package_type: package_type,
187
+ language: package_language(details),
188
+ details: details
189
+ )
190
+ end.sort_by(&:version).reverse
191
+ end
192
+
193
+ sig { params(version: String).returns(String) }
194
+ def package_version_url(version)
195
+ "#{dependency_registry}/#{@dependency.name}/v/#{version}"
196
+ end
197
+
198
+ sig do
199
+ params(version_details: T::Hash[String, T.untyped])
200
+ .returns(T.nilable(Dependabot::Package::PackageLanguage))
201
+ end
202
+ def package_language(version_details)
203
+ # Fetch the engines hash from the version details
204
+ engines = version_details.is_a?(Hash) ? version_details[RELEASE_ENGINES_KEY] : nil
205
+ # Check if engines is a hash and fetch the node requirement
206
+ node_requirement = engines.is_a?(Hash) ? engines.fetch(RELEASE_LANGUAGE_KEY, nil) : nil
207
+
208
+ return nil unless node_requirement
209
+
210
+ if node_requirement
211
+ Dependabot::Package::PackageLanguage.new(
212
+ name: RELEASE_LANGUAGE_KEY,
213
+ version: nil,
214
+ requirement: Requirement.new(node_requirement)
215
+ )
216
+ end
217
+ rescue Gem::Requirement::BadRequirementError
218
+ nil
219
+ end
220
+
221
+ sig { returns(T.nilable(T::Hash[String, String])) }
222
+ def dist_tags
223
+ @dist_tags ||= fetch_value_from_hash(npm_details, RELEASE_DIST_TAGS_KEY)
224
+ end
225
+
226
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
227
+ def fetch_npm_details
228
+ npm_response = fetch_npm_response
229
+ check_npm_response(npm_response) if npm_response
230
+ JSON.parse(npm_response.body)
231
+ rescue JSON::ParserError, Excon::Error::Timeout, Excon::Error::Socket, RegistryError => e
232
+ if git_dependency?
233
+ nil
234
+ else
235
+ raise_npm_details_error(e)
236
+ end
237
+ end
238
+
239
+ sig { returns(Excon::Response) }
240
+ def fetch_npm_response
241
+ response = Dependabot::RegistryClient.get(
242
+ url: dependency_url,
243
+ headers: registry_auth_headers
244
+ )
245
+
246
+ # If response is successful, return it
247
+ return response if response.status.to_s.start_with?(API_RESPONSE_STATUS_SUCCESS_PREFIX)
248
+
249
+ # If the registry is public (not explicitly private) and the request fails, return the response as is
250
+ return response if dependency_registry == GLOBAL_REGISTRY
251
+
252
+ # If a private registry returns a 500 error, check authentication
253
+ return response unless response.status == 500
254
+
255
+ auth = fetch_value_from_hash(registry_auth_headers, API_AUTHORIZATION_KEY)
256
+ return response unless auth
257
+
258
+ return response unless auth&.start_with?(API_AUTHORIZATION_VALUE_BASIC_PREFIX)
259
+
260
+ decoded_token = Base64.decode64(auth.gsub("#{API_AUTHORIZATION_VALUE_BASIC_PREFIX} ", "")).strip
261
+
262
+ # Ensure decoded token is not empty and contains a colon
263
+ if decoded_token.empty? || !decoded_token.include?(":")
264
+ raise PrivateSourceAuthenticationFailure, "Malformed basic auth credentials for #{dependency_registry}"
265
+ end
266
+
267
+ username, password = decoded_token.split(":")
268
+
269
+ Dependabot::RegistryClient.get(
270
+ url: dependency_url,
271
+ options: {
272
+ user: username,
273
+ password: password
274
+ }
275
+ )
276
+ rescue URI::InvalidURIError => e
277
+ raise DependencyFileNotResolvable, e.message
278
+ end
279
+
280
+ sig do
281
+ params(
282
+ details: T::Hash[String, T.untyped],
283
+ git_dependency: T::Boolean
284
+ )
285
+ .returns(String)
286
+ end
287
+ def infer_package_type(details, git_dependency: false)
288
+ return RELEASE_PACKAGE_TYPE_GIT if git_dependency
289
+
290
+ repository = fetch_value_from_hash(details, RELEASE_REPOSITORY_KEY)
291
+
292
+ case repository
293
+ when String
294
+ return repository.start_with?("git+") ? RELEASE_PACKAGE_TYPE_GIT : RELEASE_PACKAGE_TYPE_NPM
295
+ when Hash
296
+ type = fetch_value_from_hash(repository, RELEASE_PACKAGE_TYPE_KEY)
297
+ return RELEASE_PACKAGE_TYPE_GIT if type == RELEASE_PACKAGE_TYPE_GIT
298
+ end
299
+
300
+ RELEASE_PACKAGE_TYPE_NPM
301
+ end
302
+
303
+ sig { params(npm_response: Excon::Response).void }
304
+ def check_npm_response(npm_response)
305
+ return if git_dependency?
306
+
307
+ if private_dependency_not_reachable?(npm_response)
308
+ raise PrivateSourceAuthenticationFailure, dependency_registry
309
+ end
310
+
311
+ # handles scenario when private registry returns a server error 5xx
312
+ if private_dependency_server_error?(npm_response)
313
+ msg = "Server error #{npm_response.status} returned while accessing registry" \
314
+ " #{dependency_registry}."
315
+ raise DependencyFileNotResolvable, msg
316
+ end
317
+
318
+ status = npm_response.status
319
+
320
+ # handles issue when status 200 is returned from registry but with an invalid JSON object
321
+ if status.to_s.start_with?(API_RESPONSE_STATUS_SUCCESS_PREFIX) && response_invalid_json?(npm_response)
322
+ msg = "Invalid JSON object returned from registry #{dependency_registry}."
323
+ Dependabot.logger.warn("#{msg} Response body (truncated) : #{npm_response.body[0..500]}...")
324
+ raise DependencyFileNotResolvable, msg
325
+ end
326
+
327
+ return if status.to_s.start_with?(API_RESPONSE_STATUS_SUCCESS_PREFIX)
328
+
329
+ # Ignore 404s from the registry for updates where a lockfile doesn't
330
+ # need to be generated. The 404 won't cause problems later.
331
+ return if status == 404 && dependency.version.nil?
332
+
333
+ msg = "Got #{status} response with body #{npm_response.body}"
334
+ raise RegistryError.new(status, msg)
335
+ end
336
+
337
+ sig { params(error: StandardError).void }
338
+ def raise_npm_details_error(error)
339
+ raise if dependency_registry == GLOBAL_REGISTRY
340
+ raise unless error.is_a?(Excon::Error::Timeout)
341
+
342
+ raise PrivateSourceTimedOut, dependency_registry
343
+ end
344
+
345
+ sig { params(npm_response: Excon::Response).returns(T::Boolean) }
346
+ def private_dependency_not_reachable?(npm_response)
347
+ return true if npm_response.body.start_with?(/user ".*?" is not a /)
348
+ return false unless [401, 402, 403, 404].include?(npm_response.status)
349
+
350
+ # Check whether this dependency is (likely to be) private
351
+ if dependency_registry == GLOBAL_REGISTRY
352
+ return false unless dependency.name.start_with?("@")
353
+
354
+ web_response = Dependabot::RegistryClient.get(url: "#{NPM_OFFICIAL_WEBSITE}/package/#{dependency.name}")
355
+ # NOTE: returns 429 when the login page is rate limited
356
+ return web_response.body.include?("Forgot password?") ||
357
+ web_response.status == 429
358
+ end
359
+
360
+ true
361
+ end
362
+
363
+ sig { params(npm_response: Excon::Response).returns(T::Boolean) }
364
+ def private_dependency_server_error?(npm_response)
365
+ if [500, 501, 502, 503].include?(npm_response.status)
366
+ Dependabot.logger.warn(
367
+ "#{dependency_registry} returned code #{npm_response.status} " \
368
+ "with body #{npm_response.body}."
369
+ )
370
+ return true
371
+ end
372
+ false
373
+ end
374
+
375
+ sig { params(npm_response: Excon::Response).returns(T::Boolean) }
376
+ def response_invalid_json?(npm_response)
377
+ result = JSON.parse(npm_response.body)
378
+ result.is_a?(Hash) || result.is_a?(Array)
379
+ false
380
+ rescue JSON::ParserError, TypeError
381
+ true
382
+ end
383
+
384
+ sig { returns(T::Hash[String, String]) }
385
+ def registry_auth_headers
386
+ registry_finder.auth_headers
387
+ end
388
+
389
+ sig { returns(String) }
390
+ def dependency_registry
391
+ registry_finder.registry
392
+ end
393
+
394
+ sig { returns(Package::RegistryFinder) }
395
+ def registry_finder
396
+ @registry_finder ||= Package::RegistryFinder.new(
397
+ dependency: dependency,
398
+ credentials: credentials,
399
+ npmrc_file: npmrc_file,
400
+ yarnrc_file: yarnrc_file,
401
+ yarnrc_yml_file: yarnrc_yml_file
402
+ )
403
+ end
404
+
405
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
406
+ def npmrc_file
407
+ dependency_files.find { |f| f.name.end_with?(REGISTRY_FILE_NPMRC) }
408
+ end
409
+
410
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
411
+ def yarnrc_file
412
+ dependency_files.find { |f| f.name.end_with?(REGISTRY_FILE_YARNRC) }
413
+ end
414
+
415
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
416
+ def yarnrc_yml_file
417
+ dependency_files.find { |f| f.name.end_with?(REGISTRY_FILE_YARNRC_YML) }
418
+ end
419
+
420
+ sig { returns(T::Boolean) }
421
+ def git_dependency?
422
+ # ignored_version/raise_on_ignored are irrelevant.
423
+ GitCommitChecker.new(
424
+ dependency: dependency,
425
+ credentials: credentials
426
+ ).git_dependency?
427
+ end
428
+
429
+ # This function safely retrieves a value for a given key from a Hash.
430
+ # If the hash is valid and the key exists, it will return the value, otherwise nil.
431
+ sig { params(hash: T.untyped, key: T.untyped).returns(T.untyped) }
432
+ def fetch_value_from_hash(hash, key)
433
+ return nil unless hash.is_a?(Hash) # Return nil if the hash is not a Hash
434
+
435
+ hash.fetch(key, nil) # Fetch the value for the given key, defaulting to nil if not found
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end