class Dependabot::NpmAndYarn::FileUpdater::NpmrcBuilder

Build a .npmrc file from the lockfile content, credentials, and any committed .npmrc

Constants

CENTRAL_REGISTRIES
SCOPED_REGISTRY

Attributes

credentials[R]
dependency_files[R]

Public Class Methods

new(dependency_files:, credentials:) click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 18
def initialize(dependency_files:, credentials:)
  @dependency_files = dependency_files
  @credentials = credentials
end

Public Instance Methods

npmrc_content() click to toggle source

PROXY WORK

# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 24
def npmrc_content
  initial_content =
    if npmrc_file then complete_npmrc_from_credentials
    elsif yarnrc_file then build_npmrc_from_yarnrc
    else build_npmrc_content_from_lockfile
    end

  return initial_content || "" unless registry_credentials.any?

  ([initial_content] + credential_lines_for_npmrc).compact.join("\n")
end

Private Instance Methods

build_npmrc_content_from_lockfile() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 40
def build_npmrc_content_from_lockfile
  return unless yarn_lock || package_lock
  return unless global_registry

  "registry = https://#{global_registry['registry']}\n"\
  "#{global_registry_auth_line}"\
  "always-auth = true"
end
build_npmrc_from_yarnrc() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 119
def build_npmrc_from_yarnrc
  yarnrc_global_registry =
    yarnrc_file.content.
    lines.find { |line| line.match?(/^\s*registry\s/) }&.
    match(/^\s*registry\s+"(?<registry>[^"]+)"/)&.
    named_captures&.fetch("registry")

  return "registry = #{yarnrc_global_registry}\n" if yarnrc_global_registry

  build_npmrc_content_from_lockfile
end
complete_npmrc_from_credentials() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 107
def complete_npmrc_from_credentials
  initial_content = npmrc_file.content.
                    gsub(/^.*\$\{.*\}.*/, "").strip + "\n"
  return initial_content unless yarn_lock || package_lock
  return initial_content unless global_registry

  initial_content +
    "registry = https://#{global_registry['registry']}\n"\
    "#{global_registry_auth_line}"\
    "always-auth = true\n"
end
credential_lines_for_npmrc() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 131
def credential_lines_for_npmrc
  lines = []
  registry_credentials.each do |cred|
    registry = cred.fetch("registry").sub(%r{\/?$}, "/")

    lines += registry_scopes(registry) if registry_scopes(registry)

    token = cred.fetch("token", nil)
    next unless token

    if token.include?(":")
      encoded_token = Base64.encode64(token).delete("\n")
      lines << "//#{registry}:_auth=#{encoded_token}"
    elsif Base64.decode64(token).ascii_only? &&
          Base64.decode64(token).include?(":")
      lines << %(//#{registry}:_auth=#{token.delete("\n")})
    else
      lines << "//#{registry}:_authToken=#{token}"
    end
  end

  return lines unless lines.any? { |str| str.include?("auth=") }

  # Work around a suspected yarn bug
  ["always-auth = true"] + lines
end
dependency_urls() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 82
def dependency_urls
  return @dependency_urls if defined?(@dependency_urls)

  @dependency_urls = []
  if package_lock
    @dependency_urls +=
      parsed_package_lock.fetch("dependencies", {}).
      map { |_, details| details["resolved"] }.compact.
      select { |url| url.is_a?(String) }.
      reject { |url| url.start_with?("git") }
  end
  if yarn_lock
    @dependency_urls +=
      yarn_lock.content.scan(/ resolved "(.*?)"/).flatten
  end

  # The registry URL for Bintray goes into the lockfile in a
  # modified format, so we modify it back before checking against
  # our credentials
  @dependency_urls =
    @dependency_urls.map do |url|
      url.gsub("dl.bintray.com//", "api.bintray.com/npm/")
    end
end
global_registry() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 49
def global_registry # rubocop:disable Metrics/PerceivedComplexity
  @global_registry ||=
    registry_credentials.find do |cred|
      next false if CENTRAL_REGISTRIES.include?(cred["registry"])

      # If all the URLs include this registry, it's global
      next true if dependency_urls.all? { |url| url.include?(cred["registry"]) }

      # Check if this registry has already been defined in .npmrc as a scoped registry
      next false if npmrc_scoped_registries.any? { |sr| sr.include?(cred["registry"]) }

      # If any unscoped URLs include this registry, assume it's global
      dependency_urls.
        reject { |u| u.include?("@") || u.include?("%40") }.
        any? { |url| url.include?(cred["registry"]) }
    end
end
global_registry_auth_line() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 67
def global_registry_auth_line
  token = global_registry.fetch("token", nil)
  return "" unless token

  if token.include?(":")
    encoded_token = Base64.encode64(token).delete("\n")
    "_auth = #{encoded_token}\n"
  elsif Base64.decode64(token).ascii_only? &&
        Base64.decode64(token).include?(":")
    "_auth = #{token.delete("\n")}\n"
  else
    "_authToken = #{token}\n"
  end
end
npmrc_file() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 204
def npmrc_file
  @npmrc_file ||= dependency_files.
                  find { |f| f.name.end_with?(".npmrc") }
end
npmrc_scoped_registries() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 158
def npmrc_scoped_registries
  return [] unless npmrc_file

  @npmrc_scoped_registries ||=
    npmrc_file.content.lines.select { |line| line.match?(SCOPED_REGISTRY) }.
    map { |line| line.match(SCOPED_REGISTRY)&.named_captures&.fetch("registry") }.
    compact
end
package_lock() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 218
def package_lock
  @package_lock ||=
    dependency_files.find { |f| f.name == "package-lock.json" }
end
parsed_package_lock() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 200
def parsed_package_lock
  @parsed_package_lock ||= JSON.parse(package_lock.content)
end
registry_credentials() click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 196
def registry_credentials
  credentials.select { |cred| cred.fetch("type") == "npm_registry" }
end
registry_scopes(registry) click to toggle source

rubocop:disable Metrics/PerceivedComplexity

# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 168
def registry_scopes(registry)
  # Central registries don't just apply to scopes
  return if CENTRAL_REGISTRIES.include?(registry)

  return unless dependency_urls

  other_regs =
    registry_credentials.map { |c| c.fetch("registry") } -
    [registry]
  affected_urls =
    dependency_urls.
    select do |url|
      next false unless url.include?(registry)

      other_regs.none? { |r| r.include?(registry) && url.include?(r) }
    end

  scopes = affected_urls.map do |url|
    url.split(/\%40|@/)[1]&.split(%r{\%2[fF]|/})&.first
  end

  # Registry used for unscoped packages
  return if scopes.include?(nil)

  scopes.map { |scope| "@#{scope}:registry=https://#{registry}" }
end
yarn_lock() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 214
def yarn_lock
  @yarn_lock ||= dependency_files.find { |f| f.name == "yarn.lock" }
end
yarnrc_file() click to toggle source
# File lib/dependabot/npm_and_yarn/file_updater/npmrc_builder.rb, line 209
def yarnrc_file
  @yarnrc_file ||= dependency_files.
                   find { |f| f.name.end_with?(".yarnrc") }
end