dependabot-npm_and_yarn 0.291.0 → 0.293.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.
@@ -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
@@ -41,9 +45,7 @@ module Dependabot
41
45
  # Otherwise, we are going to use old versionining npm 6
42
46
  sig { params(lockfile: T.nilable(DependencyFile)).returns(Integer) }
43
47
  def self.npm_version_numeric(lockfile)
44
- if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
45
- return npm_version_numeric_latest(lockfile)
46
- end
48
+ return npm_version_numeric_latest(lockfile) if Dependabot::Experiments.enabled?(:npm_v6_deprecation_warning)
47
49
 
48
50
  fallback_version_npm8 = Dependabot::Experiments.enabled?(:npm_fallback_version_above_v6)
49
51
 
@@ -161,6 +163,11 @@ module Dependabot
161
163
  PNPM_FALLBACK_VERSION
162
164
  end
163
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
+
164
171
  sig { params(key: String, default_value: String).returns(T.untyped) }
165
172
  def self.fetch_yarnrc_yml_value(key, default_value)
166
173
  if File.exist?(".yarnrc.yml") && (yarnrc = YAML.load_file(".yarnrc.yml"))
@@ -174,7 +181,7 @@ module Dependabot
174
181
  def self.npm8?(package_lock)
175
182
  return true unless package_lock&.content
176
183
 
177
- if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn)
184
+ if Dependabot::Experiments.enabled?(:npm_v6_deprecation_warning)
178
185
  return npm_version_numeric_latest(package_lock) >= NPM_V8
179
186
  end
180
187
 
@@ -354,6 +361,35 @@ module Dependabot
354
361
  raise
355
362
  end
356
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
+
357
393
  # Setup yarn and run a single yarn command returning stdout/stderr
358
394
  sig { params(command: String, fingerprint: T.nilable(String)).returns(String) }
359
395
  def self.run_yarn_command(command, fingerprint: nil)
@@ -498,6 +534,8 @@ module Dependabot
498
534
  ).returns(String)
499
535
  end
500
536
  def self.package_manager_run_command(name, command, fingerprint: nil)
537
+ return run_bun_command(command, fingerprint: fingerprint) if name == BunPackageManager::NAME
538
+
501
539
  full_command = "corepack #{name} #{command}"
502
540
 
503
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