Module: Dependabot::NpmAndYarn

Defined in:
lib/dependabot/npm_and_yarn.rb,
lib/dependabot/npm_and_yarn/helpers.rb,
lib/dependabot/npm_and_yarn/version.rb,
lib/dependabot/npm_and_yarn/file_parser.rb,
lib/dependabot/npm_and_yarn/requirement.rb,
lib/dependabot/npm_and_yarn/file_fetcher.rb,
lib/dependabot/npm_and_yarn/file_updater.rb,
lib/dependabot/npm_and_yarn/package_name.rb,
lib/dependabot/npm_and_yarn/native_helpers.rb,
lib/dependabot/npm_and_yarn/update_checker.rb,
lib/dependabot/npm_and_yarn/metadata_finder.rb,
lib/dependabot/npm_and_yarn/package_manager.rb,
lib/dependabot/npm_and_yarn/registry_parser.rb,
lib/dependabot/npm_and_yarn/version_selector.rb,
lib/dependabot/npm_and_yarn/file_parser/json_lock.rb,
lib/dependabot/npm_and_yarn/file_parser/pnpm_lock.rb,
lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb,
lib/dependabot/npm_and_yarn/dependency_files_filterer.rb,
lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb,
lib/dependabot/npm_and_yarn/file_parser/lockfile_parser.rb,
lib/dependabot/npm_and_yarn/sub_dependency_files_filterer.rb,
lib/dependabot/npm_and_yarn/update_checker/registry_finder.rb,
lib/dependabot/npm_and_yarn/update_checker/library_detector.rb,
lib/dependabot/npm_and_yarn/update_checker/version_resolver.rb,
lib/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater.rb,
lib/dependabot/npm_and_yarn/file_updater/package_json_updater.rb,
lib/dependabot/npm_and_yarn/file_updater/package_json_preparer.rb,
lib/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater.rb,
lib/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater.rb,
lib/dependabot/npm_and_yarn/update_checker/requirements_updater.rb,
lib/dependabot/npm_and_yarn/file_fetcher/path_dependency_builder.rb,
lib/dependabot/npm_and_yarn/update_checker/latest_version_finder.rb,
lib/dependabot/npm_and_yarn/update_checker/vulnerability_auditor.rb,
lib/dependabot/npm_and_yarn/update_checker/dependency_files_builder.rb,
lib/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver.rb,
lib/dependabot/npm_and_yarn/update_checker/conflicting_dependency_resolver.rb

Overview

rubocop:disable Metrics/ModuleLength

Defined Under Namespace

Modules: Helpers, NativeHelpers Classes: DependencyFilesFilterer, FileFetcher, FileParser, FileUpdater, MetadataFinder, PackageManager, PackageName, RegistryParser, Requirement, SubDependencyFilesFilterer, UpdateChecker, Utils, Version, VersionSelector, YarnErrorHandler

Constant Summary collapse

NODE_VERSION_NOT_SATISFY_REGEX =

rubocop:disable Layout/LineLength

/The current Node version (?<current_version>v?\d+\.\d+\.\d+) does not satisfy the required version (?<required_version>v?\d+\.\d+\.\d+)\./
NPM_REGISTRY =

Used to check if package manager registry is public npm registry

"registry.npmjs.org"
HTTP_CHECK_REGEX =

Used to check if url is http or https

