dependabot-npm_and_yarn 0.214.0 → 0.216.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +1 -1
  3. data/helpers/lib/yarn/subdependency-updater.js +15 -44
  4. data/helpers/package-lock.json +2584 -1559
  5. data/helpers/package.json +7 -8
  6. data/helpers/test/npm6/conflicting-dependency-parser.test.js +1 -2
  7. data/helpers/test/npm6/fixtures/conflicting-dependency-parser/deeply-nested/package-lock.json +3 -3
  8. data/helpers/test/npm6/updater.test.js +1 -2
  9. data/helpers/test/yarn/conflicting-dependency-parser.test.js +1 -2
  10. data/helpers/test/yarn/fixtures/conflicting-dependency-parser/deeply-nested/yarn.lock +3 -3
  11. data/helpers/test/yarn/updater.test.js +1 -2
  12. data/lib/dependabot/npm_and_yarn/file_fetcher.rb +26 -38
  13. data/lib/dependabot/npm_and_yarn/file_parser/json_lock.rb +86 -0
  14. data/lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb +21 -183
  15. data/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb +80 -0
  16. data/lib/dependabot/npm_and_yarn/file_parser.rb +23 -36
  17. data/lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb +58 -31
  18. data/lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb +43 -16
  19. data/lib/dependabot/npm_and_yarn/file_updater.rb +1 -4
  20. data/lib/dependabot/npm_and_yarn/helpers.rb +17 -4
  21. data/lib/dependabot/npm_and_yarn/native_helpers.rb +15 -2
  22. data/lib/dependabot/npm_and_yarn/update_checker/dependency_files_builder.rb +6 -0
  23. data/lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb +5 -4
  24. data/lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb +20 -13
  25. data/lib/dependabot/npm_and_yarn/update_checker.rb +7 -24
  26. data/lib/dependabot/npm_and_yarn/version.rb +13 -2
  27. metadata +37 -32
@@ -95,7 +95,7 @@ module Dependabot
95
95
  requirement: requirement,
96
96
  manifest_name: file.name
97
97
  )
98
- version = version_for(name, requirement, file.name)
98
+ version = version_for(requirement, lockfile_details)
99
99
 
100
100
  return if lockfile_details && !version
101
101
  return if ignore_requirement?(requirement)
@@ -116,7 +116,7 @@ module Dependabot
116
116
  requirement: requirement_for(requirement),
117
117
  file: file.name,
118
118
  groups: [type],
119
- source: source_for(name, requirement, file.name)
119
+ source: source_for(name, requirement, lockfile_details)
120
120
  }]
121
121
  )
122
122
  end
@@ -165,29 +165,21 @@ module Dependabot
165
165
  package_files.filter_map { |f| JSON.parse(f.content)["name"] }
166
166
  end
167
167
 
168
- def version_for(name, requirement, manifest_name)
168
+ def version_for(requirement, lockfile_details)
169
169
  if git_url_with_semver?(requirement)
170
- semver_version = semver_version_for(name, requirement, manifest_name)
170
+ semver_version = semver_version_for(lockfile_details)
171
171
  return semver_version if semver_version
172
172
 
173
- git_revision = git_revision_for(name, requirement, manifest_name)
173
+ git_revision = git_revision_for(lockfile_details)
174
174
  version_from_git_revision(requirement, git_revision) || git_revision
175
175
  elsif git_url?(requirement)
176
- git_revision_for(name, requirement, manifest_name)
176
+ git_revision_for(lockfile_details)
177
177
  else
178
- semver_version_for(name, requirement, manifest_name)
178
+ semver_version_for(lockfile_details)
179
179
  end
180
180
  end
181
181
 
