dependabot-npm_and_yarn 0.303.0 → 0.304.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,10 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
5
  require "dependabot/npm_and_yarn/update_checker"
6
6
  require "dependabot/registry_client"
7
+ require "sorbet-runtime"
7
8
 
8
9
  module Dependabot
9
10
  module NpmAndYarn
@@ -11,6 +12,8 @@ module Dependabot
11
12
  class RegistryFinder
12
13
  extend T::Sig
13
14
 
15
+ GLOBAL_NPM_REGISTRY = "https://registry.npmjs.org"
16
+
14
17
  CENTRAL_REGISTRIES = %w(
15
18
  https://registry.npmjs.org
16
19
  http://registry.npmjs.org
@@ -23,6 +26,15 @@ module Dependabot
23
26
  NPM_SCOPED_REGISTRY_REGEX = /^(?<scope>@[^:]+)\s*:registry\s*=\s*['"]?(?<registry>.*?)['"]?$/
24
27
  YARN_SCOPED_REGISTRY_REGEX = /['"](?<scope>@[^:]+):registry['"]\s((['"](?<registry>.*)['"])|(?<registry>.*))/
25
28
 
29
+ sig do
30
+ params(
31
+ dependency: T.nilable(Dependabot::Dependency),
32
+ credentials: T::Array[Dependabot::Credential],
33
+ npmrc_file: T.nilable(Dependabot::DependencyFile),
34
+ yarnrc_file: T.nilable(Dependabot::DependencyFile),
35
+ yarnrc_yml_file: T.nilable(Dependabot::DependencyFile)
36
+ ).void
37
+ end
26
38
  def initialize(dependency:, credentials:, npmrc_file: nil,
27
39
  yarnrc_file: nil, yarnrc_yml_file: nil)
28
40
  @dependency = dependency
@@ -30,58 +42,91 @@ module Dependabot
30
42
  @npmrc_file = npmrc_file
31
43
  @yarnrc_file = yarnrc_file
32
44
  @yarnrc_yml_file = yarnrc_yml_file
45
+
46
+ @registry = T.let(nil, T.nilable(String))
47
+ @first_registry_with_dependency_details = T.let(nil, T.nilable(String))
48
+ @known_registries = T.let([], T::Array[T::Hash[String, T.nilable(String)]])
49
+ @configured_global_registry = T.let(nil, T.nilable(String))
50
+ @global_registry = T.let(nil, T.nilable(String))
51
+ @parsed_yarnrc_yml = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
33
52
  end
34
53
 
54
+ sig { returns(String) }
35
55
  def registry
36
- @registry ||= locked_registry || configured_registry || first_registry_with_dependency_details
56
+ return @registry if @registry
57
+
58
+ @registry = locked_registry || configured_registry || first_registry_with_dependency_details
59
+ T.must(@registry)
37
60
  end
38
61
 
62
+ sig { returns(T::Hash[String, String]) }
39
63
  def auth_headers
40
64
  auth_header_for(auth_token)
41
65
  end
42
66
 
67
+ sig { returns(String) }
43
68
  def dependency_url
44
69
  "#{registry_url}/#{escaped_dependency_name}"
45
70
  end
46
71
 
72
+ sig { params(version: T.any(String, Gem::Version)).returns(String) }
47
73
  def tarball_url(version)
48
74
  version_without_build_metadata = version.to_s.gsub(/\+.*/, "")
49
75
 
50
76
  # Dependency name needs to be unescaped since tarball URLs don't always work with escaped slashes
51
- "#{registry_url}/#{dependency.name}/-/#{scopeless_name}-#{version_without_build_metadata}.tgz"
77
+ "#{registry_url}/#{dependency&.name}/-/#{scopeless_name}-#{version_without_build_metadata}.tgz"
52
78
  end
53
79
 
80
+ sig { params(registry: String).returns(T::Boolean) }
54
81
  def self.central_registry?(registry)
55
82
  CENTRAL_REGISTRIES.any? do |r|
56
83
  r.include?(registry)
57
84
  end
58
85
  end
59
86
 
87
+ sig { params(dependency_name: String).returns(T.nilable(String)) }
60
88
  def registry_from_rc(dependency_name)
61
89
  explicit_registry_from_rc(dependency_name) || global_registry
62
90
  end
63
91
 
92
+ sig { returns(T::Boolean) }
93
+ def custom_registry?
94
+ return false if CENTRAL_REGISTRIES.include?(registry_url)
95
+
96
+ !(registry_url || "").match?(/registry\.npmjs\.(org|com)/)
97
+ end
98
+
64
99
  private
65
100
 
101
+ sig { returns(T.nilable(Dependabot::Dependency)) }
66
102
  attr_reader :dependency
103
+
104
+ sig { returns(T::Array[Dependabot::Credential]) }
67
105
  attr_reader :credentials
106
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
68
107
  attr_reader :npmrc_file
108
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
69
109
  attr_reader :yarnrc_file
110
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
70
111
  attr_reader :yarnrc_yml_file
71
112
 
113
+ sig { params(dependency_name: T.nilable(String)).returns(T.nilable(String)) }
72
114
  def explicit_registry_from_rc(dependency_name)
73
- if dependency_name.start_with?("@") && dependency_name.include?("/")
115
+ if dependency_name&.start_with?("@") && dependency_name.include?("/")
74
116
  scope = dependency_name.split("/").first
75
- scoped_registry(scope) || configured_global_registry
117
+ scoped_registry(T.must(scope)) || configured_global_registry
76
118
  else
77
119
  configured_global_registry
78
120
  end
79
121
  end
80
122
 
123
+ sig { returns(T.nilable(String)) }
81
124
  def first_registry_with_dependency_details
125
+ return @first_registry_with_dependency_details if @first_registry_with_dependency_details
126
+
82
127
  @first_registry_with_dependency_details ||=
83
128
  known_registries.find do |details|
84
- url = "#{details['registry'].gsub(%r{/+$}, '')}/#{escaped_dependency_name}"
129
+ url = "#{details['registry']&.gsub(%r{/+$}, '')}/#{escaped_dependency_name}"
85
130
  url = "https://#{url}" unless url.start_with?("http")
86
131
  response = Dependabot::RegistryClient.get(
87
132
  url: url,
@@ -99,6 +144,7 @@ module Dependabot
99
144
  @first_registry_with_dependency_details ||= global_registry.sub(%r{/+$}, "").sub(%r{^.*?//}, "")
100
145
  end
101
146
 
147
+ sig { returns(T.nilable(String)) }
102
148
  def registry_url
103
149
  url =
104
150
  if registry.start_with?("http")
@@ -106,7 +152,7 @@ module Dependabot
106
152
  else
107
153
  protocol =
108
154
  if registry_source_url
109
- registry_source_url.split("://").first
155
+ registry_source_url&.split("://")&.first
110
156
  else
111
157
  "https"
112
158
  end
@@ -117,6 +163,7 @@ module Dependabot
117
163
  url.gsub(%r{/+$}, "")
118
164
  end
119
165
 
166
+ sig { params(token: T.nilable(String)).returns(T::Hash[String, String]) }
120
167
  def auth_header_for(token)
121
168
  return {} unless token
122
169
 
@@ -131,36 +178,45 @@ module Dependabot
131
178
  end
132
179
  end
133
180
 
181
+ sig { returns(T.nilable(String)) }
134
182
  def auth_token
135
183
  known_registries
136
184
  .find { |cred| cred["registry"] == registry }
137
185
  &.fetch("token", nil)
138
186
  end
139
187
 
188
+ sig { returns(T.nilable(String)) }
140
189
  def locked_registry
141
190
  return unless registry_source_url
142
191
 
143
192
  lockfile_registry =
144
193
  registry_source_url
145
- .gsub("https://", "")
146
- .gsub("http://", "")
147
- detailed_registry =
148
- known_registries
149
- .find { |h| h["registry"].include?(lockfile_registry) }
150
- &.fetch("registry")
194
+ &.gsub("https://", "")
195
+ &.gsub("http://", "")
196
+
197
+ if lockfile_registry
198
+ detailed_registry =
199
+ known_registries
200
+ .find { |h| h["registry"]&.include?(lockfile_registry) }
201
+ &.fetch("registry")
202
+ end
151
203
 
152
204
  detailed_registry || lockfile_registry
153
205
  end
154
206
 
207
+ sig { returns(T.nilable(String)) }
155
208
  def configured_registry
156
- configured_registry_url = explicit_registry_from_rc(dependency.name)
209
+ configured_registry_url = explicit_registry_from_rc(dependency&.name)
157
210
  return unless configured_registry_url
158
211
 
159
212
  normalize_configured_registry(configured_registry_url)
160
213
  end
161
214
 
215
+ sig { returns(T::Array[T::Hash[String, T.nilable(String)]]) }
162
216
  def known_registries
163
- @known_registries ||=
217
+ return @known_registries if @known_registries.any?
218
+
219
+ @known_registries =
164
220
  begin
165
221
  registries = []
166
222
  registries += credentials
@@ -171,13 +227,15 @@ module Dependabot
171
227
 
172
228
  unique_registries(registries)
173
229
  end
230
+ @known_registries
174
231
  end
175
232
 
233
+ sig { returns(T::Array[T::Hash[String, T.nilable(String)]]) }
176
234
  def npmrc_registries
177
235
  return [] unless npmrc_file
178
236
 
179
237
  registries = []
180
- npmrc_file.content.scan(NPM_AUTH_TOKEN_REGEX) do
238
+ npmrc_file&.content&.scan(NPM_AUTH_TOKEN_REGEX) do
181
239
  next if Regexp.last_match&.[](:registry)&.include?("${")
182
240
 
183
241
  registry = T.must(Regexp.last_match)[:registry]
@@ -193,12 +251,17 @@ module Dependabot
193
251
  registries += npmrc_global_registries
194
252
  end
195
253
 
254
+ sig { returns(T::Array[T::Hash[String, T.nilable(String)]]) }
196
255
  def yarnrc_registries
197
256
  return [] unless yarnrc_file
198
257
 
199
258
  yarnrc_global_registries
200
259
  end
201
260
 
261
+ sig do
262
+ params(registries: T::Array[T::Hash[String, T.nilable(String)]])
263
+ .returns(T::Array[T::Hash[String, T.nilable(String)]])
264
+ end
202
265
  def unique_registries(registries)
203
266
  registries.uniq.reject do |registry|
204
267
  next if registry["token"]
@@ -210,28 +273,31 @@ module Dependabot
210
273
  end
211
274
  end
212
275
 
276
+ sig { returns(String) }
213
277
  def global_registry
214
- return @global_registry if defined? @global_registry
278
+ return @global_registry if @global_registry
215
279
 
216
- @global_registry ||= configured_global_registry || "https://registry.npmjs.org"
280
+ @global_registry = configured_global_registry || GLOBAL_NPM_REGISTRY
281
+ @global_registry
217
282
  end
218
283
 
219
284
  # rubocop:disable Metrics/PerceivedComplexity
285
+ sig { returns(T.nilable(String)) }
220
286
  def configured_global_registry
221
- return @configured_global_registry if defined? @configured_global_registry
287
+ return @configured_global_registry if @configured_global_registry
222
288
 
223
289
  @configured_global_registry = (npmrc_file && npmrc_global_registries.first&.fetch("url")) ||
224
290
  (yarnrc_file && yarnrc_global_registries.first&.fetch("url"))
225
291
  return @configured_global_registry if @configured_global_registry
226
292
 
227
293
  if parsed_yarnrc_yml&.key?("npmRegistryServer")
228
- return @configured_global_registry = parsed_yarnrc_yml["npmRegistryServer"]
294
+ return @configured_global_registry = T.must(parsed_yarnrc_yml)["npmRegistryServer"]
229
295
  end
230
296
 
231
297
  replaces_base = credentials.find { |cred| cred["type"] == "npm_registry" && cred.replaces_base? }
232
298
  if replaces_base
233
299
  registry = replaces_base["registry"]
234
- registry = "https://#{registry}" unless registry.start_with?("http")
300
+ registry = "https://#{registry}" unless registry&.start_with?("http")
235
301
  return @configured_global_registry = registry
236
302
  end
237
303
 
@@ -239,31 +305,40 @@ module Dependabot
239
305
  end
240
306
  # rubocop:enable Metrics/PerceivedComplexity
241
307
 
308
+ sig { returns(T::Array[T::Hash[String, String]]) }
242
309
  def npmrc_global_registries
243
310
  global_rc_registries(npmrc_file, syntax: NPM_GLOBAL_REGISTRY_REGEX)
244
311
  end
245
312
 
313
+ sig { returns(T::Array[T::Hash[String, String]]) }
246
314
  def yarnrc_global_registries
247
315
  global_rc_registries(yarnrc_file, syntax: YARN_GLOBAL_REGISTRY_REGEX)
248
316
  end
249
317
 
318
+ sig { params(scope: String).returns(T.nilable(String)) }
250
319
  def scoped_registry(scope)
251
320
  scoped_rc_registry = scoped_rc_registry(npmrc_file, syntax: NPM_SCOPED_REGISTRY_REGEX, scope: scope) ||
252
321
  scoped_rc_registry(yarnrc_file, syntax: YARN_SCOPED_REGISTRY_REGEX, scope: scope)
253
322
  return scoped_rc_registry if scoped_rc_registry
254
323
 
255
324
  if parsed_yarnrc_yml
256
- yarn_berry_registry = parsed_yarnrc_yml.dig("npmScopes", scope.delete_prefix("@"), "npmRegistryServer")
325
+ yarn_berry_registry = parsed_yarnrc_yml&.dig("npmScopes", scope.delete_prefix("@"), "npmRegistryServer")
257
326
  return yarn_berry_registry if yarn_berry_registry
258
327
  end
259
328
 
260
329
  nil
261
330
  end
262
331
 
332
+ sig do
333
+ params(
334
+ file: T.nilable(Dependabot::DependencyFile),
335
+ syntax: T.any(String, Regexp)
336
+ ).returns(T::Array[T::Hash[String, String]])
337
+ end
263
338
  def global_rc_registries(file, syntax:)
264
339
  registries = []
265
340
 
266
- file.content.scan(syntax) do
341
+ file&.content&.scan(syntax) do
267
342
  next if Regexp.last_match&.[](:registry)&.include?("${")
268
343
 
269
344
  url = T.must(T.must(Regexp.last_match)[:registry]).strip
@@ -279,6 +354,13 @@ module Dependabot
279
354
  registries
280
355
  end
281
356
 
357
+ sig do
358
+ params(
359
+ file: T.nilable(Dependabot::DependencyFile),
360
+ syntax: T.any(String, Regexp),
361
+ scope: String
362
+ ).returns(T.nilable(String))
363
+ end
282
364
  def scoped_rc_registry(file, syntax:, scope:)
283
365
  file&.content.to_s.scan(syntax) do
284
366
  next if Regexp.last_match&.[](:registry)&.include?("${") || Regexp.last_match&.[](:scope) != scope
@@ -290,29 +372,36 @@ module Dependabot
290
372
  end
291
373
 
292
374
  # npm registries expect slashes to be escaped
375
+ sig { returns(T.nilable(String)) }
293
376
  def escaped_dependency_name
294
- dependency.name.gsub("/", "%2F")
377
+ dependency&.name&.gsub("/", "%2F")
295
378
  end
296
379
 
380
+ sig { returns(T.nilable(String)) }
297
381
  def scopeless_name
298
- dependency.name.split("/").last
382
+ dependency&.name&.split("/")&.last
299
383
  end
300
384
 
301
- def registry_source_url
302
- sources = dependency.requirements
303
- .map { |r| r.fetch(:source) }.uniq.compact
304
- .sort_by { |source| self.class.central_registry?(source[:url]) ? 1 : 0 }
385
+ sig { returns(T.nilable(String)) }
386
+ def registry_source_url # rubocop:disable Metrics/PerceivedComplexity
387
+ sources = dependency&.requirements
388
+ &.map { |r| r.fetch(:source) }&.uniq&.compact
389
+ &.sort_by { |source| self.class.central_registry?(source[:url]) ? 1 : 0 }
305
390
 
306
- sources.find { |s| s[:type] == "registry" }&.fetch(:url)
391
+ sources&.find { |s| s[:type] == "registry" }&.fetch(:url)
307
392
  end
308
393
 
394
+ sig { returns(T.nilable(T::Hash[String, T.untyped])) }
309
395
  def parsed_yarnrc_yml
310
- return unless yarnrc_yml_file
311
- return @parsed_yarnrc_yml if defined? @parsed_yarnrc_yml
396
+ yarnrc_yml_file_content = yarnrc_yml_file&.content
397
+ return unless yarnrc_yml_file_content
398
+ return @parsed_yarnrc_yml if @parsed_yarnrc_yml
312
399
 
313
- @parsed_yarnrc_yml = YAML.safe_load(yarnrc_yml_file.content)
400
+ @parsed_yarnrc_yml = YAML.safe_load(yarnrc_yml_file_content)
401
+ @parsed_yarnrc_yml
314
402
  end
315
403
 
404
+ sig { params(url: String).returns(String) }
316
405
  def normalize_configured_registry(url)
317
406
  url.sub(%r{/+$}, "")
318
407
  .sub(%r{^.*?//}, "")