dependabot-npm_and_yarn 0.292.0 → 0.294.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 +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
@@ -110,7 +110,8 @@ module Dependabot
|
|
110
110
|
{
|
111
111
|
npm: package_lock || shrinkwrap,
|
112
112
|
yarn: yarn_lock,
|
113
|
-
pnpm: pnpm_lock
|
113
|
+
pnpm: pnpm_lock,
|
114
|
+
bun: bun_lock
|
114
115
|
}
|
115
116
|
end
|
116
117
|
|
@@ -142,49 +143,56 @@ module Dependabot
|
|
142
143
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
143
144
|
def shrinkwrap
|
144
145
|
@shrinkwrap ||= T.let(dependency_files.find do |f|
|
145
|
-
f.name
|
146
|
+
f.name.end_with?(NpmPackageManager::SHRINKWRAP_LOCKFILE_NAME)
|
146
147
|
end, T.nilable(Dependabot::DependencyFile))
|
147
148
|
end
|
148
149
|
|
149
150
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
150
151
|
def package_lock
|
151
152
|
@package_lock ||= T.let(dependency_files.find do |f|
|
152
|
-
f.name
|
153
|
+
f.name.end_with?(NpmPackageManager::LOCKFILE_NAME)
|
153
154
|
end, T.nilable(Dependabot::DependencyFile))
|
154
155
|
end
|
155
156
|
|
156
157
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
157
158
|
def yarn_lock
|
158
159
|
@yarn_lock ||= T.let(dependency_files.find do |f|
|
159
|
-
f.name
|
160
|
+
f.name.end_with?(YarnPackageManager::LOCKFILE_NAME)
|
160
161
|
end, T.nilable(Dependabot::DependencyFile))
|
161
162
|
end
|
162
163
|
|
163
164
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
164
165
|
def pnpm_lock
|
165
166
|
@pnpm_lock ||= T.let(dependency_files.find do |f|
|
166
|
-
f.name
|
167
|
+
f.name.end_with?(PNPMPackageManager::LOCKFILE_NAME)
|
168
|
+
end, T.nilable(Dependabot::DependencyFile))
|
169
|
+
end
|
170
|
+
|
171
|
+
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
172
|
+
def bun_lock
|
173
|
+
@bun_lock ||= T.let(dependency_files.find do |f|
|
174
|
+
f.name.end_with?(BunPackageManager::LOCKFILE_NAME)
|
167
175
|
end, T.nilable(Dependabot::DependencyFile))
|
168
176
|
end
|
169
177
|
|
170
178
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
171
179
|
def npmrc
|
172
180
|
@npmrc ||= T.let(dependency_files.find do |f|
|
173
|
-
f.name
|
181
|
+
f.name.end_with?(NpmPackageManager::RC_FILENAME)
|
174
182
|
end, T.nilable(Dependabot::DependencyFile))
|
175
183
|
end
|
176
184
|
|
177
185
|
sig { returns(T.nilable(Dependabot::DependencyFile)) }
|
178
186
|
def yarnrc
|
179
187
|
@yarnrc ||= T.let(dependency_files.find do |f|
|
180
|
-
f.name
|
188
|
+
f.name.end_with?(YarnPackageManager::RC_FILENAME)
|
181
189
|
end, T.nilable(Dependabot::DependencyFile))
|
182
190
|
end
|
183
191
|
|
184
192
|
sig { returns(T.nilable(DependencyFile)) }
|
185
193
|
def yarnrc_yml
|
186
194
|
@yarnrc_yml ||= T.let(dependency_files.find do |f|
|
187
|
-
f.name
|
195
|
+
f.name.end_with?(YarnPackageManager::RC_YML_FILENAME)
|
188
196
|
end, T.nilable(Dependabot::DependencyFile))
|
189
197
|
end
|
190
198
|
|
@@ -204,7 +212,7 @@ module Dependabot
|
|
204
212
|
next unless requirement.is_a?(String)
|
205
213
|
|
206
214
|
# Skip dependencies using Yarn workspace cross-references as requirements
|
207
|
-
next if requirement.start_with?("workspace:")
|
215
|
+
next if requirement.start_with?("workspace:", "catalog:")
|
208
216
|
|
209
217
|
requirement = "*" if requirement == ""
|
210
218
|
dep = build_dependency(
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# typed: true
|
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
|
+
module Dependabot
|
10
|
+
module NpmAndYarn
|
11
|
+
class FileUpdater < Dependabot::FileUpdaters::Base
|
12
|
+
class BunLockfileUpdater
|
13
|
+
require_relative "npmrc_builder"
|
14
|
+
require_relative "package_json_updater"
|
15
|
+
|
16
|
+
def initialize(dependencies:, dependency_files:, repo_contents_path:, credentials:)
|
17
|
+
@dependencies = dependencies
|
18
|
+
@dependency_files = dependency_files
|
19
|
+
@repo_contents_path = repo_contents_path
|
20
|
+
@credentials = credentials
|
21
|
+
end
|
22
|
+
|
23
|
+
def updated_bun_lock_content(bun_lock)
|
24
|
+
@updated_bun_lock_content ||= {}
|
25
|
+
return @updated_bun_lock_content[bun_lock.name] if @updated_bun_lock_content[bun_lock.name]
|
26
|
+
|
27
|
+
new_content = run_bun_update(bun_lock: bun_lock)
|
28
|
+
@updated_bun_lock_content[bun_lock.name] = new_content
|
29
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
30
|
+
handle_bun_lock_updater_error(e, bun_lock)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :dependencies
|
36
|
+
attr_reader :dependency_files
|
37
|
+
attr_reader :repo_contents_path
|
38
|
+
attr_reader :credentials
|
39
|
+
|
40
|
+
ERR_PATTERNS = {
|
41
|
+
/get .* 404/i => Dependabot::DependencyNotFound,
|
42
|
+
/installfailed cloning repository/i => Dependabot::DependencyNotFound,
|
43
|
+
/file:.* failed to resolve/i => Dependabot::DependencyNotFound,
|
44
|
+
/no version matching/i => Dependabot::DependencyFileNotResolvable,
|
45
|
+
/failed to resolve/i => Dependabot::DependencyFileNotResolvable
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
def run_bun_update(bun_lock:)
|
49
|
+
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
50
|
+
File.write(".npmrc", npmrc_content(bun_lock))
|
51
|
+
|
52
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
53
|
+
run_bun_updater
|
54
|
+
|
55
|
+
write_final_package_json_files
|
56
|
+
|
57
|
+
run_bun_install
|
58
|
+
|
59
|
+
File.read(bun_lock.name)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def run_bun_updater
|
65
|
+
dependency_updates = dependencies.map do |d|
|
66
|
+
"#{d.name}@#{d.version}"
|
67
|
+
end.join(" ")
|
68
|
+
|
69
|
+
Helpers.run_bun_command(
|
70
|
+
"install #{dependency_updates} --save-text-lockfile",
|
71
|
+
fingerprint: "install <dependency_updates> --save-text-lockfile"
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def run_bun_install
|
76
|
+
Helpers.run_bun_command(
|
77
|
+
"install --save-text-lockfile"
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
def lockfile_dependencies(lockfile)
|
82
|
+
@lockfile_dependencies ||= {}
|
83
|
+
@lockfile_dependencies[lockfile.name] ||=
|
84
|
+
NpmAndYarn::FileParser.new(
|
85
|
+
dependency_files: [lockfile, *package_files],
|
86
|
+
source: nil,
|
87
|
+
credentials: credentials
|
88
|
+
).parse
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_bun_lock_updater_error(error, _bun_lock)
|
92
|
+
error_message = error.message
|
93
|
+
|
94
|
+
ERR_PATTERNS.each do |pattern, error_class|
|
95
|
+
raise error_class, error_message if error_message.match?(pattern)
|
96
|
+
end
|
97
|
+
|
98
|
+
raise error
|
99
|
+
end
|
100
|
+
|
101
|
+
def write_final_package_json_files
|
102
|
+
package_files.each do |file|
|
103
|
+
path = file.name
|
104
|
+
FileUtils.mkdir_p(Pathname.new(path).dirname)
|
105
|
+
File.write(path, updated_package_json_content(file))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def npmrc_content(bun_lock)
|
110
|
+
NpmrcBuilder.new(
|
111
|
+
credentials: credentials,
|
112
|
+
dependency_files: dependency_files,
|
113
|
+
dependencies: lockfile_dependencies(bun_lock)
|
114
|
+
).npmrc_content
|
115
|
+
end
|
116
|
+
|
117
|
+
def updated_package_json_content(file)
|
118
|
+
@updated_package_json_content ||= {}
|
119
|
+
@updated_package_json_content[file.name] ||=
|
120
|
+
PackageJsonUpdater.new(
|
121
|
+
package_json: file,
|
122
|
+
dependencies: dependencies
|
123
|
+
).updated_package_json.content
|
124
|
+
end
|
125
|
+
|
126
|
+
def package_files
|
127
|
+
@package_files ||= dependency_files.select { |f| f.name.end_with?("package.json") }
|
128
|
+
end
|
129
|
+
|
130
|
+
def base_dir
|
131
|
+
dependency_files.first.directory
|
132
|
+
end
|
133
|
+
|
134
|
+
def npmrc_file
|
135
|
+
dependency_files.find { |f| f.name == ".npmrc" }
|
136
|
+
end
|
137
|
+
|
138
|
+
def sanitize_message(message)
|
139
|
+
message.gsub(/"|\[|\]|\}|\{/, "")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -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/
|
@@ -62,6 +67,13 @@ module Dependabot
|
|
62
67
|
|
63
68
|
ERR_PNPM_PATCH_NOT_APPLIED = /ERR_PNPM_PATCH_NOT_APPLIED/
|
64
69
|
|
70
|
+
# this intermittent issue is related with Node v20
|
71
|
+
ERR_INVALID_THIS = /ERR_INVALID_THIS/
|
72
|
+
URL_SEARCH_PARAMS = /URLSearchParams/
|
73
|
+
|
74
|
+
# A modules directory is present and is linked to a different store directory.
|
75
|
+
ERR_PNPM_UNEXPECTED_STORE = /ERR_PNPM_UNEXPECTED_STORE/
|
76
|
+
|
65
77
|
# ERR_PNPM_UNSUPPORTED_PLATFORM
|
66
78
|
ERR_PNPM_UNSUPPORTED_PLATFORM = /ERR_PNPM_UNSUPPORTED_PLATFORM/
|
67
79
|
PLATFORM_PACAKGE_DEP = /Unsupported platform for (?<dep>.*)\: wanted/
|
@@ -78,12 +90,22 @@ module Dependabot
|
|
78
90
|
ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND = /ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND*.*Could not install from \"(?<dir>.*)\" /
|
79
91
|
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND = /ERR_PNPM_WORKSPACE_PKG_NOT_FOUND/
|
80
92
|
|
93
|
+
# Unparsable package.json file
|
94
|
+
ERR_PNPM_INVALID_PACKAGE_JSON = /Invalid package.json in package/
|
95
|
+
|
96
|
+
# Unparsable lockfile
|
97
|
+
ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE = /ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE/
|
98
|
+
ERR_PNPM_OUTDATED_LOCKFILE = /ERR_PNPM_OUTDATED_LOCKFILE/
|
99
|
+
|
100
|
+
# Peer dependencies configuration error
|
101
|
+
ERR_PNPM_PEER_DEP_ISSUES = /ERR_PNPM_PEER_DEP_ISSUES/
|
102
|
+
|
81
103
|
def run_pnpm_update(pnpm_lock:)
|
82
104
|
SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
|
83
105
|
File.write(".npmrc", npmrc_content(pnpm_lock))
|
84
106
|
|
85
107
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
86
|
-
|
108
|
+
run_pnpm_update_packages
|
87
109
|
|
88
110
|
write_final_package_json_files
|
89
111
|
|
@@ -94,15 +116,22 @@ module Dependabot
|
|
94
116
|
end
|
95
117
|
end
|
96
118
|
|
97
|
-
def
|
119
|
+
def run_pnpm_update_packages
|
98
120
|
dependency_updates = dependencies.map do |d|
|
99
121
|
"#{d.name}@#{d.version}"
|
100
122
|
end.join(" ")
|
101
123
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
106
135
|
end
|
107
136
|
|
108
137
|
def run_pnpm_install
|
@@ -196,15 +225,46 @@ module Dependabot
|
|
196
225
|
raise Dependabot::DependencyFileNotResolvable, msg
|
197
226
|
end
|
198
227
|
|
228
|
+
if error_message.match?(ERR_PNPM_INVALID_PACKAGE_JSON) || error_message.match?(ERR_PNPM_UNEXPECTED_STORE)
|
229
|
+
msg = "Error while resolving package.json."
|
230
|
+
Dependabot.logger.warn(error_message)
|
231
|
+
raise Dependabot::DependencyFileNotResolvable, msg
|
232
|
+
end
|
233
|
+
|
234
|
+
[ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE, ERR_PNPM_OUTDATED_LOCKFILE]
|
235
|
+
.each do |regexp|
|
236
|
+
next unless error_message.match?(regexp)
|
237
|
+
|
238
|
+
error_msg = T.let("Error while resolving pnpm-lock.yaml file.", String)
|
239
|
+
|
240
|
+
Dependabot.logger.warn(error_message)
|
241
|
+
raise Dependabot::DependencyFileNotResolvable, error_msg
|
242
|
+
end
|
243
|
+
|
244
|
+
if error_message.match?(ERR_PNPM_PEER_DEP_ISSUES)
|
245
|
+
msg = "Missing or invalid configuration while installing peer dependencies."
|
246
|
+
|
247
|
+
Dependabot.logger.warn(error_message)
|
248
|
+
raise Dependabot::DependencyFileNotResolvable, msg
|
249
|
+
end
|
250
|
+
|
199
251
|
raise_patch_dependency_error(error_message) if error_message.match?(ERR_PNPM_PATCH_NOT_APPLIED)
|
200
252
|
|
201
253
|
raise_unsupported_engine_error(error_message, pnpm_lock) if error_message.match?(ERR_PNPM_UNSUPPORTED_ENGINE)
|
202
254
|
|
255
|
+
if error_message.match?(ERR_INVALID_THIS) && error_message.match?(URL_SEARCH_PARAMS)
|
256
|
+
msg = "Error while resolving dependencies."
|
257
|
+
Dependabot.logger.warn(error_message)
|
258
|
+
raise Dependabot::DependencyFileNotResolvable, msg
|
259
|
+
end
|
260
|
+
|
203
261
|
if error_message.match?(ERR_PNPM_UNSUPPORTED_PLATFORM)
|
204
262
|
raise_unsupported_platform_error(error_message,
|
205
263
|
pnpm_lock)
|
206
264
|
end
|
207
265
|
|
266
|
+
error_handler.handle_pnpm_error(error)
|
267
|
+
|
208
268
|
raise
|
209
269
|
end
|
210
270
|
# rubocop:enable Metrics/AbcSize
|
@@ -314,5 +374,60 @@ module Dependabot
|
|
314
374
|
end
|
315
375
|
end
|
316
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
|
317
432
|
end
|
318
433
|
end
|
@@ -18,6 +18,7 @@ module Dependabot
|
|
18
18
|
require_relative "file_updater/npm_lockfile_updater"
|
19
19
|
require_relative "file_updater/yarn_lockfile_updater"
|
20
20
|
require_relative "file_updater/pnpm_lockfile_updater"
|
21
|
+
require_relative "file_updater/bun_lockfile_updater"
|
21
22
|
|
22
23
|
class NoChangeError < StandardError
|
23
24
|
extend T::Sig
|
@@ -47,6 +48,7 @@ module Dependabot
|
|
47
48
|
]
|
48
49
|
end
|
49
50
|
|
51
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
50
52
|
sig { override.returns(T::Array[DependencyFile]) }
|
51
53
|
def updated_dependency_files
|
52
54
|
updated_files = T.let([], T::Array[DependencyFile])
|
@@ -55,6 +57,22 @@ module Dependabot
|
|
55
57
|
updated_files += updated_lockfiles
|
56
58
|
|
57
59
|
if updated_files.none?
|
60
|
+
|
61
|
+
if Dependabot::Experiments.enabled?(:enable_fix_for_pnpm_no_change_error)
|
62
|
+
# when all dependencies are transitive
|
63
|
+
all_transitive = dependencies.none?(&:top_level?)
|
64
|
+
# when there is no update in package.json
|
65
|
+
no_package_json_update = package_files.empty?
|
66
|
+
# handle the no change error for transitive dependency updates
|
67
|
+
if pnpm_locks.any? && dependencies.length.positive? && all_transitive && no_package_json_update
|
68
|
+
raise ToolFeatureNotSupported.new(
|
69
|
+
tool_name: "pnpm",
|
70
|
+
tool_type: "package_manager",
|
71
|
+
feature: "updating transitive dependencies"
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
58
76
|
raise NoChangeError.new(
|
59
77
|
message: "No files were updated!",
|
60
78
|
error_context: error_context(updated_files: updated_files)
|
@@ -71,6 +89,7 @@ module Dependabot
|
|
71
89
|
|
72
90
|
vendor_updated_files(updated_files)
|
73
91
|
end
|
92
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
74
93
|
|
75
94
|
private
|
76
95
|
|
@@ -189,6 +208,15 @@ module Dependabot
|
|
189
208
|
)
|
190
209
|
end
|
191
210
|
|
211
|
+
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
212
|
+
def bun_locks
|
213
|
+
@bun_locks ||= T.let(
|
214
|
+
filtered_dependency_files
|
215
|
+
.select { |f| f.name.end_with?("bun.lock") },
|
216
|
+
T.nilable(T::Array[Dependabot::DependencyFile])
|
217
|
+
)
|
218
|
+
end
|
219
|
+
|
192
220
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
193
221
|
def shrinkwraps
|
194
222
|
@shrinkwraps ||= T.let(
|
@@ -217,6 +245,11 @@ module Dependabot
|
|
217
245
|
pnpm_lock.content != updated_pnpm_lock_content(pnpm_lock)
|
218
246
|
end
|
219
247
|
|
248
|
+
sig { params(bun_lock: Dependabot::DependencyFile).returns(T::Boolean) }
|
249
|
+
def bun_lock_changed?(bun_lock)
|
250
|
+
bun_lock.content != updated_bun_lock_content(bun_lock)
|
251
|
+
end
|
252
|
+
|
220
253
|
sig { params(package_lock: Dependabot::DependencyFile).returns(T::Boolean) }
|
221
254
|
def package_lock_changed?(package_lock)
|
222
255
|
package_lock.content != updated_lockfile_content(package_lock)
|
@@ -237,6 +270,8 @@ module Dependabot
|
|
237
270
|
end
|
238
271
|
end
|
239
272
|
|
273
|
+
# rubocop:disable Metrics/MethodLength
|
274
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
240
275
|
sig { returns(T::Array[Dependabot::DependencyFile]) }
|
241
276
|
def updated_lockfiles
|
242
277
|
updated_files = []
|
@@ -259,6 +294,15 @@ module Dependabot
|
|
259
294
|
)
|
260
295
|
end
|
261
296
|
|
297
|
+
bun_locks.each do |bun_lock|
|
298
|
+
next unless bun_lock_changed?(bun_lock)
|
299
|
+
|
300
|
+
updated_files << updated_file(
|
301
|
+
file: bun_lock,
|
302
|
+
content: updated_bun_lock_content(bun_lock)
|
303
|
+
)
|
304
|
+
end
|
305
|
+
|
262
306
|
package_locks.each do |package_lock|
|
263
307
|
next unless package_lock_changed?(package_lock)
|
264
308
|
|
@@ -279,6 +323,8 @@ module Dependabot
|
|
279
323
|
|
280
324
|
updated_files
|
281
325
|
end
|
326
|
+
# rubocop:enable Metrics/MethodLength
|
327
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
282
328
|
|
283
329
|
sig { params(yarn_lock: Dependabot::DependencyFile).returns(String) }
|
284
330
|
def updated_yarn_lock_content(yarn_lock)
|
@@ -294,6 +340,13 @@ module Dependabot
|
|
294
340
|
pnpm_lockfile_updater.updated_pnpm_lock_content(pnpm_lock)
|
295
341
|
end
|
296
342
|
|
343
|
+
sig { params(bun_lock: Dependabot::DependencyFile).returns(String) }
|
344
|
+
def updated_bun_lock_content(bun_lock)
|
345
|
+
@updated_bun_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
|
346
|
+
@updated_bun_lock_content[bun_lock.name] ||=
|
347
|
+
bun_lockfile_updater.updated_bun_lock_content(bun_lock)
|
348
|
+
end
|
349
|
+
|
297
350
|
sig { returns(Dependabot::NpmAndYarn::FileUpdater::YarnLockfileUpdater) }
|
298
351
|
def yarn_lockfile_updater
|
299
352
|
@yarn_lockfile_updater ||= T.let(
|
@@ -320,6 +373,19 @@ module Dependabot
|
|
320
373
|
)
|
321
374
|
end
|
322
375
|
|
376
|
+
sig { returns(Dependabot::NpmAndYarn::FileUpdater::BunLockfileUpdater) }
|
377
|
+
def bun_lockfile_updater
|
378
|
+
@bun_lockfile_updater ||= T.let(
|
379
|
+
BunLockfileUpdater.new(
|
380
|
+
dependencies: dependencies,
|
381
|
+
dependency_files: dependency_files,
|
382
|
+
repo_contents_path: repo_contents_path,
|
383
|
+
credentials: credentials
|
384
|
+
),
|
385
|
+
T.nilable(Dependabot::NpmAndYarn::FileUpdater::BunLockfileUpdater)
|
386
|
+
)
|
387
|
+
end
|
388
|
+
|
323
389
|
sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
|
324
390
|
def updated_lockfile_content(file)
|
325
391
|
@updated_lockfile_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
|
@@ -29,6 +29,10 @@ module Dependabot
|
|
29
29
|
PNPM_DEFAULT_VERSION = PNPM_V9
|
30
30
|
PNPM_FALLBACK_VERSION = PNPM_V6
|
31
31
|
|
32
|
+
# BUN Version Constants
|
33
|
+
BUN_V1 = 1
|
34
|
+
BUN_DEFAULT_VERSION = BUN_V1
|
35
|
+
|
32
36
|
# YARN Version Constants
|
33
37
|
YARN_V3 = 3
|
34
38
|
YARN_V2 = 2
|
@@ -36,6 +40,9 @@ module Dependabot
|
|
36
40
|
YARN_DEFAULT_VERSION = YARN_V3
|
37
41
|
YARN_FALLBACK_VERSION = YARN_V1
|
38
42
|
|
43
|
+
# corepack supported package managers
|
44
|
+
SUPPORTED_COREPACK_PACKAGE_MANAGERS = %w(npm yarn pnpm).freeze
|
45
|
+
|
39
46
|
# Determines the npm version depends to the feature flag
|
40
47
|
# If the feature flag is enabled, we are going to use the minimum version npm 8
|
41
48
|
# Otherwise, we are going to use old versionining npm 6
|
@@ -159,6 +166,11 @@ module Dependabot
|
|
159
166
|
PNPM_FALLBACK_VERSION
|
160
167
|
end
|
161
168
|
|
169
|
+
sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) }
|
170
|
+
def self.bun_version_numeric(_bun_lock)
|
171
|
+
BUN_DEFAULT_VERSION
|
172
|
+
end
|
173
|
+
|
162
174
|
sig { params(key: String, default_value: String).returns(T.untyped) }
|
163
175
|
def self.fetch_yarnrc_yml_value(key, default_value)
|
164
176
|
if File.exist?(".yarnrc.yml") && (yarnrc = YAML.load_file(".yarnrc.yml"))
|
@@ -315,8 +327,8 @@ module Dependabot
|
|
315
327
|
package_manager_run_command(NpmPackageManager::NAME, command, fingerprint: fingerprint)
|
316
328
|
else
|
317
329
|
Dependabot::SharedHelpers.run_shell_command(
|
318
|
-
"
|
319
|
-
fingerprint: "
|
330
|
+
"npm #{command}",
|
331
|
+
fingerprint: "npm #{fingerprint}"
|
320
332
|
)
|
321
333
|
end
|
322
334
|
end
|
@@ -352,6 +364,35 @@ module Dependabot
|
|
352
364
|
raise
|
353
365
|
end
|
354
366
|
|
367
|
+
sig { returns(T.nilable(String)) }
|
368
|
+
def self.bun_version
|
369
|
+
version = run_bun_command("--version", fingerprint: "--version").strip
|
370
|
+
if version.include?("+")
|
371
|
+
version.split("+").first # Remove build info, if present
|
372
|
+
end
|
373
|
+
rescue StandardError => e
|
374
|
+
Dependabot.logger.error("Error retrieving Bun version: #{e.message}")
|
375
|
+
nil
|
376
|
+
end
|
377
|
+
|
378
|
+
sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
|
379
|
+
def self.run_bun_command(command, fingerprint: nil)
|
380
|
+
full_command = "bun #{command}"
|
381
|
+
|
382
|
+
Dependabot.logger.info("Running bun command: #{full_command}")
|
383
|
+
|
384
|
+
result = Dependabot::SharedHelpers.run_shell_command(
|
385
|
+
full_command,
|
386
|
+
fingerprint: "bun #{fingerprint || command}"
|
387
|
+
)
|
388
|
+
|
389
|
+
Dependabot.logger.info("Command executed successfully: #{full_command}")
|
390
|
+
result
|
391
|
+
rescue StandardError => e
|
392
|
+
Dependabot.logger.error("Error running bun command: #{full_command}, Error: #{e.message}")
|
393
|
+
raise
|
394
|
+
end
|
395
|
+
|
355
396
|
# Setup yarn and run a single yarn command returning stdout/stderr
|
356
397
|
sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
|
357
398
|
def self.run_yarn_command(command, fingerprint: nil)
|
@@ -446,6 +487,8 @@ module Dependabot
|
|
446
487
|
.returns(String)
|
447
488
|
end
|
448
489
|
def self.package_manager_install(name, version, env: {})
|
490
|
+
return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name)
|
491
|
+
|
449
492
|
Dependabot::SharedHelpers.run_shell_command(
|
450
493
|
"corepack install #{name}@#{version} --global --cache-only",
|
451
494
|
fingerprint: "corepack install <name>@<version> --global --cache-only",
|
@@ -456,6 +499,8 @@ module Dependabot
|
|
456
499
|
# Prepare the package manager for use by using corepack
|
457
500
|
sig { params(name: String, version: String).returns(String) }
|
458
501
|
def self.package_manager_activate(name, version)
|
502
|
+
return "Corepack does not support #{name}" unless corepack_supported_package_manager?(name)
|
503
|
+
|
459
504
|
Dependabot::SharedHelpers.run_shell_command(
|
460
505
|
"corepack prepare #{name}@#{version} --activate",
|
461
506
|
fingerprint: "corepack prepare <name>@<version> --activate"
|
@@ -496,6 +541,8 @@ module Dependabot
|
|
496
541
|
).returns(String)
|
497
542
|
end
|
498
543
|
def self.package_manager_run_command(name, command, fingerprint: nil)
|
544
|
+
return run_bun_command(command, fingerprint: fingerprint) if name == BunPackageManager::NAME
|
545
|
+
|
499
546
|
full_command = "corepack #{name} #{command}"
|
500
547
|
|
501
548
|
result = Dependabot::SharedHelpers.run_shell_command(
|
@@ -526,6 +573,11 @@ module Dependabot
|
|
526
573
|
dependency
|
527
574
|
end
|
528
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
|
529
581
|
end
|
530
582
|
end
|
531
583
|
end
|