%r{https?://}
URL_CAPTURE =

Used to check capture url match in regex capture group

"url"
INVALID_NAME_IN_PACKAGE_JSON =

When package name contains package.json name cannot contain characters like empty string or @.

"Name contains illegal characters"
PACKAGE_MISSING_REGEX =

Used to identify error messages indicating a package is missing, unreachable, or there are network issues (e.g., ENOBUFS, ETIMEDOUT, registry down).

/(ENOBUFS|ETIMEDOUT|The registry may be down)/
TIMEOUT_FETCHING_PACKAGE_REGEX =

Used to check if error message contains timeout fetching package

%r{(?<url>.+)/(?<package>[^/]+): ETIMEDOUT}
ESOCKETTIMEDOUT =
/(?<package>.*?): ESOCKETTIMEDOUT/
SOCKET_HANG_UP =
/(?<url>.*?): socket hang up/
EEXIST =

Misc errors

/EEXIST: file already exists, mkdir '(?<regis>.*)'/
REQUEST_ERROR_E403 =

registry access errors

/Request "(?<url>.*)" returned a 403/
AUTH_REQUIRED_ERROR =

Authentication is required for the URL.

/(?<url>.*): authentication required/
PERMISSION_DENIED =

Lack of permission to access the URL.

/(?<url>.*): Permission denied/
BAD_REQUEST =

Inconsistent request while accessing resource.

/(?<url>.*): bad_request/
INTERNAL_SERVER_ERROR =

Server error response by remote registry.

/Request failed "500 Internal Server Error"/
UNREACHABLE_GIT_CHECK_REGEX =

Used to identify git unreachable error

/ls-remote --tags --heads (?<url>.*)/
ONLY_PRIVATE_WORKSPACE_TEXT =

Used to check if yarn workspace is enabled in non-private workspace

"Workspaces can only be enabled in priva"
SUB_DEP_LOCAL_PATH_TEXT =

Used to identify local path error in yarn when installing sub-dependency

"refers to a non-existing file"
INVALID_PACKAGE_REGEX =

Used to identify invalid package error when package is not found in registry

/Can't add "[\w\-.]+": invalid/
PACKAGE_NOT_FOUND =

Used to identify error if package not found in registry

"Couldn't find package"
PACKAGE_NOT_FOUND_PACKAGE_NAME_REGEX =
/package "(?<package_req>.*?)"/
PACKAGE_NOT_FOUND_PACKAGE_NAME_CAPTURE =
"package_req"
PACKAGE_NOT_FOUND_PACKAGE_NAME_CAPTURE_SPLIT_REGEX =
/(?<=\w)\@/
YARN_PACKAGE_NOT_FOUND_CODE =
/npm package "(?<dep>.*)" does not exist under owner "(?<regis>.*)"/
YARN_PACKAGE_NOT_FOUND_CODE_1 =
/Couldn't find package "[^@].*(?<dep>.*)" on the "(?<regis>.*)" registry./
YARN_PACKAGE_NOT_FOUND_CODE_2 =

rubocop:disable Layout/LineLength

/Couldn't find package "[^@].*(?<dep>.*)" required by "(?<pkg>.*)" on the "(?<regis>.*)" registry./
YN0035 =
T.let({
  PACKAGE_NOT_FOUND: %r{(?<package_req>@[\w-]+\/[\w-]+@\S+): Package not found},
  FAILED_TO_RETRIEVE: %r{(?<package_req>@[\w-]+\/[\w-]+@\S+): The remote server failed to provide the requested resource} # rubocop:disable Layout/LineLength
}.freeze, T::Hash[String, Regexp])
YN0082_PACKAGE_NOT_FOUND_REGEX =
/YN0082:.*?(\S+@\S+): No candidates found/
PACKAGE_NOT_FOUND2 =
%r{/[^/]+: Not found}
PACKAGE_NOT_FOUND2_PACKAGE_NAME_REGEX =
%r{/(?<package_name>[^/]+): Not found}
PACKAGE_NOT_FOUND2_PACKAGE_NAME_CAPTURE =
"package_name"
DEPENDENCY_VERSION_NOT_FOUND =

Used to identify error if package not found in registry

"Couldn't find any versions"
DEPENDENCY_NOT_FOUND =
": Not found"
DEPENDENCY_MATCH_NOT_FOUND =
"Couldn't find match for"
DEPENDENCY_NO_VERSION_FOUND =
"Couldn't find any versions"
MANIFEST_NOT_FOUND =

Manifest not found

/Cannot read properties of undefined \(reading '(?<file>.*)'\)/
NODE_MODULES_STATE_FILE_NOT_FOUND =

Used to identify error if node_modules state file not resolved

"Couldn't find the node_modules state file"
YARN_USAGE_ERROR_TEXT =

Used to find error message in yarn error output

"Usage Error:"
TARBALL_IS_NOT_IN_NETWORK =

Used to identify error if tarball is not in network

"Tarball is not in network and can not be located in cache"
AUTHENTICATION_TOKEN_NOT_PROVIDED =

Used to identify if authentication failure error

"authentication token not provided"
AUTHENTICATION_IS_NOT_CONFIGURED =
"No authentication configured for request"
AUTHENTICATION_HEADER_NOT_PROVIDED =
"Unauthenticated: request did not include an Authorization header."
DEPENDENCY_FILE_NOT_RESOLVABLE =

Used to identify if error message is related to yarn workspaces

"conflicts with direct dependency"
ENV_VAR_NOT_RESOLVABLE =
/Failed to replace env in config: \$\{(?<var>.*)\}/
OUT_OF_DISKSPACE =
/ Out of diskspace/
YARNRC_PARSE_ERROR =

yarnrc.yml errors

/Parse error when loading (?<filename>.*?); /
YARNRC_ENV_NOT_FOUND =
/Usage Error: Environment variable not found /
YARNRC_ENV_NOT_FOUND_REGEX =
/Usage Error: Environment variable not found \((?<token>.*)\) in (?<filename>.*?) /
YARNRC_EAI_AGAIN =
/getaddrinfo EAI_AGAIN/
YARNRC_ENOENT =
/Internal Error: ENOENT/
YARNRC_ENOENT_REGEX =
/Internal Error: ENOENT: no such file or directory, stat '(?<filename>.*?)'/
YARN_PACKAGE_NOT_FOUND =

if not package found with specified version

/MessageError: Couldn't find any versions for "(?<pkg>.*?)" that matches "(?<ver>.*?)"/
YN0001_FILE_NOT_RESOLVED_CODES =
T.let({
  FIND_PACKAGE_LOCATION: /YN0001:(.*?)UsageError: Couldn't find the (?<pkg>.*) state file/,
  NO_CANDIDATE_FOUND: /YN0001:(.*?)Error: (?<pkg>.*): No candidates found/,
  NO_SUPPORTED_RESOLVER: /YN0001:(.*?)Error: (?<pkg>.*) isn't supported by any available resolver/,
  WORKSPACE_NOT_FOUND: /YN0001:(.*?)Error: (?<pkg>.*): Workspace not found/,
  ENOENT: /YN0001:(.*?)Thrown Error: (?<pkg>.*) ENOENT/,
  MANIFEST_NOT_FOUND: /YN0001:(.*?)Error: (?<pkg>.*): Manifest not found/,
  LIBZIP_ERROR: /YN0001:(.*?)Libzip Error: Failed to open the cache entry for (?<pkg>.*): Not a zip archive/
}.freeze, T::Hash[String, Regexp])
YN0001_AUTH_ERROR_CODES =
T.let({
  AUTH_ERROR: /YN0001:*.*Fatal Error: could not read Username for '(?<url>.*)': terminal prompts disabled/
}.freeze, T::Hash[String, Regexp])
YN0001_REQ_NOT_FOUND_CODES =
T.let({
  REQUIREMENT_NOT_SATISFIED: /provides (?<dep>.*)(.*?)with version (?<ver>.*), which doesn't satisfy what (?<pkg>.*) requests/, # rubocop:disable Layout/LineLength
  REQUIREMENT_NOT_PROVIDED: /(?<dep>.*)(.*?)doesn't provide (?<pkg>.*)(.*?), requested by (?<parent>.*)/
}.freeze, T::Hash[String, Regexp])
REGISTRY_NOT_REACHABLE =

registry returns malformed response

/Received malformed response from registry for "(?<ver>.*)". The registry may be down./
YARN_CODE_REGEX =
/(YN\d{4})/
YARN_ERROR_CODES =
T.let({
  "YN0001" => {
    message: "Exception error",
    handler: lambda { |message, _error, _params|
      YN0001_FILE_NOT_RESOLVED_CODES.each do |(_yn0001_key, yn0001_regex)|
        if (msg = message.match(yn0001_regex))
          return Dependabot::DependencyFileNotResolvable.new(msg)
        end
      end

      YN0001_AUTH_ERROR_CODES.each do |(_yn0001_key, yn0001_regex)|
        if (msg = message.match(yn0001_regex))
          url = msg.named_captures.fetch(URL_CAPTURE)
          return Dependabot::PrivateSourceAuthenticationFailure.new(url)
        end
      end

      YN0001_REQ_NOT_FOUND_CODES.each do |(_yn0001_key, yn0001_regex)|
        if (msg = message.match(yn0001_regex))
          return Dependabot::DependencyFileNotResolvable.new(msg)
        end
      end

      Dependabot::DependabotError.new(message)
    }
  },
  "YN0002" => {
    message: "Missing peer dependency",
    handler: lambda { |message, _error, _params|
      Dependabot::DependencyFileNotResolvable.new(message)
    }
  },
  "YN0009" => {
    message: "Build Failed",
    handler: lambda { |message, _error, _params|
      Dependabot::DependencyFileNotResolvable.new(message)
    }
  },
  "YN0016" => {
    message: "Remote not found",
    handler: lambda { |message, _error, _params|
      Dependabot::GitDependenciesNotReachable.new(message)
    }
  },
  "YN0020" => {
    message: "Missing lockfile entry",
    handler: lambda { |message, _error, _params|
      Dependabot::DependencyFileNotFound.new(message)
    }
  },
  "YN0035" => {
    message: "Package not found",
    handler: lambda { |message, _error, _params|
      YN0035.each do |(_yn0035_key, yn0035_regex)|
        if (match_data = message.match(yn0035_regex)) && (package_req = match_data[:package_req])
          return Dependabot::DependencyNotFound.new(
            "#{package_req} Detail: #{message}"
          )
        end
      end
      Dependabot::DependencyNotFound.new(message)
    }
  },
  "YN0041" => {
    message: "Invalid authentication",
    handler: lambda { |message, _error, _params|
      url = T.must(URI.decode_www_form_component(message).split("https://").last).split("/").first
      Dependabot::PrivateSourceAuthenticationFailure.new(url)
    }
  },
  "YN0046" => {
    message: "Automerge failed to parse",
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    }
  },
  "YN0047" => {
    message: "Automerge immutable",
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    }
  },
  "YN0062" => {
    message: "Incompatible OS",
    handler: lambda { |message, _error, _params|
      Dependabot::DependabotError.new(message)
    }
  },
  "YN0063" => {
    message: "Incompatible CPU",
    handler: lambda { |message, _error, _params|
      Dependabot::IncompatibleCPU.new(message)
    }
  },
  "YN0068" => {
    message: "No matching package",
    handler: lambda { |message, _error, _params|
      Dependabot::DependencyFileNotResolvable.new(message)
    }
  },
  "YN0071" => {
    message: "NM can't install external soft link",
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    }
  },
  "YN0072" => {
    message: "NM preserve symlinks required",
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    }
  },
  "YN0075" => {
    message: "Prolog instantiation error",
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    }
  },
  "YN0077" => {
    message: "Ghost architecture",
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    }
  },
  "YN0080" => {
    message: "Network disabled",
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    }
  },
  "YN0081" => {
    message: "Network unsafe HTTP",
    handler: lambda { |message, _error, _params|
      Dependabot::NetworkUnsafeHTTP.new(message)
    }
  },
  "YN0082" => {
    message: "No candidates found",
    handler: lambda { |message, _error, _params|
      match_data = message.match(YN0082_PACKAGE_NOT_FOUND_REGEX)
      if match_data
        package_req = match_data[1]
        Dependabot::DependencyNotFound.new("#{package_req} Detail: #{message}")
      else
        Dependabot::DependencyNotFound.new(message)
      end
    }
  }
}.freeze, T::Hash[String, {
  message: T.any(String, NilClass),
  handler: ErrorHandler
}])
VALIDATION_GROUP_PATTERNS =

