dependabot-npm_and_yarn 0.293.0 → 0.295.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/npm/vulnerability-auditor.js +16 -16
- data/helpers/lib/npm6/updater.js +1 -1
- data/lib/dependabot/npm_and_yarn/bun_package_manager.rb +1 -1
- data/lib/dependabot/npm_and_yarn/constraint_helper.rb +306 -0
- data/lib/dependabot/npm_and_yarn/file_fetcher.rb +12 -3
- data/lib/dependabot/npm_and_yarn/file_parser/bun_lock.rb +0 -1
- data/lib/dependabot/npm_and_yarn/file_parser.rb +47 -10
- data/lib/dependabot/npm_and_yarn/file_updater/package_json_updater.rb +25 -2
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb +81 -12
- data/lib/dependabot/npm_and_yarn/file_updater/pnpm_workspace_updater.rb +140 -0
- data/lib/dependabot/npm_and_yarn/file_updater.rb +56 -1
- data/lib/dependabot/npm_and_yarn/helpers.rb +14 -2
- data/lib/dependabot/npm_and_yarn/npm_package_manager.rb +4 -10
- data/lib/dependabot/npm_and_yarn/package_manager.rb +59 -24
- data/lib/dependabot/npm_and_yarn/version.rb +4 -0
- data/lib/dependabot/npm_and_yarn/version_selector.rb +32 -7
- data/lib/dependabot/npm_and_yarn.rb +19 -0
- metadata +7 -5
@@ -18,6 +18,10 @@ module Dependabot
|
|
18
18
|
@dependency_files = dependency_files
|
19
19
|
@repo_contents_path = repo_contents_path
|
20
20
|
@credentials = credentials
|
21
|
+
@error_handler = PnpmErrorHandler.new(
|
22
|
+
dependencies: dependencies,
|
23
|
+
dependency_files: dependency_files
|
24
|
+
)
|
21
25
|
end
|
22
26
|
|
23
27
|
def updated_pnpm_lock_content(pnpm_lock)
|
@@ -36,6 +40,7 @@ module Dependabot
|
|
36
40
|
attr_reader :dependency_files
|
37
41
|
attr_reader :repo_contents_path
|
38
42
|
attr_reader :credentials
|
43
|
+
attr_reader :error_handler
|
39
44
|
|
40
45
|
IRRESOLVABLE_PACKAGE = "ERR_PNPM_NO_MATCHING_VERSION"
|
41
46
|
INVALID_REQUIREMENT = "ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER"
|
@@ -46,12 +51,12 @@ module Dependabot
|
|
46
51
|
UNAUTHORIZED_PACKAGE = /ERR_PNPM_FETCH_401[ [^:print:]]+GET (?<dependency_url>.*): Unauthorized - 401/
|
47
52
|
|
48
53
|
# ERR_PNPM_FETCH ERROR CODES
|
49
|
-
ERR_PNPM_FETCH_401 = /ERR_PNPM_FETCH_401.*GET (?<dependency_url>.*)
|
50
|
-
ERR_PNPM_FETCH_403 = /ERR_PNPM_FETCH_403.*GET (?<dependency_url>.*)
|
51
|
-
ERR_PNPM_FETCH_404 = /ERR_PNPM_FETCH_404.*GET (?<dependency_url>.*)
|
52
|
-
ERR_PNPM_FETCH_500 = /ERR_PNPM_FETCH_500.*GET (?<dependency_url>.*)
|
53
|
-
ERR_PNPM_FETCH_502 = /ERR_PNPM_FETCH_502.*GET (?<dependency_url>.*)
|
54
|
-
ERR_PNPM_FETCH_503 = /ERR_PNPM_FETCH_503.*GET (?<dependency_url>.*)
|
54
|
+
ERR_PNPM_FETCH_401 = /ERR_PNPM_FETCH_401.*GET (?<dependency_url>.*):/
|
55
|
+
ERR_PNPM_FETCH_403 = /ERR_PNPM_FETCH_403.*GET (?<dependency_url>.*):/
|
56
|
+
ERR_PNPM_FETCH_404 = /ERR_PNPM_FETCH_404.*GET (?<dependency_url>.*):/
|
57
|
+
ERR_PNPM_FETCH_500 = /ERR_PNPM_FETCH_500.*GET (?<dependency_url>.*):/
|
58
|
+
ERR_PNPM_FETCH_502 = /ERR_PNPM_FETCH_502.*GET (?<dependency_url>.*):/
|
59
|
+
ERR_PNPM_FETCH_503 = /ERR_PNPM_FETCH_503.*GET (?<dependency_url>.*):/
|
55
60
|
|
56
61
|
# ERR_PNPM_UNSUPPORTED_ENGINE
|
57
62
|
ERR_PNPM_UNSUPPORTED_ENGINE = /ERR_PNPM_UNSUPPORTED_ENGINE/
|
@@ -100,7 +105,7 @@ module Dependabot
|
|
100
105
|
File.write(".npmrc", npmrc_content(pnpm_lock))
|
101
106
|
|
102
107
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
103
|
-
|
108
|
+
run_pnpm_update_packages
|
104
109
|
|
105
110
|
write_final_package_json_files
|
106
111
|
|
@@ -111,15 +116,22 @@ module Dependabot
|
|
111
116
|
end
|
112
117
|
end
|
113
118
|
|
114
|
-
def
|
119
|
+
def run_pnpm_update_packages
|
115
120
|
dependency_updates = dependencies.map do |d|
|
116
121
|
"#{d.name}@#{d.version}"
|
117
122
|
end.join(" ")
|
118
123
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
124
|
+
if Dependabot::Experiments.enabled?(:enable_fix_for_pnpm_no_change_error)
|
125
|
+
Helpers.run_pnpm_command(
|
126
|
+
"update #{dependency_updates} --lockfile-only --no-save -r",
|
127
|
+
fingerprint: "update <dependency_updates> --lockfile-only --no-save -r"
|
128
|
+
)
|
129
|
+
else
|
130
|
+
Helpers.run_pnpm_command(
|
131
|
+
"install #{dependency_updates} --lockfile-only --ignore-workspace-root-check",
|
132
|
+
fingerprint: "install <dependency_updates> --lockfile-only --ignore-workspace-root-check"
|
133
|
+
)
|
134
|
+
end
|
123
135
|
end
|
124
136
|
|
125
137
|
def run_pnpm_install
|
@@ -251,6 +263,8 @@ module Dependabot
|
|
251
263
|
pnpm_lock)
|
252
264
|
end
|
253
265
|
|
266
|
+
error_handler.handle_pnpm_error(error)
|
267
|
+
|
254
268
|
raise
|
255
269
|
end
|
256
270
|
# rubocop:enable Metrics/AbcSize
|
@@ -360,5 +374,60 @@ module Dependabot
|
|
360
374
|
end
|
361
375
|
end
|
362
376
|
end
|
377
|
+
|
378
|
+
class PnpmErrorHandler
|
379
|
+
extend T::Sig
|
380
|
+
|
381
|
+
# remote connection closed
|
382
|
+
ECONNRESET_ERROR = /ECONNRESET/
|
383
|
+
|
384
|
+
# socket hang up error code
|
385
|
+
SOCKET_HANG_UP = /socket hang up/
|
386
|
+
|
387
|
+
# ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC error
|
388
|
+
ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC = /ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC/
|
389
|
+
|
390
|
+
# duplicate package error code
|
391
|
+
DUPLICATE_PACKAGE = /Found duplicates/
|
392
|
+
|
393
|
+
ERR_PNPM_NO_VERSIONS = /ERR_PNPM_NO_VERSIONS/
|
394
|
+
|
395
|
+
# Initializes the YarnErrorHandler with dependencies and dependency files
|
396
|
+
sig do
|
397
|
+
params(
|
398
|
+
dependencies: T::Array[Dependabot::Dependency],
|
399
|
+
dependency_files: T::Array[Dependabot::DependencyFile]
|
400
|
+
).void
|
401
|
+
end
|
402
|
+
def initialize(dependencies:, dependency_files:)
|
403
|
+
@dependencies = dependencies
|
404
|
+
@dependency_files = dependency_files
|
405
|
+
end
|
406
|
+
|
407
|
+
private
|
408
|
+
|
409
|
+
sig { returns(T::Array[Dependabot::Dependency]) }
|
410
|
+
attr_reader :dependencies
|
411
|
+
|
412
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
413
|
+
attr_reader :dependency_files
|
414
|
+
|
415
|
+
public
|
416
|
+
|
417
|
+
# Handles errors with specific to yarn error codes
|
418
|
+
sig { params(error: SharedHelpers::HelperSubprocessFailed).void }
|
419
|
+
def handle_pnpm_error(error)
|
420
|
+
if error.message.match?(DUPLICATE_PACKAGE) || error.message.match?(ERR_PNPM_NO_VERSIONS) ||
|
421
|
+
error.message.match?(ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC)
|
422
|
+
|
423
|
+
raise DependencyFileNotResolvable, "Error resolving dependency"
|
424
|
+
end
|
425
|
+
|
426
|
+
## Clean error message from ANSI escape codes
|
427
|
+
return unless error.message.match?(ECONNRESET_ERROR) || error.message.match?(SOCKET_HANG_UP)
|
428
|
+
|
429
|
+
raise InconsistentRegistryResponse, "Inconsistent registry response while resolving dependency"
|
430
|
+
end
|
431
|
+
end
|
363
432
|
end
|
364
433
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "dependabot/npm_and_yarn/helpers"
|
5
|
+
require "dependabot/npm_and_yarn/update_checker/registry_finder"
|
6
|
+
require "dependabot/npm_and_yarn/registry_parser"
|
7
|
+
require "dependabot/shared_helpers"
|
8
|
+
|
9
|
+
class DependencyRequirement < T::Struct
|
10
|
+
const :file, String
|
11
|
+
const :requirement, String
|
12
|
+
const :groups, T::Array[String]
|
13
|
+
const :source, T.nilable(String)
|
14
|
+
end
|
15
|
+
|
16
|
+
module Dependabot
|
17
|
+
module NpmAndYarn
|
18
|
+
class FileUpdater
|
19
|
+
class PnpmWorkspaceUpdater
|
20
|
+
extend T::Sig
|
21
|
+
|
22
|
+
sig do
|
23
|
+
params(
|
24
|
+
workspace_file: Dependabot::DependencyFile,
|
25
|
+
dependencies: T::Array[Dependabot::Dependency]
|
26
|
+
) .void
|
27
|
+
end
|
28
|
+
def initialize(workspace_file:, dependencies:)
|
29
|
+
@dependencies = dependencies
|
30
|
+
@workspace_file = workspace_file
|
31
|
+
end
|
32
|
+
|
33
|
+
sig { returns(Dependabot::DependencyFile) }
|
34
|
+
def updated_pnpm_workspace
|
35
|
+
updated_file = workspace_file.dup
|
36
|
+
updated_file.content = updated_pnpm_workspace_content
|
37
|
+
updated_file
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
sig { returns(Dependabot::DependencyFile) }
|
43
|
+
attr_reader :workspace_file
|
44
|
+
|
45
|
+
sig { returns(T::Array[Dependabot::Dependency]) }
|
46
|
+
attr_reader :dependencies
|
47
|
+
|
48
|
+
sig { returns(T.nilable(String)) }
|
49
|
+
def updated_pnpm_workspace_content
|
50
|
+
content = workspace_file.content.dup
|
51
|
+
dependencies.each do |dependency|
|
52
|
+
content = update_dependency_versions(T.must(content), dependency)
|
53
|
+
end
|
54
|
+
|
55
|
+
content
|
56
|
+
end
|
57
|
+
|
58
|
+
sig { params(content: String, dependency: Dependabot::Dependency).returns(String) }
|
59
|
+
def update_dependency_versions(content, dependency)
|
60
|
+
new_requirements(dependency).each do |requirement|
|
61
|
+
content = replace_version_in_content(
|
62
|
+
content: content,
|
63
|
+
dependency: dependency,
|
64
|
+
old_requirement: T.must(old_requirement(dependency, requirement)),
|
65
|
+
new_requirement: requirement
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
content
|
70
|
+
end
|
71
|
+
|
72
|
+
sig do
|
73
|
+
params(
|
74
|
+
content: String,
|
75
|
+
dependency: Dependabot::Dependency,
|
76
|
+
old_requirement: DependencyRequirement,
|
77
|
+
new_requirement: DependencyRequirement
|
78
|
+
).returns(String)
|
79
|
+
end
|
80
|
+
def replace_version_in_content(content:, dependency:, old_requirement:, new_requirement:)
|
81
|
+
old_version = old_requirement.requirement
|
82
|
+
new_version = new_requirement.requirement
|
83
|
+
|
84
|
+
pattern = build_replacement_pattern(
|
85
|
+
dependency_name: dependency.name,
|
86
|
+
version: old_version
|
87
|
+
)
|
88
|
+
|
89
|
+
replacement = build_replacement_string(
|
90
|
+
dependency_name: dependency.name,
|
91
|
+
version: new_version
|
92
|
+
)
|
93
|
+
|
94
|
+
content.gsub(pattern, replacement)
|
95
|
+
end
|
96
|
+
|
97
|
+
sig { params(dependency_name: String, version: String).returns(Regexp) }
|
98
|
+
def build_replacement_pattern(dependency_name:, version:)
|
99
|
+
/(["']?)#{dependency_name}\1:\s*(["']?)#{Regexp.escape(version)}\2/
|
100
|
+
end
|
101
|
+
|
102
|
+
sig { params(dependency_name: String, version: String).returns(String) }
|
103
|
+
def build_replacement_string(dependency_name:, version:)
|
104
|
+
"\\1#{dependency_name}\\1: \\2#{version}\\2"
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { params(dependency: Dependabot::Dependency).returns(T::Array[DependencyRequirement]) }
|
108
|
+
def new_requirements(dependency)
|
109
|
+
dependency.requirements
|
110
|
+
.select { |r| r[:file] == workspace_file.name }
|
111
|
+
.map do |r|
|
112
|
+
DependencyRequirement.new(
|
113
|
+
file: r[:file],
|
114
|
+
requirement: r[:requirement],
|
115
|
+
groups: r[:groups],
|
116
|
+
source: r[:source]
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
sig do
|
122
|
+
params(dependency: Dependabot::Dependency,
|
123
|
+
new_requirement: DependencyRequirement).returns(T.nilable(DependencyRequirement))
|
124
|
+
end
|
125
|
+
def old_requirement(dependency, new_requirement)
|
126
|
+
matching_req = T.must(dependency.previous_requirements).find { |r| r[:groups] == new_requirement.groups }
|
127
|
+
|
128
|
+
return nil if matching_req.nil?
|
129
|
+
|
130
|
+
DependencyRequirement.new(
|
131
|
+
file: matching_req[:file],
|
132
|
+
requirement: matching_req[:requirement],
|
133
|
+
groups: matching_req[:groups],
|
134
|
+
source: matching_req[:source]
|
135
|
+
)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -11,7 +11,7 @@ require "sorbet-runtime"
|
|
11
11
|
|
12
12
|
module Dependabot
|
13
13
|
module NpmAndYarn
|
14
|
-
class FileUpdater < Dependabot::FileUpdaters::Base
|
14
|
+
class FileUpdater < Dependabot::FileUpdaters::Base # rubocop:disable Metrics/ClassLength
|
15
15
|
extend T::Sig
|
16
16
|
|
17
17
|
require_relative "file_updater/package_json_updater"
|
@@ -19,6 +19,7 @@ module Dependabot
|
|
19
19
|
require_relative "file_updater/yarn_lockfile_updater"
|
20
20
|
require_relative "file_updater/pnpm_lockfile_updater"
|
21
21
|
require_relative "file_updater/bun_lockfile_updater"
|
22
|
+
require_relative "file_updater/pnpm_workspace_updater"
|
22
23
|
|
23
24
|
class NoChangeError < StandardError
|
24
25
|
extend T::Sig
|
@@ -43,19 +44,40 @@ module Dependabot
|
|
43
44
|
%r{^(?:.*/)?npm-shrinkwrap\.json$},
|
44
45
|
%r{^(?:.*/)?yarn\.lock$},
|
45
46
|
%r{^(?:.*/)?pnpm-lock\.yaml$},
|
47
|
+
%r{^(?:.*/)?pnpm-workspace\.yaml$},
|
46
48
|
%r{^(?:.*/)?\.yarn/.*}, # Matches any file within the .yarn/ directory
|
47
49
|
%r{^(?:.*/)?\.pnp\.(?:js|cjs)$} # Matches .pnp.js or .pnp.cjs files
|
48
50
|
]
|
49
51
|
end
|
50
52
|
|
53
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
51
54
|
sig { override.returns(T::Array[DependencyFile]) }
|
52
55
|
def updated_dependency_files
|
53
56
|
updated_files = T.let([], T::Array[DependencyFile])
|
54
57
|
|
55
58
|
updated_files += updated_manifest_files
|
59
|
+
if Dependabot::Experiments.enabled?(:enable_pnpm_workspace_catalog)
|
60
|
+
updated_files += updated_pnpm_workspace_files
|
61
|
+
end
|
56
62
|
updated_files += updated_lockfiles
|
57
63
|
|
58
64
|
if updated_files.none?
|
65
|
+
|
66
|
+
if Dependabot::Experiments.enabled?(:enable_fix_for_pnpm_no_change_error)
|
67
|
+
# when all dependencies are transitive
|
68
|
+
all_transitive = dependencies.none?(&:top_level?)
|
69
|
+
# when there is no update in package.json
|
70
|
+
no_package_json_update = package_files.empty?
|
71
|
+
# handle the no change error for transitive dependency updates
|
72
|
+
if pnpm_locks.any? && dependencies.length.positive? && all_transitive && no_package_json_update
|
73
|
+
raise ToolFeatureNotSupported.new(
|
74
|
+
tool_name: "pnpm",
|
75
|
+
tool_type: "package_manager",
|
76
|
+
feature: "updating transitive dependencies"
|
77
|
+
)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
59
81
|
raise NoChangeError.new(
|
60
82
|
message: "No files were updated!",
|
61
83
|
error_context: error_context(updated_files: updated_files)
|
@@ -72,6 +94,7 @@ module Dependabot
|
|
72
94
|
|
73
95
|
vendor_updated_files(updated_files)
|
74
96
|
end
|
97
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
75
98
|
|
76
99
|
private
|
77
100
|
|
@@ -190,6 +213,15 @@ module Dependabot
|
|
190
213
|
)
|
191
214
|
end
|
192
215
|
|
216
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
217
|
+
def pnpm_workspace
|
218
|
+
@pnpm_workspace ||= T.let(
|
219
|
+
filtered_dependency_files
|
220
|
+
.select { |f| f.name.end_with?("pnpm-workspace.yaml") },
|
221
|
+
T.nilable(T::Array[Dependabot::DependencyFile])
|
222
|
+
)
|
223
|
+
end
|
224
|
+
|
193
225
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
194
226
|
def bun_locks
|
195
227
|
@bun_locks ||= T.let(
|
@@ -252,6 +284,16 @@ module Dependabot
|
|
252
284
|
end
|
253
285
|
end
|
254
286
|
|
287
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
288
|
+
def updated_pnpm_workspace_files
|
289
|
+
pnpm_workspace.filter_map do |file|
|
290
|
+
updated_content = updated_pnpm_workspace_content(file)
|
291
|
+
next if updated_content == file.content
|
292
|
+
|
293
|
+
updated_file(file: file, content: T.must(updated_content))
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
255
297
|
# rubocop:disable Metrics/MethodLength
|
256
298
|
# rubocop:disable Metrics/PerceivedComplexity
|
257
299
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
@@ -389,6 +431,19 @@ module Dependabot
|
|
389
431
|
dependencies: dependencies
|
390
432
|
).updated_package_json.content
|
391
433
|
end
|
434
|
+
|
435
|
+
sig do
|
436
|
+
params(file: Dependabot::DependencyFile)
|
437
|
+
.returns(T.nilable(String))
|
438
|
+
end
|
439
|
+
def updated_pnpm_workspace_content(file)
|
440
|
+
@updated_pnpm_workspace_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
|
441
|
+
@updated_pnpm_workspace_content[file.name] ||=
|
442
|
+
PnpmWorkspaceUpdater.new(
|
443
|
+
workspace_file: file,
|
444
|
+
dependencies: dependencies
|
445
|
+
).updated_pnpm_workspace.content
|
446
|
+
end
|
392
447
|
end
|
393
448
|
end
|
394
449
|
end
|
@@ -40,6 +40,9 @@ module Dependabot
|
|
40
40
|
YARN_DEFAULT_VERSION = YARN_V3
|
41
41
|
YARN_FALLBACK_VERSION = YARN_V1
|
42
42
|
|
43
|
+
# corepack supported package managers
|
44
|
+
SUPPORTED_COREPACK_PACKAGE_MANAGERS = %w(npm yarn pnpm).freeze
|
45
|
+
|
43
46
|
# Determines the npm version depends to the feature flag
|
44
47
|
# If the feature flag is enabled, we are going to use the minimum version npm 8
|
45
48
|
# Otherwise, we are going to use old versionining npm 6
|
@@ -324,8 +327,8 @@ module Dependabot
|
|
324
327
|
package_manager_run_command(NpmPackageManager::NAME, command, fingerprint: fingerprint)
|
325
328
|
else
|
326
329
|
Dependabot::SharedHelpers.run_shell_command(
|
327
|
-
"
|
328
|
-
fingerprint: "
|
330
|
+
"npm #{command}",
|
331
|
+
fingerprint: "npm #{fingerprint}"
|
329
332
|
)
|
330
333
|
end
|
331
334
|
end
|
@@ -484,6 +487,8 @@ module Dependabot
|
|
484
487
|
.returns(String)
|
485
488
|
end
|
486
489
|
def self.package_manager_install(name, version, env: {})
|
490
|
+
return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name)
|
491
|
+
|
487
492
|
Dependabot::SharedHelpers.run_shell_command(
|
488
493
|
"corepack install #{name}@#{version} --global --cache-only",
|
489
494
|
fingerprint: "corepack install <name>@<version> --global --cache-only",
|
@@ -494,6 +499,8 @@ module Dependabot
|
|
494
499
|
# Prepare the package manager for use by using corepack
|
495
500
|
sig { params(name: String, version: String).returns(String) }
|
496
501
|
def self.package_manager_activate(name, version)
|
502
|
+
return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name)
|
503
|
+
|
497
504
|
Dependabot::SharedHelpers.run_shell_command(
|
498
505
|
"corepack prepare #{name}@#{version} --activate",
|
499
506
|
fingerprint: "corepack prepare <name>@<version> --activate"
|
@@ -566,6 +573,11 @@ module Dependabot
|
|
566
573
|
dependency
|
567
574
|
end
|
568
575
|
end
|
576
|
+
|
577
|
+
sig { params(name: String).returns(T::Boolean) }
|
578
|
+
def self.corepack_supported_package_manager?(name)
|
579
|
+
SUPPORTED_COREPACK_PACKAGE_MANAGERS.include?(name)
|
580
|
+
end
|
569
581
|
end
|
570
582
|
end
|
571
583
|
end
|
@@ -38,8 +38,8 @@ module Dependabot
|
|
38
38
|
def initialize(detected_version: nil, raw_version: nil, requirement: nil)
|
39
39
|
super(
|
40
40
|
name: NAME,
|
41
|
-
detected_version: detected_version ? Version.new(detected_version) : nil,
|
42
|
-
version: raw_version ? Version.new(raw_version) : nil,
|
41
|
+
detected_version: detected_version && !detected_version.empty? ? Version.new(detected_version) : nil,
|
42
|
+
version: raw_version && !raw_version.empty? ? Version.new(raw_version) : nil,
|
43
43
|
deprecated_versions: DEPRECATED_VERSIONS,
|
44
44
|
supported_versions: SUPPORTED_VERSIONS,
|
45
45
|
requirement: requirement
|
@@ -48,22 +48,16 @@ module Dependabot
|
|
48
48
|
|
49
49
|
sig { override.returns(T::Boolean) }
|
50
50
|
def deprecated?
|
51
|
-
return false unless detected_version
|
52
|
-
|
53
|
-
return false if unsupported?
|
54
|
-
|
55
51
|
return false unless Dependabot::Experiments.enabled?(:npm_v6_deprecation_warning)
|
56
52
|
|
57
|
-
|
53
|
+
super
|
58
54
|
end
|
59
55
|
|
60
56
|
sig { override.returns(T::Boolean) }
|
61
57
|
def unsupported?
|
62
|
-
return false unless detected_version
|
63
|
-
|
64
58
|
return false unless Dependabot::Experiments.enabled?(:npm_v6_unsupported_error)
|
65
59
|
|
66
|
-
|
60
|
+
super
|
67
61
|
end
|
68
62
|
end
|
69
63
|
end
|
@@ -11,6 +11,7 @@ require "dependabot/npm_and_yarn/yarn_package_manager"
|
|
11
11
|
require "dependabot/npm_and_yarn/pnpm_package_manager"
|
12
12
|
require "dependabot/npm_and_yarn/bun_package_manager"
|
13
13
|
require "dependabot/npm_and_yarn/language"
|
14
|
+
require "dependabot/npm_and_yarn/constraint_helper"
|
14
15
|
|
15
16
|
module Dependabot
|
16
17
|
module NpmAndYarn
|
@@ -59,14 +60,16 @@ module Dependabot
|
|
59
60
|
T.any(
|
60
61
|
T.class_of(Dependabot::NpmAndYarn::NpmPackageManager),
|
61
62
|
T.class_of(Dependabot::NpmAndYarn::YarnPackageManager),
|
62
|
-
T.class_of(Dependabot::NpmAndYarn::PNPMPackageManager)
|
63
|
+
T.class_of(Dependabot::NpmAndYarn::PNPMPackageManager),
|
64
|
+
T.class_of(Dependabot::NpmAndYarn::BunPackageManager)
|
63
65
|
)
|
64
66
|
end
|
65
67
|
|
66
68
|
PACKAGE_MANAGER_CLASSES = T.let({
|
67
69
|
NpmPackageManager::NAME => NpmPackageManager,
|
68
70
|
YarnPackageManager::NAME => YarnPackageManager,
|
69
|
-
PNPMPackageManager::NAME => PNPMPackageManager
|
71
|
+
PNPMPackageManager::NAME => PNPMPackageManager,
|
72
|
+
BunPackageManager::NAME => BunPackageManager
|
70
73
|
}.freeze, T::Hash[String, NpmAndYarnPackageManagerClassType])
|
71
74
|
|
72
75
|
# Error malformed version number string
|
@@ -187,7 +190,7 @@ module Dependabot
|
|
187
190
|
end
|
188
191
|
|
189
192
|
sig { params(name: String).returns(T.nilable(Requirement)) }
|
190
|
-
def find_engine_constraints_as_requirement(name)
|
193
|
+
def find_engine_constraints_as_requirement(name) # rubocop:disable Metrics/PerceivedComplexity
|
191
194
|
Dependabot.logger.info("Processing engine constraints for #{name}")
|
192
195
|
|
193
196
|
return nil unless @engines.is_a?(Hash) && @engines[name]
|
@@ -195,19 +198,31 @@ module Dependabot
|
|
195
198
|
raw_constraint = @engines[name].to_s.strip
|
196
199
|
return nil if raw_constraint.empty?
|
197
200
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
"=#{constraint}"
|
207
|
-
else
|
208
|
-
Dependabot.logger.warn("Unrecognized constraint format for #{name}: #{constraint}")
|
209
|
-
constraint
|
201
|
+
if Dependabot::Experiments.enabled?(:enable_engine_version_detection)
|
202
|
+
constraints = ConstraintHelper.extract_constraints(raw_constraint)
|
203
|
+
|
204
|
+
# When constraints are invalid we return constraints array nil
|
205
|
+
if constraints.nil?
|
206
|
+
Dependabot.logger.warn(
|
207
|
+
"Unrecognized constraint format for #{name}: #{raw_constraint}"
|
208
|
+
)
|
210
209
|
end
|
210
|
+
else
|
211
|
+
raw_constraints = raw_constraint.split
|
212
|
+
constraints = raw_constraints.map do |constraint|
|
213
|
+
case constraint
|
214
|
+
when /^\d+$/
|
215
|
+
">=#{constraint}.0.0 <#{constraint.to_i + 1}.0.0"
|
216
|
+
when /^\d+\.\d+$/
|
217
|
+
">=#{constraint} <#{constraint.split('.').first.to_i + 1}.0.0"
|
218
|
+
when /^\d+\.\d+\.\d+$/
|
219
|
+
"=#{constraint}"
|
220
|
+
else
|
221
|
+
Dependabot.logger.warn("Unrecognized constraint format for #{name}: #{constraint}")
|
222
|
+
constraint
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
211
226
|
end
|
212
227
|
|
213
228
|
Dependabot.logger.info("Parsed constraints for #{name}: #{constraints.join(', ')}")
|
@@ -287,19 +302,24 @@ module Dependabot
|
|
287
302
|
|
288
303
|
sig { params(name: String).returns(T.nilable(String)) }
|
289
304
|
def detect_version(name)
|
290
|
-
#
|
305
|
+
# Prioritize version mentioned in "packageManager" instead of "engines"
|
291
306
|
if @manifest_package_manager&.start_with?("#{name}@")
|
292
307
|
detected_version = @manifest_package_manager.split("@").last.to_s
|
293
308
|
end
|
294
309
|
|
295
|
-
#
|
296
|
-
detected_version
|
310
|
+
# If "packageManager" has no version specified, check if we can extract "engines" information
|
311
|
+
detected_version ||= check_engine_version(name) if detected_version.to_s.empty?
|
297
312
|
|
298
|
-
#
|
299
|
-
|
300
|
-
detected_version = guessed_version(name) if !detected_version || detected_version.empty?
|
313
|
+
# If neither "packageManager" nor "engines" have versions, infer version from lockfileVersion
|
314
|
+
detected_version ||= guessed_version(name) if detected_version.to_s.empty?
|
301
315
|
|
302
|
-
|
316
|
+
# Strip and validate version format
|
317
|
+
detected_version_string = detected_version.to_s.strip
|
318
|
+
|
319
|
+
# Ensure detected_version is neither "0" nor invalid format
|
320
|
+
return if detected_version_string == "0" || !detected_version_string.match?(ConstraintHelper::VERSION_REGEX)
|
321
|
+
|
322
|
+
detected_version_string
|
303
323
|
end
|
304
324
|
|
305
325
|
sig { params(name: T.nilable(String)).returns(Ecosystem::VersionManager) }
|
@@ -330,7 +350,7 @@ module Dependabot
|
|
330
350
|
end
|
331
351
|
|
332
352
|
package_manager_class.new(
|
333
|
-
detected_version: detected_version
|
353
|
+
detected_version: detected_version,
|
334
354
|
raw_version: installed_version,
|
335
355
|
requirement: package_manager_requirement
|
336
356
|
)
|
@@ -432,7 +452,8 @@ module Dependabot
|
|
432
452
|
return if @package_json.nil?
|
433
453
|
|
434
454
|
version_selector = VersionSelector.new
|
435
|
-
|
455
|
+
|
456
|
+
engine_versions = version_selector.setup(@package_json, name, dependabot_versions(name))
|
436
457
|
|
437
458
|
return if engine_versions.empty?
|
438
459
|
|
@@ -440,6 +461,20 @@ module Dependabot
|
|
440
461
|
Dependabot.logger.info("Returned (#{MANIFEST_ENGINES_KEY}) info \"#{name}\" : \"#{version}\"")
|
441
462
|
version
|
442
463
|
end
|
464
|
+
|
465
|
+
sig { params(name: String).returns(T.nilable(T::Array[Dependabot::Version])) }
|
466
|
+
def dependabot_versions(name)
|
467
|
+
case name
|
468
|
+
when "npm"
|
469
|
+
NpmPackageManager::SUPPORTED_VERSIONS
|
470
|
+
when "yarn"
|
471
|
+
YarnPackageManager::SUPPORTED_VERSIONS
|
472
|
+
when "bun"
|
473
|
+
BunPackageManager::SUPPORTED_VERSIONS
|
474
|
+
when "pnpm"
|
475
|
+
PNPMPackageManager::SUPPORTED_VERSIONS
|
476
|
+
end
|
477
|
+
end
|
443
478
|
end
|
444
479
|
end
|
445
480
|
end
|
@@ -80,6 +80,10 @@ module Dependabot
|
|
80
80
|
# Matches @ followed by x.y.z (digits separated by dots)
|
81
81
|
if (match = version.match(/@(\d+\.\d+\.\d+)/))
|
82
82
|
version = match[1] # Just "4.5.3"
|
83
|
+
|
84
|
+
# Extract version in case the output contains Corepack verbose data
|
85
|
+
elsif version.include?("Corepack")
|
86
|
+
version = T.must(T.must(version.tr("\n", " ").match(/(\d+\.\d+\.\d+)/))[-1])
|
83
87
|
end
|
84
88
|
version = version&.gsub(/^v/, "")
|
85
89
|
end
|