dependabot-npm_and_yarn 0.292.0 → 0.293.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -62,6 +62,13 @@ module Dependabot
62
62
 
63
63
  ERR_PNPM_PATCH_NOT_APPLIED = /ERR_PNPM_PATCH_NOT_APPLIED/
64
64
 
65
+ # this intermittent issue is related with Node v20
66
+ ERR_INVALID_THIS = /ERR_INVALID_THIS/
67
+ URL_SEARCH_PARAMS = /URLSearchParams/
68
+
69
+ # A modules directory is present and is linked to a different store directory.
70
+ ERR_PNPM_UNEXPECTED_STORE = /ERR_PNPM_UNEXPECTED_STORE/
71
+
65
72
  # ERR_PNPM_UNSUPPORTED_PLATFORM
66
73
  ERR_PNPM_UNSUPPORTED_PLATFORM = /ERR_PNPM_UNSUPPORTED_PLATFORM/
67
74
  PLATFORM_PACAKGE_DEP = /Unsupported platform for (?<dep>.*)\: wanted/
@@ -78,6 +85,16 @@ module Dependabot
78
85
  ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND = /ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND*.*Could not install from \"(?<dir>.*)\" /
79
86
  ERR_PNPM_WORKSPACE_PKG_NOT_FOUND = /ERR_PNPM_WORKSPACE_PKG_NOT_FOUND/
80
87
 
88
+ # Unparsable package.json file
89
+ ERR_PNPM_INVALID_PACKAGE_JSON = /Invalid package.json in package/
90
+
91
+ # Unparsable lockfile
92
+ ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE = /ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE/
93
+ ERR_PNPM_OUTDATED_LOCKFILE = /ERR_PNPM_OUTDATED_LOCKFILE/
94
+
95
+ # Peer dependencies configuration error
96
+ ERR_PNPM_PEER_DEP_ISSUES = /ERR_PNPM_PEER_DEP_ISSUES/
97
+
81
98
  def run_pnpm_update(pnpm_lock:)
82
99
  SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do
83
100
  File.write(".npmrc", npmrc_content(pnpm_lock))
@@ -196,10 +213,39 @@ module Dependabot
196
213
  raise Dependabot::DependencyFileNotResolvable, msg
197
214
  end
198
215
 
216
+ if error_message.match?(ERR_PNPM_INVALID_PACKAGE_JSON) || error_message.match?(ERR_PNPM_UNEXPECTED_STORE)
217
+ msg = "Error while resolving package.json."
218
+ Dependabot.logger.warn(error_message)
219
+ raise Dependabot::DependencyFileNotResolvable, msg
220
+ end
221
+
222
+ [ERR_PNPM_UNEXPECTED_PKG_CONTENT_IN_STORE, ERR_PNPM_OUTDATED_LOCKFILE]
223
+ .each do |regexp|
224
+ next unless error_message.match?(regexp)
225
+
226
+ error_msg = T.let("Error while resolving pnpm-lock.yaml file.", String)
227
+
228
+ Dependabot.logger.warn(error_message)
229
+ raise Dependabot::DependencyFileNotResolvable, error_msg
230
+ end
231
+
232
+ if error_message.match?(ERR_PNPM_PEER_DEP_ISSUES)
233
+ msg = "Missing or invalid configuration while installing peer dependencies."
234
+
235
+ Dependabot.logger.warn(error_message)
236
+ raise Dependabot::DependencyFileNotResolvable, msg
237
+ end
238
+
199
239
  raise_patch_dependency_error(error_message) if error_message.match?(ERR_PNPM_PATCH_NOT_APPLIED)
200
240
 
201
241
  raise_unsupported_engine_error(error_message, pnpm_lock) if error_message.match?(ERR_PNPM_UNSUPPORTED_ENGINE)
202
242
 
243
+ if error_message.match?(ERR_INVALID_THIS) && error_message.match?(URL_SEARCH_PARAMS)
244
+ msg = "Error while resolving dependencies."
245
+ Dependabot.logger.warn(error_message)
246
+ raise Dependabot::DependencyFileNotResolvable, msg
247
+ end
248
+
203
249
  if error_message.match?(ERR_PNPM_UNSUPPORTED_PLATFORM)
204
250
  raise_unsupported_platform_error(error_message,
205
251
  pnpm_lock)
@@ -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
@@ -189,6 +190,15 @@ module Dependabot
189
190
  )
190
191
  end
191
192
 
193
+ sig { returns(T::Array[Dependabot::DependencyFile]) }
194
+ def bun_locks
195
+ @bun_locks ||= T.let(
196
+ filtered_dependency_files
197
+ .select { |f| f.name.end_with?("bun.lock") },
198
+ T.nilable(T::Array[Dependabot::DependencyFile])
199
+ )
200
+ end
201
+
192
202
  sig { returns(T::Array[Dependabot::DependencyFile]) }
193
203
  def shrinkwraps
