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 +4 -4
- data/lib/dependabot/npm_and_yarn/file_updater/bun_lockfile_updater.rb +1 -1
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +3 -3
- data/lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb +3 -3
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb +2 -2
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_workspace_updater.rb +1 -1
- data/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +5 -5
- data/lib/dependabot/npm_and_yarn/metadata_finder.rb +2 -2
- data/lib/dependabot/npm_and_yarn/package/package_details_fetcher.rb +440 -0
- data/lib/dependabot/npm_and_yarn/{update_checker → package}/registry_finder.rb +125 -34
- data/lib/dependabot/npm_and_yarn/update_checker/dependency_files_builder.rb +2 -2
- data/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb +532 -30
- data/lib/dependabot/npm_and_yarn/update_checker/library_detector.rb +1 -1
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +270 -61
- data/lib/dependabot/npm_and_yarn/update_checker.rb +140 -23
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 03ceb27ff40ce2354675588a932c77c0aba9957d7ada32b82c73edcd4566c470
|
4
|
+
data.tar.gz: 9670f9f1102fe62f0b2d838aa359300040f486b93a181f768bb96869f514b15e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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/
|
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 =
|
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
|
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
|
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
|
-
|
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(
|
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/
|
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 =
|
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/
|
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/
|
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 =
|
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
|
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 =
|
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
|
-
|
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/
|
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|
|
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
|