dependabot-npm_and_yarn 0.395.0 → 0.396.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.
- checksums.yaml +4 -4
- data/helpers/package-lock.json +11 -7
- data/lib/dependabot/npm_and_yarn/dependency_files_filterer.rb +8 -7
- data/lib/dependabot/npm_and_yarn/dependency_grapher/yarn_relationship_resolver.rb +15 -10
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +3 -11
- data/lib/dependabot/npm_and_yarn/file_parser/json_lock/record.rb +155 -0
- data/lib/dependabot/npm_and_yarn/file_parser/json_lock.rb +58 -47
- data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +3 -2
- data/lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb +99 -82
- data/lib/dependabot/npm_and_yarn/file_parser/yarn_lock/record.rb +79 -0
- data/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb +63 -33
- data/lib/dependabot/npm_and_yarn/file_parser.rb +22 -22
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7701a231519c944cf2fb1c6141f84c2ed5e391e5b5d29c72e42c2a85923947b
|
|
4
|
+
data.tar.gz: cdfe34310c136b297c99f715a53e665b53ebaa751022d29bb473c6e80ea82729
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 29e772e38e68ffb7483a2e0315ae8ff49cf53e62a3d1c3133074641403329173052ae9a6d18bb04109bbb25065c150db195b86c83c81f792c77e654b6e353ecc
|
|
7
|
+
data.tar.gz: 2edf72ca8610f9145e2388871ae0b05cb952bf512cd9a9a974e643f0e0f73f98d7303388e631df6539db80c87f33e1461528d163d095e4fdbe19558b1d6965d8
|
data/helpers/package-lock.json
CHANGED
|
@@ -4572,12 +4572,16 @@
|
|
|
4572
4572
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
|
4573
4573
|
},
|
|
4574
4574
|
"node_modules/baseline-browser-mapping": {
|
|
4575
|
-
"version": "2.
|
|
4576
|
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.
|
|
4577
|
-
"integrity": "sha512-
|
|
4575
|
+
"version": "2.11.21",
|
|
4576
|
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz",
|
|
4577
|
+
"integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==",
|
|
4578
4578
|
"dev": true,
|
|
4579
|
+
"license": "Apache-2.0",
|
|
4579
4580
|
"bin": {
|
|
4580
|
-
"baseline-browser-mapping": "dist/cli.
|
|
4581
|
+
"baseline-browser-mapping": "dist/cli.cjs"
|
|
4582
|
+
},
|
|
4583
|
+
"engines": {
|
|
4584
|
+
"node": ">=6.0.0"
|
|
4581
4585
|
}
|
|
4582
4586
|
},
|
|
4583
4587
|
"node_modules/bcrypt-pbkdf": {
|
|
@@ -20131,9 +20135,9 @@
|
|
|
20131
20135
|
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
|
20132
20136
|
},
|
|
20133
20137
|
"baseline-browser-mapping": {
|
|
20134
|
-
"version": "2.
|
|
20135
|
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.
|
|
20136
|
-
"integrity": "sha512-
|
|
20138
|
+
"version": "2.11.21",
|
|
20139
|
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz",
|
|
20140
|
+
"integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==",
|
|
20137
20141
|
"dev": true
|
|
20138
20142
|
},
|
|
20139
20143
|
"bcrypt-pbkdf": {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "dependabot/utils"
|
|
5
|
+
require "dependabot/package/npm_package_json"
|
|
5
6
|
require "dependabot/npm_and_yarn/file_parser/lockfile_parser"
|
|
6
7
|
require "sorbet-runtime"
|
|
7
8
|
|
|
@@ -88,7 +89,7 @@ module Dependabot
|
|
|
88
89
|
return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lock", "npm-shrinkwrap.json"]
|
|
89
90
|
.include?(lockfile.name)
|
|
90
91
|
|
|
91
|
-
return false unless
|
|
92
|
+
return false unless root_package_json_document.workspaces? || dependency_files.any? do |file|
|
|
92
93
|
file.name.end_with?("pnpm-workspace.yaml") && File.dirname(file.name) == File.dirname(lockfile.name)
|
|
93
94
|
end
|
|
94
95
|
|
|
@@ -115,14 +116,14 @@ module Dependabot
|
|
|
115
116
|
)
|
|
116
117
|
end
|
|
117
118
|
|
|
118
|
-
sig { returns(
|
|
119
|
-
def
|
|
120
|
-
@
|
|
119
|
+
sig { returns(Dependabot::Package::NpmPackageJson) }
|
|
120
|
+
def root_package_json_document
|
|
121
|
+
@root_package_json_document ||= T.let(
|
|
121
122
|
begin
|
|
122
123
|
package = T.must(dependency_files.find { |f| f.name == "package.json" })
|
|
123
|
-
|
|
124
|
+
Dependabot::Package::NpmPackageJson.from_file(package)
|
|
124
125
|
end,
|
|
125
|
-
T.nilable(
|
|
126
|
+
T.nilable(Dependabot::Package::NpmPackageJson)
|
|
126
127
|
)
|
|
127
128
|
end
|
|
128
129
|
|
|
@@ -20,13 +20,11 @@ module Dependabot
|
|
|
20
20
|
parsed = FileParser::YarnLock.new(@lockfile).parsed
|
|
21
21
|
|
|
22
22
|
parsed.each_with_object({}) do |(req, details), rels|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
version = details["version"]
|
|
23
|
+
version = details.version
|
|
26
24
|
parent_name = T.must(req.split(/(?<=\w)\@/).first)
|
|
27
|
-
children = details.
|
|
25
|
+
children = details.dependencies
|
|
28
26
|
|
|
29
|
-
next if children.
|
|
27
|
+
next if children.empty?
|
|
30
28
|
|
|
31
29
|
key = "#{parent_name}@#{version}"
|
|
32
30
|
resolved_children = resolve_children(children, parsed)
|
|
@@ -38,7 +36,10 @@ module Dependabot
|
|
|
38
36
|
|
|
39
37
|
private
|
|
40
38
|
|
|
41
|
-
sig
|
|
39
|
+
sig do
|
|
40
|
+
params(children: T::Hash[String, String], parsed: T::Hash[String, FileParser::YarnLock::Record])
|
|
41
|
+
.returns(T::Array[String])
|
|
42
|
+
end
|
|
42
43
|
def resolve_children(children, parsed)
|
|
43
44
|
children.filter_map do |child_name, child_req|
|
|
44
45
|
version = resolve_child_version(child_name, child_req, parsed)
|
|
@@ -46,21 +47,25 @@ module Dependabot
|
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
sig
|
|
50
|
+
sig do
|
|
51
|
+
params(child_name: String, child_req: String, parsed: T::Hash[String, FileParser::YarnLock::Record])
|
|
52
|
+
.returns(T.nilable(String))
|
|
53
|
+
end
|
|
50
54
|
def resolve_child_version(child_name, child_req, parsed)
|
|
51
55
|
# Try exact key first
|
|
52
56
|
child_entry = parsed["#{child_name}@#{child_req}"]
|
|
53
|
-
return child_entry
|
|
57
|
+
return child_entry.version if child_entry&.version
|
|
54
58
|
|
|
55
59
|
# Yarn groups multiple requirements into single keys like "foo@^1.0.0, foo@^1.2.0"
|
|
56
60
|
target_req = "#{child_name}@#{child_req}"
|
|
57
61
|
grouped_match = parsed.find { |k, _| k.split(", ").include?(target_req) }
|
|
58
|
-
|
|
62
|
+
grouped_version = grouped_match&.last&.version
|
|
63
|
+
return grouped_version if grouped_version
|
|
59
64
|
|
|
60
65
|
# Fallback: find by name only if there's exactly one candidate
|
|
61
66
|
candidates = parsed.select { |k, _| k.split(/(?<=\w)\@/).first == child_name }
|
|
62
67
|
candidate = candidates.first
|
|
63
|
-
candidate.last
|
|
68
|
+
candidate.last.version if candidates.size == 1 && candidate
|
|
64
69
|
end
|
|
65
70
|
end
|
|
66
71
|
end
|
|
@@ -179,17 +179,9 @@ module Dependabot
|
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
known_registries = []
|
|
182
|
-
FileParser::JsonLock.new(T.must(package_lock)).
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
).each do |dependency_name, details|
|
|
186
|
-
resolved = details.fetch("resolved", DEFAULT_NPM_REGISTRY)
|
|
187
|
-
|
|
188
|
-
begin
|
|
189
|
-
uri = URI.parse(resolved)
|
|
190
|
-
rescue URI::InvalidURIError
|
|
191
|
-
next
|
|
192
|
-
end
|
|
182
|
+
FileParser::JsonLock.new(T.must(package_lock)).legacy_dependencies.each do |dependency_name, details|
|
|
183
|
+
uri = details.registry_uri(DEFAULT_NPM_REGISTRY)
|
|
184
|
+
next unless uri
|
|
193
185
|
|
|
194
186
|
next unless uri.scheme && uri.host
|
|
195
187
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "uri"
|
|
5
|
+
require "sorbet-runtime"
|
|
6
|
+
require "dependabot/npm_and_yarn/file_parser/json_lock"
|
|
7
|
+
|
|
8
|
+
module Dependabot
|
|
9
|
+
module NpmAndYarn
|
|
10
|
+
class FileParser < Dependabot::FileParsers::Base
|
|
11
|
+
class JsonLock
|
|
12
|
+
class Record
|
|
13
|
+
extend T::Sig
|
|
14
|
+
|
|
15
|
+
ObjectHash = T.type_alias { T::Hash[String, Object] }
|
|
16
|
+
|
|
17
|
+
sig { params(data: Object, path: String, context: String).void }
|
|
18
|
+
def initialize(data, path:, context:)
|
|
19
|
+
@data = data
|
|
20
|
+
@path = path
|
|
21
|
+
@context = context
|
|
22
|
+
@object = T.let(nil, T.nilable(ObjectHash))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
sig { returns(T.nilable(String)) }
|
|
26
|
+
def version
|
|
27
|
+
optional_string("version")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { returns(T.nilable(String)) }
|
|
31
|
+
def name
|
|
32
|
+
optional_string("name")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
sig { returns(T.nilable(String)) }
|
|
36
|
+
def resolved
|
|
37
|
+
optional_string("resolved")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
sig { returns(T.nilable(String)) }
|
|
41
|
+
def resolution
|
|
42
|
+
optional_string("resolution")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
sig { returns(T::Boolean) }
|
|
46
|
+
def dev?
|
|
47
|
+
boolean("dev")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
sig { returns(T::Boolean) }
|
|
51
|
+
def bundled?
|
|
52
|
+
boolean("bundled")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sig { returns(T::Hash[String, Record]) }
|
|
56
|
+
def dependency_entries
|
|
57
|
+
field = object["dependencies"] ? "dependencies" : "packages"
|
|
58
|
+
context = "#{@context}.#{field}"
|
|
59
|
+
mapping = object_value(object.fetch(field, {}), context)
|
|
60
|
+
|
|
61
|
+
# Modern package records contain requirement strings rather than nested lock entries.
|
|
62
|
+
mapping.reject { |_, value| value.is_a?(String) }.to_h do |key, data|
|
|
63
|
+
[key, self.class.new(data, path: @path, context: "#{context}.#{key}")]
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
sig { returns(T::Hash[String, Record]) }
|
|
68
|
+
def legacy_entries
|
|
69
|
+
entries(object.fetch("dependencies", {}), "#{@context}.dependencies")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
sig { params(name: String).returns(T.nilable(Record)) }
|
|
73
|
+
def legacy_entry(name)
|
|
74
|
+
dependencies = object["dependencies"]
|
|
75
|
+
return if dependencies.nil?
|
|
76
|
+
|
|
77
|
+
data = object_value(dependencies, "#{@context}.dependencies")[name]
|
|
78
|
+
self.class.new(data, path: @path, context: "dependencies.#{name}") unless data.nil?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
sig { params(primary: String, fallback: String).returns(T.nilable(Record)) }
|
|
82
|
+
def package_entry(primary, fallback)
|
|
83
|
+
packages = object["packages"]
|
|
84
|
+
return if packages.nil?
|
|
85
|
+
|
|
86
|
+
mapping = object_value(packages, "#{@context}.packages")
|
|
87
|
+
key = mapping[primary] ? primary : fallback
|
|
88
|
+
data = mapping[key]
|
|
89
|
+
self.class.new(data, path: @path, context: "packages.#{key}") unless data.nil?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
sig { params(default_registry: String).returns(T.nilable(URI::Generic)) }
|
|
93
|
+
def registry_uri(default_registry)
|
|
94
|
+
value = object.fetch("resolved", default_registry)
|
|
95
|
+
# Registry inference filters invalid URIs, including non-string values.
|
|
96
|
+
return unless value.is_a?(String)
|
|
97
|
+
|
|
98
|
+
URI.parse(value)
|
|
99
|
+
rescue URI::InvalidURIError
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
sig { returns(ObjectHash) }
|
|
106
|
+
def object
|
|
107
|
+
@object ||= object_value(@data, @context)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
sig { params(value: Object, context: String).returns(ObjectHash) }
|
|
111
|
+
def object_value(value, context)
|
|
112
|
+
invalid!("#{context} must be an object") unless value.is_a?(Hash)
|
|
113
|
+
|
|
114
|
+
value.to_h do |raw_key, raw_value|
|
|
115
|
+
key = T.cast(raw_key, Object)
|
|
116
|
+
invalid!("#{context} keys must be strings") unless key.is_a?(String)
|
|
117
|
+
|
|
118
|
+
[key, T.cast(raw_value, Object)]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
sig { params(value: Object, context: String).returns(T::Hash[String, Record]) }
|
|
123
|
+
def entries(value, context)
|
|
124
|
+
object_value(value, context).to_h do |key, data|
|
|
125
|
+
[key, self.class.new(data, path: @path, context: "#{context}.#{key}")]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
sig { params(key: String).returns(T.nilable(String)) }
|
|
130
|
+
def optional_string(key)
|
|
131
|
+
value = object[key]
|
|
132
|
+
return if value.nil?
|
|
133
|
+
return value if value.is_a?(String)
|
|
134
|
+
|
|
135
|
+
invalid!("#{@context}.#{key} must be a string or nil")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig { params(key: String).returns(T::Boolean) }
|
|
139
|
+
def boolean(key)
|
|
140
|
+
value = object[key]
|
|
141
|
+
return false if value.nil? || value == false
|
|
142
|
+
return true if value == true
|
|
143
|
+
|
|
144
|
+
invalid!("#{@context}.#{key} must be a boolean or nil")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
sig { params(message: String).returns(T.noreturn) }
|
|
148
|
+
def invalid!(message)
|
|
149
|
+
raise DependencyFileNotParseable.new(@path, message)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "json"
|
|
5
5
|
require "dependabot/errors"
|
|
6
|
+
require "dependabot/npm_and_yarn/file_parser"
|
|
6
7
|
require "dependabot/npm_and_yarn/helpers"
|
|
8
|
+
require "dependabot/package/npm_lockfile_details"
|
|
7
9
|
require "sorbet-runtime"
|
|
8
10
|
|
|
9
11
|
module Dependabot
|
|
@@ -12,16 +14,24 @@ module Dependabot
|
|
|
12
14
|
class JsonLock
|
|
13
15
|
extend T::Sig
|
|
14
16
|
|
|
17
|
+
require_relative "json_lock/record"
|
|
18
|
+
|
|
15
19
|
sig { params(dependency_file: DependencyFile, dealias_packages: T::Boolean).void }
|
|
16
20
|
def initialize(dependency_file, dealias_packages: false)
|
|
17
21
|
@dependency_file = dependency_file
|
|
18
22
|
@dealias_packages = dealias_packages
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
sig { returns(
|
|
25
|
+
sig { returns(Record) }
|
|
22
26
|
def parsed
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
@parsed ||= T.let(
|
|
28
|
+
Record.new(
|
|
29
|
+
T.cast(JSON.parse(T.must(@dependency_file.content)), Object),
|
|
30
|
+
path: @dependency_file.path,
|
|
31
|
+
context: "lockfile"
|
|
32
|
+
),
|
|
33
|
+
T.nilable(Record)
|
|
34
|
+
)
|
|
25
35
|
rescue JSON::ParserError
|
|
26
36
|
raise Dependabot::DependencyFileNotParseable, @dependency_file.path
|
|
27
37
|
end
|
|
@@ -31,90 +41,91 @@ module Dependabot
|
|
|
31
41
|
recursively_fetch_dependencies(parsed)
|
|
32
42
|
end
|
|
33
43
|
|
|
44
|
+
sig { returns(T::Hash[String, Record]) }
|
|
45
|
+
def legacy_dependencies
|
|
46
|
+
parsed.legacy_entries
|
|
47
|
+
end
|
|
48
|
+
|
|
34
49
|
sig do
|
|
35
|
-
params(dependency_name: String, _requirement: T.
|
|
36
|
-
.returns(T.nilable(
|
|
50
|
+
params(dependency_name: String, _requirement: T.nilable(String), manifest_name: String)
|
|
51
|
+
.returns(T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
37
52
|
end
|
|
38
53
|
def details(dependency_name, _requirement, manifest_name)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
modern = Helpers.parse_npm8?(@dependency_file)
|
|
55
|
+
details = if modern
|
|
56
|
+
# NOTE: npm 8 sometimes doesn't install workspace dependencies in the
|
|
57
|
+
# workspace folder so we need to fallback to checking top-level
|
|
58
|
+
parsed.package_entry(
|
|
59
|
+
node_modules_path(manifest_name, dependency_name),
|
|
60
|
+
"node_modules/#{dependency_name}"
|
|
61
|
+
)
|
|
62
|
+
else
|
|
63
|
+
parsed.legacy_entry(dependency_name)
|
|
64
|
+
end
|
|
65
|
+
return if details.nil?
|
|
66
|
+
|
|
67
|
+
Dependabot::Package::NpmLockfileDetails.new(
|
|
68
|
+
version: details.version,
|
|
69
|
+
resolved: details.resolved,
|
|
70
|
+
resolution: modern ? nil : details.resolution
|
|
71
|
+
)
|
|
48
72
|
end
|
|
49
73
|
|
|
50
74
|
private
|
|
51
75
|
|
|
52
76
|
sig do
|
|
53
|
-
params(object_with_dependencies:
|
|
77
|
+
params(object_with_dependencies: Record)
|
|
54
78
|
.returns(Dependabot::FileParsers::Base::DependencySet)
|
|
55
79
|
end
|
|
56
80
|
def recursively_fetch_dependencies(object_with_dependencies)
|
|
57
81
|
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
|
58
82
|
|
|
59
|
-
|
|
60
|
-
dependencies ||= object_with_dependencies.fetch("packages", {})
|
|
61
|
-
|
|
62
|
-
dependencies.each do |name, details|
|
|
83
|
+
object_with_dependencies.dependency_entries.each do |name, details|
|
|
63
84
|
next if name.empty? # v3 lockfiles include an empty key holding info of the current package
|
|
64
85
|
|
|
65
|
-
version = Version.semver_for(details
|
|
86
|
+
version = Version.semver_for(details.version)
|
|
66
87
|
next unless version
|
|
67
88
|
|
|
68
89
|
package_name = package_name_for(name, details)
|
|
69
90
|
version = version.to_s
|
|
70
91
|
|
|
71
|
-
|
|
92
|
+
metadata = aliased_package?(name, details) ? { alias: name.split("node_modules/").last } : nil
|
|
93
|
+
subdependency_metadata = T.let(nil, T.nilable(T::Array[T::Hash[Symbol, Object]]))
|
|
94
|
+
subdependency_metadata = [{ npm_bundled: true }] if details.bundled?
|
|
95
|
+
subdependency_metadata = [{ production: false }] if details.dev?
|
|
96
|
+
|
|
97
|
+
dependency_set << Dependency.new(
|
|
72
98
|
name: package_name,
|
|
73
99
|
version: version,
|
|
74
100
|
package_manager: "npm_and_yarn",
|
|
75
|
-
requirements: []
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
alias_name = name.split("node_modules/").last
|
|
80
|
-
dependency_args[:metadata] = { alias: alias_name }
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
if details["bundled"]
|
|
84
|
-
dependency_args[:subdependency_metadata] =
|
|
85
|
-
[{ npm_bundled: details["bundled"] }]
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
if details["dev"]
|
|
89
|
-
dependency_args[:subdependency_metadata] =
|
|
90
|
-
[{ production: !details["dev"] }]
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
dependency_set << Dependency.new(**dependency_args)
|
|
101
|
+
requirements: [],
|
|
102
|
+
metadata: metadata,
|
|
103
|
+
subdependency_metadata: subdependency_metadata
|
|
104
|
+
)
|
|
94
105
|
dependency_set += recursively_fetch_dependencies(details)
|
|
95
106
|
end
|
|
96
107
|
|
|
97
108
|
dependency_set
|
|
98
109
|
end
|
|
99
110
|
|
|
100
|
-
sig { params(package_path: String, details:
|
|
111
|
+
sig { params(package_path: String, details: Record).returns(String) }
|
|
101
112
|
def package_name_for(package_path, details)
|
|
102
113
|
package_name = T.must(package_path.split("node_modules/").last)
|
|
103
114
|
return package_name unless dealias_packages?
|
|
104
115
|
|
|
105
|
-
real_package_name = details
|
|
106
|
-
return package_name
|
|
116
|
+
real_package_name = details.name
|
|
117
|
+
return package_name if real_package_name.nil?
|
|
107
118
|
return package_name if real_package_name == package_name
|
|
108
119
|
|
|
109
120
|
real_package_name
|
|
110
121
|
end
|
|
111
122
|
|
|
112
|
-
sig { params(package_path: String, details:
|
|
123
|
+
sig { params(package_path: String, details: Record).returns(T::Boolean) }
|
|
113
124
|
def aliased_package?(package_path, details)
|
|
114
125
|
return false unless dealias_packages?
|
|
115
126
|
|
|
116
|
-
real_package_name = details
|
|
117
|
-
return false
|
|
127
|
+
real_package_name = details.name
|
|
128
|
+
return false if real_package_name.nil?
|
|
118
129
|
|
|
119
130
|
package_name = T.must(package_path.split("node_modules/").last)
|
|
120
131
|
real_package_name != package_name
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
require "dependabot/dependency_file"
|
|
5
5
|
require "dependabot/npm_and_yarn/file_parser"
|
|
6
6
|
require "dependabot/npm_and_yarn/helpers"
|
|
7
|
+
require "dependabot/package/npm_lockfile_details"
|
|
7
8
|
require "sorbet-runtime"
|
|
8
9
|
|
|
9
10
|
module Dependabot
|
|
@@ -48,10 +49,10 @@ module Dependabot
|
|
|
48
49
|
|
|
49
50
|
sig do
|
|
50
51
|
params(dependency_name: String, requirement: T.nilable(String), manifest_name: String)
|
|
51
|
-
.returns(T.nilable(
|
|
52
|
+
.returns(T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
52
53
|
end
|
|
53
54
|
def lockfile_details(dependency_name:, requirement:, manifest_name:)
|
|
54
|
-
details = T.let(nil, T.nilable(
|
|
55
|
+
details = T.let(nil, T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
55
56
|
potential_lockfiles_for_manifest(manifest_name).each do |lockfile|
|
|
56
57
|
details = lockfile_for(lockfile).details(dependency_name, requirement, manifest_name)
|
|
57
58
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
5
|
|
|
6
|
+
require "dependabot/npm_and_yarn/file_parser"
|
|
6
7
|
require "dependabot/errors"
|
|
7
8
|
require "dependabot/dependency"
|
|
8
|
-
require "dependabot/file_parsers/base"
|
|
9
9
|
require "dependabot/npm_and_yarn/native_helpers"
|
|
10
|
+
require "dependabot/package/npm_lockfile_details"
|
|
10
11
|
require "dependabot/shared_helpers"
|
|
11
12
|
|
|
12
13
|
module Dependabot
|
|
@@ -15,99 +16,112 @@ module Dependabot
|
|
|
15
16
|
class PnpmLock
|
|
16
17
|
extend T::Sig
|
|
17
18
|
|
|
19
|
+
class Record < T::ImmutableStruct
|
|
20
|
+
extend T::Sig
|
|
21
|
+
|
|
22
|
+
const :name, String
|
|
23
|
+
const :version, String
|
|
24
|
+
const :resolved, T.nilable(String)
|
|
25
|
+
const :dev, T::Boolean
|
|
26
|
+
const :specifiers, T::Array[String]
|
|
27
|
+
const :aliased, T::Boolean
|
|
28
|
+
|
|
29
|
+
sig { params(value: Object, path: String, index: Integer).returns(Record) }
|
|
30
|
+
def self.from_object(value, path:, index:)
|
|
31
|
+
context = "pnpm helper result[#{index}]"
|
|
32
|
+
raise DependencyFileNotParseable.new(path, "#{context} must be an object") unless value.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
resolved = string(T.cast(value["resolved"], Object), path, "#{context}.resolved") if value.key?("resolved")
|
|
35
|
+
|
|
36
|
+
new(
|
|
37
|
+
name: string(T.cast(value["name"], Object), path, "#{context}.name"),
|
|
38
|
+
version: string(T.cast(value["version"], Object), path, "#{context}.version"),
|
|
39
|
+
resolved: resolved,
|
|
40
|
+
dev: boolean(T.cast(value["dev"], Object), path, "#{context}.dev"),
|
|
41
|
+
specifiers: strings(T.cast(value["specifiers"], Object), path, "#{context}.specifiers"),
|
|
42
|
+
aliased: boolean(T.cast(value["aliased"], Object), path, "#{context}.aliased")
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
sig { params(value: Object, path: String, field: String).returns(String) }
|
|
47
|
+
def self.string(value, path, field)
|
|
48
|
+
return value if value.is_a?(String)
|
|
49
|
+
|
|
50
|
+
raise DependencyFileNotParseable.new(path, "#{field} must be a string")
|
|
51
|
+
end
|
|
52
|
+
private_class_method :string
|
|
53
|
+
|
|
54
|
+
sig { params(value: Object, path: String, field: String).returns(T::Boolean) }
|
|
55
|
+
def self.boolean(value, path, field)
|
|
56
|
+
return value if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
|
57
|
+
|
|
58
|
+
raise DependencyFileNotParseable.new(path, "#{field} must be a boolean")
|
|
59
|
+
end
|
|
60
|
+
private_class_method :boolean
|
|
61
|
+
|
|
62
|
+
sig { params(value: Object, path: String, field: String).returns(T::Array[String]) }
|
|
63
|
+
def self.strings(value, path, field)
|
|
64
|
+
raise DependencyFileNotParseable.new(path, "#{field} must be an array") unless value.is_a?(Array)
|
|
65
|
+
|
|
66
|
+
value.each_with_index.map do |specifier, index|
|
|
67
|
+
string(T.cast(specifier, Object), path, "#{field}[#{index}]")
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
private_class_method :strings
|
|
71
|
+
end
|
|
72
|
+
|
|
18
73
|
sig { params(dependency_file: Dependabot::DependencyFile, dealias_packages: T::Boolean).void }
|
|
19
74
|
def initialize(dependency_file, dealias_packages: false)
|
|
20
75
|
@dependency_file = dependency_file
|
|
21
76
|
@dealias_packages = dealias_packages
|
|
77
|
+
@parsed = T.let(nil, T.nilable(T::Array[Record]))
|
|
22
78
|
end
|
|
23
79
|
|
|
24
|
-
sig { returns(T::Array[
|
|
80
|
+
sig { returns(T::Array[Record]) }
|
|
25
81
|
def parsed
|
|
26
|
-
@parsed
|
|
27
|
-
T.cast(
|
|
28
|
-
SharedHelpers.in_a_temporary_directory do
|
|
29
|
-
File.write("pnpm-lock.yaml", @dependency_file.content)
|
|
30
|
-
|
|
31
|
-
SharedHelpers.run_helper_subprocess(
|
|
32
|
-
command: NativeHelpers.helper_path,
|
|
33
|
-
function: "pnpm:parseLockfile",
|
|
34
|
-
args: [Dir.pwd]
|
|
35
|
-
)
|
|
36
|
-
rescue SharedHelpers::HelperSubprocessFailed
|
|
37
|
-
raise Dependabot::DependencyFileNotParseable, @dependency_file.path
|
|
38
|
-
end,
|
|
39
|
-
T::Array[T::Hash[String, T.untyped]]
|
|
40
|
-
),
|
|
41
|
-
T.nilable(T::Array[T::Hash[String, T.untyped]])
|
|
42
|
-
)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# rubocop:disable Metrics/MethodLength
|
|
46
|
-
# rubocop:disable Metrics/AbcSize
|
|
47
|
-
# rubocop:disable Metrics/PerceivedComplexity
|
|
48
|
-
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
|
49
|
-
def dependencies
|
|
50
|
-
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
|
51
|
-
|
|
52
|
-
# Separate dependencies into two categories: with specifiers and without specifiers.
|
|
53
|
-
dependencies_with_specifiers = T.let([], T::Array[T::Hash[Symbol, T.untyped]])
|
|
54
|
-
dependencies_without_specifiers = T.let([], T::Array[T::Hash[Symbol, T.untyped]])
|
|
55
|
-
|
|
56
|
-
parsed.each do |details|
|
|
57
|
-
next if details["aliased"] && !dealias_packages?
|
|
58
|
-
|
|
59
|
-
name = T.cast(details["name"], String)
|
|
60
|
-
version = T.cast(details["version"], T.nilable(String))
|
|
61
|
-
|
|
62
|
-
dependency_args = {
|
|
63
|
-
name: name,
|
|
64
|
-
version: version,
|
|
65
|
-
package_manager: "npm_and_yarn",
|
|
66
|
-
requirements: []
|
|
67
|
-
}
|
|
82
|
+
return @parsed if @parsed
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
|
|
84
|
+
result = SharedHelpers.in_a_temporary_directory do
|
|
85
|
+
File.write("pnpm-lock.yaml", @dependency_file.content)
|
|
71
86
|
|
|
72
|
-
|
|
73
|
-
|
|
87
|
+
SharedHelpers.run_helper_subprocess(
|
|
88
|
+
command: NativeHelpers.helper_path,
|
|
89
|
+
function: "pnpm:parseLockfile",
|
|
90
|
+
args: [Dir.pwd]
|
|
91
|
+
)
|
|
92
|
+
rescue SharedHelpers::HelperSubprocessFailed
|
|
93
|
+
raise Dependabot::DependencyFileNotParseable, @dependency_file.path
|
|
94
|
+
end
|
|
74
95
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
dependencies_with_specifiers << dependency_args
|
|
78
|
-
else
|
|
79
|
-
dependencies_without_specifiers << dependency_args
|
|
80
|
-
end
|
|
96
|
+
unless result.is_a?(Array)
|
|
97
|
+
raise DependencyFileNotParseable.new(@dependency_file.path, "pnpm helper result must be an array")
|
|
81
98
|
end
|
|
82
99
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
dependency_set << Dependency.new(
|
|
86
|
-
name: dependency_args[:name],
|
|
87
|
-
version: dependency_args[:version],
|
|
88
|
-
package_manager: dependency_args[:package_manager],
|
|
89
|
-
requirements: dependency_args[:requirements],
|
|
90
|
-
subdependency_metadata: dependency_args[:subdependency_metadata],
|
|
91
|
-
metadata: dependency_args[:metadata]
|
|
92
|
-
)
|
|
100
|
+
@parsed = result.each_with_index.map do |record, index|
|
|
101
|
+
Record.from_object(T.cast(record, Object), path: @dependency_file.path, index: index)
|
|
93
102
|
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
|
106
|
+
def dependencies
|
|
107
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
|
108
|
+
with_specifiers, without_specifiers = parsed.partition { |record| record.specifiers.any? }
|
|
109
|
+
|
|
110
|
+
(with_specifiers + without_specifiers).each do |record|
|
|
111
|
+
next if record.aliased && !dealias_packages?
|
|
94
112
|
|
|
95
|
-
dependencies_without_specifiers.each do |dependency_args|
|
|
96
113
|
dependency_set << Dependency.new(
|
|
97
|
-
name:
|
|
98
|
-
version:
|
|
99
|
-
package_manager:
|
|
100
|
-
requirements:
|
|
101
|
-
subdependency_metadata:
|
|
102
|
-
metadata:
|
|
114
|
+
name: record.name,
|
|
115
|
+
version: record.version,
|
|
116
|
+
package_manager: "npm_and_yarn",
|
|
117
|
+
requirements: [],
|
|
118
|
+
subdependency_metadata: record.dev ? [{ production: false }] : nil,
|
|
119
|
+
metadata: record.aliased ? { alias: record.name } : nil
|
|
103
120
|
)
|
|
104
121
|
end
|
|
105
122
|
|
|
106
123
|
dependency_set
|
|
107
124
|
end
|
|
108
|
-
# rubocop:enable Metrics/PerceivedComplexity
|
|
109
|
-
# rubocop:enable Metrics/AbcSize
|
|
110
|
-
# rubocop:enable Metrics/MethodLength
|
|
111
125
|
|
|
112
126
|
sig do
|
|
113
127
|
params(
|
|
@@ -115,18 +129,21 @@ module Dependabot
|
|
|
115
129
|
requirement: T.nilable(String),
|
|
116
130
|
_manifest_name: T.nilable(String)
|
|
117
131
|
)
|
|
118
|
-
.returns(T.nilable(
|
|
132
|
+
.returns(T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
119
133
|
end
|
|
120
134
|
def details(dependency_name, requirement, _manifest_name)
|
|
121
|
-
details_candidates = parsed.select { |
|
|
135
|
+
details_candidates = parsed.select { |record| record.name == dependency_name }
|
|
122
136
|
|
|
123
137
|
# If there's only one entry for this dependency, use it, even if
|
|
124
138
|
# the requirement in the lockfile doesn't match
|
|
125
|
-
if details_candidates.one?
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
139
|
+
details = if details_candidates.one?
|
|
140
|
+
details_candidates.first
|
|
141
|
+
elsif requirement
|
|
142
|
+
details_candidates.find { |record| record.specifiers.include?(requirement) }
|
|
143
|
+
end
|
|
144
|
+
return if details.nil?
|
|
145
|
+
|
|
146
|
+
Dependabot::Package::NpmLockfileDetails.new(version: details.version, resolved: details.resolved)
|
|
130
147
|
end
|
|
131
148
|
|
|
132
149
|
private
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# typed: strong
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
require "dependabot/npm_and_yarn/file_parser/yarn_lock"
|
|
6
|
+
|
|
7
|
+
module Dependabot
|
|
8
|
+
module NpmAndYarn
|
|
9
|
+
class FileParser < Dependabot::FileParsers::Base
|
|
10
|
+
class YarnLock
|
|
11
|
+
class Record
|
|
12
|
+
extend T::Sig
|
|
13
|
+
|
|
14
|
+
ObjectHash = T.type_alias { T::Hash[String, Object] }
|
|
15
|
+
|
|
16
|
+
sig { params(data: Object, path: String, context: String).void }
|
|
17
|
+
def initialize(data, path:, context:)
|
|
18
|
+
@data = data
|
|
19
|
+
@path = path
|
|
20
|
+
@context = context
|
|
21
|
+
@object = T.let(nil, T.nilable(ObjectHash))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sig { returns(T.nilable(String)) }
|
|
25
|
+
def version
|
|
26
|
+
value = object["version"]
|
|
27
|
+
return if value.nil?
|
|
28
|
+
return value if value.is_a?(String)
|
|
29
|
+
|
|
30
|
+
invalid!("#{@context}.version must be a string or nil")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sig { returns(Dependabot::Package::NpmLockfileDetails) }
|
|
34
|
+
def lookup_details
|
|
35
|
+
Dependabot::Package::NpmLockfileDetails.from_object(object, path: @path, context: @context)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
sig { returns(T::Hash[String, String]) }
|
|
39
|
+
def dependencies
|
|
40
|
+
value = object["dependencies"]
|
|
41
|
+
return {} if value.nil?
|
|
42
|
+
|
|
43
|
+
invalid!("#{@context}.dependencies must be an object") unless value.is_a?(Hash)
|
|
44
|
+
value.to_h do |raw_name, raw_requirement|
|
|
45
|
+
name = T.cast(raw_name, Object)
|
|
46
|
+
requirement = T.cast(raw_requirement, Object)
|
|
47
|
+
invalid!("#{@context}.dependencies keys must be strings") unless name.is_a?(String)
|
|
48
|
+
invalid!("#{@context}.dependencies.#{name} must be a string") unless requirement.is_a?(String)
|
|
49
|
+
|
|
50
|
+
[name, requirement]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
sig { returns(ObjectHash) }
|
|
57
|
+
def object
|
|
58
|
+
return @object if @object
|
|
59
|
+
|
|
60
|
+
data = @data
|
|
61
|
+
invalid!("#{@context} must be an object") unless data.is_a?(Hash)
|
|
62
|
+
|
|
63
|
+
@object = data.to_h do |raw_key, raw_value|
|
|
64
|
+
key = T.cast(raw_key, Object)
|
|
65
|
+
invalid!("#{@context} keys must be strings") unless key.is_a?(String)
|
|
66
|
+
|
|
67
|
+
[key, T.cast(raw_value, Object)]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
sig { params(message: String).returns(T.noreturn) }
|
|
72
|
+
def invalid!(message)
|
|
73
|
+
raise DependencyFileNotParseable.new(@path, message)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
require "dependabot/shared_helpers"
|
|
5
5
|
require "dependabot/errors"
|
|
6
|
+
require "dependabot/npm_and_yarn/file_parser"
|
|
6
7
|
require "dependabot/npm_and_yarn/native_helpers"
|
|
8
|
+
require "dependabot/package/npm_lockfile_details"
|
|
7
9
|
require "sorbet-runtime"
|
|
8
10
|
|
|
9
11
|
module Dependabot
|
|
@@ -12,35 +14,36 @@ module Dependabot
|
|
|
12
14
|
class YarnLock
|
|
13
15
|
extend T::Sig
|
|
14
16
|
|
|
17
|
+
require_relative "yarn_lock/record"
|
|
18
|
+
|
|
15
19
|
sig { params(dependency_file: Dependabot::DependencyFile, dealias_packages: T::Boolean).void }
|
|
16
20
|
def initialize(dependency_file, dealias_packages: false)
|
|
17
21
|
@dependency_file = dependency_file
|
|
18
22
|
@dealias_packages = dealias_packages
|
|
23
|
+
@parsed = T.let(nil, T.nilable(T::Hash[String, Record]))
|
|
19
24
|
end
|
|
20
25
|
|
|
21
|
-
sig { returns(T::Hash[String,
|
|
26
|
+
sig { returns(T::Hash[String, Record]) }
|
|
22
27
|
def parsed
|
|
23
|
-
@parsed
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
T.nilable(T::Hash[String, T::Hash[String, T.untyped]])
|
|
43
|
-
)
|
|
28
|
+
return @parsed if @parsed
|
|
29
|
+
|
|
30
|
+
result = SharedHelpers.in_a_temporary_directory do
|
|
31
|
+
File.write("yarn.lock", @dependency_file.content)
|
|
32
|
+
|
|
33
|
+
SharedHelpers.run_helper_subprocess(
|
|
34
|
+
command: NativeHelpers.helper_path,
|
|
35
|
+
function: "yarn:parseLockfile",
|
|
36
|
+
args: [Dir.pwd]
|
|
37
|
+
)
|
|
38
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
|
39
|
+
raise Dependabot::OutOfDisk, e.message if e.message.end_with?("No space left on device")
|
|
40
|
+
raise Dependabot::OutOfDisk, e.message if e.message.end_with?("Out of diskspace")
|
|
41
|
+
raise Dependabot::OutOfMemory, e.message if e.message.end_with?("MemoryError")
|
|
42
|
+
|
|
43
|
+
raise Dependabot::DependencyFileNotParseable, @dependency_file.path
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@parsed = parse_records(result)
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
|
@@ -49,11 +52,12 @@ module Dependabot
|
|
|
49
52
|
|
|
50
53
|
parsed.each do |reqs, details|
|
|
51
54
|
reqs.split(", ").each do |req|
|
|
52
|
-
version = Version.semver_for(details["version"])
|
|
53
|
-
next unless version
|
|
54
55
|
next if workspace_package?(req)
|
|
55
56
|
next if req == "__metadata"
|
|
56
57
|
|
|
58
|
+
version = Version.semver_for(details.version)
|
|
59
|
+
next unless version
|
|
60
|
+
|
|
57
61
|
if alias_package?(req)
|
|
58
62
|
# Skip unless we are dealiasing packages
|
|
59
63
|
next unless dealias_packages?
|
|
@@ -88,9 +92,9 @@ module Dependabot
|
|
|
88
92
|
params(
|
|
89
93
|
dependency_name: String,
|
|
90
94
|
requirement: T.nilable(String),
|
|
91
|
-
_manifest_name:
|
|
95
|
+
_manifest_name: String
|
|
92
96
|
)
|
|
93
|
-
.returns(T.nilable(
|
|
97
|
+
.returns(T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
94
98
|
end
|
|
95
99
|
def details(dependency_name, requirement, _manifest_name)
|
|
96
100
|
details_candidates =
|
|
@@ -99,17 +103,43 @@ module Dependabot
|
|
|
99
103
|
|
|
100
104
|
# If there's only one entry for this dependency, use it, even if
|
|
101
105
|
# the requirement in the lockfile doesn't match
|
|
102
|
-
if details_candidates.one?
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
details = if details_candidates.one?
|
|
107
|
+
T.must(details_candidates.first).last
|
|
108
|
+
else
|
|
109
|
+
details_candidates.find do |k, _|
|
|
110
|
+
k.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement)
|
|
111
|
+
end&.last
|
|
112
|
+
end
|
|
113
|
+
return if details.nil?
|
|
114
|
+
|
|
115
|
+
details.lookup_details
|
|
109
116
|
end
|
|
110
117
|
|
|
111
118
|
private
|
|
112
119
|
|
|
120
|
+
sig { params(value: Object).returns(T::Hash[String, Record]) }
|
|
121
|
+
def parse_records(value)
|
|
122
|
+
unless value.is_a?(Hash)
|
|
123
|
+
raise DependencyFileNotParseable.new(@dependency_file.path, "yarn helper result must be an object")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
records = T.let({}, T::Hash[String, Record])
|
|
127
|
+
value.each_with_index do |(raw_key, data), index|
|
|
128
|
+
key = T.cast(raw_key, Object)
|
|
129
|
+
unless key.is_a?(String)
|
|
130
|
+
raise DependencyFileNotParseable.new(@dependency_file.path, "yarn helper descriptors must be strings")
|
|
131
|
+
end
|
|
132
|
+
next if key == "__metadata"
|
|
133
|
+
|
|
134
|
+
records[key] = Record.new(
|
|
135
|
+
T.cast(data, Object),
|
|
136
|
+
path: @dependency_file.path,
|
|
137
|
+
context: "yarn helper result[#{index}]"
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
records
|
|
141
|
+
end
|
|
142
|
+
|
|
113
143
|
sig { returns(T::Boolean) }
|
|
114
144
|
def dealias_packages?
|
|
115
145
|
@dealias_packages
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
require "cgi/escape"
|
|
7
7
|
require "dependabot/dependency"
|
|
8
|
+
require "dependabot/package/npm_lockfile_details"
|
|
9
|
+
require "dependabot/package/npm_package_json"
|
|
8
10
|
require "dependabot/file_parsers"
|
|
9
11
|
require "dependabot/file_parsers/base"
|
|
10
12
|
require "dependabot/shared_helpers"
|
|
@@ -99,7 +101,7 @@ module Dependabot
|
|
|
99
101
|
def package_manager_helper
|
|
100
102
|
@package_manager_helper ||= T.let(
|
|
101
103
|
PackageManagerHelper.new(
|
|
102
|
-
|
|
104
|
+
package_json_document.package_manager_config,
|
|
103
105
|
lockfiles,
|
|
104
106
|
registry_config_files,
|
|
105
107
|
credentials
|
|
@@ -126,9 +128,9 @@ module Dependabot
|
|
|
126
128
|
}
|
|
127
129
|
end
|
|
128
130
|
|
|
129
|
-
sig { returns(
|
|
130
|
-
def
|
|
131
|
-
|
|
131
|
+
sig { returns(Dependabot::Package::NpmPackageJson) }
|
|
132
|
+
def package_json_document
|
|
133
|
+
Dependabot::Package::NpmPackageJson.from_file(package_json)
|
|
132
134
|
rescue JSON::ParserError
|
|
133
135
|
raise Dependabot::DependencyFileNotParseable, package_json.path
|
|
134
136
|
end
|
|
@@ -227,16 +229,14 @@ module Dependabot
|
|
|
227
229
|
dependency_set = DependencySet.new
|
|
228
230
|
|
|
229
231
|
package_files.each do |file|
|
|
230
|
-
|
|
232
|
+
manifest = Dependabot::Package::NpmPackageJson.from_file(file)
|
|
231
233
|
|
|
232
234
|
# TODO: Currently, Dependabot can't handle flat dependency files
|
|
233
235
|
# (and will error at the FileUpdater stage, because the
|
|
234
236
|
# UpdateChecker doesn't take account of flat resolution).
|
|
235
|
-
next if
|
|
236
|
-
|
|
237
|
-
self.class.each_dependency(json) do |name, requirement, type|
|
|
238
|
-
next unless requirement.is_a?(String)
|
|
237
|
+
next if manifest.flat?
|
|
239
238
|
|
|
239
|
+
manifest.each_dependency do |name, requirement, type|
|
|
240
240
|
# Skip dependencies using Yarn workspace cross-references as requirements
|
|
241
241
|
next if requirement.start_with?("workspace:", "catalog:")
|
|
242
242
|
|
|
@@ -295,7 +295,7 @@ module Dependabot
|
|
|
295
295
|
end
|
|
296
296
|
|
|
297
297
|
sig do
|
|
298
|
-
params(file: DependencyFile, type:
|
|
298
|
+
params(file: DependencyFile, type: String, name: String, requirement: String)
|
|
299
299
|
.returns(T.nilable(Dependency))
|
|
300
300
|
end
|
|
301
301
|
def build_dependency(file:, type:, name:, requirement:)
|
|
@@ -454,14 +454,14 @@ module Dependabot
|
|
|
454
454
|
def workspace_package_names
|
|
455
455
|
@workspace_package_names ||= T.let(
|
|
456
456
|
package_files.filter_map do |f|
|
|
457
|
-
|
|
457
|
+
Dependabot::Package::NpmPackageJson.from_file(f).name
|
|
458
458
|
end,
|
|
459
459
|
T.nilable(T::Array[String])
|
|
460
460
|
)
|
|
461
461
|
end
|
|
462
462
|
|
|
463
463
|
sig do
|
|
464
|
-
params(requirement: String, lockfile_details: T.nilable(
|
|
464
|
+
params(requirement: String, lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
465
465
|
.returns(T.nilable(T.any(String, Integer, Gem::Version)))
|
|
466
466
|
end
|
|
467
467
|
def version_for(requirement, lockfile_details)
|
|
@@ -483,10 +483,10 @@ module Dependabot
|
|
|
483
483
|
end
|
|
484
484
|
end
|
|
485
485
|
|
|
486
|
-
sig { params(lockfile_details: T.nilable(
|
|
486
|
+
sig { params(lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails)).returns(T.nilable(String)) }
|
|
487
487
|
def git_revision_for(lockfile_details)
|
|
488
|
-
version =
|
|
489
|
-
resolved =
|
|
488
|
+
version = lockfile_details&.version
|
|
489
|
+
resolved = lockfile_details&.resolved
|
|
490
490
|
[
|
|
491
491
|
version&.split("#")&.last,
|
|
492
492
|
resolved&.split("#")&.last,
|
|
@@ -526,11 +526,11 @@ module Dependabot
|
|
|
526
526
|
end
|
|
527
527
|
|
|
528
528
|
sig do
|
|
529
|
-
params(lockfile_details: T.nilable(
|
|
529
|
+
params(lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
530
530
|
.returns(T.nilable(T.any(String, Integer, Gem::Version)))
|
|
531
531
|
end
|
|
532
532
|
def lockfile_version_for(lockfile_details)
|
|
533
|
-
semver_version_for(lockfile_details&.
|
|
533
|
+
semver_version_for(lockfile_details&.version)
|
|
534
534
|
end
|
|
535
535
|
|
|
536
536
|
sig { params(version: T.nilable(String)).returns(T.nilable(T.any(String, Integer, Gem::Version))) }
|
|
@@ -549,18 +549,18 @@ module Dependabot
|
|
|
549
549
|
end
|
|
550
550
|
|
|
551
551
|
sig do
|
|
552
|
-
params(name: String, requirement: String, lockfile_details: T.nilable(
|
|
552
|
+
params(name: String, requirement: String, lockfile_details: T.nilable(Dependabot::Package::NpmLockfileDetails))
|
|
553
553
|
.returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
|
554
554
|
end
|
|
555
555
|
def source_for(name, requirement, lockfile_details)
|
|
556
556
|
return git_source_for(requirement) if git_url?(requirement)
|
|
557
557
|
return jsr_registry_source if requirement.start_with?("jsr:")
|
|
558
558
|
|
|
559
|
-
resolved_url = lockfile_details&.
|
|
559
|
+
resolved_url = lockfile_details&.resolved
|
|
560
560
|
|
|
561
|
-
resolution = lockfile_details&.
|
|
562
|
-
|
|
563
|
-
resolved_url = CGI.unescape(
|
|
561
|
+
resolution = lockfile_details&.resolution
|
|
562
|
+
package_url = resolution&.[](/__archiveUrl=(.+)/, 1)
|
|
563
|
+
resolved_url = CGI.unescape(package_url) if package_url
|
|
564
564
|
|
|
565
565
|
return unless resolved_url
|
|
566
566
|
return unless resolved_url.start_with?("http")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-npm_and_yarn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.396.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.396.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.396.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -346,9 +346,11 @@ files:
|
|
|
346
346
|
- lib/dependabot/npm_and_yarn/file_fetcher/path_dependency_builder.rb
|
|
347
347
|
- lib/dependabot/npm_and_yarn/file_parser.rb
|
|
348
348
|
- lib/dependabot/npm_and_yarn/file_parser/json_lock.rb
|
|
349
|
+
- lib/dependabot/npm_and_yarn/file_parser/json_lock/record.rb
|
|
349
350
|
- lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb
|
|
350
351
|
- lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb
|
|
351
352
|
- lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb
|
|
353
|
+
- lib/dependabot/npm_and_yarn/file_parser/yarn_lock/record.rb
|
|
352
354
|
- lib/dependabot/npm_and_yarn/file_updater.rb
|
|
353
355
|
- lib/dependabot/npm_and_yarn/file_updater/berry_lockfile_handler.rb
|
|
354
356
|
- lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb
|
|
@@ -390,7 +392,7 @@ licenses:
|
|
|
390
392
|
- MIT
|
|
391
393
|
metadata:
|
|
392
394
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
393
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
395
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.396.0
|
|
394
396
|
rdoc_options: []
|
|
395
397
|
require_paths:
|
|
396
398
|
- lib
|