dependabot-npm_and_yarn 0.292.0 → 0.294.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/lib/npm/vulnerability-auditor.js +16 -16
- data/helpers/lib/npm6/updater.js +1 -1
- data/lib/dependabot/npm_and_yarn/bun_package_manager.rb +46 -0
- data/lib/dependabot/npm_and_yarn/dependency_files_filterer.rb +2 -1
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +61 -35
- data/lib/dependabot/npm_and_yarn/file_parser/bun_lock.rb +141 -0
- data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +33 -27
- data/lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb +47 -0
- data/lib/dependabot/npm_and_yarn/file_parser.rb +17 -9
- data/lib/dependabot/npm_and_yarn/file_updater/bun_lockfile_updater.rb +144 -0
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb +127 -12
- data/lib/dependabot/npm_and_yarn/file_updater.rb +66 -0
- data/lib/dependabot/npm_and_yarn/helpers.rb +54 -2
- data/lib/dependabot/npm_and_yarn/language.rb +45 -0
- data/lib/dependabot/npm_and_yarn/npm_package_manager.rb +70 -0
- data/lib/dependabot/npm_and_yarn/package_manager.rb +16 -196
- data/lib/dependabot/npm_and_yarn/pnpm_package_manager.rb +55 -0
- data/lib/dependabot/npm_and_yarn/sub_dependency_files_filterer.rb +1 -0
- data/lib/dependabot/npm_and_yarn/update_checker/dependency_files_builder.rb +14 -7
- data/lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb +14 -0
- data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +19 -0
- data/lib/dependabot/npm_and_yarn/version.rb +4 -0
- data/lib/dependabot/npm_and_yarn/yarn_package_manager.rb +56 -0
- metadata +12 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72e338772b3c3aac3cf86538fc2d70dbbc45f5f7cb854cd7fd74913b140fe056
|
4
|
+
data.tar.gz: 1856c138b871ebe80e5cc6faa5984ba80c10b342aa8bf0822e325e5a31a3f815
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e3267d0aafcf35e345505c87a23b2b783cfc36377e46f5f10875a4bd8b0c4fe201b6dea0dbed75463b75dccba175014af850451cfc52b39c0a2a410a5c5ab34
|
7
|
+
data.tar.gz: a1c6be5ccbebcf43a76a51d7871a37743428f052c734a985a48fc90d4b1e2944679a8b6eb17910a8ef7e14c69dbe46d9761319b28d6a57d7dcbac7e94fbb9c09
|
@@ -97,9 +97,9 @@ async function findVulnerableDependencies(directory, advisories) {
|
|
97
97
|
|
98
98
|
for (const group of groupedFixUpdateChains.values()) {
|
99
99
|
const fixUpdateNode = group[0].nodes[0]
|
100
|
-
const groupTopLevelAncestors = group.reduce((
|
100
|
+
const groupTopLevelAncestors = group.reduce((ancestor, chain) => {
|
101
101
|
const topLevelNode = chain.nodes[chain.nodes.length - 1]
|
102
|
-
return
|
102
|
+
return ancestor.add(topLevelNode.name)
|
103
103
|
}, new Set())
|
104
104
|
|
105
105
|
// Add group's top-level ancestors to the set of all top-level ancestors of
|
@@ -269,23 +269,23 @@ const maybeReadFile = file => {
|
|
269
269
|
}
|
270
270
|
|
271
271
|
function loadCACerts(npmConfig) {
|
272
|
-
|
273
|
-
|
274
|
-
|
272
|
+
if (npmConfig.ca) {
|
273
|
+
return npmConfig.ca
|
274
|
+
}
|
275
275
|
|
276
|
-
|
277
|
-
|
278
|
-
|
276
|
+
if (!npmConfig.cafile) {
|
277
|
+
return
|
278
|
+
}
|
279
279
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
280
|
+
const raw = maybeReadFile(npmConfig.cafile)
|
281
|
+
if (!raw) {
|
282
|
+
return
|
283
|
+
}
|
284
284
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
285
|
+
const delim = '-----END CERTIFICATE-----'
|
286
|
+
return raw.replace(/\r\n/g, '\n').split(delim)
|
287
|
+
.filter(section => section.trim())
|
288
|
+
.map(section => section.trimStart() + delim)
|
289
289
|
}
|
290
290
|
|
291
291
|
module.exports = { findVulnerableDependencies }
|
data/helpers/lib/npm6/updater.js
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
# typed: strong
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Dependabot
|
5
|
+
module NpmAndYarn
|
6
|
+
class BunPackageManager < Ecosystem::VersionManager
|
7
|
+
extend T::Sig
|
8
|
+
NAME = "bun"
|
9
|
+
LOCKFILE_NAME = "bun.lock"
|
10
|
+
|
11
|
+
# In Bun 1.1.39, the lockfile format was changed from a binary bun.lockb to a text-based bun.lock.
|
12
|
+
# https://bun.sh/blog/bun-lock-text-lockfile
|
13
|
+
MIN_SUPPORTED_VERSION = Version.new("1.1.39")
|
14
|
+
SUPPORTED_VERSIONS = T.let([MIN_SUPPORTED_VERSION].freeze, T::Array[Dependabot::Version])
|
15
|
+
DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
|
16
|
+
|
17
|
+
sig do
|
18
|
+
params(
|
19
|
+
detected_version: T.nilable(String),
|
20
|
+
raw_version: T.nilable(String),
|
21
|
+
requirement: T.nilable(Dependabot::NpmAndYarn::Requirement)
|
22
|
+
).void
|
23
|
+
end
|
24
|
+
def initialize(detected_version: nil, raw_version: nil, requirement: nil)
|
25
|
+
super(
|
26
|
+
name: NAME,
|
27
|
+
detected_version: detected_version ? Version.new(detected_version) : nil,
|
28
|
+
version: raw_version ? Version.new(raw_version) : nil,
|
29
|
+
deprecated_versions: DEPRECATED_VERSIONS,
|
30
|
+
supported_versions: SUPPORTED_VERSIONS,
|
31
|
+
requirement: requirement
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { override.returns(T::Boolean) }
|
36
|
+
def deprecated?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { override.returns(T::Boolean) }
|
41
|
+
def unsupported?
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -82,7 +82,7 @@ module Dependabot
|
|
82
82
|
|
83
83
|
sig { params(lockfile: DependencyFile).returns(T::Boolean) }
|
84
84
|
def workspaces_lockfile?(lockfile)
|
85
|
-
return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml"].include?(lockfile.name)
|
85
|
+
return false unless ["yarn.lock", "package-lock.json", "pnpm-lock.yaml", "bun.lock"].include?(lockfile.name)
|
86
86
|
|
87
87
|
return false unless parsed_root_package_json["workspaces"] || dependency_files.any? do |file|
|
88
88
|
file.name.end_with?("pnpm-workspace.yaml") && File.dirname(file.name) == File.dirname(lockfile.name)
|
@@ -148,6 +148,7 @@ module Dependabot
|
|
148
148
|
"package-lock.json",
|
149
149
|
"yarn.lock",
|
150
150
|
"pnpm-lock.yaml",
|
151
|
+
"bun.lock",
|
151
152
|
"npm-shrinkwrap.json"
|
152
153
|
)
|
153
154
|
end
|
@@ -68,6 +68,7 @@ module Dependabot
|
|
68
68
|
package_managers["npm"] = npm_version if npm_version
|
69
69
|
package_managers["yarn"] = yarn_version if yarn_version
|
70
70
|
package_managers["pnpm"] = pnpm_version if pnpm_version
|
71
|
+
package_managers["bun"] = bun_version if bun_version
|
71
72
|
package_managers["unknown"] = 1 if package_managers.empty?
|
72
73
|
|
73
74
|
{
|
@@ -83,6 +84,7 @@ module Dependabot
|
|
83
84
|
fetched_files += npm_files if npm_version
|
84
85
|
fetched_files += yarn_files if yarn_version
|
85
86
|
fetched_files += pnpm_files if pnpm_version
|
87
|
+
fetched_files += bun_files if bun_version
|
86
88
|
fetched_files += lerna_files
|
87
89
|
fetched_files += workspace_package_jsons
|
88
90
|
fetched_files += path_dependencies(fetched_files)
|
@@ -120,6 +122,13 @@ module Dependabot
|
|
120
122
|
fetched_pnpm_files
|
121
123
|
end
|
122
124
|
|
125
|
+
sig { returns(T::Array[DependencyFile]) }
|
126
|
+
def bun_files
|
127
|
+
fetched_bun_files = []
|
128
|
+
fetched_bun_files << bun_lock if bun_lock
|
129
|
+
fetched_bun_files
|
130
|
+
end
|
131
|
+
|
123
132
|
sig { returns(T::Array[DependencyFile]) }
|
124
133
|
def lerna_files
|
125
134
|
fetched_lerna_files = []
|
@@ -202,6 +211,16 @@ module Dependabot
|
|
202
211
|
)
|
203
212
|
end
|
204
213
|
|
214
|
+
sig { returns(T.nilable(T.any(Integer, String))) }
|
215
|
+
def bun_version
|
216
|
+
return @bun_version = nil unless allow_beta_ecosystems?
|
217
|
+
|
218
|
+
@bun_version ||= T.let(
|
219
|
+
package_manager_helper.setup(BunPackageManager::NAME),
|
220
|
+
T.nilable(T.any(Integer, String))
|
221
|
+
)
|
222
|
+
end
|
223
|
+
|
205
224
|
sig { returns(PackageManagerHelper) }
|
206
225
|
def package_manager_helper
|
207
226
|
@package_manager_helper ||= T.let(
|
@@ -219,7 +238,8 @@ module Dependabot
|
|
219
238
|
{
|
220
239
|
npm: package_lock || shrinkwrap,
|
221
240
|
yarn: yarn_lock,
|
222
|
-
pnpm: pnpm_lock
|
241
|
+
pnpm: pnpm_lock,
|
242
|
+
bun: bun_lock
|
223
243
|
}
|
224
244
|
end
|
225
245
|
|
@@ -261,17 +281,18 @@ module Dependabot
|
|
261
281
|
|
262
282
|
return @pnpm_lock if @pnpm_lock || directory == "/"
|
263
283
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
284
|
+
@pnpm_lock = fetch_file_from_parent_directories(PNPMPackageManager::LOCKFILE_NAME)
|
285
|
+
end
|
286
|
+
|
287
|
+
sig { returns(T.nilable(DependencyFile)) }
|
288
|
+
def bun_lock
|
289
|
+
return @bun_lock if defined?(@bun_lock)
|
290
|
+
|
291
|
+
@bun_lock ||= T.let(fetch_file_if_present(BunPackageManager::LOCKFILE_NAME), T.nilable(DependencyFile))
|
292
|
+
|
293
|
+
return @bun_lock if @bun_lock || directory == "/"
|
273
294
|
|
274
|
-
@
|
295
|
+
@bun_lock = fetch_file_from_parent_directories(BunPackageManager::LOCKFILE_NAME)
|
275
296
|
end
|
276
297
|
|
277
298
|
sig { returns(T.nilable(DependencyFile)) }
|
@@ -294,17 +315,7 @@ module Dependabot
|
|
294
315
|
|
295
316
|
return @npmrc if @npmrc || directory == "/"
|
296
317
|
|
297
|
-
|
298
|
-
(1..directory.split("/").count).each do |i|
|
299
|
-
@npmrc = fetch_file_from_host(("../" * i) + NpmPackageManager::RC_FILENAME)
|
300
|
-
.tap { |f| f.support_file = true }
|
301
|
-
break if @npmrc
|
302
|
-
rescue Dependabot::DependencyFileNotFound
|
303
|
-
# Ignore errors (.npmrc may not be present)
|
304
|
-
nil
|
305
|
-
end
|
306
|
-
|
307
|
-
@npmrc
|
318
|
+
@npmrc = fetch_file_from_parent_directories(NpmPackageManager::RC_FILENAME)
|
308
319
|
end
|
309
320
|
|
310
321
|
sig { returns(T.nilable(DependencyFile)) }
|
@@ -315,17 +326,7 @@ module Dependabot
|
|
315
326
|
|
316
327
|
return @yarnrc if @yarnrc || directory == "/"
|
317
328
|
|
318
|
-
|
319
|
-
(1..directory.split("/").count).each do |i|
|
320
|
-
@yarnrc = fetch_file_from_host(("../" * i) + YarnPackageManager::RC_FILENAME)
|
321
|
-
.tap { |f| f.support_file = true }
|
322
|
-
break if @yarnrc
|
323
|
-
rescue Dependabot::DependencyFileNotFound
|
324
|
-
# Ignore errors (.yarnrc may not be present)
|
325
|
-
nil
|
326
|
-
end
|
327
|
-
|
328
|
-
@yarnrc
|
329
|
+
@yarnrc = fetch_file_from_parent_directories(YarnPackageManager::RC_FILENAME)
|
329
330
|
end
|
330
331
|
|
331
332
|
sig { returns(T.nilable(DependencyFile)) }
|
@@ -452,6 +453,15 @@ module Dependabot
|
|
452
453
|
|
453
454
|
resolution_deps = resolution_objects.flat_map(&:to_a)
|
454
455
|
.map do |path, value|
|
456
|
+
# skip dependencies that contain invalid values such as inline comments, null, etc.
|
457
|
+
|
458
|
+
unless value.is_a?(String)
|
459
|
+
Dependabot.logger.warn("File fetcher: Skipping dependency \"#{path}\" " \
|
460
|
+
"with value: \"#{value}\"")
|
461
|
+
|
462
|
+
next
|
463
|
+
end
|
464
|
+
|
455
465
|
convert_dependency_path_to_name(path, value)
|
456
466
|
end
|
457
467
|
|
@@ -644,8 +654,8 @@ module Dependabot
|
|
644
654
|
def parsed_pnpm_workspace_yaml
|
645
655
|
return {} unless pnpm_workspace_yaml
|
646
656
|
|
647
|
-
YAML.safe_load(T.must(T.must(pnpm_workspace_yaml).content))
|
648
|
-
rescue Psych::SyntaxError
|
657
|
+
YAML.safe_load(T.must(T.must(pnpm_workspace_yaml).content), aliases: true)
|
658
|
+
rescue Psych::SyntaxError, Psych::BadAlias
|
649
659
|
raise Dependabot::DependencyFileNotParseable, T.must(pnpm_workspace_yaml).path
|
650
660
|
end
|
651
661
|
|
@@ -699,6 +709,22 @@ module Dependabot
|
|
699
709
|
Dependabot.logger.info("Repository contents path does not exist")
|
700
710
|
end
|
701
711
|
end
|
712
|
+
|
713
|
+
sig { params(filename: String).returns(T.nilable(DependencyFile)) }
|
714
|
+
def fetch_file_with_support(filename)
|
715
|
+
fetch_file_from_host(filename).tap { |f| f.support_file = true }
|
716
|
+
rescue Dependabot::DependencyFileNotFound
|
717
|
+
nil
|
718
|
+
end
|
719
|
+
|
720
|
+
sig { params(filename: String).returns(T.nilable(DependencyFile)) }
|
721
|
+
def fetch_file_from_parent_directories(filename)
|
722
|
+
(1..directory.split("/").count).each do |i|
|
723
|
+
file = fetch_file_with_support(("../" * i) + filename)
|
724
|
+
return file if file
|
725
|
+
end
|
726
|
+
nil
|
727
|
+
end
|
702
728
|
end
|
703
729
|
end
|
704
730
|
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "yaml"
|
5
|
+
require "dependabot/errors"
|
6
|
+
require "dependabot/npm_and_yarn/helpers"
|
7
|
+
require "sorbet-runtime"
|
8
|
+
|
9
|
+
module Dependabot
|
10
|
+
module NpmAndYarn
|
11
|
+
class FileParser < Dependabot::FileParsers::Base
|
12
|
+
class BunLock
|
13
|
+
extend T::Sig
|
14
|
+
|
15
|
+
sig { params(dependency_file: DependencyFile).void }
|
16
|
+
def initialize(dependency_file)
|
17
|
+
@dependency_file = dependency_file
|
18
|
+
end
|
19
|
+
|
20
|
+
sig { returns(T::Hash[String, T.untyped]) }
|
21
|
+
def parsed
|
22
|
+
@parsed ||= begin
|
23
|
+
content = begin
|
24
|
+
# Since bun.lock is a JSONC file, which is a subset of YAML, we can use YAML to parse it
|
25
|
+
YAML.load(T.must(@dependency_file.content))
|
26
|
+
rescue Psych::SyntaxError => e
|
27
|
+
raise_invalid!("malformed JSONC at line #{e.line}, column #{e.column}")
|
28
|
+
end
|
29
|
+
raise_invalid!("expected to be an object") unless content.is_a?(Hash)
|
30
|
+
|
31
|
+
version = content["lockfileVersion"]
|
32
|
+
raise_invalid!("expected 'lockfileVersion' to be an integer") unless version.is_a?(Integer)
|
33
|
+
raise_invalid!("expected 'lockfileVersion' to be >= 0") unless version >= 0
|
34
|
+
raise_invalid!("unsupported 'lockfileVersion' = #{version}") unless version.zero?
|
35
|
+
|
36
|
+
T.let(content, T.untyped)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { returns(Dependabot::FileParsers::Base::DependencySet) }
|
41
|
+
def dependencies
|
42
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
43
|
+
|
44
|
+
# bun.lock v0 format:
|
45
|
+
# https://github.com/oven-sh/bun/blob/c130df6c589fdf28f9f3c7f23ed9901140bc9349/src/install/bun.lock.zig#L595-L605
|
46
|
+
|
47
|
+
packages = parsed["packages"]
|
48
|
+
raise_invalid!("expected 'packages' to be an object") unless packages.is_a?(Hash)
|
49
|
+
|
50
|
+
packages.each do |key, details|
|
51
|
+
raise_invalid!("expected 'packages.#{key}' to be an array") unless details.is_a?(Array)
|
52
|
+
|
53
|
+
resolution = details.first
|
54
|
+
raise_invalid!("expected 'packages.#{key}[0]' to be a string") unless resolution.is_a?(String)
|
55
|
+
|
56
|
+
name, version = resolution.split(/(?<=\w)\@/)
|
57
|
+
next if name.empty?
|
58
|
+
|
59
|
+
semver = Version.semver_for(version)
|
60
|
+
next unless semver
|
61
|
+
|
62
|
+
dependency_set << Dependency.new(
|
63
|
+
name: name,
|
64
|
+
version: semver.to_s,
|
65
|
+
package_manager: "npm_and_yarn",
|
66
|
+
requirements: []
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
dependency_set
|
71
|
+
end
|
72
|
+
|
73
|
+
sig do
|
74
|
+
params(dependency_name: String, requirement: T.untyped, _manifest_name: String)
|
75
|
+
.returns(T.nilable(T::Hash[String, T.untyped]))
|
76
|
+
end
|
77
|
+
def details(dependency_name, requirement, _manifest_name)
|
78
|
+
packages = parsed["packages"]
|
79
|
+
return unless packages.is_a?(Hash)
|
80
|
+
|
81
|
+
candidates =
|
82
|
+
packages
|
83
|
+
.select { |name, _| name == dependency_name }
|
84
|
+
.values
|
85
|
+
|
86
|
+
# If there's only one entry for this dependency, use it, even if
|
87
|
+
# the requirement in the lockfile doesn't match
|
88
|
+
if candidates.one?
|
89
|
+
parse_details(candidates.first)
|
90
|
+
else
|
91
|
+
candidate = candidates.find do |label, _|
|
92
|
+
label.scan(/(?<=\w)\@(?:npm:)?([^\s,]+)/).flatten.include?(requirement)
|
93
|
+
end&.last
|
94
|
+
parse_details(candidate)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
sig { params(message: String).void }
|
101
|
+
def raise_invalid!(message)
|
102
|
+
raise Dependabot::DependencyFileNotParseable.new(@dependency_file.path, "Invalid bun.lock file: #{message}")
|
103
|
+
end
|
104
|
+
|
105
|
+
sig do
|
106
|
+
params(entry: T.nilable(T::Array[T.untyped])).returns(T.nilable(T::Hash[String, T.untyped]))
|
107
|
+
end
|
108
|
+
def parse_details(entry)
|
109
|
+
return unless entry.is_a?(Array)
|
110
|
+
|
111
|
+
# Either:
|
112
|
+
# - "{name}@{version}", registry, details, integrity
|
113
|
+
# - "{name}@{resolution}", details
|
114
|
+
resolution = entry.first
|
115
|
+
return unless resolution.is_a?(String)
|
116
|
+
|
117
|
+
name, version = resolution.split(/(?<=\w)\@/)
|
118
|
+
semver = Version.semver_for(version)
|
119
|
+
|
120
|
+
if semver
|
121
|
+
registry, details, integrity = entry[1..3]
|
122
|
+
{
|
123
|
+
"name" => name,
|
124
|
+
"version" => semver.to_s,
|
125
|
+
"registry" => registry,
|
126
|
+
"details" => details,
|
127
|
+
"integrity" => integrity
|
128
|
+
}
|
129
|
+
else
|
130
|
+
details = entry[1]
|
131
|
+
{
|
132
|
+
"name" => name,
|
133
|
+
"resolution" => version,
|
134
|
+
"details" => details
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -15,6 +15,11 @@ module Dependabot
|
|
15
15
|
require "dependabot/npm_and_yarn/file_parser/yarn_lock"
|
16
16
|
require "dependabot/npm_and_yarn/file_parser/pnpm_lock"
|
17
17
|
require "dependabot/npm_and_yarn/file_parser/json_lock"
|
18
|
+
require "dependabot/npm_and_yarn/file_parser/bun_lock"
|
19
|
+
|
20
|
+
DEFAULT_LOCKFILES = %w(package-lock.json yarn.lock pnpm-lock.yaml bun.lock npm-shrinkwrap.json).freeze
|
21
|
+
|
22
|
+
LockFile = T.type_alias { T.any(JsonLock, YarnLock, PnpmLock, BunLock) }
|
18
23
|
|
19
24
|
sig { params(dependency_files: T::Array[DependencyFile]).void }
|
20
25
|
def initialize(dependency_files:)
|
@@ -29,7 +34,7 @@ module Dependabot
|
|
29
34
|
# end up unique by name. That's not a perfect representation of
|
30
35
|
# the nested nature of JS resolution, but it makes everything work
|
31
36
|
# comparably to other flat-resolution strategies
|
32
|
-
(yarn_locks + pnpm_locks + package_locks + shrinkwraps).each do |file|
|
37
|
+
(yarn_locks + pnpm_locks + package_locks + bun_locks + shrinkwraps).each do |file|
|
33
38
|
dependency_set += lockfile_for(file).dependencies
|
34
39
|
end
|
35
40
|
|
@@ -64,58 +69,59 @@ module Dependabot
|
|
64
69
|
sig { params(manifest_filename: String).returns(T::Array[DependencyFile]) }
|
65
70
|
def potential_lockfiles_for_manifest(manifest_filename)
|
66
71
|
dir_name = File.dirname(manifest_filename)
|
67
|
-
possible_lockfile_names =
|
68
|
-
|
69
|
-
|
70
|
-
end +
|
71
|
-
%w(yarn.lock pnpm-lock.yaml package-lock.json npm-shrinkwrap.json)
|
72
|
+
possible_lockfile_names = DEFAULT_LOCKFILES.map do |f|
|
73
|
+
Pathname.new(File.join(dir_name, f)).cleanpath.to_path
|
74
|
+
end + DEFAULT_LOCKFILES
|
72
75
|
|
73
76
|
possible_lockfile_names.uniq
|
74
77
|
.filter_map { |nm| dependency_files.find { |f| f.name == nm } }
|
75
78
|
end
|
76
79
|
|
77
|
-
sig { params(file: DependencyFile).returns(
|
80
|
+
sig { params(file: DependencyFile).returns(LockFile) }
|
78
81
|
def lockfile_for(file)
|
79
|
-
@lockfiles ||= T.let({}, T.nilable(T::Hash[String,
|
80
|
-
@lockfiles[file.name] ||=
|
82
|
+
@lockfiles ||= T.let({}, T.nilable(T::Hash[String, LockFile]))
|
83
|
+
@lockfiles[file.name] ||= case file.name
|
84
|
+
when *package_locks.map(&:name), *shrinkwraps.map(&:name)
|
81
85
|
JsonLock.new(file)
|
82
|
-
|
86
|
+
when *yarn_locks.map(&:name)
|
83
87
|
YarnLock.new(file)
|
84
|
-
|
88
|
+
when *pnpm_locks.map(&:name)
|
85
89
|
PnpmLock.new(file)
|
90
|
+
when *bun_locks.map(&:name)
|
91
|
+
BunLock.new(file)
|
92
|
+
else
|
93
|
+
raise "Unexpected lockfile: #{file.name}"
|
86
94
|
end
|
87
95
|
end
|
88
96
|
|
97
|
+
sig { params(extension: String).returns(T::Array[DependencyFile]) }
|
98
|
+
def select_files_by_extension(extension)
|
99
|
+
dependency_files.select { |f| f.name.end_with?(extension) }
|
100
|
+
end
|
101
|
+
|
89
102
|
sig { returns(T::Array[DependencyFile]) }
|
90
103
|
def package_locks
|
91
|
-
@package_locks ||= T.let(
|
92
|
-
dependency_files
|
93
|
-
.select { |f| f.name.end_with?("package-lock.json") }, T.nilable(T::Array[DependencyFile])
|
94
|
-
)
|
104
|
+
@package_locks ||= T.let(select_files_by_extension("package-lock.json"), T.nilable(T::Array[DependencyFile]))
|
95
105
|
end
|
96
106
|
|
97
107
|
sig { returns(T::Array[DependencyFile]) }
|
98
108
|
def pnpm_locks
|
99
|
-
@pnpm_locks ||= T.let(
|
100
|
-
|
101
|
-
|
102
|
-
|
109
|
+
@pnpm_locks ||= T.let(select_files_by_extension("pnpm-lock.yaml"), T.nilable(T::Array[DependencyFile]))
|
110
|
+
end
|
111
|
+
|
112
|
+
sig { returns(T::Array[DependencyFile]) }
|
113
|
+
def bun_locks
|
114
|
+
@bun_locks ||= T.let(select_files_by_extension("bun.lock"), T.nilable(T::Array[DependencyFile]))
|
103
115
|
end
|
104
116
|
|
105
117
|
sig { returns(T::Array[DependencyFile]) }
|
106
118
|
def yarn_locks
|
107
|
-
@yarn_locks ||= T.let(
|
108
|
-
dependency_files
|
109
|
-
.select { |f| f.name.end_with?("yarn.lock") }, T.nilable(T::Array[DependencyFile])
|
110
|
-
)
|
119
|
+
@yarn_locks ||= T.let(select_files_by_extension("yarn.lock"), T.nilable(T::Array[DependencyFile]))
|
111
120
|
end
|
112
121
|
|
113
122
|
sig { returns(T::Array[DependencyFile]) }
|
114
123
|
def shrinkwraps
|
115
|
-
@shrinkwraps ||= T.let(
|
116
|
-
dependency_files
|
117
|
-
.select { |f| f.name.end_with?("npm-shrinkwrap.json") }, T.nilable(T::Array[DependencyFile])
|
118
|
-
)
|
124
|
+
@shrinkwraps ||= T.let(select_files_by_extension("npm-shrinkwrap.json"), T.nilable(T::Array[DependencyFile]))
|
119
125
|
end
|
120
126
|
|
121
127
|
sig { returns(T.class_of(Dependabot::NpmAndYarn::Version)) }
|
@@ -26,6 +26,10 @@ module Dependabot
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def dependencies
|
29
|
+
if Dependabot::Experiments.enabled?(:enable_fix_for_pnpm_no_change_error)
|
30
|
+
return dependencies_with_prioritization
|
31
|
+
end
|
32
|
+
|
29
33
|
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
30
34
|
|
31
35
|
parsed.each do |details|
|
@@ -52,6 +56,49 @@ module Dependabot
|
|
52
56
|
dependency_set
|
53
57
|
end
|
54
58
|
|
59
|
+
def dependencies_with_prioritization
|
60
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
61
|
+
|
62
|
+
# Separate dependencies into two categories: with specifiers and without specifiers.
|
63
|
+
dependencies_with_specifiers = [] # Main dependencies with specifiers.
|
64
|
+
dependencies_without_specifiers = [] # Subdependencies without specifiers.
|
65
|
+
|
66
|
+
parsed.each do |details|
|
67
|
+
next if details["aliased"]
|
68
|
+
|
69
|
+
name = details["name"]
|
70
|
+
version = details["version"]
|
71
|
+
|
72
|
+
dependency_args = {
|
73
|
+
name: name,
|
74
|
+
version: version,
|
75
|
+
package_manager: "npm_and_yarn",
|
76
|
+
requirements: []
|
77
|
+
}
|
78
|
+
|
79
|
+
# Add metadata for subdependencies if marked as a dev dependency.
|
80
|
+
dependency_args[:subdependency_metadata] = [{ production: !details["dev"] }] if details["dev"]
|
81
|
+
|
82
|
+
specifiers = details["specifiers"]
|
83
|
+
if specifiers&.any?
|
84
|
+
dependencies_with_specifiers << dependency_args
|
85
|
+
else
|
86
|
+
dependencies_without_specifiers << dependency_args
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Add prioritized dependencies to the dependency set.
|
91
|
+
dependencies_with_specifiers.each do |dependency_args|
|
92
|
+
dependency_set << Dependency.new(**dependency_args)
|
93
|
+
end
|
94
|
+
|
95
|
+
dependencies_without_specifiers.each do |dependency_args|
|
96
|
+
dependency_set << Dependency.new(**dependency_args)
|
97
|
+
end
|
98
|
+
|
99
|
+
dependency_set
|
100
|
+
end
|
101
|
+
|
55
102
|
def details(dependency_name, requirement, _manifest_name)
|
56
103
|
details_candidates = parsed.select { |info| info["name"] == dependency_name }
|
57
104
|
|