dependabot-npm_and_yarn 0.262.0 → 0.264.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,8 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "sorbet-runtime"
5
+
4
6
  require "dependabot/errors"
5
7
  require "dependabot/logger"
6
8
  require "dependabot/npm_and_yarn/file_parser"
@@ -15,9 +17,20 @@ module Dependabot
15
17
  module NpmAndYarn
16
18
  class FileUpdater < Dependabot::FileUpdaters::Base
17
19
  class NpmLockfileUpdater
20
+ extend T::Sig
21
+
18
22
  require_relative "npmrc_builder"
19
23
  require_relative "package_json_updater"
20
24
 
25
+ sig do
26
+ params(
27
+ lockfile: Dependabot::DependencyFile,
28
+ dependencies: T::Array[Dependabot::Dependency],
29
+ dependency_files: T::Array[Dependabot::DependencyFile],
30
+ credentials: T::Array[Credential]
31
+ )
32
+ .void
33
+ end
21
34
  def initialize(lockfile:, dependencies:, dependency_files:, credentials:)
22
35
  @lockfile = lockfile
23
36
  @dependencies = dependencies
@@ -25,6 +38,7 @@ module Dependabot
25
38
  @credentials = credentials
26
39
  end
27
40
 
41
+ sig { returns(Dependabot::DependencyFile) }
28
42
  def updated_lockfile
29
43
  updated_file = lockfile.dup
30
44
  updated_file.content = updated_lockfile_content
@@ -33,9 +47,16 @@ module Dependabot
33
47
 
34
48
  private
35
49
 
50
+ sig { returns(Dependabot::DependencyFile) }
36
51
  attr_reader :lockfile
52
+
53
+ sig { returns(T::Array[Dependabot::Dependency]) }
37
54
  attr_reader :dependencies
55
+
56
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
38
57
  attr_reader :dependency_files
58
+
59
+ sig { returns(T::Array[Credential]) }
39
60
  attr_reader :credentials
40
61
 
41
62
  UNREACHABLE_GIT = /fatal: repository '(?<url>.*)' not found/
@@ -54,44 +75,54 @@ module Dependabot
54
75
  NPM8_MISSING_GIT_REF = /already exists and is not an empty directory/
55
76
  NPM6_MISSING_GIT_REF = /did not match any file\(s\) known to git/
56
77
 
78
+ sig { returns(T.nilable(String)) }
57
79
  def updated_lockfile_content
58
80
  return lockfile.content if npmrc_disables_lockfile?
59
81
  return lockfile.content unless updatable_dependencies.any?
60
82
 
61
- @updated_lockfile_content ||=
83
+ @updated_lockfile_content ||= T.let(
62
84
  SharedHelpers.in_a_temporary_directory do
63
85
  write_temporary_dependency_files
64
86
  updated_files = Dir.chdir(lockfile_directory) { run_current_npm_update }
65
87
  updated_lockfile_content = updated_files.fetch(lockfile_basename)
66
88
  post_process_npm_lockfile(updated_lockfile_content)
67
- end
89
+ end,
90
+ T.nilable(String)
91
+ )
68
92
  rescue SharedHelpers::HelperSubprocessFailed => e
69
93
  handle_npm_updater_error(e)
70
94
  end
71
95
 
96
+ sig { returns(T::Array[Dependabot::Dependency]) }
72
97
  def top_level_dependencies
73
98
  dependencies.select(&:top_level?)
74
99
  end
75
100
 
101
+ sig { returns(T::Array[Dependabot::Dependency]) }
76
102
  def sub_dependencies
77
103
  dependencies.reject(&:top_level?)
78
104
  end
79
105
 
106
+ sig { returns(T::Array[Dependabot::Dependency]) }
80
107
  def updatable_dependencies
81
108
  dependencies.reject do |dependency|
82
109
  dependency_up_to_date?(dependency) || top_level_dependency_update_not_required?(dependency)
83
110
  end
84
111
  end
85
112
 
113
+ sig { returns(T::Array[Dependabot::Dependency]) }
86
114
  def lockfile_dependencies
87
- @lockfile_dependencies ||=
115
+ @lockfile_dependencies ||= T.let(
88
116
  NpmAndYarn::FileParser.new(
89
117
  dependency_files: [lockfile, *package_files],
90
118
  source: nil,
91
119
  credentials: credentials
92
- ).parse
120
+ ).parse,
121
+ T.nilable(T::Array[Dependabot::Dependency])
122
+ )
93
123
  end
94
124
 
