dependabot-npm_and_yarn 0.217.0 → 0.219.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/helpers/lib/pnpm/index.js +5 -0
- data/helpers/lib/pnpm/lockfile-parser.js +77 -0
- data/helpers/package-lock.json +794 -177
- data/helpers/package.json +2 -0
- data/lib/dependabot/npm_and_yarn/dependency_files_filterer.rb +33 -2
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +103 -46
- data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +13 -4
- data/lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb +68 -0
- data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +6 -0
- data/lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb +6 -4
- data/lib/dependabot/npm_and_yarn/file_updater/package_json_updater.rb +3 -2
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb +145 -0
- data/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +1 -1
- data/lib/dependabot/npm_and_yarn/file_updater.rb +38 -1
- data/lib/dependabot/npm_and_yarn/helpers.rb +13 -0
- data/lib/dependabot/npm_and_yarn/package_manager.rb +19 -0
- data/lib/dependabot/npm_and_yarn/sub_dependency_files_filterer.rb +2 -1
- data/lib/dependabot/npm_and_yarn/update_checker/dependency_files_builder.rb +19 -2
- data/lib/dependabot/npm_and_yarn/update_checker/requirements_updater.rb +3 -1
- data/lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb +15 -5
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +56 -21
- data/lib/dependabot/npm_and_yarn/update_checker.rb +4 -0
- metadata +11 -6
data/helpers/package.json
CHANGED
@@ -14,6 +14,10 @@ module Dependabot
|
|
14
14
|
@updated_dependencies = updated_dependencies
|
15
15
|
end
|
16
16
|
|
17
|
+
def paths_requiring_update_check
|
18
|
+
@paths_requiring_update_check ||= fetch_paths_requiring_update_check
|
19
|
+
end
|
20
|
+
|
17
21
|
def files_requiring_update
|
18
22
|
@files_requiring_update ||=
|
19
23
|
dependency_files.select do |file|
|
@@ -34,6 +38,15 @@ module Dependabot
|
|
34
38
|
|
35
39
|
attr_reader :dependency_files, :updated_dependencies
|
36
40
|
|
41
|
+
def fetch_paths_requiring_update_check
|
42
|
+
# if only a root lockfile exists, it tracks all dependencies
|
43
|
+
return [File.dirname(root_lockfile.name)] if lockfiles == [root_lockfile]
|
44
|
+
|
45
|
+
package_files_requiring_update.map do |file|
|
46
|
+
File.dirname(file.name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
37
50
|
def dependency_manifest_requirements
|
38
51
|
@dependency_manifest_requirements ||=
|
39
52
|
updated_dependencies.flat_map do |dep|
|
@@ -50,12 +63,29 @@ module Dependabot
|
|
50
63
|
end
|
51
64
|
|
52
65
|
def workspaces_lockfile?(lockfile)
|
53
|
-
return false unless ["yarn.lock", "package-lock.json"].include?(lockfile.name)
|
54
|
-
|
66
|
+
return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml"].include?(lockfile.name)
|
67
|
+
|
68
|
+
return false unless parsed_root_package_json["workspaces"] || dependency_files.any? do |file|
|
69
|
+
file.name.end_with?("pnpm-workspace.yaml") && File.dirname(file.name) == File.dirname(lockfile.name)
|
70
|
+
end
|
55
71
|
|
56
72
|
updated_dependencies_in_lockfile?(lockfile)
|
57
73
|
end
|
58
74
|
|
75
|
+
def root_lockfile
|
76
|
+
@root_lockfile ||=
|
77
|
+
lockfiles.find do |file|
|
78
|
+
File.dirname(file.name) == "."
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def lockfiles
|
83
|
+
@lockfiles ||=
|
84
|
+
dependency_files.select do |file|
|
85
|
+
lockfile?(file)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
59
89
|
def parsed_root_package_json
|
60
90
|
@parsed_root_package_json ||=
|
61
91
|
begin
|
@@ -88,6 +118,7 @@ module Dependabot
|
|
88
118
|
file.name.end_with?(
|
89
119
|
"package-lock.json",
|
90
120
|
"yarn.lock",
|
121
|
+
"pnpm-lock.yaml",
|
91
122
|
"npm-shrinkwrap.json"
|
92
123
|
)
|
93
124
|
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "json"
|
4
|
+
require "dependabot/experiments"
|
4
5
|
require "dependabot/logger"
|
5
6
|
require "dependabot/file_fetchers"
|
6
7
|
require "dependabot/file_fetchers/base"
|
7
8
|
require "dependabot/npm_and_yarn/helpers"
|
9
|
+
require "dependabot/npm_and_yarn/package_manager"
|
8
10
|
require "dependabot/npm_and_yarn/file_parser"
|
9
11
|
require "dependabot/npm_and_yarn/file_parser/lockfile_parser"
|
10
12
|
|
@@ -55,6 +57,7 @@ module Dependabot
|
|
55
57
|
|
56
58
|
package_managers["npm"] = Helpers.npm_version_numeric(package_lock.content) if package_lock
|
57
59
|
package_managers["yarn"] = yarn_version if yarn_version
|
60
|
+
package_managers["pnpm"] = pnpm_version if pnpm_version
|
58
61
|
package_managers["shrinkwrap"] = 1 if shrinkwrap
|
59
62
|
package_managers["unknown"] = 1 if package_managers.empty?
|
60
63
|
|
@@ -71,6 +74,7 @@ module Dependabot
|
|
71
74
|
fetched_files << package_json
|
72
75
|
fetched_files += npm_files
|
73
76
|
fetched_files += yarn_files
|
77
|
+
fetched_files += pnpm_files if Experiments.enabled?(:pnpm_updates)
|
74
78
|
fetched_files += lerna_files
|
75
79
|
fetched_files += workspace_package_jsons
|
76
80
|
fetched_files += path_dependencies(fetched_files)
|
@@ -80,7 +84,7 @@ module Dependabot
|
|
80
84
|
|
81
85
|
def npm_files
|
82
86
|
fetched_npm_files = []
|
83
|
-
fetched_npm_files << package_lock if package_lock
|
87
|
+
fetched_npm_files << package_lock if package_lock
|
84
88
|
fetched_npm_files << shrinkwrap if shrinkwrap
|
85
89
|
fetched_npm_files << npmrc if npmrc
|
86
90
|
fetched_npm_files << inferred_npmrc if inferred_npmrc
|
@@ -95,6 +99,14 @@ module Dependabot
|
|
95
99
|
fetched_yarn_files
|
96
100
|
end
|
97
101
|
|
102
|
+
def pnpm_files
|
103
|
+
fetched_pnpm_files = []
|
104
|
+
fetched_pnpm_files << pnpm_lock if pnpm_lock
|
105
|
+
fetched_pnpm_files << pnpm_workspace_yaml if pnpm_workspace_yaml
|
106
|
+
fetched_pnpm_files += pnpm_workspace_package_jsons
|
107
|
+
fetched_pnpm_files
|
108
|
+
end
|
109
|
+
|
98
110
|
def lerna_files
|
99
111
|
fetched_lerna_files = []
|
100
112
|
fetched_lerna_files << lerna_json if lerna_json
|
@@ -152,17 +164,29 @@ module Dependabot
|
|
152
164
|
def yarn_version
|
153
165
|
return @yarn_version if defined?(@yarn_version)
|
154
166
|
|
155
|
-
|
156
|
-
if (package_manager = package.fetch("packageManager", nil))
|
157
|
-
get_yarn_version_from_package_json(package_manager)
|
158
|
-
elsif yarn_lock
|
159
|
-
Helpers.yarn_version_numeric(yarn_lock)
|
160
|
-
end
|
167
|
+
@yarn_version = package_manager.locked_version("yarn") || guess_yarn_version
|
161
168
|
end
|
162
169
|
|
163
|
-
def
|
164
|
-
|
165
|
-
|
170
|
+
def guess_yarn_version
|
171
|
+
return unless yarn_lock
|
172
|
+
|
173
|
+
Helpers.yarn_version_numeric(yarn_lock)
|
174
|
+
end
|
175
|
+
|
176
|
+
def pnpm_version
|
177
|
+
return @pnpm_version if defined?(@pnpm_version)
|
178
|
+
|
179
|
+
@pnpm_version = package_manager.locked_version("pnpm") || guess_pnpm_version
|
180
|
+
end
|
181
|
+
|
182
|
+
def guess_pnpm_version
|
183
|
+
return unless pnpm_lock
|
184
|
+
|
185
|
+
Helpers.pnpm_major_version
|
186
|
+
end
|
187
|
+
|
188
|
+
def package_manager
|
189
|
+
@package_manager ||= PackageManager.new(parsed_package_json)
|
166
190
|
end
|
167
191
|
|
168
192
|
def package_json
|
@@ -170,15 +194,27 @@ module Dependabot
|
|
170
194
|
end
|
171
195
|
|
172
196
|
def package_lock
|
173
|
-
@package_lock
|
197
|
+
return @package_lock if defined?(@package_lock)
|
198
|
+
|
199
|
+
@package_lock = fetch_file_if_present("package-lock.json") unless skip_package_lock?
|
174
200
|
end
|
175
201
|
|
176
202
|
def yarn_lock
|
177
|
-
@yarn_lock
|
203
|
+
return @yarn_lock if defined?(@yarn_lock)
|
204
|
+
|
205
|
+
@yarn_lock = fetch_file_if_present("yarn.lock")
|
206
|
+
end
|
207
|
+
|
208
|
+
def pnpm_lock
|
209
|
+
return @pnpm_lock if defined?(@pnpm_lock)
|
210
|
+
|
211
|
+
@pnpm_lock = fetch_file_if_present("pnpm-lock.yaml") unless skip_pnpm_lock?
|
178
212
|
end
|
179
213
|
|
180
214
|
def shrinkwrap
|
181
|
-
@shrinkwrap
|
215
|
+
return @shrinkwrap if defined?(@shrinkwrap)
|
216
|
+
|
217
|
+
@shrinkwrap = fetch_file_if_present("npm-shrinkwrap.json")
|
182
218
|
end
|
183
219
|
|
184
220
|
def npmrc
|
@@ -224,6 +260,11 @@ module Dependabot
|
|
224
260
|
tap { |f| f.support_file = true }
|
225
261
|
end
|
226
262
|
|
263
|
+
def pnpm_workspace_yaml
|
264
|
+
@pnpm_workspace_yaml ||= fetch_file_if_present("pnpm-workspace.yaml")&.
|
265
|
+
tap { |f| f.support_file = true }
|
266
|
+
end
|
267
|
+
|
227
268
|
def lerna_json
|
228
269
|
@lerna_json ||= fetch_file_if_present("lerna.json")&.
|
229
270
|
tap { |f| f.support_file = true }
|
@@ -237,6 +278,10 @@ module Dependabot
|
|
237
278
|
@lerna_packages ||= fetch_lerna_packages
|
238
279
|
end
|
239
280
|
|
281
|
+
def pnpm_workspace_package_jsons
|
282
|
+
@pnpm_workspace_package_jsons ||= fetch_pnpm_workspace_package_jsons
|
283
|
+
end
|
284
|
+
|
240
285
|
# rubocop:disable Metrics/PerceivedComplexity
|
241
286
|
def path_dependencies(fetched_files)
|
242
287
|
package_json_files = []
|
@@ -361,50 +406,36 @@ module Dependabot
|
|
361
406
|
def fetch_workspace_package_jsons
|
362
407
|
return [] unless parsed_package_json["workspaces"]
|
363
408
|
|
364
|
-
|
365
|
-
|
366
|
-
workspace_paths(parsed_package_json["workspaces"]).each do |workspace|
|
367
|
-
file = File.join(workspace, "package.json")
|
368
|
-
|
369
|
-
begin
|
370
|
-
package_json_files << fetch_file_from_host(file)
|
371
|
-
rescue Dependabot::DependencyFileNotFound
|
372
|
-
nil
|
373
|
-
end
|
409
|
+
workspace_paths(parsed_package_json["workspaces"]).filter_map do |workspace|
|
410
|
+
fetch_package_json_if_present(workspace)
|
374
411
|
end
|
375
|
-
|
376
|
-
package_json_files
|
377
412
|
end
|
378
413
|
|
379
414
|
def fetch_lerna_packages
|
380
415
|
return [] unless parsed_lerna_json["packages"]
|
381
416
|
|
382
|
-
|
417
|
+
workspace_paths(parsed_lerna_json["packages"]).flat_map do |workspace|
|
418
|
+
fetch_lerna_packages_from_path(workspace)
|
419
|
+
end.compact
|
420
|
+
end
|
383
421
|
|
384
|
-
|
385
|
-
|
386
|
-
end
|
422
|
+
def fetch_pnpm_workspace_package_jsons
|
423
|
+
return [] unless parsed_pnpm_workspace_yaml["packages"]
|
387
424
|
|
388
|
-
|
425
|
+
workspace_paths(parsed_pnpm_workspace_yaml["packages"]).filter_map do |workspace|
|
426
|
+
fetch_package_json_if_present(workspace)
|
427
|
+
end
|
389
428
|
end
|
390
429
|
|
391
430
|
def fetch_lerna_packages_from_path(path)
|
392
|
-
|
393
|
-
|
394
|
-
package_json_path = File.join(path, "package.json")
|
431
|
+
package_json = fetch_package_json_if_present(path)
|
432
|
+
return unless package_json
|
395
433
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
fetch_file_if_present(File.join(path, "npm-shrinkwrap.json"))
|
402
|
-
].compact
|
403
|
-
rescue Dependabot::DependencyFileNotFound
|
404
|
-
nil
|
405
|
-
end
|
406
|
-
|
407
|
-
dependency_files
|
434
|
+
[package_json] + [
|
435
|
+
fetch_file_if_present(File.join(path, "package-lock.json")),
|
436
|
+
fetch_file_if_present(File.join(path, "yarn.lock")),
|
437
|
+
fetch_file_if_present(File.join(path, "npm-shrinkwrap.json"))
|
438
|
+
]
|
408
439
|
end
|
409
440
|
|
410
441
|
def workspace_paths(workspace_object)
|
@@ -466,6 +497,18 @@ module Dependabot
|
|
466
497
|
matching_paths(prefix + glob, paths)
|
467
498
|
end
|
468
499
|
|
500
|
+
def fetch_package_json_if_present(workspace)
|
501
|
+
file = File.join(workspace, "package.json")
|
502
|
+
|
503
|
+
begin
|
504
|
+
fetch_file_from_host(file)
|
505
|
+
rescue Dependabot::DependencyFileNotFound
|
506
|
+
# Not all paths matched by a workspace glob may contain a package.json
|
507
|
+
# file. Ignore if that's the case
|
508
|
+
nil
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
469
512
|
# The packages/!(not-this-package) syntax is unique to Yarn
|
470
513
|
def yarn_ignored_glob(glob)
|
471
514
|
glob.match?(/!\(.*?\)/) && glob.gsub(/(!\((.*?)\))/, '\2')
|
@@ -493,12 +536,26 @@ module Dependabot
|
|
493
536
|
{}
|
494
537
|
end
|
495
538
|
|
496
|
-
def
|
539
|
+
def parsed_pnpm_workspace_yaml
|
540
|
+
return {} unless pnpm_workspace_yaml
|
541
|
+
|
542
|
+
YAML.safe_load(pnpm_workspace_yaml.content)
|
543
|
+
rescue Pysch::SyntaxError
|
544
|
+
raise Dependabot::DependencyFileNotParseable, pnpm_workspace_yaml.path
|
545
|
+
end
|
546
|
+
|
547
|
+
def skip_package_lock?
|
497
548
|
return false unless npmrc
|
498
549
|
|
499
550
|
npmrc.content.match?(/^package-lock\s*=\s*false/)
|
500
551
|
end
|
501
552
|
|
553
|
+
def skip_pnpm_lock?
|
554
|
+
return false unless npmrc
|
555
|
+
|
556
|
+
npmrc.content.match?(/^lockfile\s*=\s*false/)
|
557
|
+
end
|
558
|
+
|
502
559
|
def build_unfetchable_deps(unfetchable_deps)
|
503
560
|
return [] unless package_lock || yarn_lock
|
504
561
|
|
@@ -9,6 +9,7 @@ module Dependabot
|
|
9
9
|
class FileParser
|
10
10
|
class LockfileParser
|
11
11
|
require "dependabot/npm_and_yarn/file_parser/yarn_lock"
|
12
|
+
require "dependabot/npm_and_yarn/file_parser/pnpm_lock"
|
12
13
|
require "dependabot/npm_and_yarn/file_parser/json_lock"
|
13
14
|
|
14
15
|
def initialize(dependency_files:)
|
@@ -22,7 +23,7 @@ module Dependabot
|
|
22
23
|
# end up unique by name. That's not a perfect representation of
|
23
24
|
# the nested nature of JS resolution, but it makes everything work
|
24
25
|
# comparably to other flat-resolution strategies
|
25
|
-
(yarn_locks + package_locks + shrinkwraps).each do |file|
|
26
|
+
(yarn_locks + pnpm_locks + package_locks + shrinkwraps).each do |file|
|
26
27
|
dependency_set += lockfile_for(file).dependencies
|
27
28
|
end
|
28
29
|
|
@@ -50,10 +51,10 @@ module Dependabot
|
|
50
51
|
def potential_lockfiles_for_manifest(manifest_filename)
|
51
52
|
dir_name = File.dirname(manifest_filename)
|
52
53
|
possible_lockfile_names =
|
53
|
-
%w(package-lock.json npm-shrinkwrap.json yarn.lock).map do |f|
|
54
|
+
%w(package-lock.json npm-shrinkwrap.json pnpm-lock.yaml yarn.lock).map do |f|
|
54
55
|
Pathname.new(File.join(dir_name, f)).cleanpath.to_path
|
55
56
|
end +
|
56
|
-
%w(yarn.lock package-lock.json npm-shrinkwrap.json)
|
57
|
+
%w(yarn.lock pnpm-lock.yaml package-lock.json npm-shrinkwrap.json)
|
57
58
|
|
58
59
|
possible_lockfile_names.uniq.
|
59
60
|
filter_map { |nm| dependency_files.find { |f| f.name == nm } }
|
@@ -67,8 +68,10 @@ module Dependabot
|
|
67
68
|
@lockfiles ||= {}
|
68
69
|
@lockfiles[file.name] ||= if [*package_locks, *shrinkwraps].include?(file)
|
69
70
|
JsonLock.new(file)
|
70
|
-
|
71
|
+
elsif yarn_locks.include?(file)
|
71
72
|
YarnLock.new(file)
|
73
|
+
else
|
74
|
+
PnpmLock.new(file)
|
72
75
|
end
|
73
76
|
end
|
74
77
|
|
@@ -78,6 +81,12 @@ module Dependabot
|
|
78
81
|
select { |f| f.name.end_with?("package-lock.json") }
|
79
82
|
end
|
80
83
|
|
84
|
+
def pnpm_locks
|
85
|
+
@pnpm_locks ||=
|
86
|
+
dependency_files.
|
87
|
+
select { |f| f.name.end_with?("pnpm-lock.yaml") }
|
88
|
+
end
|
89
|
+
|
81
90
|
def yarn_locks
|
82
91
|
@yarn_locks ||=
|
83
92
|
dependency_files.
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/errors"
|
4
|
+
|
5
|
+
module Dependabot
|
6
|
+
module NpmAndYarn
|
7
|
+
class FileParser
|
8
|
+
class PnpmLock
|
9
|
+
def initialize(dependency_file)
|
10
|
+
@dependency_file = dependency_file
|
11
|
+
end
|
12
|
+
|
13
|
+
def parsed
|
14
|
+
@parsed ||= SharedHelpers.in_a_temporary_directory do
|
15
|
+
File.write("pnpm-lock.yaml", @dependency_file.content)
|
16
|
+
|
17
|
+
SharedHelpers.run_helper_subprocess(
|
18
|
+
command: NativeHelpers.helper_path,
|
19
|
+
function: "pnpm:parseLockfile",
|
20
|
+
args: [Dir.pwd]
|
21
|
+
)
|
22
|
+
rescue SharedHelpers::HelperSubprocessFailed
|
23
|
+
raise Dependabot::DependencyFileNotParseable, @dependency_file.path
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def dependencies
|
28
|
+
dependency_set = Dependabot::NpmAndYarn::FileParser::DependencySet.new
|
29
|
+
|
30
|
+
parsed.each do |details|
|
31
|
+
next if details["aliased"]
|
32
|
+
|
33
|
+
name = details["name"]
|
34
|
+
version = details["version"]
|
35
|
+
|
36
|
+
dependency_args = {
|
37
|
+
name: name,
|
38
|
+
version: version,
|
39
|
+
package_manager: "npm_and_yarn",
|
40
|
+
requirements: []
|
41
|
+
}
|
42
|
+
|
43
|
+
if details["dev"]
|
44
|
+
dependency_args[:subdependency_metadata] =
|
45
|
+
[{ production: !details["dev"] }]
|
46
|
+
end
|
47
|
+
|
48
|
+
dependency_set << Dependency.new(**dependency_args)
|
49
|
+
end
|
50
|
+
|
51
|
+
dependency_set
|
52
|
+
end
|
53
|
+
|
54
|
+
def details(dependency_name, requirement, _manifest_name)
|
55
|
+
details_candidates = parsed.select { |info| info["name"] == dependency_name }
|
56
|
+
|
57
|
+
# If there's only one entry for this dependency, use it, even if
|
58
|
+
# the requirement in the lockfile doesn't match
|
59
|
+
if details_candidates.one?
|
60
|
+
details_candidates.first
|
61
|
+
else
|
62
|
+
details_candidates.find { |info| info["specifiers"]&.include?(requirement) }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -418,6 +418,12 @@ module Dependabot
|
|
418
418
|
raise Dependabot::DependencyFileNotParseable, msg
|
419
419
|
end
|
420
420
|
|
421
|
+
if error_message.include?("EBADENGINE")
|
422
|
+
msg = "Dependabot uses Node.js #{`node --version`} and NPM #{`npm --version`}. " \
|
423
|
+
"Due to the engine-strict setting, the update will not succeed."
|
424
|
+
raise Dependabot::DependencyFileNotResolvable, msg
|
425
|
+
end
|
426
|
+
|
421
427
|
raise error
|
422
428
|
end
|
423
429
|
# rubocop:enable Metrics/AbcSize
|
@@ -81,7 +81,9 @@ module Dependabot
|
|
81
81
|
next false if CENTRAL_REGISTRIES.include?(cred["registry"])
|
82
82
|
|
83
83
|
# If all the URLs include this registry, it's global
|
84
|
-
next true if dependency_urls.all?
|
84
|
+
next true if dependency_urls.size.positive? && dependency_urls.all? do |url|
|
85
|
+
url.include?(cred["registry"])
|
86
|
+
end
|
85
87
|
|
86
88
|
# Check if this registry has already been defined in .npmrc as a scoped registry
|
87
89
|
next false if npmrc_scoped_registries.any? { |sr| sr.include?(cred["registry"]) }
|
@@ -133,8 +135,8 @@ module Dependabot
|
|
133
135
|
@dependency_urls = []
|
134
136
|
if package_lock
|
135
137
|
@dependency_urls +=
|
136
|
-
|
137
|
-
|
138
|
+
package_lock.content.scan(/"resolved"\s*:\s*"(.*)"/).
|
139
|
+
flatten.
|
138
140
|
select { |url| url.is_a?(String) }.
|
139
141
|
reject { |url| url.start_with?("git") }
|
140
142
|
end
|
@@ -267,7 +269,7 @@ module Dependabot
|
|
267
269
|
|
268
270
|
scopes = affected_urls.map do |url|
|
269
271
|
url.split(/\%40|@/)[1]&.split(%r{\%2[fF]|/})&.first
|
270
|
-
end
|
272
|
+
end.uniq
|
271
273
|
|
272
274
|
# Registry used for unscoped packages
|
273
275
|
return if scopes.include?(nil)
|
@@ -108,8 +108,9 @@ module Dependabot
|
|
108
108
|
def update_package_json_resolutions(package_json_content:, new_req:,
|
109
109
|
dependency:, old_req:)
|
110
110
|
dep = dependency
|
111
|
+
parsed_json_content = JSON.parse(package_json_content)
|
111
112
|
resolutions =
|
112
|
-
|
113
|
+
parsed_json_content.fetch("resolutions", parsed_json_content.dig("pnpm", "overrides") || {}).
|
113
114
|
reject { |_, v| v != old_req && v != dep.previous_version }.
|
114
115
|
select { |k, _| k == dep.name || k.end_with?("/#{dep.name}") }
|
115
116
|
|
@@ -132,7 +133,7 @@ module Dependabot
|
|
132
133
|
)
|
133
134
|
|
134
135
|
content = update_package_json_sections(
|
135
|
-
|
136
|
+
%w(resolutions overrides), content, original_line, replacement_line
|
136
137
|
)
|
137
138
|
end
|
138
139
|
content
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/npm_and_yarn/helpers"
|
4
|
+
require "dependabot/npm_and_yarn/update_checker/registry_finder"
|
5
|
+
require "dependabot/shared_helpers"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module NpmAndYarn
|
9
|
+
class FileUpdater
|
10
|
+
class PnpmLockfileUpdater
|
11
|
+
require_relative "package_json_updater"
|
12
|
+
|
13
|
+
def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:)
|
14
|
+
@dependencies = dependencies
|
15
|
+
@dependency_files = dependency_files
|
16
|
+
@repo_contents_path = repo_contents_path
|
17
|
+
@credentials = credentials
|
18
|
+
end
|
19
|
+
|
20
|
+
def updated_pnpm_lock_content(pnpm_lock)
|
21
|
+
@updated_pnpm_lock_content ||= {}
|
22
|
+
return @updated_pnpm_lock_content[pnpm_lock.name] if @updated_pnpm_lock_content[pnpm_lock.name]
|
23
|
+
|
24
|
+
new_content = run_pnpm_update(pnpm_lock: pnpm_lock)
|
25
|
+
@updated_pnpm_lock_content[pnpm_lock.name] = new_content
|
26
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
27
|
+
handle_pnpm_lock_updater_error(e, pnpm_lock)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :dependencies, :dependency_files, :repo_contents_path, :credentials
|
33
|
+
|
34
|
+
IRRESOLVABLE_PACKAGE = "ERR_PNPM_NO_MATCHING_VERSION"
|
35
|
+
INVALID_REQUIREMENT = "ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER"
|
36
|
+
MISSING_PACKAGE = /(?<package_req>.*?) is not in the npm registry, or you have no permission to fetch it/
|
37
|
+
|
38
|
+
def run_pnpm_update(pnpm_lock:)
|
39
|
+
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
40
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
41
|
+
run_pnpm_updater
|
42
|
+
|
43
|
+
write_final_package_json_files
|
44
|
+
|
45
|
+
run_pnpm_install
|
46
|
+
|
47
|
+
File.read(pnpm_lock.name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def run_pnpm_updater
|
53
|
+
dependency_updates = dependencies.map do |d|
|
54
|
+
"#{d.name}@#{d.version}"
|
55
|
+
end.join(" ")
|
56
|
+
|
57
|
+
SharedHelpers.run_shell_command(
|
58
|
+
"pnpm install #{dependency_updates} --lockfile-only --ignore-workspace-root-check",
|
59
|
+
fingerprint: "pnpm install <dependency_updates> --lockfile-only --ignore-workspace-root-check"
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_pnpm_install
|
64
|
+
SharedHelpers.run_shell_command(
|
65
|
+
"pnpm install --lockfile-only"
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def lockfile_dependencies(lockfile)
|
70
|
+
@lockfile_dependencies ||= {}
|
71
|
+
@lockfile_dependencies[lockfile.name] ||=
|
72
|
+
NpmAndYarn::FileParser.new(
|
73
|
+
dependency_files: [lockfile, *package_files],
|
74
|
+
source: nil,
|
75
|
+
credentials: credentials
|
76
|
+
).parse
|
77
|
+
end
|
78
|
+
|
79
|
+
def handle_pnpm_lock_updater_error(error, pnpm_lock)
|
80
|
+
error_message = error.message
|
81
|
+
|
82
|
+
if error_message.include?(IRRESOLVABLE_PACKAGE) || error_message.include?(INVALID_REQUIREMENT)
|
83
|
+
raise_resolvability_error(error_message, pnpm_lock)
|
84
|
+
end
|
85
|
+
|
86
|
+
raise unless error_message.match?(MISSING_PACKAGE)
|
87
|
+
|
88
|
+
package_name = error_message.match(MISSING_PACKAGE).
|
89
|
+
named_captures["package_req"].
|
90
|
+
split(/(?<=\w)\@/).first
|
91
|
+
raise_missing_package_error(package_name, error_message, pnpm_lock)
|
92
|
+
end
|
93
|
+
|
94
|
+
def raise_resolvability_error(error_message, pnpm_lock)
|
95
|
+
dependency_names = dependencies.map(&:name).join(", ")
|
96
|
+
msg = "Error whilst updating #{dependency_names} in " \
|
97
|
+
"#{pnpm_lock.path}:\n#{error_message}"
|
98
|
+
raise Dependabot::DependencyFileNotResolvable, msg
|
99
|
+
end
|
100
|
+
|
101
|
+
def raise_missing_package_error(package_name, _error_message, pnpm_lock)
|
102
|
+
missing_dep = lockfile_dependencies(pnpm_lock).
|
103
|
+
find { |dep| dep.name == package_name }
|
104
|
+
|
105
|
+
reg = NpmAndYarn::UpdateChecker::RegistryFinder.new(
|
106
|
+
dependency: missing_dep,
|
107
|
+
credentials: credentials,
|
108
|
+
npmrc_file: npmrc_file
|
109
|
+
).registry
|
110
|
+
|
111
|
+
raise PrivateSourceAuthenticationFailure, reg
|
112
|
+
end
|
113
|
+
|
114
|
+
def write_final_package_json_files
|
115
|
+
package_files.each do |file|
|
116
|
+
path = file.name
|
117
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
118
|
+
File.write(path, updated_package_json_content(file))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def updated_package_json_content(file)
|
123
|
+
@updated_package_json_content ||= {}
|
124
|
+
@updated_package_json_content[file.name] ||=
|
125
|
+
PackageJsonUpdater.new(
|
126
|
+
package_json: file,
|
127
|
+
dependencies: dependencies
|
128
|
+
).updated_package_json.content
|
129
|
+
end
|
130
|
+
|
131
|
+
def package_files
|
132
|
+
@package_files ||= dependency_files.select { |f| f.name.end_with?("package.json") }
|
133
|
+
end
|
134
|
+
|
135
|
+
def base_dir
|
136
|
+
dependency_files.first.directory
|
137
|
+
end
|
138
|
+
|
139
|
+
def npmrc_file
|
140
|
+
dependency_files.find { |f| f.name == ".npmrc" }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -342,7 +342,7 @@ module Dependabot
|
|
342
342
|
if Helpers.yarn_berry?(yarn_lock)
|
343
343
|
File.write(".yarnrc.yml", yarnrc_yml_content) if yarnrc_yml_file
|
344
344
|
else
|
345
|
-
File.write(".npmrc", npmrc_content)
|
345
|
+
File.write(".npmrc", npmrc_content)
|
346
346
|
File.write(".yarnrc", yarnrc_content) if yarnrc_specifies_private_reg?
|
347
347
|
end
|
348
348
|
|