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/language.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_helper.rb,
lib/dependabot/npm_and_yarn/registry_parser.rb,
lib/dependabot/npm_and_yarn/version_selector.rb,
lib/dependabot/npm_and_yarn/constraint_helper.rb,
lib/dependabot/npm_and_yarn/npm_package_manager.rb,
lib/dependabot/npm_and_yarn/pnpm_package_manager.rb,
lib/dependabot/npm_and_yarn/yarn_package_manager.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/package/registry_finder.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/package/package_details_fetcher.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/file_updater/pnpm_workspace_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: ConstraintHelper, Helpers, NativeHelpers, Package Classes: DependencyFilesFilterer, FileFetcher, FileParser, FileUpdater, Language, MetadataFinder, NpmPackageManager, PNPMPackageManager, PackageManagerDetector, PackageManagerHelper, PackageName, PnpmErrorHandler, RegistryHelper, RegistryParser, Requirement, SubDependencyFilesFilterer, UpdateChecker, Utils, Version, VersionSelector, YarnErrorHandler, YarnPackageManager

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_DEPS_RESOLUTION_FAILED =
T.let(
  {
    DEPS_INCORRECT_MET: /peer dependencies are incorrectly met/
  }.freeze,
  T::Hash[String, Regexp]
)
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]
)
YN0001_INVALID_TYPE_ERRORS =
T.let(
  {
    INVALID_URL: /TypeError: (?<dep>.*): Invalid URL/
  }.freeze,
  T::Hash[String, Regexp]
)
YN0086_DEPS_RESOLUTION_FAILED =
/peer dependencies are incorrectly met/
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

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

        YN0001_INVALID_TYPE_ERRORS.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
      }
    },
    "YN0086" => {
      message: "deps resolution failed",
      handler: lambda { |message, _error, _params|
        msg = message.match(YN0086_DEPS_RESOLUTION_FAILED)
        Dependabot::DependencyFileNotResolvable.new(msg || message)
      }
    }
  }.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))
  }]
)
ECOSYSTEM =
"npm_and_yarn"
MANIFEST_FILENAME =
"package.json"
LERNA_JSON_FILENAME =
"lerna.json"
PACKAGE_MANAGER_VERSION_REGEX =
/
  ^                        # Start of string
  (?<major>\d+)            # Major version (required, numeric)
  \.                       # Separator between major and minor versions
  (?<minor>\d+)            # Minor version (required, numeric)
  \.                       # Separator between minor and patch versions
  (?<patch>\d+)            # Patch version (required, numeric)
  (                        # Start pre-release section
    -(?<pre_release>[a-zA-Z0-9.]+) # Pre-release label (optional, alphanumeric or dot-separated)
  )?
  (                        # Start build metadata section
    \+(?<build>[a-zA-Z0-9.]+) # Build metadata (optional, alphanumeric or dot-separated)
  )?
  $                        # End of string
/x
VALID_REQUIREMENT_CONSTRAINT =

Extended mode for readability

/
  ^                        # Start of string
  (?<operator>=|>|>=|<|<=|~>|\\^) # Allowed operators
  \s*                      # Optional whitespace
  (?<major>\d+)            # Major version (required)
  (\.(?<minor>\d+))?       # Minor version (optional)
  (\.(?<patch>\d+))?       # Patch version (optional)
  (                        # Start pre-release section
    -(?<pre_release>[a-zA-Z0-9.]+) # Pre-release label (optional)
  )?
  (                        # Start build metadata section
    \+(?<build>[a-zA-Z0-9.]+) # Build metadata (optional)
  )?
  $                        # End of string
/x
MANIFEST_PACKAGE_MANAGER_KEY =

Extended mode for readability

"packageManager"
MANIFEST_ENGINES_KEY =
"engines"
DEFAULT_PACKAGE_MANAGER =
NpmPackageManager::NAME
NpmAndYarnPackageManagerClassType =

Define a type alias for the expected class interface

T.type_alias do
  T.any(
    T.class_of(Dependabot::NpmAndYarn::NpmPackageManager),
    T.class_of(Dependabot::NpmAndYarn::YarnPackageManager),
    T.class_of(Dependabot::NpmAndYarn::PNPMPackageManager)
  )
end
PACKAGE_MANAGER_CLASSES =
T.let(
  {
    NpmPackageManager::NAME => NpmPackageManager,
    YarnPackageManager::NAME => YarnPackageManager,
    PNPMPackageManager::NAME => PNPMPackageManager
  }.freeze,
  T::Hash[String, NpmAndYarnPackageManagerClassType]
)
ERROR_MALFORMED_VERSION_NUMBER =

Error malformed version number string

"Malformed version number"