125
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
95
126
  def dependency_up_to_date?(dependency)
96
127
  existing_dep = lockfile_dependencies.find { |dep| dep.name == dependency.name }
97
128
 
@@ -101,7 +132,7 @@ module Dependabot
101
132
  # (likely it is no longer required)
102
133
  return !dependency.top_level? if existing_dep.nil?
103
134
 
104
- existing_dep&.version == dependency.version
135
+ existing_dep.version == dependency.version
105
136
  end
106
137
 
107
138
  # NOTE: Prevent changes to npm 6 lockfiles when the dependency has been
@@ -109,16 +140,19 @@ module Dependabot
109
140
  # proj). npm 7 introduces workspace support so we explicitly want to
110
141
  # update the root lockfile and check if the dependency is in the
111
142
  # lockfile
143
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
112
144
  def top_level_dependency_update_not_required?(dependency)
113
145
  dependency.top_level? &&
114
146
  !dependency_in_package_json?(dependency) &&
115
147
  !dependency_in_lockfile?(dependency)
116
148
  end
117
149
 
150
+ sig { returns(T::Hash[String, String]) }
118
151
  def run_current_npm_update
119
152
  run_npm_updater(top_level_dependencies: top_level_dependencies, sub_dependencies: sub_dependencies)
120
153
  end
121
154
 
155
+ sig { returns(T::Hash[String, String]) }
122
156
  def run_previous_npm_update
123
157
  previous_top_level_dependencies = top_level_dependencies.map do |d|
124
158
  Dependabot::Dependency.new(
@@ -126,7 +160,7 @@ module Dependabot
126
160
  package_manager: d.package_manager,
127
161
  version: d.previous_version,
128
162
  previous_version: d.previous_version,
129
- requirements: d.previous_requirements,
163
+ requirements: T.must(d.previous_requirements),
130
164
  previous_requirements: d.previous_requirements
131
165
  )
132
166
  end
@@ -146,35 +180,47 @@ module Dependabot
146
180
  sub_dependencies: previous_sub_dependencies)
147
181
  end
148
182
 
183
+ sig do
184
+ params(
185
+ top_level_dependencies: T::Array[Dependabot::Dependency],
186
+ sub_dependencies: T::Array[Dependabot::Dependency]
187
+ )
188
+ .returns(T::Hash[String, String])
189
+ end
149
190
  def run_npm_updater(top_level_dependencies:, sub_dependencies:)
150
191
  SharedHelpers.with_git_configured(credentials: credentials) do
151
- updated_files = {}
192
+ updated_files = T.let({}, T::Hash[String, String])
152
193
  if top_level_dependencies.any?
153
194
  updated_files.merge!(run_npm_top_level_updater(top_level_dependencies: top_level_dependencies))
154
195
  end
155
196
  if sub_dependencies.any?
156
- updated_files.merge!(run_npm_subdependency_updater(sub_dependencies: sub_dependencies))
197
+ updated_files.merge!(T.must(run_npm_subdependency_updater(sub_dependencies: sub_dependencies)))
157
198
  end
158
199
  updated_files
159
200
  end
160
201
  end
161
202
 
203
+ sig { params(top_level_dependencies: T::Array[Dependabot::Dependency]).returns(T::Hash[String, String]) }
162
204
  def run_npm_top_level_updater(top_level_dependencies:)
163
205
  if npm8?
164
206
  run_npm8_top_level_updater(top_level_dependencies: top_level_dependencies)
165
207
  else
