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.
- checksums.yaml +4 -4
- data/helpers/package-lock.json +693 -765
- data/helpers/package.json +5 -5
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +225 -74
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb +11 -3
- data/lib/dependabot/npm_and_yarn/update_checker/requirements_updater.rb +1 -1
- data/lib/dependabot/npm_and_yarn/update_checker.rb +1 -1
- metadata +5 -5
@@ -1,6 +1,8 @@
|
|
1
|
-
# typed:
|
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
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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(
|
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
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
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
|
-
|
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
|
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 ||=
|
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
|
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
|