dependabot-javascript 0.296.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/lib/dependabot/bun.rb +49 -0
  3. data/lib/dependabot/javascript/bun/file_fetcher.rb +77 -0
  4. data/lib/dependabot/javascript/bun/file_parser/bun_lock.rb +156 -0
  5. data/lib/dependabot/javascript/bun/file_parser/lockfile_parser.rb +55 -0
  6. data/lib/dependabot/javascript/bun/file_parser.rb +74 -0
  7. data/lib/dependabot/javascript/bun/file_updater/lockfile_updater.rb +138 -0
  8. data/lib/dependabot/javascript/bun/file_updater.rb +75 -0
  9. data/lib/dependabot/javascript/bun/helpers.rb +72 -0
  10. data/lib/dependabot/javascript/bun/package_manager.rb +48 -0
  11. data/lib/dependabot/javascript/bun/requirement.rb +11 -0
  12. data/lib/dependabot/javascript/bun/update_checker/conflicting_dependency_resolver.rb +64 -0
  13. data/lib/dependabot/javascript/bun/update_checker/dependency_files_builder.rb +47 -0
  14. data/lib/dependabot/javascript/bun/update_checker/latest_version_finder.rb +450 -0
  15. data/lib/dependabot/javascript/bun/update_checker/library_detector.rb +76 -0
  16. data/lib/dependabot/javascript/bun/update_checker/requirements_updater.rb +203 -0
  17. data/lib/dependabot/javascript/bun/update_checker/subdependency_version_resolver.rb +144 -0
  18. data/lib/dependabot/javascript/bun/update_checker/version_resolver.rb +525 -0
  19. data/lib/dependabot/javascript/bun/update_checker/vulnerability_auditor.rb +165 -0
  20. data/lib/dependabot/javascript/bun/update_checker.rb +440 -0
  21. data/lib/dependabot/javascript/bun/version.rb +11 -0
  22. data/lib/dependabot/javascript/shared/constraint_helper.rb +359 -0
  23. data/lib/dependabot/javascript/shared/dependency_files_filterer.rb +164 -0
  24. data/lib/dependabot/javascript/shared/file_fetcher.rb +283 -0
  25. data/lib/dependabot/javascript/shared/file_parser/lockfile_parser.rb +106 -0
  26. data/lib/dependabot/javascript/shared/file_parser.rb +454 -0
  27. data/lib/dependabot/javascript/shared/file_updater/npmrc_builder.rb +394 -0
  28. data/lib/dependabot/javascript/shared/file_updater/package_json_preparer.rb +87 -0
  29. data/lib/dependabot/javascript/shared/file_updater/package_json_updater.rb +376 -0
  30. data/lib/dependabot/javascript/shared/file_updater.rb +179 -0
  31. data/lib/dependabot/javascript/shared/language.rb +45 -0
  32. data/lib/dependabot/javascript/shared/metadata_finder.rb +209 -0
  33. data/lib/dependabot/javascript/shared/native_helpers.rb +21 -0
  34. data/lib/dependabot/javascript/shared/package_manager_detector.rb +72 -0
  35. data/lib/dependabot/javascript/shared/package_name.rb +118 -0
  36. data/lib/dependabot/javascript/shared/registry_helper.rb +190 -0
  37. data/lib/dependabot/javascript/shared/registry_parser.rb +93 -0
  38. data/lib/dependabot/javascript/shared/requirement.rb +144 -0
  39. data/lib/dependabot/javascript/shared/sub_dependency_files_filterer.rb +79 -0
  40. data/lib/dependabot/javascript/shared/update_checker/dependency_files_builder.rb +87 -0
  41. data/lib/dependabot/javascript/shared/update_checker/registry_finder.rb +358 -0
  42. data/lib/dependabot/javascript/shared/version.rb +133 -0
  43. data/lib/dependabot/javascript/shared/version_selector.rb +60 -0
  44. data/lib/dependabot/javascript.rb +39 -0
  45. metadata +327 -0