166
- SharedHelpers.run_helper_subprocess(
167
- command: NativeHelpers.helper_path,
168
- function: "npm6:update",
169
- args: [
170
- Dir.pwd,
171
- lockfile_basename,
172
- top_level_dependencies.map(&:to_h)
173
- ]
208
+ T.cast(
209
+ SharedHelpers.run_helper_subprocess(
210
+ command: NativeHelpers.helper_path,
211
+ function: "npm6:update",
212
+ args: [
213
+ Dir.pwd,
214
+ lockfile_basename,
215
+ top_level_dependencies.map(&:to_h)
216
+ ]
217
+ ),
218
+ T::Hash[String, String]
174
219
  )
175
220
  end
176
221
  end
177
222
 
223
+ sig { params(top_level_dependencies: T::Array[Dependabot::Dependency]).returns(T::Hash[String, String]) }
178
224
  def run_npm8_top_level_updater(top_level_dependencies:)
179
225
  dependencies_in_current_package_json = top_level_dependencies.any? do |dependency|
180
226
  dependency_in_package_json?(dependency)
@@ -186,7 +232,7 @@ module Dependabot
186
232
  # lockfile. To overcome this, we save the content before the update,
187
233
  # and then re-run `npm install` after the update against the previous
188
234
  # content to remove that
189
- previous_package_json = File.read(package_json.name)
235
+ previous_package_json = File.read(T.must(package_json).name)
190
236
  end
191
237
 
192
238
  # TODO: Update the npm 6 updater to use these args as we currently
@@ -194,10 +240,10 @@ module Dependabot
194
240
  # the npm 7 rollout
195
241
  install_args = top_level_dependencies.map { |dependency| npm_install_args(dependency) }
196
242
 
197
- run_npm_install_lockfile_only(*install_args)
243
+ run_npm_install_lockfile_only(install_args)
198
244
 
199
245
  unless dependencies_in_current_package_json
200
- File.write(package_json.name, previous_package_json)
246
+ File.write(T.must(package_json).name, previous_package_json)
201
247
 
202
248
  run_npm_install_lockfile_only
203
249
  end
@@ -205,24 +251,32 @@ module Dependabot
205
251
  { lockfile_basename => File.read(lockfile_basename) }
206
252
  end
207
253
 
254
+ sig do
255
+ params(sub_dependencies: T::Array[Dependabot::Dependency]).returns(T.nilable(T::Hash[String, String]))
256
+ end
208
257
  def run_npm_subdependency_updater(sub_dependencies:)
209
258
  if npm8?
210
259
  run_npm8_subdependency_updater(sub_dependencies: sub_dependencies)
211
260
  else
212
- SharedHelpers.run_helper_subprocess(
213
- command: NativeHelpers.helper_path,
214
- function: "npm6:updateSubdependency",
215
- args: [Dir.pwd, lockfile_basename, sub_dependencies.map(&:to_h)]
261
+ T.cast(
262
+ SharedHelpers.run_helper_subprocess(
263
+ command: NativeHelpers.helper_path,
264
+ function: "npm6:updateSubdependency",
265
+ args: [Dir.pwd, lockfile_basename, sub_dependencies.map(&:to_h)]
266
+ ),
267
+ T.nilable(T::Hash[String, String])
216
268
  )
217
269
  end
218
270
  end
219
271
 
272
+ sig { params(sub_dependencies: T::Array[Dependabot::Dependency]).returns(T::Hash[String, String]) }
220
273
  def run_npm8_subdependency_updater(sub_dependencies:)
221
274
  dependency_names = sub_dependencies.map(&:name)
222
275
  NativeHelpers.run_npm8_subdependency_update_command(dependency_names)
223
276
  { lockfile_basename => File.read(lockfile_basename) }
224
277
  end
225
278
 
279
+ sig { params(dependency: Dependabot::Dependency).returns(T.nilable(String)) }
226
280
  def updated_version_requirement_for_dependency(dependency)
227
281
  flattenend_manifest_dependencies[dependency.name]
228
282
  end
@@ -231,13 +285,14 @@ module Dependabot
231
285
  # instead of fishing it out of the updated package json, we need to do
232
286
  # this because we don't store the same requirement in
233
287
  # Dependency#requirements for git dependencies - see PackageJsonUpdater
288
+ sig { returns(T::Hash[String, String]) }
234
289
  def flattenend_manifest_dependencies
235
- return @flattenend_manifest_dependencies if defined?(@flattenend_manifest_dependencies)
236
-
237
- @flattenend_manifest_dependencies =
290
+ @flattenend_manifest_dependencies ||= T.let(
238
291
  NpmAndYarn::FileParser::DEPENDENCY_TYPES.inject({}) do |deps, type|
239
292
  deps.merge(parsed_package_json[type] || {})
240
- end
293
+ end,
294
+ T.nilable(T::Hash[String, String])
295
+ )
241
296
  end
242
297
 
243
298
  # Runs `npm install` with `--package-lock-only` flag to update the
@@ -249,7 +304,8 @@ module Dependabot
249
304
  # to work around an issue in npm 6, we don't want that here
250
305
  # - `--ignore-scripts` disables prepare and prepack scripts which are
251
306
  # run when installing git dependencies
252
- def run_npm_install_lockfile_only(*install_args)
307
+ sig { params(install_args: T::Array[String]).returns(String) }
308
+ def run_npm_install_lockfile_only(install_args = [])
253
309
  command = [
254
310
  "install",
255
311
  *install_args,
@@ -273,6 +329,7 @@ module Dependabot
273
329
  Helpers.run_npm_command(command, fingerprint: fingerprint)
274
330
  end
275
331
 
332
+ sig { params(dependency: Dependabot::Dependency).returns(String) }
276
333
  def npm_install_args(dependency)
277
334
  git_requirement = dependency.requirements.find { |req| req[:source] && req[:source][:type] == "git" }
278
335
 
@@ -302,12 +359,14 @@ module Dependabot
302
359
  end
303
360
  end
304
361
 
362
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
305
363
  def dependency_in_package_json?(dependency)
306
364
  dependency.requirements.any? do |req|
307
- req[:file] == package_json.name
365
+ req[:file] == T.must(package_json).name
308
366
  end
309
367
  end
310
368
 
369
+ sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
311
370
  def dependency_in_lockfile?(dependency)
312
371
  lockfile_dependencies.any? do |dep|
313
372
  dep.name == dependency.name
@@ -318,13 +377,14 @@ module Dependabot
318
377
  # rubocop:disable Metrics/CyclomaticComplexity
319
378
  # rubocop:disable Metrics/PerceivedComplexity
320
379
  # rubocop:disable Metrics/MethodLength
380
+ sig { params(error: Exception).returns(T.noreturn) }
321
381
  def handle_npm_updater_error(error)
322
382
  error_message = error.message
323
383
  if error_message.match?(MISSING_PACKAGE)
324
- package_name = error_message.match(MISSING_PACKAGE)
325
- .named_captures["package_req"]
326
- sanitized_name = sanitize_package_name(package_name)
327
- sanitized_error = error_message.gsub(package_name, sanitized_name)
384
+ package_name = T.must(error_message.match(MISSING_PACKAGE))
385
+ .named_captures["package_req"]
386
+ sanitized_name = sanitize_package_name(T.must(package_name))
387
+ sanitized_error = error_message.gsub(T.must(package_name), sanitized_name)
328
388
  handle_missing_package(sanitized_name, sanitized_error)
329
389
  end
330
390
 
@@ -370,26 +430,26 @@ module Dependabot
370
430
  end
371
431
 
372
432
  if error_message.match?(FORBIDDEN_PACKAGE)
373
- package_name = error_message.match(FORBIDDEN_PACKAGE)
374
- .named_captures["package_req"]
375
- sanitized_name = sanitize_package_name(package_name)
376
- sanitized_error = error_message.gsub(package_name, sanitized_name)
433
+ package_name = T.must(error_message.match(FORBIDDEN_PACKAGE))
434
+ .named_captures["package_req"]
435
+ sanitized_name = sanitize_package_name(T.must(package_name))
436
+ sanitized_error = error_message.gsub(T.must(package_name), sanitized_name)
377
437
  handle_missing_package(sanitized_name, sanitized_error)
378
438
  end
379
439
 
380
440
  # Some private registries return a 403 when the user is readonly
381
441
  if error_message.match?(FORBIDDEN_PACKAGE_403)
382
- package_name = error_message.match(FORBIDDEN_PACKAGE_403)
383
- .named_captures["package_req"]
384
- sanitized_name = sanitize_package_name(package_name)
385
- sanitized_error = error_message.gsub(package_name, sanitized_name)
442
+ package_name = T.must(error_message.match(FORBIDDEN_PACKAGE_403))
443
+ .named_captures["package_req"]
444
+ sanitized_name = sanitize_package_name(T.must(package_name))
445
+ sanitized_error = error_message.gsub(T.must(package_name), sanitized_name)
386
446
  handle_missing_package(sanitized_name, sanitized_error)
387
447
  end
388
448
 
389
449
  if (git_error = error_message.match(UNREACHABLE_GIT) || error_message.match(FORBIDDEN_GIT))
390
450
  dependency_url = git_error.named_captures.fetch("url")
391
451
 
392
- raise Dependabot::GitDependenciesNotReachable, dependency_url
452
+ raise Dependabot::GitDependenciesNotReachable, T.must(dependency_url)
393
453
  end
394
454
 
395
455
  # This error happens when the lockfile has been messed up and some
@@ -421,7 +481,7 @@ module Dependabot
421
481
  end
422
482
 
423
483
  if error_message.include?("EBADENGINE")
424
- msg = "Dependabot uses Node.js #{`node --version`} and NPM #{`npm --version`}. " \
484
+ msg = "Dependabot uses Node.js #{`node --version`.strip} and NPM #{`npm --version`.strip}. " \
425
485
  "Due to the engine-strict setting, the update will not succeed."
426
486
  raise Dependabot::DependencyFileNotResolvable, msg
427
487
  end
@@ -433,6 +493,7 @@ module Dependabot
433
493
  # rubocop:enable Metrics/PerceivedComplexity
434
494
  # rubocop:enable Metrics/MethodLength
435
495
 
496
+ sig { params(error_message: String).returns(T.noreturn) }
436
497
  def raise_resolvability_error(error_message)
437
498
  dependency_names = dependencies.map(&:name).join(", ")
438
499
  msg = "Error whilst updating #{dependency_names} in " \
@@ -440,6 +501,7 @@ module Dependabot
440
501
  raise Dependabot::DependencyFileNotResolvable, msg
441
502
  end
442
503
 
504
+ sig { params(error_message: String).returns(T.noreturn) }
443
505
  def raise_missing_lockfile_version_resolvability_error(error_message)
444
506
  modules_path = File.join(lockfile_directory, "node_modules")
445
507
  # NOTE: don't include the dependency names to prevent opening
@@ -456,6 +518,7 @@ module Dependabot
456
518
  raise Dependabot::DependencyFileNotResolvable, msg
457
519
  end
458
520
 
521
+ sig { params(package_name: String, error_message: String).void }
459
522
  def handle_missing_package(package_name, error_message)
460
523
  missing_dep = lockfile_dependencies.find { |dep| dep.name == package_name }
461
524
 
@@ -474,10 +537,9 @@ module Dependabot
474
537
  raise Dependabot::PrivateSourceAuthenticationFailure, reg
475
538
  end
476
539
 
540
+ sig { returns(T::Boolean) }
477
541
  def resolvable_before_update?
478
- return @resolvable_before_update if defined?(@resolvable_before_update)
479
-
480
- @resolvable_before_update =
542
+ @resolvable_before_update ||= T.let(
481
543
  begin
482
544
  SharedHelpers.in_a_temporary_directory do
483
545
  write_temporary_dependency_files(update_package_json: false)
@@ -487,18 +549,22 @@ module Dependabot
487
549
  true
488
550
  rescue SharedHelpers::HelperSubprocessFailed
489
551
  false
490
- end
552
+ end,
553
+ T.nilable(T::Boolean)
554
+ )
491
555
  end
492
556
 
557
+ sig { params(error_message: String).returns(T::Boolean) }
493
558
  def dependencies_in_error_message?(error_message)
494
559
  names = dependencies.map { |dep| dep.name.split("/").first }
495
560
  # Example format: No matching version found for
496
561
  # @dependabot/dummy-pkg-b@^1.3.0
497
562
  names.any? do |name|
498
- error_message.match?(%r{#{Regexp.quote(name)}[\/@]})
563
+ error_message.match?(%r{#{Regexp.quote(T.must(name))}[\/@]})
499
564
  end
500
565
  end
501
566
 
567
+ sig { params(update_package_json: T::Boolean).void }
502
568
  def write_temporary_dependency_files(update_package_json: true)
503
569
  write_lockfiles
504
570
 
@@ -508,12 +574,13 @@ module Dependabot
508
574
  path = file.name
509
575
  FileUtils.mkdir_p(Pathname.new(path).dirname)
510
576
 
511
- updated_content =
577
+ updated_content = T.must(
512
578
  if update_package_json && top_level_dependencies.any?
513
579
  updated_package_json_content(file)
514
580
  else
515
581
  file.content
516
582
  end
583
+ )
517
584
 
518
585
  package_json_preparer = package_json_preparer(updated_content)
519
586
 
@@ -532,6 +599,7 @@ module Dependabot
532
599
  end
533
600
  end
534
601
 
602
+ sig { void }
535
603
  def write_lockfiles
536
604
  excluded_lock =
537
605
  case lockfile.name
@@ -549,8 +617,9 @@ module Dependabot
549
617
 
550
618
  # Takes a JSON string and detects if it is spaces or tabs and how many
551
619
  # levels deep it is indented.
620
+ sig { params(json: String).returns(String) }
552
621
  def detect_indentation(json)
553
- indentation = json.scan(/^[[:blank:]]+/).min_by(&:length)
622
+ indentation = T.cast(json.scan(/^[[:blank:]]+/).min_by(&:length), T.nilable(String))
554
623
  return "" if indentation.nil? # let npm set the default if we can't detect any indentation
555
624
 
556
625
  indentation_size = indentation.length
@@ -559,6 +628,7 @@ module Dependabot
559
628
  indentation_type * indentation_size
560
629
  end
561
630
 
631
+ sig { params(content: String).returns(String) }
562
632
  def lock_git_deps(content)
563
633
  return content if git_dependencies_to_lock.empty?
564
634
 
@@ -574,33 +644,35 @@ module Dependabot
574
644
  JSON.pretty_generate(json, indent: indent)
575
645
  end
576
646
 
647
+ sig { returns(T::Hash[String, T.untyped]) }
577
648
  def git_dependencies_to_lock
578
649
  return {} unless package_locks.any?
579
650
  return @git_dependencies_to_lock if @git_dependencies_to_lock
580
651
 
581
- @git_dependencies_to_lock = {}
652
+ @git_dependencies_to_lock = T.let({}, T.nilable(T::Hash[String, T.untyped]))
582
653
  dependency_names = dependencies.map(&:name)
583
654
 
584
655
  package_locks.each do |package_lock|
585
- parsed_lockfile = JSON.parse(package_lock.content)
656
+ parsed_lockfile = JSON.parse(T.must(package_lock.content))
586
657
  parsed_lockfile.fetch("dependencies", {}).each do |nm, details|
587
658
  next if dependency_names.include?(nm)
588
659
  next unless details["version"]
589
660
  next unless details["version"].start_with?("git")
590
661
 
591
- @git_dependencies_to_lock[nm] = {
662
+ T.must(@git_dependencies_to_lock)[nm] = {
592
663
  version: details["version"],
593
664
  from: details["from"]
594
665
  }
595
666
  end
596
667
  end
597
- @git_dependencies_to_lock
668
+ T.must(@git_dependencies_to_lock)
598
669
  end
599
670
 
600
671
  # When a package.json version requirement is set to `latest`, npm will
601
672
  # always try to update these dependencies when doing an `npm install`,
602
673
  # regardless of lockfile version. Prevent any unrelated updates by
603
674
  # changing the version requirement to `*` while updating the lockfile.
675
+ sig { params(content: String).returns(String) }
604
676
  def lock_deps_with_latest_reqs(content)
605
677
  json = JSON.parse(content)
606
678
 
@@ -614,14 +686,17 @@ module Dependabot
614
686
  JSON.pretty_generate(json, indent: indent)
615
687
  end
616
688
 
689
+ sig { returns(T::Array[String]) }
617
690
  def git_ssh_requirements_to_swap
618
- return @git_ssh_requirements_to_swap if @git_ssh_requirements_to_swap
619
-
620
- @git_ssh_requirements_to_swap = package_files.flat_map do |file|
621
- package_json_preparer(file.content).swapped_ssh_requirements
622
- end
691
+ @git_ssh_requirements_to_swap ||= T.let(
692
+ package_files.flat_map do |file|
693
+ package_json_preparer(T.must(file.content)).swapped_ssh_requirements
694
+ end,
695
+ T.nilable(T::Array[String])
696
+ )
623
697
  end
624
698
 
699
+ sig { params(updated_lockfile_content: String).returns(String) }
625
700
  def post_process_npm_lockfile(updated_lockfile_content)
626
701
  # Switch SSH requirements back for git dependencies
627
702
  updated_lockfile_content = replace_swapped_git_ssh_requirements(updated_lockfile_content)
@@ -648,6 +723,13 @@ module Dependabot
648
723
  replace_tarball_urls(updated_lockfile_content)
649
724
  end
650
725
 
726
+ sig do
727
+ params(
728
+ updated_lockfile_content: String,
729
+ parsed_updated_lockfile_content: T::Hash[String, T.untyped]
730
+ )
731
+ .returns(String)
732
+ end
651
733
  def replace_project_name(updated_lockfile_content, parsed_updated_lockfile_content)
652
734
  current_name = parsed_updated_lockfile_content["name"]
653
735
  original_name = parsed_lockfile["name"]
@@ -659,6 +741,13 @@ module Dependabot
659
741
  updated_lockfile_content
660
742
  end
661
743
 
744
+ sig do
745
+ params(
746
+ updated_lockfile_content: String,
747
+ parsed_updated_lockfile_content: T::Hash[String, T.untyped]
748
+ )
749
+ .returns(String)
750
+ end
662
751
  def restore_packages_name(updated_lockfile_content, parsed_updated_lockfile_content)
663
752
  return updated_lockfile_content unless npm8?
664
753
 
@@ -684,6 +773,14 @@ module Dependabot
684
773
  updated_lockfile_content
685
774
  end
686
775
 
776
+ sig do
777
+ params(
778
+ current_name: String,
779
+ original_name: String,
780
+ updated_lockfile_content: String
781
+ )
782
+ .returns(String)
783
+ end
687
784
  def replace_lockfile_name_attribute(current_name, original_name, updated_lockfile_content)
688
785
  updated_lockfile_content.sub(
689
786
  /"name":\s"#{current_name}"/,
@@ -691,6 +788,14 @@ module Dependabot
691
788
  )
692
789
  end
693
790
 
791
+ sig do
792
+ params(
793
+ current_name: String,
794
+ original_name: String,
795
+ updated_lockfile_content: String
796
+ )
797
+ .returns(String)
798
+ end
694
799
  def replace_lockfile_packages_name_attribute(current_name, original_name, updated_lockfile_content)
695
800
  packages_key_line = '"": {'
696
801
  updated_lockfile_content.sub(
@@ -699,6 +804,7 @@ module Dependabot
699
804
  )
700
805
  end
701
806
 
807
+ sig { params(current_name: String, updated_lockfile_content: String).returns(String) }
702
808
  def remove_lockfile_packages_name_attribute(current_name, updated_lockfile_content)
703
809
  packages_key_line = '"": {'
704
810
  updated_lockfile_content.gsub(/(#{packages_key_line})[\n\s]+"name":\s"#{current_name}",/, '\1')
@@ -713,6 +819,13 @@ module Dependabot
713
819
  # `package.json` requirement for eslint at `^1.0.0`, in which case we
714
820
  # need to copy this from the manifest to the lockfile after the update
715
821
  # has finished.
822
+ sig do
823
+ params(
824
+ updated_lockfile_content: String,
825
+ parsed_updated_lockfile_content: T::Hash[String, T.untyped]
826
+ )
827
+ .returns(String)
828
+ end
716
829
  def restore_locked_package_dependencies(updated_lockfile_content, parsed_updated_lockfile_content)
717
830
  return updated_lockfile_content unless npm8?
718
831
 
@@ -732,6 +845,7 @@ module Dependabot
732
845
  updated_lockfile_content
733
846
  end
734
847
 
848
+ sig { params(updated_lockfile_content: String).returns(String) }
735
849
  def replace_swapped_git_ssh_requirements(updated_lockfile_content)
736
850
  git_ssh_requirements_to_swap.each do |req|
737
851
  new_r = req.gsub(%r{git\+ssh://git@(.*?)[:/]}, 'git+https://\1/')
@@ -742,6 +856,7 @@ module Dependabot
742
856
  updated_lockfile_content
743
857
  end
744
858
 
859
+ sig { params(updated_lockfile_content: String).returns(String) }
745
860
  def replace_locked_git_dependencies(updated_lockfile_content)
746
861
  # Switch from details back for git dependencies (they will have
747
862
  # changed because we locked them)
@@ -766,6 +881,7 @@ module Dependabot
766
881
  updated_lockfile_content
767
882
  end
768
883
 
884
+ sig { params(updated_lockfile_content: String).returns(String) }
769
885
  def replace_tarball_urls(updated_lockfile_content)
770
886
  tarball_urls.each do |url|
771
887
  trimmed_url = url.gsub(/(\d+\.)*tgz$/, "")
@@ -783,9 +899,10 @@ module Dependabot
783
899
  updated_lockfile_content
784
900
  end
785
901
 
902
+ sig { returns(T::Array[String]) }
786
903
  def tarball_urls
787
904
  all_urls = [*package_locks, *shrinkwraps].flat_map do |file|
788
- file.content.scan(/"resolved":\s+"(.*)\"/).flatten
905
+ T.must(file.content).scan(/"resolved":\s+"(.*)\"/).flatten
789
906
  end
790
907
  all_urls.uniq! { |url| url.gsub(/(\d+\.)*tgz$/, "") }
791
908
 
@@ -800,6 +917,7 @@ module Dependabot
800
917
  end
801
918
  end
802
919
 
920
+ sig { returns(String) }
803
921
  def npmrc_content
804
922
  NpmrcBuilder.new(
805
923
  credentials: credentials,
@@ -807,73 +925,106 @@ module Dependabot
807
925
  ).npmrc_content
808
926
  end
809
927
 
928
+ sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
810
929
  def updated_package_json_content(file)
811
- @updated_package_json_content ||= {}
812
- @updated_package_json_content[file.name] ||=
930
+ @updated_package_json_content ||= T.let(
931
+ {},
932
+ T.nilable(T::Hash[String, T.nilable(String)])
933
+ )
934
+ @updated_package_json_content[file.name] ||= T.let(
813
935
  PackageJsonUpdater.new(
814
936
  package_json: file,
815
937
  dependencies: top_level_dependencies
816
- ).updated_package_json.content
938
+ ).updated_package_json.content,
939
+ T.nilable(String)
940
+ )
817
941
  end
818
942
 
943
+ sig { params(content: String).returns(Dependabot::NpmAndYarn::FileUpdater::PackageJsonPreparer) }
819
944
  def package_json_preparer(content)
820
- @package_json_preparer ||= {}
945
+ @package_json_preparer ||= T.let(
946
+ {},
947
+ T.nilable(T::Hash[String, Dependabot::NpmAndYarn::FileUpdater::PackageJsonPreparer])
948
+ )
821
949
  @package_json_preparer[content] ||=
822
950
  PackageJsonPreparer.new(
823
951
  package_json_content: content
824
952
  )
825
953
  end
826
954
 
955
+ sig { returns(T::Boolean) }
827
956
  def npmrc_disables_lockfile?
828
957
  npmrc_content.match?(/^package-lock\s*=\s*false/)
829
958
  end
830
959
 
960
+ sig { returns(T::Boolean) }
831
961
  def npm8?
832
- return @npm8 if defined?(@npm8)
962
+ return T.must(@npm8) if defined?(@npm8)
833
963
 
834
- @npm8 = Dependabot::NpmAndYarn::Helpers.npm8?(lockfile)
964
+ @npm8 ||= T.let(
965
+ Dependabot::NpmAndYarn::Helpers.npm8?(lockfile),
966
+ T.nilable(T::Boolean)
967
+ )
835
968
  end
836
969
 
970
+ sig { params(package_name: String).returns(String) }
837
971
  def sanitize_package_name(package_name)
838
972
  package_name.gsub("%2f", "/").gsub("%2F", "/")
839
973
  end
840
974
 
975
+ sig { returns(String) }
841
976
  def lockfile_directory
842
977
  Pathname.new(lockfile.name).dirname.to_s
843
978
  end
844
979
 
980
+ sig { returns(String) }
845
981
  def lockfile_basename
846
982
  Pathname.new(lockfile.name).basename.to_s
847
983
  end
848
984
 
985
+ sig { returns(T::Hash[String, T.untyped]) }
849
986
  def parsed_lockfile
850
- @parsed_lockfile ||= JSON.parse(lockfile.content)
987
+ @parsed_lockfile ||= T.let(
988
+ JSON.parse(T.must(lockfile.content)),
989
+ T.nilable(T::Hash[String, T.untyped])
990
+ )
851
991
  end
852
992
 
993
+ sig { returns(T::Hash[String, T.untyped]) }
853
994
  def parsed_package_json
854
995
  return {} unless package_json
855
- return @parsed_package_json if defined?(@parsed_package_json)
856
996
 
857
- @parsed_package_json = JSON.parse(updated_package_json_content(package_json))
997
+ @parsed_package_json ||= T.let(
998
+ JSON.parse(T.must(updated_package_json_content(T.must(package_json)))),
999
+ T.nilable(T::Hash[String, T.untyped])
1000
+ )
858
1001
  end
859
1002
 
1003
+ sig { returns(T.nilable(Dependabot::DependencyFile)) }
860
1004
  def package_json
861
1005
  package_name = lockfile.name.sub(lockfile_basename, "package.json")
862
1006
  package_files.find { |f| f.name == package_name }
863
1007
  end
864
1008
 
1009
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
865
1010
  def package_locks
866
- @package_locks ||=
1011
+ @package_locks ||= T.let(
867
1012
  dependency_files
868
- .select { |f| f.name.end_with?("package-lock.json") }
1013
+ .select { |f| f.name.end_with?("package-lock.json") },
1014
+ T.nilable(T::Array[Dependabot::DependencyFile])
1015
+ )
869
1016
  end
870
1017
 
1018
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
871
1019
  def shrinkwraps
872
- @shrinkwraps ||=
1020
+ @shrinkwraps ||= T.let(
873
1021
  dependency_files
874
- .select { |f| f.name.end_with?("npm-shrinkwrap.json") }
1022
+ .select { |f| f.name.end_with?("npm-shrinkwrap.json") },
1023
+ T.nilable(T::Array[Dependabot::DependencyFile])
1024
+ )
875
1025
  end
876
1026
 
1027
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
877
1028
  def package_files
878
1029
  dependency_files.select { |f| f.name.end_with?("package.json") }
879
1030
  end