182
- def git_revision_for(name, requirement, manifest_name)
183
- return unless git_url?(requirement)
184
-
185
- lockfile_details = lockfile_parser.lockfile_details(
186
- dependency_name: name,
187
- requirement: requirement,
188
- manifest_name: manifest_name
189
- )
190
-
182
+ def git_revision_for(lockfile_details)
191
183
  [
192
184
  lockfile_details&.fetch("version", nil)&.split("#")&.last,
193
185
  lockfile_details&.fetch("resolved", nil)&.split("#")&.last,
@@ -224,29 +216,13 @@ module Dependabot
224
216
  nil
225
217
  end
226
218
 
227
- def semver_version_for(name, requirement, manifest_name)
228
- lock_version = lockfile_parser.lockfile_details(
229
- dependency_name: name,
230
- requirement: requirement,
231
- manifest_name: manifest_name
232
- )&.fetch("version", nil)
233
-
234
- # This line is to guard against improperly formatted versions in a
235
- # lockfile, such as additional characters. NPM/yarn fixes these when
236
- # running an update, so we can safely ignore these versions.
237
- return unless version_class.correct?(lock_version)
238
-
239
- lock_version
219
+ def semver_version_for(lockfile_details)
220
+ version_class.semver_for(lockfile_details&.fetch("version", ""))
240
221
  end
241
222
 
242
- def source_for(name, requirement, manifest_name)
223
+ def source_for(name, requirement, lockfile_details)
243
224
  return git_source_for(requirement) if git_url?(requirement)
244
225
 
245
- lockfile_details = lockfile_parser.lockfile_details(
246
- dependency_name: name,
247
- requirement: requirement,
248
- manifest_name: manifest_name
249
- )
250
226
  resolved_url = lockfile_details&.fetch("resolved", nil)
251
227
 
252
228
  resolution = lockfile_details&.fetch("resolution", nil)
@@ -313,11 +289,22 @@ module Dependabot
313
289
  end
314
290
 
315
291
  def url_for_relevant_cred(resolved_url)
292
+ resolved_url_host = URI(resolved_url).host
293
+
316
294
  credential_matching_url =
317
295
  credentials.
318
296
  select { |cred| cred["type"] == "npm_registry" }.
319
297
  sort_by { |cred| cred["registry"].length }.
320
- find { |details| resolved_url.include?(details["registry"]) }
298
+ find do |details|
299
+ next true if resolved_url_host == details["registry"]
300
+
301
+ uri = if details["registry"]&.include?("://")
302
+ URI(details["registry"])
303
+ else
304
+ URI("https://#{details['registry']}")
305
+ end
306
+ resolved_url_host == uri.host
307
+ end
321
308
 
322
309
  return unless credential_matching_url
323
310
 
@@ -176,36 +176,28 @@ module Dependabot
176
176
  dependency_in_package_json?(dependency)
177
177
  end
178
178
 
179
- # NOTE: When updating a dependency in a nested workspace project we
180
- # need to run `npm install` without any arguments to update the root
181
- # level lockfile after having updated the nested packages package.json
182
- # requirement, otherwise npm will add the dependency as a new
183
- # top-level dependency to the root lockfile.
184
- install_args = ""
185
- if dependencies_in_current_package_json
186
- # TODO: Update the npm 6 updater to use these args as we currently
187
- # do the same in the js updater helper, we've kept it seperate for
188
- # the npm 7 rollout
189
- install_args = top_level_dependencies.map { |dependency| npm_install_args(dependency) }
190
- end
191
-
192
- # NOTE: npm options
193
- # - `--force` ignores checks for platform (os, cpu) and engines
194
- # - `--dry-run=false` the updater sets a global .npmrc with dry-run:
195
- # true to work around an issue in npm 6, we don't want that here
196
- # - `--ignore-scripts` disables prepare and prepack scripts which are
197
- # run when installing git dependencies
198
- command = [
199
- "npm",
200
- "install",
201
- *install_args,
202
- "--force",
203
- "--dry-run",
204
- "false",
205
- "--ignore-scripts",
206
- "--package-lock-only"
207
- ].join(" ")
208
- SharedHelpers.run_shell_command(command)
179
+ unless dependencies_in_current_package_json
180
+ # NOTE: When updating a dependency in a nested workspace project, npm
181
+ # will add the dependency as a new top-level dependency to the root
182
+ # lockfile. To overcome this, we save the content before the update,
183
+ # and then re-run `npm install` after the update against the previous
184
+ # content to remove that
185
+ previous_package_json = File.read(package_json.name)
186
+ end
187
+
188
+ # TODO: Update the npm 6 updater to use these args as we currently
189
+ # do the same in the js updater helper, we've kept it separate for
190
+ # the npm 7 rollout
191
+ install_args = top_level_dependencies.map { |dependency| npm_install_args(dependency) }
192
+
193
+ run_npm_install_lockfile_only(*install_args)
194
+
195
+ unless dependencies_in_current_package_json
196
+ File.write(package_json.name, previous_package_json)
197
+
198
+ run_npm_install_lockfile_only
199
+ end
200
+
209
201
  { lockfile_basename => File.read(lockfile_basename) }
210
202
  end
211
203
 
@@ -223,7 +215,7 @@ module Dependabot
223
215
 
224
216
  def run_npm8_subdependency_updater(sub_dependencies:)
225
217
  dependency_names = sub_dependencies.map(&:name)
226
- SharedHelpers.run_shell_command(NativeHelpers.npm8_subdependency_update_command(dependency_names))
218
+ NativeHelpers.run_npm8_subdependency_update_command(dependency_names)
227
219
  { lockfile_basename => File.read(lockfile_basename) }
228
220
  end
229
221
 
@@ -244,6 +236,41 @@ module Dependabot
244
236
  end
245
237
  end
246
238
 
239
+ # Runs `npm install` with `--package-lock-only` flag to update the
240
+ # lockfiile.
241
+ #
242
+ # Other npm flags:
243
+ # - `--force` ignores checks for platform (os, cpu) and engines
244
+ # - `--dry-run=false` the updater sets a global .npmrc with `dry-run: true`
245
+ # to work around an issue in npm 6, we don't want that here
246
+ # - `--ignore-scripts` disables prepare and prepack scripts which are
247
+ # run when installing git dependencies
248
+ def run_npm_install_lockfile_only(*install_args)
249
+ command = [
250
+ "npm",
251
+ "install",
252
+ *install_args,
253
+ "--force",
254
+ "--dry-run",
255
+ "false",
256
+ "--ignore-scripts",
257
+ "--package-lock-only"
258
+ ].join(" ")
259
+
260
+ fingerprint = [
261
+ "npm",
262
+ "install",
263
+ install_args.empty? ? "" : "<install_args>",
264
+ "--force",
265
+ "--dry-run",
266
+ "false",
267
+ "--ignore-scripts",
268
+ "--package-lock-only"
269
+ ].join(" ")
270
+
271
+ SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
272
+ end
273
+
247
274
  def npm_install_args(dependency)
248
275
  git_requirement = dependency.requirements.find { |req| req[:source] && req[:source][:type] == "git" }
249
276
 
@@ -149,16 +149,18 @@ module Dependabot
149
149
  # lockfile in the right state. Otherwise we'll need to manually update
150
150
  # the lockfile.
151
151
 
152
- command = if top_level_dependency_updates.all? { |dep| requirements_changed?(dep[:name]) }
153
- "yarn install #{Helpers.yarn_berry_args}".strip
154
- else
155
- updates = top_level_dependency_updates.collect do |dep|
156
- dep[:name]
157
- end
158
-
159
- "yarn up -R #{updates.join(' ')} #{Helpers.yarn_berry_args}".strip
160
- end
161
- Helpers.run_yarn_commands(command)
152
+ if top_level_dependency_updates.all? { |dep| requirements_changed?(dep[:name]) }
153
+ Helpers.run_yarn_command("yarn install #{yarn_berry_args}".strip)
154
+ else
155
+ updates = top_level_dependency_updates.collect do |dep|
156
+ dep[:name]
157
+ end
158
+
159
+ Helpers.run_yarn_command(
160
+ "yarn up -R #{updates.join(' ')} #{yarn_berry_args}".strip,
161
+ fingerprint: "yarn up -R <dependency_names> #{yarn_berry_args}".strip
162
+ )
163
+ end
162
164
  { yarn_lock.name => File.read(yarn_lock.name) }
163
165
  end
164
166
 
@@ -171,14 +173,20 @@ module Dependabot
171
173
  dep = sub_dependencies.first
172
174
  update = "#{dep.name}@#{dep.version}"
173
175
 
174
- Helpers.run_yarn_commands(
175
- "yarn add #{update} #{Helpers.yarn_berry_args}".strip,
176
- "yarn dedupe #{dep.name} #{Helpers.yarn_berry_args}".strip,
177
- "yarn remove #{dep.name} #{Helpers.yarn_berry_args}".strip
178
- )
176
+ commands = [
177
+ ["yarn add #{update} #{yarn_berry_args}".strip, "yarn add <update> #{yarn_berry_args}".strip],
178
+ ["yarn dedupe #{dep.name} #{yarn_berry_args}".strip, "yarn dedupe <dep_name> #{yarn_berry_args}".strip],
179
+ ["yarn remove #{dep.name} #{yarn_berry_args}".strip, "yarn remove <dep_name> #{yarn_berry_args}".strip]
180
+ ]
181
+
182
+ Helpers.run_yarn_commands(*commands)
179
183
  { yarn_lock.name => File.read(yarn_lock.name) }
180
184
  end
181
185
 
186
+ def yarn_berry_args
187
+ Helpers.yarn_berry_args
188
+ end
189
+
182
190
  def run_yarn_top_level_updater(top_level_dependency_updates:)
183
191
  SharedHelpers.run_helper_subprocess(
184
192
  command: NativeHelpers.helper_path,
@@ -195,7 +203,7 @@ module Dependabot
195
203
  SharedHelpers.run_helper_subprocess(
196
204
  command: NativeHelpers.helper_path,
197
205
  function: "yarn:updateSubdependency",
198
- args: [Dir.pwd, lockfile_name, sub_dependencies.first.to_h]
206
+ args: [Dir.pwd, lockfile_name, sub_dependencies.map(&:to_h)]
199
207
  )
200
208
  end
201
209
 
@@ -358,6 +366,25 @@ module Dependabot
358
366
  updated_content = sanitized_package_json_content(updated_content)
359
367
  File.write(file.name, updated_content)
360
368
  end
369
+
370
+ clean_npmrc_in_path(yarn_lock)
371
+ end
372
+
373
+ def clean_npmrc_in_path(yarn_lock)
374
+ # Berry does not read npmrc files.
375
+ return if Helpers.yarn_berry?(yarn_lock)
376
+
377
+ # Find .npmrc files in parent directories and remove variables in them
378
+ # to avoid errors when running yarn 1.
379
+ dirs = Dir.getwd.split("/")
380
+ dirs.pop
381
+ while dirs.any?
382
+ npmrc = dirs.join("/") + "/.npmrc"
383
+ break unless File.exist?(npmrc)
384
+
385
+ File.write(npmrc, File.read(npmrc).gsub(/\$\{.*\}/, ""))
386
+ dirs.pop
387
+ end
361
388
  end
362
389
 
363
390
  def write_lockfiles
@@ -64,10 +64,7 @@ module Dependabot
64
64
  pnp_updater.updated_vendor_cache_files(base_directory: base_dir).each do |file|
65
65
  updated_files << file if file.name == ".pnp.cjs" || file.name == ".pnp.data.json"
66
66
  end
67
- # updated .pnp.cjs means zero install, include cache
68
- if updated_files.find { |f| f.name == ".pnp.cjs" }
69
- vendor_updater.updated_vendor_cache_files(base_directory: base_dir).each { |file| updated_files << file }
70
- end
67
+ vendor_updater.updated_vendor_cache_files(base_directory: base_dir).each { |file| updated_files << file }
71
68
  install_state_updater.updated_vendor_cache_files(base_directory: base_dir).each do |file|
72
69
  updated_files << file
73
70
  end
@@ -40,19 +40,32 @@ module Dependabot
40
40
  File.exist?(".pnp.cjs")
41
41
  end
42
42
 
43
+ def self.yarn_offline_cache?
44
+ yarn_cache_dir = fetch_yarnrc_yml_value("cacheFolder", ".yarn/cache")
45
+ File.exist?(yarn_cache_dir) && (fetch_yarnrc_yml_value("nodeLinker", "") == "node-modules")
46
+ end
47
+
43
48
  def self.yarn_berry_args
44
49
  if yarn_major_version == 2
45
50
  ""
46
- elsif yarn_major_version >= 3 && yarn_zero_install?
51
+ elsif yarn_berry_skip_build?
47
52
  "--mode=skip-build"
48
53
  else
54
+ # We only want this mode if the cache is not being updated/managed
55
+ # as this improperly leaves old versions in the cache
49
56
  "--mode=update-lockfile"
50
57
  end
51
58
  end
52
59
 
60
+ def self.yarn_berry_skip_build?
61
+ yarn_major_version >= 3 && (yarn_zero_install? || yarn_offline_cache?)
62
+ end
63
+
53
64
  def self.setup_yarn_berry
54
65
  # Always disable immutable installs so yarn's CI detection doesn't prevent updates.
55
66
  SharedHelpers.run_shell_command("yarn config set enableImmutableInstalls false")
67
+ # Do not generate a cache if offline cache disabled. Otherwise side effects may confuse further checks
68
+ SharedHelpers.run_shell_command("yarn config set enableGlobalCache true") unless yarn_berry_skip_build?
56
69
  # We never want to execute postinstall scripts, either set this config or mode=skip-build must be set
57
70
  if yarn_major_version == 2 || !yarn_zero_install?
58
71
  SharedHelpers.run_shell_command("yarn config set enableScripts false")
@@ -78,13 +91,13 @@ module Dependabot
78
91
  # contain malicious code.
79
92
  def self.run_yarn_commands(*commands)
80
93
  setup_yarn_berry
81
- commands.each { |cmd| SharedHelpers.run_shell_command(cmd) }
94
+ commands.each { |cmd, fingerprint| SharedHelpers.run_shell_command(cmd, fingerprint: fingerprint) }
82
95
  end
83
96
 
84
97
  # Run a single yarn command returning stdout/stderr
85
- def self.run_yarn_command(command)
98
+ def self.run_yarn_command(command, fingerprint: nil)
86
99
  setup_yarn_berry
87
- SharedHelpers.run_shell_command(command)
100
+ SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
88
101
  end
89
102
 
90
103
  def self.dependencies_with_all_versions_metadata(dependency_set)
@@ -14,14 +14,14 @@ module Dependabot
14
14
  File.join(__dir__, "../../../helpers")
15
15
  end
16
16
 
17
- def self.npm8_subdependency_update_command(dependency_names)
17
+ def self.run_npm8_subdependency_update_command(dependency_names)
18
18
  # NOTE: npm options
19
19
  # - `--force` ignores checks for platform (os, cpu) and engines
20
20
  # - `--dry-run=false` the updater sets a global .npmrc with dry-run: true to
21
21
  # work around an issue in npm 6, we don't want that here
22
22
  # - `--ignore-scripts` disables prepare and prepack scripts which are run
23
23
  # when installing git dependencies
24
- [
24
+ command = [
25
25
  "npm",
26
26
  "update",
27
27
  *dependency_names,
@@ -31,6 +31,19 @@ module Dependabot
31
31
  "--ignore-scripts",
32
32
  "--package-lock-only"
33
33
  ].join(" ")
34
+
35
+ fingerprint = [
36
+ "npm",
37
+ "update",
38
+ "<dependency_names>",
39
+ "--force",
40
+ "--dry-run",
41
+ "false",
42
+ "--ignore-scripts",
43
+ "--package-lock-only"
44
+ ].join(" ")
45
+
46
+ SharedHelpers.run_shell_command(command, fingerprint: fingerprint)
34
47
  end
35
48
  end
36
49
  end
@@ -42,6 +42,12 @@ module Dependabot
42
42
  select { |f| f.name.end_with?("yarn.lock") }
43
43
  end
44
44
 
45
+ def root_yarn_lock
46
+ @root_yarn_lock ||=
47
+ dependency_files.
48
+ find { |f| f.name == "yarn.lock" }
49
+ end
50
+
45
51
  def shrinkwraps
46
52
  @shrinkwraps ||=
47
53
  dependency_files.
@@ -95,7 +95,7 @@ module Dependabot
95
95
  SharedHelpers.run_helper_subprocess(
96
96
  command: NativeHelpers.helper_path,
97
97
  function: "yarn:updateSubdependency",
98
- args: [Dir.pwd, lockfile_name]
98
+ args: [Dir.pwd, lockfile_name, [dependency.to_h]]
99
99
  )
100
100
  end
101
101
  end
@@ -116,8 +116,9 @@ module Dependabot
116
116
  def run_yarn_berry_updater(path, lockfile_name)
117
117
  SharedHelpers.with_git_configured(credentials: credentials) do
118
118
  Dir.chdir(path) do
119
- Helpers.run_yarn_commands(
120
- "yarn up -R #{dependency.name} #{Helpers.yarn_berry_args}".strip
119
+ Helpers.run_yarn_command(
120
+ "yarn up -R #{dependency.name} #{Helpers.yarn_berry_args}".strip,
121
+ fingerprint: "yarn up -R <dependency_name> #{Helpers.yarn_berry_args}".strip
121
122
  )
122
123
  { lockfile_name => File.read(lockfile_name) }
123
124
  end
@@ -130,7 +131,7 @@ module Dependabot
130
131
  npm_version = Dependabot::NpmAndYarn::Helpers.npm_version(lockfile_content)
131
132
 
132
133
  if npm_version == "npm8"
133
- SharedHelpers.run_shell_command(NativeHelpers.npm8_subdependency_update_command([dependency.name]))
134
+ NativeHelpers.run_npm8_subdependency_update_command([dependency.name])
134
135
  { lockfile_name => File.read(lockfile_name) }
135
136
  else
136
137
  SharedHelpers.run_helper_subprocess(
@@ -27,13 +27,12 @@ module Dependabot
27
27
  }.freeze
28
28
 
29
29
  # Error message from yarn add:
30
- # " > @reach/router@1.2.1" has incorrect \
31
- # peer dependency "react@15.x || 16.x || 16.4.0-alpha.0911da3"
32
- # " > react-burger-menu@1.9.9" has unmet \
33
- # peer dependency "react@>=0.14.0 <16.0.0".
30
+ # " > @reach/router@1.2.1" has incorrect peer dependency "react@15.x || 16.x || 16.4.0-alpha.0911da3"
31
+ # "workspace-aggregator-<random-string> > test > react-dom@15.6.2" has incorrect peer dependency "react@^15.6.2"
32
+ # " > react-burger-menu@1.9.9" has unmet peer dependency "react@>=0.14.0 <16.0.0"
34
33
  YARN_PEER_DEP_ERROR_REGEX =
35
34
  /
36
- "\s>\s(?<requiring_dep>[^"]+)"\s
35
+ \s>\s(?<requiring_dep>[^>"]+)"\s
37
36
  has\s(incorrect|unmet)\speer\sdependency\s
38
37
  "(?<required_dep>[^"]+)"
39
38
  /x
@@ -324,8 +323,6 @@ module Dependabot
324
323
  filtered_package_files.flat_map do |file|
325
324
  path = Pathname.new(file.name).dirname
326
325
  run_checker(path: path, version: version)
327
- rescue SharedHelpers::HelperSubprocessFailed => e
328
- handle_peer_dependency_errors(e)
329
326
  end.compact
330
327
  end
331
328
  rescue SharedHelpers::HelperSubprocessFailed
@@ -488,14 +485,24 @@ module Dependabot
488
485
  def run_checker(path:, version:)
489
486
  # If there are both yarn lockfiles and npm lockfiles only run the
490
487
  # yarn updater
491
- lockfiles = lockfiles_for_path(lockfiles: dependency_files_builder.yarn_locks, path: path)
492
- if lockfiles.any?
493
- return run_yarn_berry_checker(path: path, version: version) if Helpers.yarn_berry?(lockfiles.first)
488
+ yarn_lockfiles = lockfiles_for_path(lockfiles: dependency_files_builder.yarn_locks, path: path)
489
+ return run_yarn_checker(path: path, version: version, lockfile: yarn_lockfiles.first) if yarn_lockfiles.any?
494
490
 
495
- return run_yarn_checker(path: path, version: version)
496
- end
491
+ npm_lockfiles = lockfiles_for_path(lockfiles: dependency_files_builder.package_locks, path: path)
492
+ return run_npm_checker(path: path, version: version) if npm_lockfiles.any?
493
+
494
+ root_yarn_lock = dependency_files_builder.root_yarn_lock
495
+ return run_yarn_checker(path: path, version: version, lockfile: root_yarn_lock) if root_yarn_lock
497
496
 
498
497
  run_npm_checker(path: path, version: version)
498
+ rescue SharedHelpers::HelperSubprocessFailed => e
499
+ handle_peer_dependency_errors(e)
500
+ end
501
+
502
+ def run_yarn_checker(path:, version:, lockfile:)
503
+ return run_yarn_berry_checker(path: path, version: version) if Helpers.yarn_berry?(lockfile)
504
+
505
+ run_yarn_classic_checker(path: path, version: version)
499
506
  end
500
507
 
501
508
  def run_yarn_berry_checker(path:, version:)
@@ -519,7 +526,7 @@ module Dependabot
519
526
  end
520
527
  end
521
528
 
522
- def run_yarn_checker(path:, version:)
529
+ def run_yarn_classic_checker(path:, version:)
523
530
  SharedHelpers.with_git_configured(credentials: credentials) do
524
531
  Dir.chdir(path) do
525
532
  SharedHelpers.run_helper_subprocess(
@@ -118,6 +118,7 @@ module Dependabot
118
118
  dependency: dependency,
119
119
  target_version: lowest_security_fix_version
120
120
  )
121
+ return conflicts unless vulnerability_audit_performed?
121
122
 
122
123
  vulnerable = [vulnerability_audit].select do |hash|
123
124
  !hash["fix_available"] && hash["explanation"]
@@ -128,6 +129,10 @@ module Dependabot
128
129
 
129
130
  private
130
131
 
132
+ def vulnerability_audit_performed?
133
+ defined?(@vulnerability_audit)
134
+ end
135
+
131
136
  def vulnerability_audit
132
137
  @vulnerability_audit ||=
133
138
  VulnerabilityAuditor.new(
@@ -279,9 +284,7 @@ module Dependabot
279
284
 
280
285
  def latest_version_for_git_dependency
281
286
  @latest_version_for_git_dependency ||=
282
- if git_branch_or_ref_in_latest_release?
283
- latest_released_version
284
- elsif version_class.correct?(dependency.version)
287
+ if version_class.correct?(dependency.version)
285
288
  latest_git_version_details[:version] &&
286
289
  version_class.new(latest_git_version_details[:version])
287
290
  else
@@ -294,26 +297,9 @@ module Dependabot
294
297
  latest_version_finder.latest_version_from_registry
295
298
  end
296
299
 
297
- def should_switch_source_from_git_to_registry?
298
- return false unless git_dependency?
299
- return false unless git_branch_or_ref_in_latest_release?
300
- return false if latest_version_for_git_dependency.nil?
301
-
302
- version_class.correct?(latest_version_for_git_dependency)
303
- end
304
-
305
- def git_branch_or_ref_in_latest_release?
306
- return false unless latest_released_version
307
-
308
- return @git_branch_or_ref_in_latest_release if defined?(@git_branch_or_ref_in_latest_release)
309
-
310
- @git_branch_or_ref_in_latest_release ||=
311
- git_commit_checker.branch_or_ref_in_release?(latest_released_version)
312
- end
313
-
314
300
  def latest_version_details
315
301
  @latest_version_details ||=
316
- if git_dependency? && !should_switch_source_from_git_to_registry?
302
+ if git_dependency?
317
303
  latest_git_version_details
318
304
  else
319
305
  { version: latest_released_version }
@@ -389,9 +375,6 @@ module Dependabot
389
375
  # Never need to update source, unless a git_dependency
390
376
  return dependency_source_details unless git_dependency?
391
377
 
392
- # Source becomes `nil` if switching to default rubygems
393
- return nil if should_switch_source_from_git_to_registry?
394
-
395
378
  # Update the git tag if updating a pinned version
396
379
  if git_commit_checker.pinned_ref_looks_like_version? &&
397
380
  !git_commit_checker.local_tag_for_latest_version.nil?
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dependabot/version"
3
4
  require "dependabot/utils"
4
- require "rubygems_version_patch"
5
5
 
6
6
  # JavaScript pre-release versions use 1.0.1-rc1 syntax, which Gem::Version
7
7
  # converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that
@@ -11,7 +11,7 @@ require "rubygems_version_patch"
11
11
 
12
12
  module Dependabot
13
13
  module NpmAndYarn
14
- class Version < Gem::Version
14
+ class Version < Dependabot::Version
15
15
  attr_reader :build_info
16
16
 
17
17
  VERSION_PATTERN = Gem::Version::VERSION_PATTERN + '(\+[0-9a-zA-Z\-.]+)?'
@@ -25,6 +25,17 @@ module Dependabot
25
25
  version.to_s.match?(ANCHORED_VERSION_PATTERN)
26
26
  end
27
27
 
28
+ def self.semver_for(version)
29
+ # The next two lines are to guard against improperly formatted
30
+ # versions in a lockfile, such as an empty string or additional
31
+ # characters. NPM/yarn fixes these when running an update, so we can
32
+ # safely ignore these versions.
33
+ return if version == ""
34
+ return unless correct?(version)
35
+
36
+ version
37
+ end
38
+
28
39
  def initialize(version)
29
40
  @version_string = version.to_s
30
41
  version = version.gsub(/^v/, "") if version.is_a?(String)