class Dependabot::NpmAndYarn::UpdateChecker::SubdependencyVersionResolver

Attributes

credentials[R]
dependency[R]
dependency_files[R]
ignored_versions[R]
latest_allowable_version[R]

Public Class Methods

new(dependency:, credentials:, dependency_files:, ignored_versions:, latest_allowable_version:) click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 21
def initialize(dependency:, credentials:, dependency_files:,
               ignored_versions:, latest_allowable_version:)
  @dependency = dependency
  @credentials = credentials
  @dependency_files = dependency_files
  @ignored_versions = ignored_versions
  @latest_allowable_version = latest_allowable_version
end

Public Instance Methods

latest_resolvable_version() click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 30
def latest_resolvable_version
  raise "Not a subdependency!" if dependency.requirements.any?
  return if bundled_dependency?

  SharedHelpers.in_a_temporary_directory do
    dependency_files_builder.write_temporary_dependency_files

    updated_lockfiles = filtered_lockfiles.map do |lockfile|
      updated_content = update_subdependency_in_lockfile(lockfile)
      updated_lockfile = lockfile.dup
      updated_lockfile.content = updated_content
      updated_lockfile
    end

    version_from_updated_lockfiles(updated_lockfiles)
  end
rescue SharedHelpers::HelperSubprocessFailed
  # TODO: Move error handling logic from the FileUpdater to this class

  # Return nil (no update possible) if an unknown error occurred
  nil
end

Private Instance Methods

bundled_dependency?() click to toggle source

TODO: We should try and fix this by updating the parent that's not bundled. For this case: `chokidar > fsevents > node-pre-gyp > tar` we would need to update `fsevents`

We shouldn't update bundled sub-dependencies as they have been bundled into the release at an exact version by a parent using `bundledDependencies`.

For example, fsevents < 2 bundles node-pre-gyp meaning all it's sub-dependencies get bundled into the release tarball at publish time so you always get the same sub-dependency versions if you re-install a specific version of fsevents.

Updating the sub-dependency by deleting the entry works but it gets removed from the bundled set of dependencies and moved top level resulting in a bunch of package duplication which is pretty confusing.

# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 173
def bundled_dependency?
  dependency.subdependency_metadata&.
    any? { |h| h.fetch(:npm_bundled, false) } ||
    false
end
dependency_files_builder() click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 148
def dependency_files_builder
  @dependency_files_builder ||=
    DependencyFilesBuilder.new(
      dependency: dependency,
      dependency_files: dependency_files,
      credentials: credentials
    )
end
filtered_lockfiles() click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 140
def filtered_lockfiles
  @filtered_lockfiles ||=
    SubDependencyFilesFilterer.new(
      dependency_files: dependency_files,
      updated_dependencies: [updated_dependency]
    ).files_requiring_update
end
run_npm_updater(path, lockfile_name, lockfile_content) click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 112
def run_npm_updater(path, lockfile_name, lockfile_content)
  SharedHelpers.with_git_configured(credentials: credentials) do
    Dir.chdir(path) do
      npm_version = Dependabot::NpmAndYarn::Helpers.npm_version(lockfile_content)

      SharedHelpers.run_helper_subprocess(
        command: NativeHelpers.helper_path,
        function: "#{npm_version}:updateSubdependency",
        args: [Dir.pwd, lockfile_name, [dependency.to_h]]
      )
    end
  end
end
run_yarn_updater(path, lockfile_name) click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 88
def run_yarn_updater(path, lockfile_name)
  SharedHelpers.with_git_configured(credentials: credentials) do
    Dir.chdir(path) do
      SharedHelpers.run_helper_subprocess(
        command: NativeHelpers.helper_path,
        function: "yarn:updateSubdependency",
        args: [Dir.pwd, lockfile_name]
      )
    end
  end
rescue SharedHelpers::HelperSubprocessFailed => e
  unfindable_str = "find package \"#{dependency.name}"
  raise unless e.message.include?("The registry may be down") ||
               e.message.include?("ETIMEDOUT") ||
               e.message.include?("ENOBUFS") ||
               e.message.include?(unfindable_str)

  retry_count ||= 0
  retry_count += 1
  raise if retry_count > 2

  sleep(rand(3.0..10.0)) && retry
end
update_subdependency_in_lockfile(lockfile) click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 58
def update_subdependency_in_lockfile(lockfile)
  lockfile_name = Pathname.new(lockfile.name).basename.to_s
  path = Pathname.new(lockfile.name).dirname.to_s

  updated_files = if lockfile.name.end_with?("yarn.lock")
                    run_yarn_updater(path, lockfile_name)
                  else
                    run_npm_updater(path, lockfile_name, lockfile.content)
                  end

  updated_files.fetch(lockfile_name)
end
updated_dependency() click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 130
def updated_dependency
  Dependabot::Dependency.new(
    name: dependency.name,
    version: latest_allowable_version,
    previous_version: dependency.version,
    requirements: [],
    package_manager: dependency.package_manager
  )
end
version_class() click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 126
def version_class
  NpmAndYarn::Version
end
version_from_updated_lockfiles(updated_lockfiles) click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb, line 71
def version_from_updated_lockfiles(updated_lockfiles)
  updated_files = dependency_files -
                  dependency_files_builder.yarn_locks -
                  dependency_files_builder.package_locks -
                  dependency_files_builder.shrinkwraps +
                  updated_lockfiles

  updated_version = NpmAndYarn::FileParser.new(
    dependency_files: updated_files,
    source: nil,
    credentials: credentials
  ).parse.find { |d| d.name == dependency.name }&.version
  return unless updated_version

  version_class.new(updated_version)
end