194
204
  @shrinkwraps ||= T.let(
@@ -217,6 +227,11 @@ module Dependabot
217
227
  pnpm_lock.content != updated_pnpm_lock_content(pnpm_lock)
218
228
  end
219
229
 
230
+ sig { params(bun_lock: Dependabot::DependencyFile).returns(T::Boolean) }
231
+ def bun_lock_changed?(bun_lock)
232
+ bun_lock.content != updated_bun_lock_content(bun_lock)
233
+ end
234
+
220
235
  sig { params(package_lock: Dependabot::DependencyFile).returns(T::Boolean) }
221
236
  def package_lock_changed?(package_lock)
222
237
  package_lock.content != updated_lockfile_content(package_lock)
@@ -237,6 +252,8 @@ module Dependabot
237
252
  end
238
253
  end
239
254
 
255
+ # rubocop:disable Metrics/MethodLength
256
+ # rubocop:disable Metrics/PerceivedComplexity
240
257
  sig { returns(T::Array[Dependabot::DependencyFile]) }
241
258
  def updated_lockfiles
242
259
  updated_files = []
@@ -259,6 +276,15 @@ module Dependabot
259
276
  )
260
277
  end
261
278
 
279
+ bun_locks.each do |bun_lock|
280
+ next unless bun_lock_changed?(bun_lock)
281
+
282
+ updated_files << updated_file(
283
+ file: bun_lock,
284
+ content: updated_bun_lock_content(bun_lock)
285
+ )
286
+ end
287
+
262
288
  package_locks.each do |package_lock|
263
289
  next unless package_lock_changed?(package_lock)
264
290
 
@@ -279,6 +305,8 @@ module Dependabot
279
305
 
280
306
  updated_files
281
307
  end
308
+ # rubocop:enable Metrics/MethodLength
309
+ # rubocop:enable Metrics/PerceivedComplexity
282
310
 
283
311
  sig { params(yarn_lock: Dependabot::DependencyFile).returns(String) }
284
312
  def updated_yarn_lock_content(yarn_lock)
@@ -294,6 +322,13 @@ module Dependabot
294
322
  pnpm_lockfile_updater.updated_pnpm_lock_content(pnpm_lock)
295
323
  end
296
324
 
325
+ sig { params(bun_lock: Dependabot::DependencyFile).returns(String) }
326
+ def updated_bun_lock_content(bun_lock)
327
+ @updated_bun_lock_content ||= T.let({}, T.nilable(T::Hash[String, T.nilable(String)]))
328
+ @updated_bun_lock_content[bun_lock.name] ||=
329
+ bun_lockfile_updater.updated_bun_lock_content(bun_lock)
330
+ end
331
+
297
332
  sig { returns(Dependabot::NpmAndYarn::FileUpdater::YarnLockfileUpdater) }
298
333
  def yarn_lockfile_updater
299
334
  @yarn_lockfile_updater ||= T.let(
@@ -320,6 +355,19 @@ module Dependabot
320
355
  )
321
356
  end
322
357
 
358
+ sig { returns(Dependabot::NpmAndYarn::FileUpdater::BunLockfileUpdater) }
359
+ def bun_lockfile_updater
360
+ @bun_lockfile_updater ||= T.let(
361
+ BunLockfileUpdater.new(
362
+ dependencies: dependencies,
363
+ dependency_files: dependency_files,
364
+ repo_contents_path: repo_contents_path,
365
+ credentials: credentials
366
+ ),
367
+ T.nilable(Dependabot::NpmAndYarn::FileUpdater::BunLockfileUpdater)
368
+ )
369
+ end
370
+
323
371
  sig { params(file: Dependabot::DependencyFile).returns(T.nilable(String)) }
324
372
  def updated_lockfile_content(file)
325
373
  @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
@@ -159,6 +163,11 @@ module Dependabot
159
163
  PNPM_FALLBACK_VERSION
160
164
  end
161
165
 
166
+ sig { params(_bun_lock: T.nilable(DependencyFile)).returns(Integer) }
167
+ def self.bun_version_numeric(_bun_lock)
168
+ BUN_DEFAULT_VERSION
169
+ end
170
+
162
171
  sig { params(key: String, default_value: String).returns(T.untyped) }
163
172
  def self.fetch_yarnrc_yml_value(key, default_value)
164
173
  if File.exist?(".yarnrc.yml") && (yarnrc = YAML.load_file(".yarnrc.yml"))
@@ -352,6 +361,35 @@ module Dependabot
352
361
  raise
353
362
  end
354
363
 
364
+ sig { returns(T.nilable(String)) }
365
+ def self.bun_version
366
+ version = run_bun_command("--version", fingerprint: "--version").strip
367
+ if version.include?("+")
368
+ version.split("+").first # Remove build info, if present
369
+ end
370
+ rescue StandardError => e
371
+ Dependabot.logger.error("Error retrieving Bun version: #{e.message}")
372
+ nil
373
+ end
374
+
375
+ sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
376
+ def self.run_bun_command(command, fingerprint: nil)
377
+ full_command = "bun #{command}"
378
+
379
+ Dependabot.logger.info("Running bun command: #{full_command}")
380
+
381
+ result = Dependabot::SharedHelpers.run_shell_command(
382
+ full_command,
383
+ fingerprint: "bun #{fingerprint || command}"
384
+ )
385
+
386
+ Dependabot.logger.info("Command executed successfully: #{full_command}")
387
+ result
388
+ rescue StandardError => e
389
+ Dependabot.logger.error("Error running bun command: #{full_command}, Error: #{e.message}")
390
+ raise
391
+ end
392
+
355
393
  # Setup yarn and run a single yarn command returning stdout/stderr
