dependabot-npm_and_yarn 0.91.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/helpers/build +14 -0
- data/helpers/npm/.eslintrc +14 -0
- data/helpers/npm/bin/run.js +34 -0
- data/helpers/npm/lib/helpers.js +25 -0
- data/helpers/npm/lib/peer-dependency-checker.js +102 -0
- data/helpers/npm/lib/subdependency-updater.js +48 -0
- data/helpers/npm/lib/updater.js +101 -0
- data/helpers/npm/package-lock.json +8868 -0
- data/helpers/npm/package.json +17 -0
- data/helpers/npm/test/fixtures/npm-left-pad.json +1 -0
- data/helpers/npm/test/fixtures/updater/original/package-lock.json +16 -0
- data/helpers/npm/test/fixtures/updater/original/package.json +9 -0
- data/helpers/npm/test/fixtures/updater/updated/package-lock.json +16 -0
- data/helpers/npm/test/helpers.js +7 -0
- data/helpers/npm/test/updater.test.js +50 -0
- data/helpers/npm/yarn.lock +6176 -0
- data/helpers/yarn/.eslintrc +14 -0
- data/helpers/yarn/bin/run.js +36 -0
- data/helpers/yarn/lib/fix-duplicates.js +78 -0
- data/helpers/yarn/lib/helpers.js +5 -0
- data/helpers/yarn/lib/lockfile-parser.js +21 -0
- data/helpers/yarn/lib/peer-dependency-checker.js +130 -0
- data/helpers/yarn/lib/replace-lockfile-declaration.js +57 -0
- data/helpers/yarn/lib/subdependency-updater.js +69 -0
- data/helpers/yarn/lib/updater.js +266 -0
- data/helpers/yarn/package.json +17 -0
- data/helpers/yarn/test/fixtures/updater/original/package.json +6 -0
- data/helpers/yarn/test/fixtures/updater/original/yarn.lock +11 -0
- data/helpers/yarn/test/fixtures/updater/updated/yarn.lock +12 -0
- data/helpers/yarn/test/fixtures/updater/with-version-comments/package.json +5 -0
- data/helpers/yarn/test/fixtures/updater/with-version-comments/yarn.lock +13 -0
- data/helpers/yarn/test/fixtures/yarnpkg-is-positive.json +1 -0
- data/helpers/yarn/test/fixtures/yarnpkg-left-pad.json +1 -0
- data/helpers/yarn/test/helpers.js +7 -0
- data/helpers/yarn/test/updater.test.js +93 -0
- data/helpers/yarn/yarn.lock +4760 -0
- data/lib/dependabot/npm_and_yarn/file_fetcher/path_dependency_builder.rb +146 -0
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +332 -0
- data/lib/dependabot/npm_and_yarn/file_parser.rb +397 -0
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +527 -0
- data/lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb +190 -0
- data/lib/dependabot/npm_and_yarn/file_updater/package_json_preparer.rb +87 -0
- data/lib/dependabot/npm_and_yarn/file_updater/package_json_updater.rb +218 -0
- data/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +471 -0
- data/lib/dependabot/npm_and_yarn/file_updater.rb +189 -0
- data/lib/dependabot/npm_and_yarn/metadata_finder.rb +217 -0
- data/lib/dependabot/npm_and_yarn/native_helpers.rb +28 -0
- data/lib/dependabot/npm_and_yarn/requirement.rb +145 -0
- data/lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb +340 -0
- data/lib/dependabot/npm_and_yarn/update_checker/library_detector.rb +67 -0
- data/lib/dependabot/npm_and_yarn/update_checker/registry_finder.rb +224 -0
- data/lib/dependabot/npm_and_yarn/update_checker/requirements_updater.rb +193 -0
- data/lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb +223 -0
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +495 -0
- data/lib/dependabot/npm_and_yarn/update_checker.rb +282 -0
- data/lib/dependabot/npm_and_yarn/version.rb +34 -0
- data/lib/dependabot/npm_and_yarn.rb +11 -0
- metadata +226 -0
@@ -0,0 +1,340 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "dependabot/npm_and_yarn/update_checker"
|
5
|
+
require "dependabot/npm_and_yarn/update_checker/registry_finder"
|
6
|
+
require "dependabot/npm_and_yarn/version"
|
7
|
+
require "dependabot/npm_and_yarn/requirement"
|
8
|
+
require "dependabot/shared_helpers"
|
9
|
+
require "dependabot/errors"
|
10
|
+
|
11
|
+
module Dependabot
|
12
|
+
module NpmAndYarn
|
13
|
+
class UpdateChecker
|
14
|
+
class LatestVersionFinder
|
15
|
+
class RegistryError < StandardError; end
|
16
|
+
|
17
|
+
def initialize(dependency:, credentials:, dependency_files:,
|
18
|
+
ignored_versions:)
|
19
|
+
@dependency = dependency
|
20
|
+
@credentials = credentials
|
21
|
+
@dependency_files = dependency_files
|
22
|
+
@ignored_versions = ignored_versions
|
23
|
+
end
|
24
|
+
|
25
|
+
def latest_version_details_from_registry
|
26
|
+
return nil unless npm_details&.fetch("dist-tags", nil)
|
27
|
+
|
28
|
+
dist_tag_version = version_from_dist_tags(npm_details)
|
29
|
+
return { version: dist_tag_version } if dist_tag_version
|
30
|
+
return nil if specified_dist_tag_requirement?
|
31
|
+
|
32
|
+
{ version: version_from_versions_array }
|
33
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout
|
34
|
+
raise if dependency_registry == "registry.npmjs.org"
|
35
|
+
# Custom registries can be flaky. We don't want to make that
|
36
|
+
# our problem, so we quietly return `nil` here.
|
37
|
+
end
|
38
|
+
|
39
|
+
def latest_resolvable_version_with_no_unlock
|
40
|
+
return unless npm_details
|
41
|
+
|
42
|
+
if specified_dist_tag_requirement?
|
43
|
+
return version_from_dist_tags(npm_details)
|
44
|
+
end
|
45
|
+
|
46
|
+
reqs = dependency.requirements.map do |r|
|
47
|
+
NpmAndYarn::Requirement.
|
48
|
+
requirements_array(r.fetch(:requirement))
|
49
|
+
end.compact
|
50
|
+
|
51
|
+
possible_versions.
|
52
|
+
find do |version|
|
53
|
+
reqs.all? { |r| r.any? { |opt| opt.satisfied_by?(version) } } &&
|
54
|
+
!yanked?(version)
|
55
|
+
end
|
56
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout
|
57
|
+
raise if dependency_registry == "registry.npmjs.org"
|
58
|
+
# Sometimes custom registries are flaky. We don't want to make that
|
59
|
+
# our problem, so we quietly return `nil` here.
|
60
|
+
end
|
61
|
+
|
62
|
+
def possible_versions
|
63
|
+
npm_details.fetch("versions", {}).
|
64
|
+
reject { |_, details| details["deprecated"] }.
|
65
|
+
keys.map { |v| version_class.new(v) }.
|
66
|
+
reject { |v| v.prerelease? && !related_to_current_pre?(v) }.
|
67
|
+
reject { |v| ignore_reqs.any? { |r| r.satisfied_by?(v) } }.
|
68
|
+
sort.reverse
|
69
|
+
end
|
70
|
+
|
71
|
+
def possible_versions_with_details
|
72
|
+
npm_details.fetch("versions", {}).
|
73
|
+
reject { |_, details| details["deprecated"] }.
|
74
|
+
transform_keys { |k| version_class.new(k) }.
|
75
|
+
reject { |k, _| k.prerelease? && !related_to_current_pre?(k) }.
|
76
|
+
reject { |k, _| ignore_reqs.any? { |r| r.satisfied_by?(k) } }.
|
77
|
+
sort_by(&:first).reverse
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
attr_reader :dependency, :credentials, :dependency_files,
|
83
|
+
:ignored_versions
|
84
|
+
|
85
|
+
def version_from_dist_tags(npm_details)
|
86
|
+
dist_tags = npm_details["dist-tags"].keys
|
87
|
+
|
88
|
+
# Check if a dist tag was specified as a requirement. If it was, and
|
89
|
+
# it exists, use it.
|
90
|
+
dist_tag_req = dependency.requirements.
|
91
|
+
find { |r| dist_tags.include?(r[:requirement]) }&.
|
92
|
+
fetch(:requirement)
|
93
|
+
|
94
|
+
if dist_tag_req
|
95
|
+
tag_vers =
|
96
|
+
version_class.new(npm_details["dist-tags"][dist_tag_req])
|
97
|
+
return tag_vers unless yanked?(tag_vers)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Use the latest dist tag unless there's a reason not to
|
101
|
+
return nil unless npm_details["dist-tags"]["latest"]
|
102
|
+
|
103
|
+
latest = version_class.new(npm_details["dist-tags"]["latest"])
|
104
|
+
|
105
|
+
wants_latest_dist_tag?(latest) ? latest : nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def related_to_current_pre?(version)
|
109
|
+
current_version = dependency.version
|
110
|
+
if current_version &&
|
111
|
+
version_class.correct?(current_version) &&
|
112
|
+
version_class.new(current_version).prerelease? &&
|
113
|
+
version_class.new(current_version).release == version.release
|
114
|
+
return true
|
115
|
+
end
|
116
|
+
|
117
|
+
dependency.requirements.any? do |req|
|
118
|
+
next unless req[:requirement]&.match?(/\d-[A-Za-z]/)
|
119
|
+
|
120
|
+
NpmAndYarn::Requirement.
|
121
|
+
requirements_array(req.fetch(:requirement)).
|
122
|
+
any? do |r|
|
123
|
+
r.requirements.any? { |a| a.last.release == version.release }
|
124
|
+
end
|
125
|
+
rescue Gem::Requirement::BadRequirementError
|
126
|
+
false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def specified_dist_tag_requirement?
|
131
|
+
dependency.requirements.any? do |req|
|
132
|
+
next false if req[:requirement].nil?
|
133
|
+
|
134
|
+
req[:requirement].match?(/^[A-Za-z]/)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def wants_latest_dist_tag?(latest_version)
|
139
|
+
ver = latest_version
|
140
|
+
return false if related_to_current_pre?(ver) ^ ver.prerelease?
|
141
|
+
return false if current_version_greater_than?(ver)
|
142
|
+
return false if current_requirement_greater_than?(ver)
|
143
|
+
return false if ignore_reqs.any? { |r| r.satisfied_by?(ver) }
|
144
|
+
return false if yanked?(ver)
|
145
|
+
|
146
|
+
true
|
147
|
+
end
|
148
|
+
|
149
|
+
def current_version_greater_than?(version)
|
150
|
+
return false unless dependency.version
|
151
|
+
return false unless version_class.correct?(dependency.version)
|
152
|
+
|
153
|
+
version_class.new(dependency.version) > version
|
154
|
+
end
|
155
|
+
|
156
|
+
def current_requirement_greater_than?(version)
|
157
|
+
dependency.requirements.any? do |req|
|
158
|
+
next false unless req[:requirement]
|
159
|
+
|
160
|
+
req_version = req[:requirement].sub(/^\^|~|>=?/, "")
|
161
|
+
next false unless version_class.correct?(req_version)
|
162
|
+
|
163
|
+
version_class.new(req_version) > version
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def version_from_versions_array
|
168
|
+
possible_versions.find { |version| !yanked?(version) }
|
169
|
+
end
|
170
|
+
|
171
|
+
def yanked?(version)
|
172
|
+
@yanked ||= {}
|
173
|
+
return @yanked[version] if @yanked.key?(version)
|
174
|
+
|
175
|
+
@yanked[version] =
|
176
|
+
begin
|
177
|
+
version_not_found =
|
178
|
+
Excon.get(
|
179
|
+
dependency_url + "/#{version}",
|
180
|
+
SharedHelpers.excon_defaults.merge(
|
181
|
+
headers: registry_auth_headers,
|
182
|
+
idempotent: true
|
183
|
+
)
|
184
|
+
).status == 404
|
185
|
+
version_not_found && version_endpoint_working?
|
186
|
+
rescue Excon::Error::Timeout
|
187
|
+
# Give the benefit of the doubt if the registry is playing up
|
188
|
+
false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def version_endpoint_working?
|
193
|
+
return true if dependency_registry == "registry.npmjs.org"
|
194
|
+
|
195
|
+
if defined?(@version_endpoint_working)
|
196
|
+
return @version_endpoint_working
|
197
|
+
end
|
198
|
+
|
199
|
+
@version_endpoint_working =
|
200
|
+
begin
|
201
|
+
Excon.get(
|
202
|
+
dependency_url + "/latest",
|
203
|
+
SharedHelpers.excon_defaults.merge(
|
204
|
+
headers: registry_auth_headers,
|
205
|
+
idempotent: true
|
206
|
+
)
|
207
|
+
).status < 400
|
208
|
+
rescue Excon::Error::Timeout
|
209
|
+
# Give the benefit of the doubt if the registry is playing up
|
210
|
+
true
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def npm_details
|
215
|
+
return @npm_details if @npm_details_lookup_attempted
|
216
|
+
|
217
|
+
@npm_details_lookup_attempted = true
|
218
|
+
@npm_details ||=
|
219
|
+
begin
|
220
|
+
npm_response = fetch_npm_response
|
221
|
+
|
222
|
+
check_npm_response(npm_response)
|
223
|
+
JSON.parse(npm_response.body)
|
224
|
+
rescue JSON::ParserError, Excon::Error::Timeout,
|
225
|
+
RegistryError => error
|
226
|
+
retry_count ||= 0
|
227
|
+
retry_count += 1
|
228
|
+
raise_npm_details_error(error) if retry_count > 2
|
229
|
+
sleep(rand(3.0..10.0)) && retry
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def fetch_npm_response
|
234
|
+
response = Excon.get(
|
235
|
+
dependency_url,
|
236
|
+
SharedHelpers.excon_defaults.merge(
|
237
|
+
headers: registry_auth_headers,
|
238
|
+
idempotent: true
|
239
|
+
)
|
240
|
+
)
|
241
|
+
|
242
|
+
return response unless response.status == 500
|
243
|
+
return response unless registry_auth_headers["Authorization"]
|
244
|
+
|
245
|
+
auth = registry_auth_headers["Authorization"]
|
246
|
+
return response unless auth.start_with?("Basic")
|
247
|
+
|
248
|
+
decoded_token = Base64.decode64(auth.gsub("Basic ", ""))
|
249
|
+
return unless decoded_token.include?(":")
|
250
|
+
|
251
|
+
username, password = decoded_token.split(":")
|
252
|
+
Excon.get(
|
253
|
+
dependency_url,
|
254
|
+
SharedHelpers.excon_defaults.merge(
|
255
|
+
user: username,
|
256
|
+
password: password,
|
257
|
+
idempotent: true
|
258
|
+
)
|
259
|
+
)
|
260
|
+
end
|
261
|
+
|
262
|
+
def check_npm_response(npm_response)
|
263
|
+
return if git_dependency?
|
264
|
+
|
265
|
+
if private_dependency_not_reachable?(npm_response)
|
266
|
+
raise PrivateSourceAuthenticationFailure, dependency_registry
|
267
|
+
end
|
268
|
+
|
269
|
+
status = npm_response.status
|
270
|
+
return if status.to_s.start_with?("2")
|
271
|
+
|
272
|
+
# Ignore 404s from the registry for updates where a lockfile doesn't
|
273
|
+
# need to be generated. The 404 won't cause problems later.
|
274
|
+
return if status == 404 && dependency.version.nil?
|
275
|
+
|
276
|
+
msg = "Got #{status} response with body #{npm_response.body}"
|
277
|
+
raise RegistryError, msg
|
278
|
+
end
|
279
|
+
|
280
|
+
def raise_npm_details_error(error)
|
281
|
+
raise if dependency_registry == "registry.npmjs.org"
|
282
|
+
raise unless error.is_a?(Excon::Error::Timeout)
|
283
|
+
|
284
|
+
raise PrivateSourceTimedOut, dependency_registry
|
285
|
+
end
|
286
|
+
|
287
|
+
def private_dependency_not_reachable?(npm_response)
|
288
|
+
# Check whether this dependency is (likely to be) private
|
289
|
+
if dependency_registry == "registry.npmjs.org" &&
|
290
|
+
!dependency.name.start_with?("@")
|
291
|
+
return false
|
292
|
+
end
|
293
|
+
|
294
|
+
[401, 402, 403, 404].include?(npm_response.status)
|
295
|
+
end
|
296
|
+
|
297
|
+
def dependency_url
|
298
|
+
registry_finder.dependency_url
|
299
|
+
end
|
300
|
+
|
301
|
+
def dependency_registry
|
302
|
+
registry_finder.registry
|
303
|
+
end
|
304
|
+
|
305
|
+
def registry_auth_headers
|
306
|
+
registry_finder.auth_headers
|
307
|
+
end
|
308
|
+
|
309
|
+
def registry_finder
|
310
|
+
@registry_finder ||=
|
311
|
+
RegistryFinder.new(
|
312
|
+
dependency: dependency,
|
313
|
+
credentials: credentials,
|
314
|
+
npmrc_file: dependency_files.
|
315
|
+
find { |f| f.name.end_with?(".npmrc") },
|
316
|
+
yarnrc_file: dependency_files.
|
317
|
+
find { |f| f.name.end_with?(".yarnrc") }
|
318
|
+
)
|
319
|
+
end
|
320
|
+
|
321
|
+
def ignore_reqs
|
322
|
+
ignored_versions.
|
323
|
+
map { |req| NpmAndYarn::Requirement.new(req.split(",")) }
|
324
|
+
end
|
325
|
+
|
326
|
+
def version_class
|
327
|
+
NpmAndYarn::Version
|
328
|
+
end
|
329
|
+
|
330
|
+
# TODO: Remove need for me
|
331
|
+
def git_dependency?
|
332
|
+
GitCommitChecker.new(
|
333
|
+
dependency: dependency,
|
334
|
+
credentials: credentials
|
335
|
+
).git_dependency?
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "dependabot/npm_and_yarn/update_checker"
|
5
|
+
require "dependabot/shared_helpers"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module NpmAndYarn
|
9
|
+
class UpdateChecker
|
10
|
+
class LibraryDetector
|
11
|
+
def initialize(package_json_file:)
|
12
|
+
@package_json_file = package_json_file
|
13
|
+
end
|
14
|
+
|
15
|
+
def library?
|
16
|
+
return false unless package_json_may_be_for_library?
|
17
|
+
|
18
|
+
npm_response_matches_package_json?
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :package_json_file
|
24
|
+
|
25
|
+
def package_json_may_be_for_library?
|
26
|
+
return false unless project_name
|
27
|
+
return false if project_name.match?(/\{\{.*\}\}/)
|
28
|
+
return false unless parsed_package_json["version"]
|
29
|
+
return false if parsed_package_json["private"]
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def npm_response_matches_package_json?
|
35
|
+
project_description = parsed_package_json["description"]
|
36
|
+
return false unless project_description
|
37
|
+
|
38
|
+
# Check if the project is listed on npm. If it is, it's a library
|
39
|
+
@project_npm_response ||= Excon.get(
|
40
|
+
"https://registry.npmjs.org/#{escaped_project_name}",
|
41
|
+
idempotent: true,
|
42
|
+
**SharedHelpers.excon_defaults
|
43
|
+
)
|
44
|
+
|
45
|
+
return false unless @project_npm_response.status == 200
|
46
|
+
|
47
|
+
@project_npm_response.body.force_encoding("UTF-8").encode.
|
48
|
+
include?(project_description)
|
49
|
+
rescue Excon::Error::Socket, Excon::Error::Timeout
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def project_name
|
54
|
+
parsed_package_json.fetch("name", nil)
|
55
|
+
end
|
56
|
+
|
57
|
+
def escaped_project_name
|
58
|
+
project_name&.gsub("/", "%2F")
|
59
|
+
end
|
60
|
+
|
61
|
+
def parsed_package_json
|
62
|
+
@parsed_package_json ||= JSON.parse(package_json_file.content)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "dependabot/npm_and_yarn/update_checker"
|
5
|
+
require "dependabot/shared_helpers"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module NpmAndYarn
|
9
|
+
class UpdateChecker
|
10
|
+
class RegistryFinder
|
11
|
+
NPM_AUTH_TOKEN_REGEX =
|
12
|
+
%r{//(?<registry>.*)/:_authToken=(?<token>.*)$}.freeze
|
13
|
+
NPM_GLOBAL_REGISTRY_REGEX =
|
14
|
+
/^registry\s*=\s*(?<registry>.*)$/.freeze
|
15
|
+
YARN_GLOBAL_REGISTRY_REGEX =
|
16
|
+
/^registry\s+['"](?<registry>.*)['"]/.freeze
|
17
|
+
|
18
|
+
def initialize(dependency:, credentials:, npmrc_file: nil,
|
19
|
+
yarnrc_file: nil)
|
20
|
+
@dependency = dependency
|
21
|
+
@credentials = credentials
|
22
|
+
@npmrc_file = npmrc_file
|
23
|
+
@yarnrc_file = yarnrc_file
|
24
|
+
end
|
25
|
+
|
26
|
+
def registry
|
27
|
+
locked_registry || first_registry_with_dependency_details
|
28
|
+
end
|
29
|
+
|
30
|
+
def auth_headers
|
31
|
+
auth_header_for(auth_token)
|
32
|
+
end
|
33
|
+
|
34
|
+
def dependency_url
|
35
|
+
"#{registry_url.gsub(%r{/+$}, '')}/#{escaped_dependency_name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :dependency, :credentials, :npmrc_file, :yarnrc_file
|
41
|
+
|
42
|
+
def first_registry_with_dependency_details
|
43
|
+
@first_registry_with_dependency_details ||=
|
44
|
+
known_registries.find do |details|
|
45
|
+
Excon.get(
|
46
|
+
"https://#{details['registry'].gsub(%r{/+$}, '')}/"\
|
47
|
+
"#{escaped_dependency_name}",
|
48
|
+
headers: auth_header_for(details["token"]),
|
49
|
+
idempotent: true,
|
50
|
+
**SharedHelpers.excon_defaults
|
51
|
+
).status < 400
|
52
|
+
rescue Excon::Error::Timeout, Excon::Error::Socket
|
53
|
+
nil
|
54
|
+
end&.fetch("registry")
|
55
|
+
|
56
|
+
@first_registry_with_dependency_details ||= global_registry
|
57
|
+
end
|
58
|
+
|
59
|
+
def registry_url
|
60
|
+
protocol =
|
61
|
+
if private_registry_source_url
|
62
|
+
private_registry_source_url.split("://").first
|
63
|
+
else
|
64
|
+
"https"
|
65
|
+
end
|
66
|
+
|
67
|
+
"#{protocol}://#{registry}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def auth_header_for(token)
|
71
|
+
return {} unless token
|
72
|
+
|
73
|
+
if token.include?(":")
|
74
|
+
encoded_token = Base64.encode64(token).delete("\n")
|
75
|
+
{ "Authorization" => "Basic #{encoded_token}" }
|
76
|
+
elsif Base64.decode64(token).ascii_only? &&
|
77
|
+
Base64.decode64(token).include?(":")
|
78
|
+
{ "Authorization" => "Basic #{token.delete("\n")}" }
|
79
|
+
else
|
80
|
+
{ "Authorization" => "Bearer #{token}" }
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def auth_token
|
85
|
+
known_registries.
|
86
|
+
find { |cred| cred["registry"] == registry }&.
|
87
|
+
fetch("token")
|
88
|
+
end
|
89
|
+
|
90
|
+
def locked_registry
|
91
|
+
return unless private_registry_source_url
|
92
|
+
|
93
|
+
lockfile_registry =
|
94
|
+
private_registry_source_url.
|
95
|
+
gsub("https://", "").
|
96
|
+
gsub("http://", "")
|
97
|
+
detailed_registry =
|
98
|
+
known_registries.
|
99
|
+
find { |h| h["registry"].include?(lockfile_registry) }&.
|
100
|
+
fetch("registry")
|
101
|
+
|
102
|
+
detailed_registry || lockfile_registry
|
103
|
+
end
|
104
|
+
|
105
|
+
def known_registries
|
106
|
+
@known_registries ||=
|
107
|
+
begin
|
108
|
+
registries = []
|
109
|
+
registries += credentials.
|
110
|
+
select { |cred| cred["type"] == "npm_registry" }
|
111
|
+
registries += npmrc_registries
|
112
|
+
registries += yarnrc_registries
|
113
|
+
|
114
|
+
unique_registries(registries)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def npmrc_registries
|
119
|
+
return [] unless npmrc_file
|
120
|
+
|
121
|
+
registries = []
|
122
|
+
npmrc_file.content.scan(NPM_AUTH_TOKEN_REGEX) do
|
123
|
+
next if Regexp.last_match[:registry].include?("${")
|
124
|
+
|
125
|
+
registries << {
|
126
|
+
"type" => "npm_registry",
|
127
|
+
"registry" => Regexp.last_match[:registry],
|
128
|
+
"token" => Regexp.last_match[:token]
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
npmrc_file.content.scan(NPM_GLOBAL_REGISTRY_REGEX) do
|
133
|
+
next if Regexp.last_match[:registry].include?("${")
|
134
|
+
|
135
|
+
registry = Regexp.last_match[:registry].strip.
|
136
|
+
sub(%r{/+$}, "").
|
137
|
+
sub(%r{^.*?//}, "")
|
138
|
+
next if registries.map { |r| r["registry"] }.include?(registry)
|
139
|
+
|
140
|
+
registries << {
|
141
|
+
"type" => "npm_registry",
|
142
|
+
"registry" => registry,
|
143
|
+
"token" => nil
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
registries
|
148
|
+
end
|
149
|
+
|
150
|
+
def yarnrc_registries
|
151
|
+
return [] unless yarnrc_file
|
152
|
+
|
153
|
+
registries = []
|
154
|
+
yarnrc_file.content.scan(YARN_GLOBAL_REGISTRY_REGEX) do
|
155
|
+
next if Regexp.last_match[:registry].include?("${")
|
156
|
+
|
157
|
+
registry = Regexp.last_match[:registry].strip.
|
158
|
+
sub(%r{/+$}, "").
|
159
|
+
sub(%r{^.*?//}, "")
|
160
|
+
registries << {
|
161
|
+
"type" => "npm_registry",
|
162
|
+
"registry" => registry,
|
163
|
+
"token" => nil
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
registries
|
168
|
+
end
|
169
|
+
|
170
|
+
def unique_registries(registries)
|
171
|
+
registries.uniq.reject do |registry|
|
172
|
+
next if registry["token"]
|
173
|
+
|
174
|
+
# Reject this entry if an identical one with a token exists
|
175
|
+
registries.any? do |r|
|
176
|
+
r["token"] && r["registry"] == registry["registry"]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def global_registry
|
182
|
+
npmrc_file&.content.to_s.scan(NPM_GLOBAL_REGISTRY_REGEX) do
|
183
|
+
next if Regexp.last_match[:registry].include?("${")
|
184
|
+
|
185
|
+
registry = Regexp.last_match[:registry].strip.
|
186
|
+
sub(%r{/+$}, "").
|
187
|
+
sub(%r{^.*?//}, "")
|
188
|
+
return registry
|
189
|
+
end
|
190
|
+
|
191
|
+
yarnrc_file&.content.to_s.scan(YARN_GLOBAL_REGISTRY_REGEX) do
|
192
|
+
next if Regexp.last_match[:registry].include?("${")
|
193
|
+
|
194
|
+
registry = Regexp.last_match[:registry].strip.
|
195
|
+
sub(%r{/+$}, "").
|
196
|
+
sub(%r{^.*?//}, "")
|
197
|
+
return registry
|
198
|
+
end
|
199
|
+
|
200
|
+
"registry.npmjs.org"
|
201
|
+
end
|
202
|
+
|
203
|
+
# npm registries expect slashes to be escaped
|
204
|
+
def escaped_dependency_name
|
205
|
+
dependency.name.gsub("/", "%2F")
|
206
|
+
end
|
207
|
+
|
208
|
+
def private_registry_source_url
|
209
|
+
sources = dependency.requirements.
|
210
|
+
map { |r| r.fetch(:source) }.uniq.compact
|
211
|
+
|
212
|
+
# If there are multiple source types, or multiple source URLs, then
|
213
|
+
# it's unclear how we should proceed
|
214
|
+
if sources.map { |s| [s[:type], s[:url]] }.uniq.count > 1
|
215
|
+
raise "Multiple sources! #{sources.join(', ')}"
|
216
|
+
end
|
217
|
+
|
218
|
+
# Otherwise we just take the URL of the first private registry
|
219
|
+
sources.find { |s| s[:type] == "private_registry" }&.fetch(:url)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|