Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes RuntimeError-No package.json issue #10392

Merged
merged 5 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion npm_and_yarn/lib/dependabot/npm_and_yarn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ module NpmAndYarn
# Used to check if error message contains timeout fetching package
TIMEOUT_FETCHING_PACKAGE_REGEX = %r{(?<url>.+)/(?<package>[^/]+): ETIMEDOUT}

ESOCKETTIMEDOUT = /(?<package>.*?): ESOCKETTIMEDOUT/
sachin-sandhu marked this conversation as resolved.
Show resolved Hide resolved

SOCKET_HANG_UP = /(?<url>.*?): socket hang up/

# Used to identify git unreachable error
UNREACHABLE_GIT_CHECK_REGEX = /ls-remote --tags --heads (?<url>.*)/

Expand Down Expand Up @@ -104,6 +108,7 @@ module NpmAndYarn
# Used to identify if authentication failure error
AUTHENTICATION_TOKEN_NOT_PROVIDED = "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."

# Used to identify if error message is related to yarn workspaces
DEPENDENCY_FILE_NOT_RESOLVABLE = "conflicts with direct dependency"
Expand Down Expand Up @@ -317,7 +322,8 @@ def self.sanitize_resolvability_message(error_message, dependencies, yarn_lock)
matchfn: nil
},
{
patterns: [AUTHENTICATION_TOKEN_NOT_PROVIDED, AUTHENTICATION_IS_NOT_CONFIGURED],
patterns: [AUTHENTICATION_TOKEN_NOT_PROVIDED, AUTHENTICATION_IS_NOT_CONFIGURED,
AUTHENTICATION_HEADER_NOT_PROVIDED],
handler: lambda { |message, _error, _params|
Dependabot::PrivateSourceAuthenticationFailure.new(message)
},
Expand Down Expand Up @@ -359,7 +365,28 @@ def self.sanitize_resolvability_message(error_message, dependencies, yarn_lock)
},
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
}

].freeze, T::Array[{
patterns: T::Array[T.any(String, Regexp)],
handler: ErrorHandler,
Expand Down
2 changes: 1 addition & 1 deletion npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ def build_dependency(file:, type:, name:, requirement:)

sig { override.void }
def check_required_files
raise "No package.json!" unless get_original_file("package.json")
raise DependencyFileNotFound.new(nil, "package.json not found.") unless get_original_file("package.json")
sachin-sandhu marked this conversation as resolved.
Show resolved Hide resolved
end

sig { params(requirement: String).returns(T::Boolean) }
Expand Down
2 changes: 1 addition & 1 deletion npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ def filtered_dependency_files

sig { override.void }
def check_required_files
raise "No package.json!" unless get_original_file("package.json")
raise DependencyFileNotFound.new(nil, "package.json not found.") unless get_original_file("package.json")
sachin-sandhu marked this conversation as resolved.
Show resolved Hide resolved
end

sig { params(updated_files: T::Array[DependencyFile]).returns(T::Hash[Symbol, T.untyped]) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def updated_lockfile_reponse(response)
ERROR_E401 = /code E401/
ERROR_E403 = /code E403/
ERROR_EAI_AGAIN = /request to (?<url>.*) failed, reason: getaddrinfo EAI_AGAIN/
PACKAGE_DISCOVERY_FAIL = /Couldn't find package "(?<pkg>.*)" *.* on the "(?<regis>.*)" registry./

# TODO: look into fixing this in npm, seems like a bug in the git
# downloader introduced in npm 7
Expand Down Expand Up @@ -575,6 +576,8 @@ def handle_npm_updater_error(error)
raise Dependabot::DependencyFileNotResolvable, msg
end

raise Dependabot::DependencyFileNotResolvable, error_message if error_message.match(PACKAGE_DISCOVERY_FAIL)

raise error
end
# rubocop:enable Metrics/AbcSize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ def handle_yarn_lock_updater_error(error, yarn_lock)
error_message.include?(DEPENDENCY_MATCH_NOT_FOUND)

unless resolvable_before_update?(yarn_lock)
error_handler.raise_resolvability_error(error_message, yarn_lock)
error_handler.raise_resolvability_error(error_message,
yarn_lock)
end

# Dependabot has probably messed something up with the update and we
Expand Down
68 changes: 68 additions & 0 deletions npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1540,4 +1540,72 @@
end
end
end

describe "missing package.json manifest file" do
let(:child_class) do
Class.new(described_class) do
def check_required_files
%w(manifest).each do |filename|
unless get_original_file(filename)
raise Dependabot::DependencyFileNotFound.new(nil,
"package.json not found.")
end
end
end
end
end
let(:parser_instance) do
child_class.new(dependency_files: files, source: source)
end
let(:source) do
Dependabot::Source.new(
provider: "github",
repo: "gocardless/bump",
directory: "/"
)
end

let(:gemfile) do
Dependabot::DependencyFile.new(
content: "a",
name: "manifest",
directory: "/path/to"
)
end
let(:files) { [gemfile] }

describe ".new" do
context "when the required file is present" do
let(:files) { [gemfile] }

it "doesn't raise" do
expect { parser_instance }.not_to raise_error
end
end

context "when the required file is missing" do
let(:files) { [] }

it "raises" do
expect { parser_instance }.to raise_error(Dependabot::DependencyFileNotFound)
end
end
end

describe "#get_original_file" do
subject { parser_instance.send(:get_original_file, filename) }

context "when the requested file is present" do
let(:filename) { "manifest" }

it { is_expected.to eq(gemfile) }
end

context "when the requested file is not present" do
let(:filename) { "package.json" }

it { is_expected.to be_nil }
end
end
end
end
79 changes: 79 additions & 0 deletions npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4031,4 +4031,83 @@
end
end
end

describe "without a package.json file" do
let(:child_class) do
Class.new(described_class) do
def check_required_files
%w(manifest).each do |filename|
unless get_original_file(filename)
raise Dependabot::DependencyFileNotFound.new(nil,
"package.json not found.")
end
end
end
end
end
let(:updater_instance) do
child_class.new(
dependency_files: files,
dependencies: [dependency],
credentials: [{
"type" => "git_source",
"host" => "github.com"
}]
)
end

let(:manifest) do
Dependabot::DependencyFile.new(
content: "a",
name: "manifest",
directory: "/path/to"
)
end
let(:dependency) do
Dependabot::Dependency.new(
name: "business",
version: "1.5.0",
package_manager: "bundler",
requirements:
[{ file: "manifest", requirement: "~> 1.4.0", groups: [], source: nil }]
)
end
let(:files) { [manifest] }

describe "new file updater" do
subject { -> { updater_instance } }

context "when the required file is present" do
let(:files) { [manifest] }

it "doesn't raise" do
expect { updater_instance }.not_to raise_error
end
end

context "when the required file is missing" do
let(:files) { [] }

it "raises" do
expect { updater_instance }.to raise_error(Dependabot::DependencyFileNotFound)
end
end
end

describe "#get_original_file" do
subject { updater_instance.send(:get_original_file, filename) }

context "when the requested file is present" do
let(:filename) { "manifest" }

it { is_expected.to eq(manifest) }
end

context "when the requested file is not present" do
let(:filename) { "package.json" }

it { is_expected.to be_nil }
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,31 @@
end
end

context "when the error message contains ESOCKETTIMEDOUT" do
let(:error_message) do
"https://registry.us.gympass.cloud/repository/npm-group/@gympass%2fmep-utils: ESOCKETTIMEDOUT"
end

it "raises the corresponding error class with the correct message" do
expect { error_handler.handle_group_patterns(error, usage_error_message, { yarn_lock: yarn_lock }) }
.to raise_error(Dependabot::PrivateSourceTimedOut, "The following source timed out: " \
"registry.us.gympass.cloud/repository/" \
"npm-group/@gympass%2fmep-utils")
end
end

context "when the error message contains socket hang up" do
let(:error_message) do
"https://registry.npm.taobao.org/vue-template-compiler: socket hang up"
end

it "raises the corresponding error class with the correct message" do
expect { error_handler.handle_group_patterns(error, usage_error_message, { yarn_lock: yarn_lock }) }
.to raise_error(Dependabot::PrivateSourceTimedOut, "The following source timed out: " \
"registry.npm.taobao.org/vue-template-compiler")
end
end

context "when the error message contains a recognized pattern in the error message" do
it "raises the corresponding error class with the correct message" do
expect { error_handler.handle_group_patterns(error, "", { yarn_lock: yarn_lock }) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ def dependencies
def check_and_create_pr_with_error_handling(dependency)
check_and_create_pull_request(dependency)
rescue URI::InvalidURIError => e
msg = e.class.to_s + " with message: " + e.message
e = Dependabot::DependencyFileNotResolvable.new(msg)
error_handler.handle_dependency_error(error: e, dependency: dependency)
error_handler.handle_dependency_error(error: Dependabot::DependencyFileNotResolvable.new(e.message),
dependency: dependency)
rescue Dependabot::InconsistentRegistryResponse => e
error_handler.log_dependency_error(
dependency: dependency,
Expand Down
76 changes: 76 additions & 0 deletions updater/spec/dependabot/updater_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2028,6 +2028,44 @@ def expect_update_checker_with_ignored_versions(versions, dependency_matcher: an
end
end

context "when Dependabot::PrivateSourceAuthenticationFailure is raised with Unauthenticated message" do
it "doesn't report the error to the service" do
checker = stub_update_checker
error = Dependabot::PrivateSourceAuthenticationFailure.new("npm.fury.io")
values = [-> { raise error }, -> { true }, -> { true }, -> { true }]
allow(checker).to receive(:can_update?) { values.shift.call }

job = build_job
service = build_service
updater = build_updater(service: service, job: job)

expect(service).not_to receive(:capture_exception)

updater.run
end

it "tells the main backend" do
checker = stub_update_checker
error = Dependabot::PrivateSourceAuthenticationFailure.new("npm.fury.io")
values = [-> { raise error }, -> { true }, -> { true }, -> { true }]
allow(checker).to receive(:can_update?) { values.shift.call }

job = build_job
service = build_service
updater = build_updater(service: service, job: job)

expect(service)
.to receive(:record_update_job_error)
.with(
error_type: "private_source_authentication_failure",
error_details: { source: "npm.fury.io" },
dependency: an_instance_of(Dependabot::Dependency)
)

updater.run
end
end

context "when Dependabot::GitDependenciesNotReachable is raised" do
it "doesn't report the error to the service" do
checker = stub_update_checker
Expand Down Expand Up @@ -2066,6 +2104,44 @@ def expect_update_checker_with_ignored_versions(versions, dependency_matcher: an
end
end

context "when URI::InvalidURIError is raised" do
it "doesn't report the error to the service" do
checker = stub_update_checker
error = URI::InvalidURIError.new("https://registry.yarnpkg.com}/")
values = [-> { raise error }, -> { true }, -> { true }, -> { true }]
allow(checker).to receive(:can_update?) { values.shift.call }

job = build_job
service = build_service
updater = build_updater(service: service, job: job)

expect(service).not_to receive(:capture_exception)

updater.run
end

it "tells the main backend" do
checker = stub_update_checker
error = URI::InvalidURIError.new("https://registry.yarnpkg.com}/")
values = [-> { raise error }, -> { true }, -> { true }, -> { true }]
allow(checker).to receive(:can_update?) { values.shift.call }

job = build_job
service = build_service
updater = build_updater(service: service, job: job)

expect(service)
.to receive(:record_update_job_error)
.with(
error_type: "dependency_file_not_resolvable",
error_details: { message: "https://registry.yarnpkg.com}/" },
dependency: an_instance_of(Dependabot::Dependency)
)

updater.run
end
end

context "when Dependabot::GitDependencyReferenceNotFound is raised" do
it "doesn't report the error to the service" do
checker = stub_update_checker
Expand Down
Loading