Skip to content

Commit

Permalink
Merge branch 'main' into harry/removing-corepack-from-else-clause
Browse files Browse the repository at this point in the history
  • Loading branch information
thavaahariharangit authored Jan 16, 2025
2 parents 284d938 + 66caeef commit b82014d
Show file tree
Hide file tree
Showing 6 changed files with 834 additions and 17 deletions.
38 changes: 28 additions & 10 deletions bundler/lib/dependabot/bundler/file_updater/gemfile_updater.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "dependabot/bundler/file_updater"
Expand All @@ -7,19 +7,23 @@ module Dependabot
module Bundler
class FileUpdater
class GemfileUpdater
extend T::Sig

GEMFILE_FILENAMES = %w(Gemfile gems.rb).freeze

require_relative "git_pin_replacer"
require_relative "git_source_remover"
require_relative "requirement_replacer"

sig { params(dependencies: T::Array[Dependabot::Dependency], gemfile: Dependabot::DependencyFile).void }
def initialize(dependencies:, gemfile:)
@dependencies = dependencies
@gemfile = gemfile
end

sig { returns(String) }
def updated_gemfile_content
content = gemfile.content
content = T.must(gemfile.content)

dependencies.each do |dependency|
content = replace_gemfile_version_requirement(
Expand All @@ -38,21 +42,27 @@ def updated_gemfile_content

private

sig { returns(T::Array[Dependabot::Dependency]) }
attr_reader :dependencies

sig { returns(Dependabot::DependencyFile) }
attr_reader :gemfile

sig do
params(dependency: Dependabot::Dependency, file: Dependabot::DependencyFile, content: String).returns(String)
end
def replace_gemfile_version_requirement(dependency, file, content)
return content unless requirement_changed?(file, dependency)

updated_requirement =
dependency.requirements
.find { |r| r[:file] == file.name }
.fetch(:requirement)
&.fetch(:requirement)

previous_requirement =
dependency.previous_requirements
.find { |r| r[:file] == file.name }
.fetch(:requirement)
&.find { |r| r[:file] == file.name }
&.fetch(:requirement)

RequirementReplacer.new(
dependency: dependency,
Expand All @@ -62,27 +72,30 @@ def replace_gemfile_version_requirement(dependency, file, content)
).rewrite(content)
end

sig { params(file: Dependabot::DependencyFile, dependency: Dependabot::Dependency).returns(T::Boolean) }
def requirement_changed?(file, dependency)
changed_requirements =
dependency.requirements - dependency.previous_requirements
dependency.requirements - T.must(dependency.previous_requirements)

changed_requirements.any? { |f| f[:file] == file.name }
end

sig { params(dependency: Dependabot::Dependency).returns(T::Boolean) }
def remove_git_source?(dependency)
old_gemfile_req =
dependency.previous_requirements
.find { |f| GEMFILE_FILENAMES.include?(f[:file]) }
&.find { |f| GEMFILE_FILENAMES.include?(f[:file]) }

return false unless old_gemfile_req&.dig(:source, :type) == "git"

new_gemfile_req =
dependency.requirements
.find { |f| GEMFILE_FILENAMES.include?(f[:file]) }

new_gemfile_req[:source].nil?
T.must(new_gemfile_req)[:source].nil?
end

sig { params(dependency: Dependabot::Dependency, file: Dependabot::DependencyFile).returns(T::Boolean) }
def update_git_pin?(dependency, file)
new_gemfile_req =
dependency.requirements
Expand All @@ -91,18 +104,23 @@ def update_git_pin?(dependency, file)

# If the new requirement is a git dependency with a ref then there's
# no harm in doing an update
new_gemfile_req.dig(:source, :ref)
!T.must(new_gemfile_req).dig(:source, :ref).nil?
end

sig { params(dependency: Dependabot::Dependency, content: String).returns(String) }
def remove_gemfile_git_source(dependency, content)
GitSourceRemover.new(dependency: dependency).rewrite(content)
end

sig do
params(dependency: Dependabot::Dependency, file: Dependabot::DependencyFile, content: String).returns(String)
end
def update_gemfile_git_pin(dependency, file, content)
new_pin =
dependency.requirements
.find { |f| f[:file] == file.name }
.fetch(:source).fetch(:ref)
&.fetch(:source)
&.fetch(:ref)

GitPinReplacer
.new(dependency: dependency, new_pin: new_pin)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# typed: true
# typed: strict
# frozen_string_literal: true

require "parser/current"
Expand All @@ -8,13 +8,20 @@ module Dependabot
module Bundler
class FileUpdater
class GemspecDependencyNameFinder
extend T::Sig

ChildNode = T.type_alias { T.nilable(T.any(Parser::AST::Node, Symbol, String)) }

sig { returns(String) }
attr_reader :gemspec_content

sig { params(gemspec_content: String).void }
def initialize(gemspec_content:)
@gemspec_content = gemspec_content
end

# rubocop:disable Security/Eval
sig { returns(T.nilable(String)) }
def dependency_name
ast = Parser::CurrentRuby.parse(gemspec_content)
dependency_name_node = find_dependency_name_node(ast)
Expand All @@ -30,6 +37,7 @@ def dependency_name

private

sig { params(node: ChildNode).returns(T.nilable(Parser::AST::Node)) }
def find_dependency_name_node(node)
return unless node.is_a?(Parser::AST::Node)
return node if declares_dependency_name?(node)
Expand All @@ -40,6 +48,7 @@ def find_dependency_name_node(node)
end
end

sig { params(node: ChildNode).returns(T::Boolean) }
def declares_dependency_name?(node)
return false unless node.is_a?(Parser::AST::Node)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ def initialize(dependencies:, dependency_files:, repo_contents_path:, credential
@dependency_files = dependency_files
@repo_contents_path = repo_contents_path
@credentials = credentials
@error_handler = PnpmErrorHandler.new(
dependencies: dependencies,
dependency_files: dependency_files
)
end

def updated_pnpm_lock_content(pnpm_lock)
Expand All @@ -36,6 +40,7 @@ def updated_pnpm_lock_content(pnpm_lock)
attr_reader :dependency_files
attr_reader :repo_contents_path
attr_reader :credentials
attr_reader :error_handler

IRRESOLVABLE_PACKAGE = "ERR_PNPM_NO_MATCHING_VERSION"
INVALID_REQUIREMENT = "ERR_PNPM_SPEC_NOT_SUPPORTED_BY_ANY_RESOLVER"
Expand All @@ -46,12 +51,12 @@ def updated_pnpm_lock_content(pnpm_lock)
UNAUTHORIZED_PACKAGE = /ERR_PNPM_FETCH_401[ [^:print:]]+GET (?<dependency_url>.*): Unauthorized - 401/

# ERR_PNPM_FETCH ERROR CODES
ERR_PNPM_FETCH_401 = /ERR_PNPM_FETCH_401.*GET (?<dependency_url>.*): - 401/
ERR_PNPM_FETCH_403 = /ERR_PNPM_FETCH_403.*GET (?<dependency_url>.*): - 403/
ERR_PNPM_FETCH_404 = /ERR_PNPM_FETCH_404.*GET (?<dependency_url>.*): - 404/
ERR_PNPM_FETCH_500 = /ERR_PNPM_FETCH_500.*GET (?<dependency_url>.*): - 500/
ERR_PNPM_FETCH_502 = /ERR_PNPM_FETCH_502.*GET (?<dependency_url>.*): - 502/
ERR_PNPM_FETCH_503 = /ERR_PNPM_FETCH_503.*GET (?<dependency_url>.*): - 503/
ERR_PNPM_FETCH_401 = /ERR_PNPM_FETCH_401.*GET (?<dependency_url>.*):/
ERR_PNPM_FETCH_403 = /ERR_PNPM_FETCH_403.*GET (?<dependency_url>.*):/
ERR_PNPM_FETCH_404 = /ERR_PNPM_FETCH_404.*GET (?<dependency_url>.*):/
ERR_PNPM_FETCH_500 = /ERR_PNPM_FETCH_500.*GET (?<dependency_url>.*):/
ERR_PNPM_FETCH_502 = /ERR_PNPM_FETCH_502.*GET (?<dependency_url>.*):/
ERR_PNPM_FETCH_503 = /ERR_PNPM_FETCH_503.*GET (?<dependency_url>.*):/

# ERR_PNPM_UNSUPPORTED_ENGINE
ERR_PNPM_UNSUPPORTED_ENGINE = /ERR_PNPM_UNSUPPORTED_ENGINE/
Expand Down Expand Up @@ -251,6 +256,8 @@ def handle_pnpm_lock_updater_error(error, pnpm_lock)
pnpm_lock)
end

error_handler.handle_pnpm_error(error)

raise
end
# rubocop:enable Metrics/AbcSize
Expand Down Expand Up @@ -360,5 +367,60 @@ def sanitize_message(message)
end
end
end

class PnpmErrorHandler
extend T::Sig

# remote connection closed
ECONNRESET_ERROR = /ECONNRESET/

# socket hang up error code
SOCKET_HANG_UP = /socket hang up/

# ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC error
ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC = /ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC/

# duplicate package error code
DUPLICATE_PACKAGE = /Found duplicates/

ERR_PNPM_NO_VERSIONS = /ERR_PNPM_NO_VERSIONS/

# Initializes the YarnErrorHandler with dependencies and dependency files
sig do
params(
dependencies: T::Array[Dependabot::Dependency],
dependency_files: T::Array[Dependabot::DependencyFile]
).void
end
def initialize(dependencies:, dependency_files:)
@dependencies = dependencies
@dependency_files = dependency_files
end

private

sig { returns(T::Array[Dependabot::Dependency]) }
attr_reader :dependencies

sig { returns(T::Array[Dependabot::DependencyFile]) }
attr_reader :dependency_files

public

# Handles errors with specific to yarn error codes
sig { params(error: SharedHelpers::HelperSubprocessFailed).void }
def handle_pnpm_error(error)
if error.message.match?(DUPLICATE_PACKAGE) || error.message.match?(ERR_PNPM_NO_VERSIONS) ||
error.message.match?(ERR_PNPM_CATALOG_ENTRY_NOT_FOUND_FOR_SPEC)

raise DependencyFileNotResolvable, "Error resolving dependency"
end

## Clean error message from ANSI escape codes
return unless error.message.match?(ECONNRESET_ERROR) || error.message.match?(SOCKET_HANG_UP)

raise InconsistentRegistryResponse, "Inconsistent registry response while resolving dependency"
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# typed: false
# frozen_string_literal: true

require "spec_helper"
require "dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater"
require "dependabot/npm_and_yarn/dependency_files_filterer"
require "dependabot/dependency"
require "dependabot/dependency_file"
require "dependabot/shared_helpers"
require "dependabot/errors"

RSpec.describe Dependabot::NpmAndYarn::PnpmErrorHandler do
subject(:error_handler) { described_class.new(dependencies: dependencies, dependency_files: dependency_files) }

let(:dependencies) { [dependency] }
let(:error) { instance_double(Dependabot::SharedHelpers::HelperSubprocessFailed, message: error_message) }

let(:dependency) do
Dependabot::Dependency.new(
name: dependency_name,
version: version,
requirements: [],
previous_requirements: [],
package_manager: "npm_and_yarn"
)
end
let(:dependency_files) { project_dependency_files("pnpm/git_dependency_local_file") }

let(:credentials) do
[Dependabot::Credential.new({
"type" => "git_source",
"host" => "github.com"
})]
end

let(:dependency_name) { "@segment/analytics.js-integration-facebook-pixel" }
let(:version) { "github:segmentio/analytics.js-integrations#2.4.1" }
let(:yarn_lock) do
dependency_files.find { |f| f.name == "pnpm.lock" }
end

describe "#initialize" do
it "initializes with dependencies and dependency files" do
expect(error_handler.send(:dependencies)).to eq(dependencies)
expect(error_handler.send(:dependency_files)).to eq(dependency_files)
end
end

describe "#handle_error" do
context "when the error message contains Inconsistent Registry Response" do
let(:error_message) do
"ECONNRESET  request to https://artifactory.schaeffler.com/as.zip failed, reason: socket hang up"
end

it "raises a InconsistentRegistryResponse error with the correct message" do
expect do
error_handler.handle_pnpm_error(error)
end.to raise_error(Dependabot::InconsistentRegistryResponse)
end
end

context "when the error message contains package error" do
let(:error_message) do
"ERR_PNPM_NO_VERSIONS  No versions available for prosemirror-gapcursor. The package may be unpublished."
end

it "raises a DependencyFileNotResolvable error with the correct message" do
expect do
error_handler.handle_pnpm_error(error)
end.to raise_error(Dependabot::DependencyFileNotResolvable)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@segment/analytics.js-integration-facebook-pixel": "github:segmentio/analytics.js-integrations#2.4.1"
}
}
Loading

0 comments on commit b82014d

Please sign in to comment.