From ee30de2a2b7d03bc657f7e0e16c71f8aaa65b00b Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Wed, 15 Jan 2025 18:14:05 -0800 Subject: [PATCH] raise ToolFeatureNotSupported when all updated dependencies are in workspace dev dependecies and there is no update error --- common/lib/dependabot/config/update_config.rb | 3 +- common/lib/dependabot/dependency.rb | 9 ++- common/lib/dependabot/errors.rb | 59 +++++++++++++++++++ .../file_parsers/base/dependency_set.rb | 1 + common/lib/dependabot/git_commit_checker.rb | 1 + common/lib/dependabot/update_checkers/base.rb | 2 + .../dependabot/npm_and_yarn/file_parser.rb | 15 ++++- .../dependabot/npm_and_yarn/file_updater.rb | 28 +++++++++ 8 files changed, 115 insertions(+), 3 deletions(-) diff --git a/common/lib/dependabot/config/update_config.rb b/common/lib/dependabot/config/update_config.rb index a027bacd758..505fbb1fb36 100644 --- a/common/lib/dependabot/config/update_config.rb +++ b/common/lib/dependabot/config/update_config.rb @@ -53,7 +53,8 @@ def extract_base_version_from_requirement(dependency) name: dependency.name, version: version, requirements: dependency.requirements, - package_manager: dependency.package_manager + package_manager: dependency.package_manager, + workspace: dependency.workspace ) end diff --git a/common/lib/dependabot/dependency.rb b/common/lib/dependabot/dependency.rb index 16658f6877c..965ef46b895 100644 --- a/common/lib/dependabot/dependency.rb +++ b/common/lib/dependabot/dependency.rb @@ -88,6 +88,9 @@ def self.register_name_normaliser(package_manager, name_builder) sig { returns(T::Hash[Symbol, T.untyped]) } attr_reader :metadata + sig { returns(T.nilable(String)) } + attr_reader :workspace + # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/PerceivedComplexity sig do @@ -100,6 +103,7 @@ def self.register_name_normaliser(package_manager, name_builder) previous_version: T.nilable(String), previous_requirements: T.nilable(T::Array[T::Hash[T.any(Symbol, String), T.untyped]]), directory: T.nilable(String), + workspace: T.nilable(String), subdependency_metadata: T.nilable(T::Array[T::Hash[T.any(Symbol, String), String]]), removed: T::Boolean, metadata: T.nilable(T::Hash[T.any(Symbol, String), String]) @@ -107,7 +111,7 @@ def self.register_name_normaliser(package_manager, name_builder) end def initialize(name:, requirements:, package_manager:, version: nil, previous_version: nil, previous_requirements: nil, directory: nil, - subdependency_metadata: [], removed: false, metadata: {}) + workspace: nil, subdependency_metadata: [], removed: false, metadata: {}) @name = name @version = T.let( case version @@ -126,6 +130,8 @@ def initialize(name:, requirements:, package_manager:, version: nil, ) @package_manager = package_manager @directory = directory + @workspace = workspace + unless top_level? || subdependency_metadata == [] @subdependency_metadata = T.let( subdependency_metadata&.map { |h| symbolize_keys(h) }, @@ -166,6 +172,7 @@ def to_h "previous_version" => previous_version, "previous_requirements" => previous_requirements, "directory" => directory, + "workspace" => workspace, "package_manager" => package_manager, "subdependency_metadata" => subdependency_metadata, "removed" => removed? ? true : nil diff --git a/common/lib/dependabot/errors.rb b/common/lib/dependabot/errors.rb index 3c604199e77..f668d5f7960 100644 --- a/common/lib/dependabot/errors.rb +++ b/common/lib/dependabot/errors.rb @@ -33,6 +33,15 @@ def self.fetcher_error_details(error) "supported-versions": error.supported_versions } } + when Dependabot::ToolFeatureNotSupported + { + "error-type": "tool_feature_not_supported", + "error-detail": { + "tool-name": error.tool_name, + "tool-type": error.tool_type, + feature: error.feature + } + } when Dependabot::BranchNotFound { "error-type": "branch_not_found", @@ -103,6 +112,15 @@ def self.fetcher_error_details(error) sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.untyped])) } def self.parser_error_details(error) case error + when Dependabot::ToolFeatureNotSupported + { + "error-type": "tool_feature_not_supported", + "error-detail": { + "tool-name": error.tool_name, + "tool-type": error.tool_type, + feature: error.feature + } + } when Dependabot::DependencyFileNotEvaluatable { "error-type": "dependency_file_not_evaluatable", @@ -166,9 +184,19 @@ def self.parser_error_details(error) # rubocop:disable Lint/RedundantCopDisableDirective # rubocop:disable Metrics/CyclomaticComplexity + # rubocop:disable Metrics/AbcSize sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.untyped])) } def self.updater_error_details(error) case error + when Dependabot::ToolFeatureNotSupported + { + "error-type": "tool_feature_not_supported", + "error-detail": { + "tool-name": error.tool_name, + "tool-type": error.tool_type, + feature: error.feature + } + } when Dependabot::DependencyFileNotResolvable { "error-type": "dependency_file_not_resolvable", @@ -291,9 +319,11 @@ def self.updater_error_details(error) } end end + # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Lint/RedundantCopDisableDirective + # rubocop:enable Metrics/AbcSize class DependabotError < StandardError extend T::Sig @@ -480,6 +510,35 @@ def initialize(tool_name, detected_version, supported_versions) end end + class ToolFeatureNotSupported < DependabotError + extend T::Sig + + sig { returns(String) } + attr_reader :tool_name, :tool_type, :feature + + sig do + params( + tool_name: String, + tool_type: String, + feature: String + ).void + end + def initialize(tool_name:, tool_type:, feature:) + @tool_name = tool_name + @tool_type = tool_type + @feature = feature + super(build_message) + end + + private + + sig { returns(String) } + def build_message + "Dependabot doesn't support the feature '#{feature}' for #{tool_name} (#{tool_type}). " \ + "Please refer to the documentation for supported features." + end + end + class DependencyFileNotFound < DependabotError extend T::Sig diff --git a/common/lib/dependabot/file_parsers/base/dependency_set.rb b/common/lib/dependabot/file_parsers/base/dependency_set.rb index b168b31a6a3..8dd032d0667 100644 --- a/common/lib/dependabot/file_parsers/base/dependency_set.rb +++ b/common/lib/dependabot/file_parsers/base/dependency_set.rb @@ -156,6 +156,7 @@ def combined_dependency(old_dep, new_dep) version: version, requirements: requirements, package_manager: old_dep.package_manager, + workspace: old_dep.workspace, metadata: old_dep.metadata, subdependency_metadata: subdependency_metadata ) diff --git a/common/lib/dependabot/git_commit_checker.rb b/common/lib/dependabot/git_commit_checker.rb index ff52536978e..4ff16997285 100644 --- a/common/lib/dependabot/git_commit_checker.rb +++ b/common/lib/dependabot/git_commit_checker.rb @@ -478,6 +478,7 @@ def listing_source_url candidate_dep = Dependency.new( name: dependency.name, version: dependency.version, + workspace: dependency.workspace, requirements: [], package_manager: dependency.package_manager ) diff --git a/common/lib/dependabot/update_checkers/base.rb b/common/lib/dependabot/update_checkers/base.rb index 05501b2d5ce..dedce5b2bd4 100644 --- a/common/lib/dependabot/update_checkers/base.rb +++ b/common/lib/dependabot/update_checkers/base.rb @@ -233,6 +233,7 @@ def updated_dependency_without_unlock previous_version: previous_version, previous_requirements: dependency.requirements, package_manager: dependency.package_manager, + workspace: dependency.workspace, metadata: dependency.metadata, subdependency_metadata: dependency.subdependency_metadata ) @@ -250,6 +251,7 @@ def updated_dependency_with_own_req_unlock previous_version: previous_version, previous_requirements: dependency.requirements, package_manager: dependency.package_manager, + workspace: dependency.workspace, metadata: dependency.metadata, subdependency_metadata: dependency.subdependency_metadata ) diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb index 7f86e81eaf7..0ae99eb8e11 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser.rb @@ -258,7 +258,19 @@ def build_dependency(file:, type:, name:, requirement:) return if lockfile_details && !version return if ignore_requirement?(requirement) - return if workspace_package_names.include?(name) + + workspace_name = nil + + if Dependabot::Experiments.enabled?(:enable_fix_for_pnpm_no_change_error) + workspaces = workspace_package_names + + return if workspaces.include?(name) + + # Extract workspace name by finding the first match in workspace_package_names + workspace_name = workspaces.find do |workspace| + file.name.start_with?(workspace + "/") + end + end # TODO: Handle aliased packages: # https://github.com/dependabot/dependabot-core/pull/1115 @@ -271,6 +283,7 @@ def build_dependency(file:, type:, name:, requirement:) name: name, version: converted_version, package_manager: ECOSYSTEM, + workspace: workspace_name, requirements: [{ requirement: requirement_for(requirement), file: file.name, diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb index bd3fb7cf9db..29ccccc3fed 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_updater.rb @@ -47,6 +47,8 @@ def self.updated_files_regex ] end + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/PerceivedComplexity sig { override.returns(T::Array[DependencyFile]) } def updated_dependency_files updated_files = T.let([], T::Array[DependencyFile]) @@ -55,6 +57,30 @@ def updated_dependency_files updated_files += updated_lockfiles if updated_files.none? + + if Dependabot::Experiments.enabled?(:enable_fix_for_pnpm_no_change_error) + all_workspaces_present = dependencies.all? do |dependency| + !dependency.workspace.nil? && !dependency.workspace&.empty? + end + + all_dev_dependencies = dependencies.all? do |dependency| + dependency.requirements.all? do |requirement| + requirement[:groups].include?("devDependencies") + end + end + + if dependencies.any?(&:top_level?) && + pnpm_locks.any? && + all_workspaces_present && + all_dev_dependencies + raise ToolFeatureNotSupported.new( + tool_name: PNPMPackageManager::NAME, + tool_type: "package_manager", + feature: "updating dev dependencies in workspaces" + ) + end + end + raise NoChangeError.new( message: "No files were updated!", error_context: error_context(updated_files: updated_files) @@ -71,6 +97,8 @@ def updated_dependency_files vendor_updated_files(updated_files) end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/PerceivedComplexity private