@@ -0,0 +1,72 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ module Helpers
8
+ extend T::Sig
9
+
10
+ # BUN Version Constants
11
+ BUN_V1 = 1
12
+ BUN_DEFAULT_VERSION = BUN_V1
13
+
14
+ sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) }
15
+ def self.bun_version_numeric(_bun_lock)
16
+ BUN_DEFAULT_VERSION
17
+ end
18
+
19
+ sig { returns(T.nilable(String)) }
20
+ def self.bun_version
21
+ run_bun_command("--version", fingerprint: "--version").strip
22
+ rescue StandardError => e
23
+ Dependabot.logger.error("Error retrieving Bun version: #{e.message}")
24
+ nil
25
+ end
26
+
27
+ sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
28
+ def self.run_bun_command(command, fingerprint: nil)
29
+ full_command = "bun #{command}"
30
+
31
+ Dependabot.logger.info("Running bun command: #{full_command}")
32
+
33
+ result = Dependabot::SharedHelpers.run_shell_command(
34
+ full_command,
35
+ fingerprint: "bun #{fingerprint || command}"
36
+ )
37
+
38
+ Dependabot.logger.info("Command executed successfully: #{full_command}")
39
+ result
40
+ rescue StandardError => e
41
+ Dependabot.logger.error("Error running bun command: #{full_command}, Error: #{e.message}")
42
+ raise
43
+ end
44
+
45
+ # Fetch the currently installed version of the package manager directly
46
+ # from the system
47
+ sig { params(name: String).returns(String) }
48
+ def self.local_package_manager_version(name)
49
+ Dependabot::SharedHelpers.run_shell_command(
50
+ "#{name} -v",
51
+ fingerprint: "#{name} -v"
52
+ ).strip
53
+ end
54
+
55
+ # Run single command on package manager returning stdout/stderr
56
+ sig do
57
+ params(
58
+ name: String,
59
+ command: String,
60
+ fingerprint: T.nilable(String)
61
+ ).returns(String)
62
+ end
63
+ def self.package_manager_run_command(name, command, fingerprint: nil)
64
+ return run_bun_command(command, fingerprint: fingerprint) if name == PackageManager::NAME
65
+
66
+ # TODO: remove this method and just use the one in the PackageManager class
67
+ "noop"
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,48 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class PackageManager < Ecosystem::VersionManager
8
+ extend T::Sig
9
+ NAME = "bun"
10
+ LOCKFILE_NAME = "bun.lock"
11
+
12
+ # In Bun 1.1.39, the lockfile format was changed from a binary bun.lockb to a text-based bun.lock.
13
+ # https://bun.sh/blog/bun-lock-text-lockfile
14
+ MIN_SUPPORTED_VERSION = T.let(Version.new("1.1.39"), Dependabot::Version)
15
+ SUPPORTED_VERSIONS = T.let([MIN_SUPPORTED_VERSION].freeze, T::Array[Dependabot::Version])
16
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Version])
17
+
18
+ sig do
19
+ params(
20
+ detected_version: T.nilable(String),
21
+ raw_version: T.nilable(String),
22
+ requirement: T.nilable(Requirement)
23
+ ).void
24
+ end
25
+ def initialize(detected_version: nil, raw_version: nil, requirement: nil)
26
+ super(
27
+ name: NAME,
28
+ detected_version: detected_version ? Version.new(detected_version) : nil,
29
+ version: raw_version ? Version.new(raw_version) : nil,
30
+ deprecated_versions: DEPRECATED_VERSIONS,
31
+ supported_versions: SUPPORTED_VERSIONS,
32
+ requirement: requirement
33
+ )
34
+ end
35
+
36
+ sig { override.returns(T::Boolean) }
37
+ def deprecated?
38
+ false
39
+ end
40
+
41
+ sig { override.returns(T::Boolean) }
42
+ def unsupported?
43
+ false
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,11 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class Requirement < Dependabot::Javascript::Shared::Requirement
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,64 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class UpdateChecker < Dependabot::UpdateCheckers::Base
8
+ class ConflictingDependencyResolver
9
+ def initialize(dependency_files:, credentials:)
10
+ @dependency_files = dependency_files
11
+ @credentials = credentials
12
+ end
13
+
14
+ # Finds any dependencies in the `yarn.lock` or `package-lock.json` that
15
+ # have a subdependency on the given dependency that does not satisfly
16
+ # the target_version.
17
+ #
18
+ # @param dependency [Dependabot::Dependency] the dependency to check
19
+ # @param target_version [String] the version to check
20
+ # @return [Array<Hash{String => String}]
21
+ # * name [String] the blocking dependencies name
22
+ # * version [String] the version of the blocking dependency
23
+ # * requirement [String] the requirement on the target_dependency
24
+ def conflicting_dependencies(dependency:, target_version:)
25
+ SharedHelpers.in_a_temporary_directory do
26
+ dependency_files_builder = DependencyFilesBuilder.new(
27
+ dependency: dependency,
28
+ dependency_files: dependency_files,
29
+ credentials: credentials
30
+ )
31
+ dependency_files_builder.write_temporary_dependency_files
32
+
33
+ # TODO: Look into using npm/arborist for parsing yarn lockfiles (there's currently partial yarn support)
34
+ #
35
+ # Prefer the npm conflicting dependency parser if there's both a npm lockfile and a yarn.lock file as the
36
+ # npm parser handles edge cases where the package.json is out of sync with the lockfile,
37
+ # something the yarn parser doesn't deal with at the moment.
38
+ if dependency_files_builder.lockfiles.any?
39
+ SharedHelpers.run_helper_subprocess(
40
+ command: Dependabot::Javascript::Shared::NativeHelpers.helper_path,
41
+ function: "npm:findConflictingDependencies",
42
+ args: [Dir.pwd, dependency.name, target_version.to_s]
43
+ )
44
+ else
45
+ SharedHelpers.run_helper_subprocess(
46
+ command: Dependabot::Javascript::Shared::NativeHelpers.helper_path,
47
+ function: "yarn:findConflictingDependencies",
48
+ args: [Dir.pwd, dependency.name, target_version.to_s]
49
+ )
50
+ end
51
+ end
52
+ rescue SharedHelpers::HelperSubprocessFailed
53
+ []
54
+ end
55
+
56
+ private
57
+
58
+ attr_reader :dependency_files
59
+ attr_reader :credentials
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,47 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module Dependabot
5
+ module Javascript
6
+ module Bun
7
+ class UpdateChecker
8
+ class DependencyFilesBuilder < Shared::UpdateChecker::DependencyFilesBuilder
9
+ extend T::Sig
10
+
11
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
12
+ def bun_locks
13
+ @bun_locks ||= T.let(
14
+ dependency_files
15
+ .select { |f| f.name.end_with?("bun.lock") },
16
+ T.nilable(T::Array[Dependabot::DependencyFile])
17
+ )
18
+ end
19
+
20
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
21
+ def root_bun_lock
22
+ @root_bun_lock ||= T.let(
23
+ dependency_files
24
+ .find { |f| f.name == "bun.lock" },
25
+ T.nilable(Dependabot::DependencyFile)
26
+ )
27
+ end
28
+
29
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
30
+ def lockfiles
31
+ [*bun_locks]
32
+ end
33
+
34
+ private
35
+
36
+ sig { override.returns(T::Array[Dependabot::DependencyFile]) }
37
+ def write_lockfiles
38
+ [*bun_locks].each do |f|
39
+ FileUtils.mkdir_p(Pathname.new(f.name).dirname)
40
+ File.write(f.name, f.content)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,450 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "excon"
5
+
6
+ module Dependabot
7
+ module Javascript
8
+ module Bun
9
+ class UpdateChecker
10
+ class LatestVersionFinder
11
+ extend T::Sig
12
+
13
+ def initialize(dependency:, credentials:, dependency_files:,
14
+ ignored_versions:, security_advisories:,
15
+ raise_on_ignored: false)
16
+ @dependency = dependency
17
+ @credentials = credentials
18
+ @dependency_files = dependency_files
19
+ @ignored_versions = ignored_versions
20
+ @raise_on_ignored = raise_on_ignored
21
+ @security_advisories = security_advisories
22
+ end
23
+
24
+ def latest_version_from_registry
25
+ return unless valid_npm_details?
26
+ return version_from_dist_tags if version_from_dist_tags
27
+ return if specified_dist_tag_requirement?
28
+
29
+ possible_versions.find { |v| !yanked?(v) }
30
+ rescue Excon::Error::Socket, Excon::Error::Timeout, RegistryError
31
+ raise if dependency_registry == "registry.npmjs.org"
32
+ # Custom registries can be flaky. We don't want to make that
33
+ # our problem, so we quietly return `nil` here.
34
+ end
35
+
36
+ def latest_version_with_no_unlock
37
+ return unless valid_npm_details?
38
+ return version_from_dist_tags if specified_dist_tag_requirement?
39
+
40
+ in_range_versions = filter_out_of_range_versions(possible_versions)
41
+ in_range_versions.find { |version| !yanked?(version) }
42
+ rescue Excon::Error::Socket, Excon::Error::Timeout
43
+ raise if dependency_registry == "registry.npmjs.org"
44
+ # Sometimes custom registries are flaky. We don't want to make that
45
+ # our problem, so we quietly return `nil` here.
46
+ end
47
+
48
+ def lowest_security_fix_version
49
+ return unless valid_npm_details?
50
+
51
+ secure_versions =
52
+ if specified_dist_tag_requirement?
53
+ [version_from_dist_tags].compact
54
+ else
55
+ possible_versions(filter_ignored: false)
56
+ end
57
+
58
+ secure_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(secure_versions,
59
+ security_advisories)
60
+ secure_versions = filter_ignored_versions(secure_versions)
61
+ secure_versions = filter_lower_versions(secure_versions)
62
+
63
+ secure_versions.reverse.find { |version| !yanked?(version) }
64
+ rescue Excon::Error::Socket, Excon::Error::Timeout
65
+ raise if dependency_registry == "registry.npmjs.org"
66
+ # Sometimes custom registries are flaky. We don't want to make that
67
+ # our problem, so we quietly return `nil` here.
68
+ end
69
+
70
+ def possible_previous_versions_with_details
71
+ @possible_previous_versions_with_details ||= npm_details.fetch("versions", {})
72
+ .transform_keys { |k| version_class.new(k) }
73
+ .reject do |v, _|
74
+ v.prerelease? && !related_to_current_pre?(v)
75
+ end
76
+ .sort_by(&:first).reverse
77
+ end
78
+
79
+ def possible_versions_with_details(filter_ignored: true)
80
+ versions = possible_previous_versions_with_details
81
+ .reject { |_, details| details["deprecated"] }
82
+
83
+ return filter_ignored_versions(versions) if filter_ignored
84
+
85
+ versions
86
+ end
87
+
88
+ def possible_versions(filter_ignored: true)
89
+ possible_versions_with_details(filter_ignored: filter_ignored)
90
+ .map(&:first)
91
+ end
92
+
93
+ private
94
+
95
+ attr_reader :dependency
96
+ attr_reader :credentials
97
+ attr_reader :dependency_files
98
+ attr_reader :ignored_versions
99
+ attr_reader :security_advisories
100
+
101
+ def valid_npm_details?
102
+ !npm_details&.fetch("dist-tags", nil).nil?
103
+ end
104
+
105
+ sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
106
+ def filter_ignored_versions(versions_array)
107
+ filtered = versions_array.reject do |v, _|
108
+ ignore_requirements.any? { |r| r.satisfied_by?(v) }
109
+ end
110
+
111
+ if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any?
112
+ raise AllVersionsIgnored
113
+ end
114
+
115
+ if versions_array.count > filtered.count
116
+ diff = versions_array.count - filtered.count
117
+ Dependabot.logger.info("Filtered out #{diff} ignored versions")
118
+ end
119
+
120
+ filtered
121
+ end
122
+
123
+ sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
124
+ def filter_out_of_range_versions(versions_array)
125
+ reqs = dependency.requirements.filter_map do |r|
126
+ NpmAndYarn::Requirement.requirements_array(r.fetch(:requirement))
127
+ end
128
+
129
+ versions_array
130
+ .select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } }
131
+ end
132
+
133
+ sig { params(versions_array: T::Array[T.untyped]).returns(T::Array[T.untyped]) }
134
+ def filter_lower_versions(versions_array)
135
+ return versions_array unless dependency.numeric_version
136
+
137
+ versions_array
138
+ .select { |version, _| version > dependency.numeric_version }
139
+ end
140
+
141
+ def version_from_dist_tags
142
+ dist_tags = npm_details["dist-tags"].keys
143
+
144
+ # Check if a dist tag was specified as a requirement. If it was, and
145
+ # it exists, use it.
146
+ dist_tag_req = dependency.requirements
147
+ .find { |r| dist_tags.include?(r[:requirement]) }
148
+ &.fetch(:requirement)
149
+
150
+ if dist_tag_req
151
+ tag_vers =
152
+ version_class.new(npm_details["dist-tags"][dist_tag_req])
153
+ return tag_vers unless yanked?(tag_vers)
154
+ end
155
+
156
+ # Use the latest dist tag unless there's a reason not to
157
+ return nil unless npm_details["dist-tags"]["latest"]
158
+
159
+ latest = version_class.new(npm_details["dist-tags"]["latest"])
160
+
161
+ wants_latest_dist_tag?(latest) ? latest : nil
162
+ end
163
+
164
+ def related_to_current_pre?(version)
165
+ current_version = dependency.numeric_version
166
+ if current_version&.prerelease? &&
167
+ current_version&.release == version.release
168
+ return true
169
+ end
170
+
171
+ dependency.requirements.any? do |req|
172
+ next unless req[:requirement]&.match?(/\d-[A-Za-z]/)
173
+
174
+ NpmAndYarn::Requirement
175
+ .requirements_array(req.fetch(:requirement))
176
+ .any? do |r|
177
+ r.requirements.any? { |a| a.last.release == version.release }
178
+ end
179
+ rescue Gem::Requirement::BadRequirementError
180
+ false
181
+ end
182
+ end
183
+
184
+ def specified_dist_tag_requirement?
185
+ dependency.requirements.any? do |req|
186
+ next false if req[:requirement].nil?
187
+ next false unless req[:requirement].match?(/^[A-Za-z]/)
188
+
189
+ !req[:requirement].match?(/^v\d/i)
190
+ end
191
+ end
192
+
193
+ def wants_latest_dist_tag?(latest_version)
194
+ ver = latest_version
195
+ return false if related_to_current_pre?(ver) ^ ver.prerelease?
196
+ return false if current_version_greater_than?(ver)
197
+ return false if current_requirement_greater_than?(ver)
198
+ return false if ignore_requirements.any? { |r| r.satisfied_by?(ver) }
199
+ return false if yanked?(ver)
200
+
201
+ true
202
+ end
203
+
204
+ def current_version_greater_than?(version)
205
+ return false unless dependency.numeric_version
206
+
207
+ dependency.numeric_version > version
208
+ end
209
+
210
+ def current_requirement_greater_than?(version)
211
+ dependency.requirements.any? do |req|
212
+ next false unless req[:requirement]
213
+
214
+ req_version = req[:requirement].sub(/^\^|~|>=?/, "")
215
+ next false unless version_class.correct?(req_version)
216
+
217
+ version_class.new(req_version) > version
218
+ end
219
+ end
220
+
221
+ def yanked?(version)
222
+ @yanked ||= {}
223
+ return @yanked[version] if @yanked.key?(version)
224
+
225
+ @yanked[version] =
226
+ begin
227
+ if dependency_registry == "registry.npmjs.org"
228
+ status = Dependabot::RegistryClient.head(
229
+ url: registry_finder.tarball_url(version),
230
+ headers: registry_auth_headers
231
+ ).status
232
+ else
233
+ status = Dependabot::RegistryClient.get(
234
+ url: dependency_url + "/#{version}",
235
+ headers: registry_auth_headers
236
+ ).status
237
+
238
+ if status == 404
239
+ # Some registries don't handle escaped package names properly
240
+ status = Dependabot::RegistryClient.get(
241
+ url: dependency_url.gsub("%2F", "/") + "/#{version}",
242
+ headers: registry_auth_headers
243
+ ).status
244
+ end
245
+ end
246
+
247
+ version_not_found = status == 404
248
+ version_not_found && version_endpoint_working?
249
+ rescue Excon::Error::Timeout, Excon::Error::Socket
250
+ # Give the benefit of the doubt if the registry is playing up
251
+ false
252
+ end
253
+ end
254
+
255
+ def version_endpoint_working?
256
+ return true if dependency_registry == "registry.npmjs.org"
257
+
258
+ return @version_endpoint_working if defined?(@version_endpoint_working)
259
+
260
+ @version_endpoint_working =
261
+ begin
262
+ Dependabot::RegistryClient.get(
263
+ url: dependency_url + "/latest",
264
+ headers: registry_auth_headers
265
+ ).status < 400
266
+ rescue Excon::Error::Timeout, Excon::Error::Socket
267
+ # Give the benefit of the doubt if the registry is playing up
268
+ true
269
+ end
270
+ end
271
+
272
+ def npm_details
273
+ return @npm_details if defined?(@npm_details)
274
+
275
+ @npm_details = fetch_npm_details
276
+ end
277
+
278
+ def fetch_npm_details
279
+ npm_response = fetch_npm_response
280
+
281
+ check_npm_response(npm_response)
282
+ JSON.parse(npm_response.body)
283
+ rescue JSON::ParserError,
284
+ Excon::Error::Timeout,
285
+ Excon::Error::Socket,
286
+ RegistryError => e
287
+ if git_dependency?
288
+ nil
289
+ else
290
+ raise_npm_details_error(e)
291
+ end
292
+ end
293
+
294
+ def fetch_npm_response
295
+ response = Dependabot::RegistryClient.get(
296
+ url: dependency_url,
297
+ headers: registry_auth_headers
298
+ )
299
+ return response unless response.status == 500
300
+ return response unless registry_auth_headers["Authorization"]
301
+
302
+ auth = registry_auth_headers["Authorization"]
303
+ return response unless auth.start_with?("Basic")
304
+
305
+ decoded_token = Base64.decode64(auth.gsub("Basic ", ""))
306
+ return unless decoded_token.include?(":")
307
+
308
+ username, password = decoded_token.split(":")
309
+ Dependabot::RegistryClient.get(
310
+ url: dependency_url,
311
+ options: {
312
+ user: username,
313
+ password: password
314
+ }
315
+ )
316
+ rescue URI::InvalidURIError => e
317
+ raise DependencyFileNotResolvable, e.message
318
+ end
319
+
320
+ def check_npm_response(npm_response)
321
+ return if git_dependency?
322
+
323
+ if private_dependency_not_reachable?(npm_response)
324
+ raise PrivateSourceAuthenticationFailure, dependency_registry
325
+ end
326
+
327
+ # handles scenario when private registry returns a server error 5xx
328
+ if private_dependency_server_error?(npm_response)
329
+ msg = "Server error #{npm_response.status} returned while accessing registry" \
330
+ " #{dependency_registry}."
331
+ raise DependencyFileNotResolvable, msg
332
+ end
333
+
334
+ status = npm_response.status
335
+
336
+ # handles issue when status 200 is returned from registry but with an invalid JSON object
337
+ if status.to_s.start_with?("2") && response_invalid_json?(npm_response)
338
+ msg = "Invalid JSON object returned from registry #{dependency_registry}."
339
+ Dependabot.logger.warn("#{msg} Response body (truncated) : #{npm_response.body[0..500]}...")
340
+ raise DependencyFileNotResolvable, msg
341
+ end
342
+
343
+ return if status.to_s.start_with?("2")
344
+
345
+ # Ignore 404s from the registry for updates where a lockfile doesn't
346
+ # need to be generated. The 404 won't cause problems later.
347
+ return if status == 404 && dependency.version.nil?
348
+
349
+ msg = "Got #{status} response with body #{npm_response.body}"
350
+ raise RegistryError.new(status, msg)
351
+ end
352
+
353
+ def raise_npm_details_error(error)
354
+ raise if dependency_registry == "registry.npmjs.org"
355
+ raise unless error.is_a?(Excon::Error::Timeout)
356
+
357
+ raise PrivateSourceTimedOut, dependency_registry
358
+ end
359
+
360
+ def private_dependency_not_reachable?(npm_response)
361
+ return true if npm_response.body.start_with?(/user ".*?" is not a /)
362
+ return false unless [401, 402, 403, 404].include?(npm_response.status)
363
+
364
+ # Check whether this dependency is (likely to be) private
365
+ if dependency_registry == "registry.npmjs.org"
366
+ return false unless dependency.name.start_with?("@")
367
+
368
+ web_response = Dependabot::RegistryClient.get(url: "https://www.npmjs.com/package/#{dependency.name}")
369
+ # NOTE: returns 429 when the login page is rate limited
370
+ return web_response.body.include?("Forgot password?") ||
371
+ web_response.status == 429
372
+ end
373
+
374
+ true
375
+ end
376
+
377
+ def private_dependency_server_error?(npm_response)
378
+ if [500, 501, 502, 503].include?(npm_response.status)
379
+ Dependabot.logger.warn("#{dependency_registry} returned code #{npm_response.status} with " \
380
+ "body #{npm_response.body}.")
381
+ return true
382
+ end
383
+ false
384
+ end
385
+
386
+ def response_invalid_json?(npm_response)
387
+ result = JSON.parse(npm_response.body)
388
+ result.is_a?(Hash) || result.is_a?(Array)
389
+ false
390
+ rescue JSON::ParserError, TypeError
391
+ true
392
+ end
393
+
394
+ def dependency_url
395
+ registry_finder.dependency_url
396
+ end
397
+
398
+ def dependency_registry
399
+ registry_finder.registry
400
+ end
401
+
402
+ def registry_auth_headers
403
+ registry_finder.auth_headers
404
+ end
405
+
406
+ def registry_finder
407
+ @registry_finder ||= Dependabot::Javascript::Shared::UpdateChecker::RegistryFinder.new(
408
+ dependency: dependency,
409
+ credentials: credentials,
410
+ rc_file: npmrc_file
411
+ )
412
+ end
413
+
414
+ def ignore_requirements
415
+ ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
416
+ end
417
+
418
+ def version_class
419
+ dependency.version_class
420
+ end
421
+
422
+ def requirement_class
423
+ dependency.requirement_class
424
+ end
425
+
426
+ def npmrc_file
427
+ dependency_files.find { |f| f.name.end_with?(".npmrc") }
428
+ end
429
+
430
+ def yarnrc_file
431
+ dependency_files.find { |f| f.name.end_with?(".yarnrc") }
432
+ end
433
+
434
+ def yarnrc_yml_file
435
+ dependency_files.find { |f| f.name.end_with?(".yarnrc.yml") }
436
+ end
437
+
438
+ # TODO: Remove need for me
439
+ def git_dependency?
440
+ # ignored_version/raise_on_ignored are irrelevant.
441
+ GitCommitChecker.new(
442
+ dependency: dependency,
443
+ credentials: credentials
444
+ ).git_dependency?
445
+ end
446
+ end
447
+ end
448
+ end
449
+ end
450
+ end