class Dependabot::NpmAndYarn::UpdateChecker::LatestVersionFinder
Attributes
credentials[R]
dependency[R]
dependency_files[R]
ignored_versions[R]
security_advisories[R]
Public Class Methods
new(dependency:, credentials:, dependency_files:, ignored_versions:, security_advisories:, raise_on_ignored: false)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 24 def initialize(dependency:, credentials:, dependency_files:, ignored_versions:, security_advisories:, raise_on_ignored: false) @dependency = dependency @credentials = credentials @dependency_files = dependency_files @ignored_versions = ignored_versions @raise_on_ignored = raise_on_ignored @security_advisories = security_advisories end
Public Instance Methods
latest_version_from_registry()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 35 def latest_version_from_registry return unless valid_npm_details? return version_from_dist_tags if version_from_dist_tags return if specified_dist_tag_requirement? possible_versions.find { |v| !yanked?(v) } rescue Excon::Error::Socket, Excon::Error::Timeout, RegistryError raise if dependency_registry == "registry.npmjs.org" # Custom registries can be flaky. We don't want to make that # our problem, so we quietly return `nil` here. end
latest_version_with_no_unlock()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 47 def latest_version_with_no_unlock return unless valid_npm_details? return version_from_dist_tags if specified_dist_tag_requirement? in_range_versions = filter_out_of_range_versions(possible_versions) in_range_versions.find { |version| !yanked?(version) } rescue Excon::Error::Socket, Excon::Error::Timeout raise if dependency_registry == "registry.npmjs.org" # Sometimes custom registries are flaky. We don't want to make that # our problem, so we quietly return `nil` here. end
lowest_security_fix_version()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 59 def lowest_security_fix_version return unless valid_npm_details? secure_versions = if specified_dist_tag_requirement? [version_from_dist_tags].compact else possible_versions(filter_ignored: false) end secure_versions = Dependabot::UpdateCheckers::VersionFilters.filter_vulnerable_versions(secure_versions, security_advisories) secure_versions = filter_ignored_versions(secure_versions) secure_versions = filter_lower_versions(secure_versions) secure_versions.reverse.find { |version| !yanked?(version) } rescue Excon::Error::Socket, Excon::Error::Timeout raise if dependency_registry == "registry.npmjs.org" # Sometimes custom registries are flaky. We don't want to make that # our problem, so we quietly return `nil` here. end
possible_previous_versions_with_details()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 80 def possible_previous_versions_with_details @possible_previous_versions_with_details ||= npm_details.fetch("versions", {}). transform_keys { |k| version_class.new(k) }. reject { |v, _| v.prerelease? && !related_to_current_pre?(v) }. sort_by(&:first).reverse end
possible_versions(filter_ignored: true)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 96 def possible_versions(filter_ignored: true) possible_versions_with_details(filter_ignored: filter_ignored). map(&:first) end
possible_versions_with_details(filter_ignored: true)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 87 def possible_versions_with_details(filter_ignored: true) versions = possible_previous_versions_with_details. reject { |_, details| details["deprecated"] } return filter_ignored_versions(versions) if filter_ignored versions end
Private Instance Methods
check_npm_response(npm_response)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 321 def check_npm_response(npm_response) return if git_dependency? if private_dependency_not_reachable?(npm_response) raise PrivateSourceAuthenticationFailure, dependency_registry end status = npm_response.status return if status.to_s.start_with?("2") # Ignore 404s from the registry for updates where a lockfile doesn't # need to be generated. The 404 won't cause problems later. return if status == 404 && dependency.version.nil? msg = "Got #{status} response with body #{npm_response.body}" raise RegistryError.new(status, msg) end
current_requirement_greater_than?(version)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 212 def current_requirement_greater_than?(version) dependency.requirements.any? do |req| next false unless req[:requirement] req_version = req[:requirement].sub(/^\^|~|>=?/, "") next false unless version_class.correct?(req_version) version_class.new(req_version) > version end end
current_version_greater_than?(version)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 205 def current_version_greater_than?(version) return false unless dependency.version return false unless version_class.correct?(dependency.version) version_class.new(dependency.version) > version end
dependency_registry()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 371 def dependency_registry registry_finder.registry end
dependency_url()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 367 def dependency_url registry_finder.dependency_url end
fetch_npm_response()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 295 def fetch_npm_response response = Excon.get( dependency_url, idempotent: true, **SharedHelpers.excon_defaults(headers: registry_auth_headers) ) return response unless response.status == 500 return response unless registry_auth_headers["Authorization"] auth = registry_auth_headers["Authorization"] return response unless auth.start_with?("Basic") decoded_token = Base64.decode64(auth.gsub("Basic ", "")) return unless decoded_token.include?(":") username, password = decoded_token.split(":") Excon.get( dependency_url, user: username, password: password, idempotent: true, **SharedHelpers.excon_defaults ) end
filter_ignored_versions(versions_array)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 110 def filter_ignored_versions(versions_array) filtered = versions_array.reject do |v, _| ignore_requirements.any? { |r| r.satisfied_by?(v) } end if @raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions_array).any? raise AllVersionsIgnored end filtered end
filter_lower_versions(versions_array)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 131 def filter_lower_versions(versions_array) return versions_array unless dependency.version && version_class.correct?(dependency.version) versions_array. select { |version, _| version > version_class.new(dependency.version) } end
filter_out_of_range_versions(versions_array)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 122 def filter_out_of_range_versions(versions_array) reqs = dependency.requirements.map do |r| NpmAndYarn::Requirement.requirements_array(r.fetch(:requirement)) end.compact versions_array. select { |v| reqs.all? { |r| r.any? { |o| o.satisfied_by?(v) } } } end
git_dependency?()
click to toggle source
TODO: Remove need for me
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 409 def git_dependency? # ignored_version/raise_on_ignored are irrelevant. GitCommitChecker.new( dependency: dependency, credentials: credentials ).git_dependency? end
ignore_requirements()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 388 def ignore_requirements ignored_versions.flat_map { |req| requirement_class.requirements_array(req) } end
npm_details()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 270 def npm_details return @npm_details if @npm_details_lookup_attempted @npm_details_lookup_attempted = true @npm_details ||= begin npm_response = fetch_npm_response check_npm_response(npm_response) JSON.parse(npm_response.body) rescue JSON::ParserError, Excon::Error::Timeout, Excon::Error::Socket, RegistryError => e if git_dependency? nil else retry_count ||= 0 retry_count += 1 raise_npm_details_error(e) if retry_count > 2 sleep(rand(3.0..10.0)) && retry end end end
npmrc_file()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 400 def npmrc_file dependency_files.find { |f| f.name.end_with?(".npmrc") } end
private_dependency_not_reachable?(npm_response)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 346 def private_dependency_not_reachable?(npm_response) return true if npm_response.body.start_with?(/user ".*?" is not a /) return false unless [401, 402, 403, 404].include?(npm_response.status) # Check whether this dependency is (likely to be) private if dependency_registry == "registry.npmjs.org" return false unless dependency.name.start_with?("@") web_response = Excon.get( "https://www.npmjs.com/package/#{dependency.name}", idempotent: true, **SharedHelpers.excon_defaults ) # NOTE: returns 429 when the login page is rate limited return web_response.body.include?("Forgot password?") || web_response.status == 429 end true end
raise_npm_details_error(error)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 339 def raise_npm_details_error(error) raise if dependency_registry == "registry.npmjs.org" raise unless error.is_a?(Excon::Error::Timeout) raise PrivateSourceTimedOut, dependency_registry end
registry_auth_headers()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 375 def registry_auth_headers registry_finder.auth_headers end
registry_finder()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 379 def registry_finder @registry_finder ||= RegistryFinder.new( dependency: dependency, credentials: credentials, npmrc_file: npmrc_file, yarnrc_file: yarnrc_file ) end
requirement_class()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 396 def requirement_class NpmAndYarn::Requirement end
specified_dist_tag_requirement?()
click to toggle source
rubocop:enable Metrics/PerceivedComplexity
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 185 def specified_dist_tag_requirement? dependency.requirements.any? do |req| next false if req[:requirement].nil? next false unless req[:requirement].match?(/^[A-Za-z]/) !req[:requirement].match?(/^v\d/i) end end
valid_npm_details?()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 106 def valid_npm_details? !npm_details&.fetch("dist-tags", nil).nil? end
version_class()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 392 def version_class NpmAndYarn::Version end
version_endpoint_working?()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 252 def version_endpoint_working? return true if dependency_registry == "registry.npmjs.org" return @version_endpoint_working if defined?(@version_endpoint_working) @version_endpoint_working = begin Excon.get( dependency_url + "/latest", idempotent: true, **SharedHelpers.excon_defaults(headers: registry_auth_headers) ).status < 400 rescue Excon::Error::Timeout, Excon::Error::Socket # Give the benefit of the doubt if the registry is playing up true end end
wants_latest_dist_tag?(latest_version)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 194 def wants_latest_dist_tag?(latest_version) ver = latest_version return false if related_to_current_pre?(ver) ^ ver.prerelease? return false if current_version_greater_than?(ver) return false if current_requirement_greater_than?(ver) return false if ignore_requirements.any? { |r| r.satisfied_by?(ver) } return false if yanked?(ver) true end
yanked?(version)
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 223 def yanked?(version) @yanked ||= {} return @yanked[version] if @yanked.key?(version) @yanked[version] = begin status = Excon.get( dependency_url + "/#{version}", idempotent: true, **SharedHelpers.excon_defaults(headers: registry_auth_headers) ).status if status == 404 && dependency_registry != "registry.npmjs.org" # Some registries don't handle escaped package names properly status = Excon.get( dependency_url.gsub("%2F", "/") + "/#{version}", idempotent: true, **SharedHelpers.excon_defaults(headers: registry_auth_headers) ).status end version_not_found = status == 404 version_not_found && version_endpoint_working? rescue Excon::Error::Timeout, Excon::Error::Socket # Give the benefit of the doubt if the registry is playing up false end end
yarnrc_file()
click to toggle source
# File lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb, line 404 def yarnrc_file dependency_files.find { |f| f.name.end_with?(".yarnrc") } end