356
394
  sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
357
395
  def self.run_yarn_command(command, fingerprint: nil)
@@ -496,6 +534,8 @@ module Dependabot
496
534
  ).returns(String)
497
535
  end
498
536
  def self.package_manager_run_command(name, command, fingerprint: nil)
537
+ return run_bun_command(command, fingerprint: fingerprint) if name == BunPackageManager::NAME
538
+
499
539
  full_command = "corepack #{name} #{command}"
500
540
 
501
541
  result = Dependabot::SharedHelpers.run_shell_command(
@@ -0,0 +1,45 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/npm_and_yarn/package_manager"
5
+
6
+ module Dependabot
7
+ module NpmAndYarn
8
+ class Language < Ecosystem::VersionManager
9
+ extend T::Sig
10
+ NAME = "node"
11
+
12
+ SUPPORTED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
13
+
14
+ DEPRECATED_VERSIONS = T.let([].freeze, T::Array[Dependabot::Version])
15
+
16
+ sig do
17
+ params(
18
+ detected_version: T.nilable(String),
19
+ raw_version: T.nilable(String),
20
+ requirement: T.nilable(Dependabot::NpmAndYarn::Requirement)
21
+ ).void
22
+ end
23
+ def initialize(detected_version: nil, raw_version: nil, requirement: nil)
24
+ super(
25
+ name: NAME,
26
+ detected_version: detected_version ? Version.new(detected_version) : nil,
27
+ version: raw_version ? Version.new(raw_version) : nil,
28
+ deprecated_versions: DEPRECATED_VERSIONS,
29
+ supported_versions: SUPPORTED_VERSIONS,
30
+ requirement: requirement
31
+ )
32
+ end
33
+
34
+ sig { override.returns(T::Boolean) }
35
+ def deprecated?
36
+ false
37
+ end
38
+
39
+ sig { override.returns(T::Boolean) }
40
+ def unsupported?
41
+ false
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,70 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ require "dependabot/npm_and_yarn/package_manager"
5
+
6
+ module Dependabot
7
+ module NpmAndYarn
8
+ class NpmPackageManager < Ecosystem::VersionManager
9
+ extend T::Sig
10
+ NAME = "npm"
11
+ RC_FILENAME = ".npmrc"
12
+ LOCKFILE_NAME = "package-lock.json"
13
+ SHRINKWRAP_LOCKFILE_NAME = "npm-shrinkwrap.json"
14
+
15
+ NPM_V6 = "6"
16
+ NPM_V7 = "7"
17
+ NPM_V8 = "8"
18
+ NPM_V9 = "9"
19
+ NPM_V10 = "10"
20
+
21
+ # Keep versions in ascending order
22
+ SUPPORTED_VERSIONS = T.let([
23
+ Version.new(NPM_V7),
24
+ Version.new(NPM_V8),
25
+ Version.new(NPM_V9),
26
+ Version.new(NPM_V10)
27
+ ].freeze, T::Array[Dependabot::Version])
28
+
29
+ DEPRECATED_VERSIONS = T.let([Version.new(NPM_V6)].freeze, T::Array[Dependabot::Version])
30
+
31
+ sig do
32
+ params(
33
+ detected_version: T.nilable(String),
34
+ raw_version: T.nilable(String),
35
+ requirement: T.nilable(Dependabot::NpmAndYarn::Requirement)
36
+ ).void
37
+ end
38
+ def initialize(detected_version: nil, raw_version: nil, requirement: nil)
39
+ super(
40
+ name: NAME,
41
+ detected_version: detected_version ? Version.new(detected_version) : nil,
42
+ version: raw_version ? Version.new(raw_version) : nil,
43
+ deprecated_versions: DEPRECATED_VERSIONS,
44
+ supported_versions: SUPPORTED_VERSIONS,
45
+ requirement: requirement
46
+ )
47
+ end
48
+
49
+ sig { override.returns(T::Boolean) }
50
+ def deprecated?
51
+ return false unless detected_version
52
+
53
+ return false if unsupported?
54
+
55
+ return false unless Dependabot::Experiments.enabled?(:npm_v6_deprecation_warning)
56
+
57
+ deprecated_versions.include?(detected_version)
58
+ end
59
+
60
+ sig { override.returns(T::Boolean) }
61
+ def unsupported?
62
+ return false unless detected_version
63
+
64
+ return false unless Dependabot::Experiments.enabled?(:npm_v6_unsupported_error)
65
+
66
+ supported_versions.all? { |supported| supported > detected_version }
67
+ end
68
+ end
69
+ end
70
+ end