Group of patterns to validate error message and raise specific error

T.let([
  {
    patterns: [INVALID_NAME_IN_PACKAGE_JSON],
    handler: lambda { |message, _error, _params|
      Dependabot::DependencyFileNotResolvable.new(message)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    # Check if sub dependency is using local path and raise a resolvability error
    patterns: [INVALID_PACKAGE_REGEX, SUB_DEP_LOCAL_PATH_TEXT],
    handler: lambda { |message, _error, params|
      Dependabot::DependencyFileNotResolvable.new(
        Utils.sanitize_resolvability_message(
          message,
          params[:dependencies],
          params[:yarn_lock]
        )
      )
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [NODE_MODULES_STATE_FILE_NOT_FOUND],
    handler: lambda { |message, _error, _params|
      Dependabot::MisconfiguredTooling.new("Yarn", message)
    },
    in_usage: true,
    matchfn: nil
  },
  {
    patterns: [TARBALL_IS_NOT_IN_NETWORK],
    handler: lambda { |message, _error, _params|
      Dependabot::DependencyFileNotResolvable.new(message)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [NODE_VERSION_NOT_SATISFY_REGEX],
    handler: lambda { |message, _error, _params|
      versions = Utils.extract_node_versions(message)
      current_version = versions[:current_version]
      required_version = versions[:required_version]

      return Dependabot::DependabotError.new(message) unless current_version && required_version

      Dependabot::ToolVersionNotSupported.new("Yarn", current_version, required_version)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [AUTHENTICATION_TOKEN_NOT_PROVIDED, AUTHENTICATION_IS_NOT_CONFIGURED,
               AUTHENTICATION_HEADER_NOT_PROVIDED],
    handler: lambda { |message, _error, _params|
      Dependabot::PrivateSourceAuthenticationFailure.new(message)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [DEPENDENCY_FILE_NOT_RESOLVABLE],
    handler: lambda { |message, _error, _params|
      DependencyFileNotResolvable.new(message)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [ENV_VAR_NOT_RESOLVABLE],
    handler: lambda { |message, _error, _params|
      var = Utils.extract_var(message)

      Dependabot::MissingEnvironmentVariable.new(var, message)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [ONLY_PRIVATE_WORKSPACE_TEXT],
    handler: lambda { |message, _error, _params|
      Dependabot::DependencyFileNotEvaluatable.new(message)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [UNREACHABLE_GIT_CHECK_REGEX],
    handler: lambda { |message, _error, _params|
      dependency_url = message.match(UNREACHABLE_GIT_CHECK_REGEX).named_captures.fetch(URL_CAPTURE)

      Dependabot::GitDependenciesNotReachable.new(dependency_url)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [SOCKET_HANG_UP],
    handler: lambda { |message, _error, _params|
      url = message.match(SOCKET_HANG_UP).named_captures.fetch(URL_CAPTURE)

      Dependabot::PrivateSourceTimedOut.new(url.gsub(HTTP_CHECK_REGEX, ""))
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [ESOCKETTIMEDOUT],
    handler: lambda { |message, _error, _params|
      package_req = message.match(ESOCKETTIMEDOUT).named_captures.fetch("package")

      Dependabot::PrivateSourceTimedOut.new(package_req.gsub(HTTP_CHECK_REGEX, ""))
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [OUT_OF_DISKSPACE],
    handler: lambda { |message, _error, _params|
      Dependabot::OutOfDisk.new(message)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [YARNRC_PARSE_ERROR],
    handler: lambda { |message, _error, _params|
      filename = message.match(YARNRC_PARSE_ERROR).named_captures["filename"]

      msg = "Error while loading \"#{filename.split('/').last}\"."
      Dependabot::DependencyFileNotResolvable.new(msg)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [YARNRC_ENV_NOT_FOUND],
    handler: lambda { |message, _error, _params|
      error_message = message.gsub(/[[:space:]]+/, " ").strip

      filename = error_message.match(YARNRC_ENV_NOT_FOUND_REGEX)
                                .named_captures["filename"]

      env_var = error_message.match(YARNRC_ENV_NOT_FOUND_REGEX)
                            .named_captures["token"]

      msg = "Environment variable \"#{env_var}\" not found in \"#{filename.split('/').last}\"."
      Dependabot::MissingEnvironmentVariable.new(env_var, msg)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [YARNRC_EAI_AGAIN],
    handler: lambda { |_message, _error, _params|
      Dependabot::DependencyFileNotResolvable.new("Network error while resolving dependency.")
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [YARNRC_ENOENT],
    handler: lambda { |message, _error, _params|
      error_message = message.gsub(/[[:space:]]+/, " ").strip
      filename = error_message.match(YARNRC_ENOENT_REGEX).named_captures["filename"]

      Dependabot::DependencyFileNotResolvable.new("Internal error while resolving dependency." \
                                                  "File not found \"#{filename.split('/').last}\"")
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [YARN_PACKAGE_NOT_FOUND],
    handler: lambda { |message, _error, _params|
      package_name = message.match(YARN_PACKAGE_NOT_FOUND).named_captures["pkg"]
      version = message.match(YARN_PACKAGE_NOT_FOUND).named_captures["ver"]

      Dependabot::InconsistentRegistryResponse.new("Couldn't find any versions for \"#{package_name}\" that " \
                                                   "matches \"#{version}\"")
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [YARN_PACKAGE_NOT_FOUND_CODE, YARN_PACKAGE_NOT_FOUND_CODE_1, YARN_PACKAGE_NOT_FOUND_CODE_2],
    handler: lambda { |message, _error, _params|
      msg = message.match(YARN_PACKAGE_NOT_FOUND_CODE) || message.match(YARN_PACKAGE_NOT_FOUND_CODE_1) ||
      message.match(YARN_PACKAGE_NOT_FOUND_CODE_2)

      Dependabot::DependencyFileNotResolvable.new(msg)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [REQUEST_ERROR_E403, AUTH_REQUIRED_ERROR, PERMISSION_DENIED, BAD_REQUEST],
    handler: lambda { |message, _error, _params|
      dependency_url = T.must(URI.decode_www_form_component(message).split("https://").last).split("/").first

      Dependabot::PrivateSourceAuthenticationFailure.new(dependency_url)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [MANIFEST_NOT_FOUND],
    handler: lambda { |message, _error, _params|
      msg = message.match(MANIFEST_NOT_FOUND)
      Dependabot::DependencyFileNotResolvable.new(msg)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [INTERNAL_SERVER_ERROR],
    handler: lambda { |message, _error, _params|
      msg = message.match(INTERNAL_SERVER_ERROR)
      Dependabot::DependencyFileNotResolvable.new(msg)
    },
    in_usage: false,
    matchfn: nil
  },
  {
    patterns: [REGISTRY_NOT_REACHABLE],
    handler: lambda { |message, _error, _params|
      msg = message.match(REGISTRY_NOT_REACHABLE)
      Dependabot::DependencyFileNotResolvable.new(msg)
    },
    in_usage: false,
    matchfn: nil
  }
].freeze, T::Array[{
  patterns: T::Array[T.any(String, Regexp)],
  handler: ErrorHandler,
  in_usage: T.nilable(T::Boolean),
  matchfn: T.nilable(T.proc.params(usage: String, message: String).returns(T::Boolean))
}])