From ddfa4dccbce71864e5ca7ae9c2d31c443e299aab Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Mon, 5 Feb 2024 09:54:48 -0800 Subject: [PATCH 01/49] Strict type `Dependabot::GitSubmodules` (#8970) --- .rubocop.yml | 6 +++ common/lib/dependabot/file_parsers/base.rb | 2 +- common/lib/dependabot/file_updaters/base.rb | 2 +- .../lib/dependabot/metadata_finders/base.rb | 2 +- git_submodules/.rubocop.yml | 2 +- .../dependabot/git_submodules/file_fetcher.rb | 37 ++++++++++++++----- .../dependabot/git_submodules/file_parser.rb | 17 ++++++++- .../dependabot/git_submodules/file_updater.rb | 27 +++++++++++--- .../git_submodules/metadata_finder.rb | 7 +++- .../dependabot/git_submodules/requirement.rb | 5 ++- .../git_submodules/update_checker.rb | 19 +++++++++- 11 files changed, 99 insertions(+), 27 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c01be5379e..af8a138672 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -335,6 +335,12 @@ Style/SelectByRegexp: Sorbet/TrueSigil: Exclude: - "**/spec/**/*" +Sorbet/StrictSigil: + Exclude: + - "**/spec/**/*" +Sorbet/StrongSigil: + Exclude: + - "**/spec/**/*" # TODO these were temporarily disabled during the Ruby 2.7 -> 3.1 upgrade # in order to keep the upgrade diff small, they will be enabled/fixed in diff --git a/common/lib/dependabot/file_parsers/base.rb b/common/lib/dependabot/file_parsers/base.rb index a8960f0c19..3251027ec5 100644 --- a/common/lib/dependabot/file_parsers/base.rb +++ b/common/lib/dependabot/file_parsers/base.rb @@ -50,7 +50,7 @@ def initialize(dependency_files:, source:, repo_contents_path: nil, check_required_files end - sig { abstract.returns(Dependabot::DependencyFile) } + sig { abstract.returns(T::Array[Dependabot::Dependency]) } def parse; end private diff --git a/common/lib/dependabot/file_updaters/base.rb b/common/lib/dependabot/file_updaters/base.rb index a7c59c1240..b984cdfcb9 100644 --- a/common/lib/dependabot/file_updaters/base.rb +++ b/common/lib/dependabot/file_updaters/base.rb @@ -26,7 +26,7 @@ class Base sig { returns(T::Hash[Symbol, T.untyped]) } attr_reader :options - sig { overridable.returns(String) } + sig { overridable.returns(T::Array[Regexp]) } def self.updated_files_regex raise NotImplementedError end diff --git a/common/lib/dependabot/metadata_finders/base.rb b/common/lib/dependabot/metadata_finders/base.rb index 6867ddf8b1..f2311ff954 100644 --- a/common/lib/dependabot/metadata_finders/base.rb +++ b/common/lib/dependabot/metadata_finders/base.rb @@ -176,7 +176,7 @@ def source @source = T.let(look_up_source, T.nilable(Dependabot::Source)) end - sig { overridable.returns(Dependabot::Source) } + sig { overridable.returns(T.nilable(Dependabot::Source)) } def look_up_source raise NotImplementedError end diff --git a/git_submodules/.rubocop.yml b/git_submodules/.rubocop.yml index b8168698e8..e5270530f5 100644 --- a/git_submodules/.rubocop.yml +++ b/git_submodules/.rubocop.yml @@ -1,4 +1,4 @@ inherit_from: ../.rubocop.yml -Sorbet/TrueSigil: +Sorbet/StrictSigil: Enabled: true diff --git a/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb b/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb index f5de3ea22b..db6b852961 100644 --- a/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb +++ b/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "parseconfig" @@ -13,10 +13,12 @@ class FileFetcher < Dependabot::FileFetchers::Base extend T::Sig extend T::Helpers + sig { override.params(filenames: T::Array[String]).returns(T::Boolean) } def self.required_files_in?(filenames) filenames.include?(".gitmodules") end + sig { override.returns(String) } def self.required_files_message "Repo must contain a .gitmodules file." end @@ -31,26 +33,40 @@ def fetch_files private + sig { returns(Dependabot::DependencyFile) } def gitmodules_file - @gitmodules_file ||= fetch_file_from_host(".gitmodules") + @gitmodules_file ||= + T.let( + fetch_file_from_host(".gitmodules"), + T.nilable(Dependabot::DependencyFile) + ) end + sig { returns(T::Array[Dependabot::DependencyFile]) } def submodule_refs @submodule_refs ||= - submodule_paths - .map { |path| fetch_submodule_ref_from_host(path) } - .tap { |refs| refs.each { |f| f.support_file = true } } - .uniq + T.let( + submodule_paths + .map { |path| fetch_submodule_ref_from_host(path) } + .tap { |refs| refs.each { |f| f.support_file = true } } + .uniq, + T.nilable(T::Array[Dependabot::DependencyFile]) + ) end + sig { returns(T::Array[String]) } def submodule_paths @submodule_paths ||= - Dependabot::SharedHelpers.in_a_temporary_directory do - File.write(".gitmodules", gitmodules_file.content) - ParseConfig.new(".gitmodules").params.values.map { |p| p["path"] } - end + T.let( + Dependabot::SharedHelpers.in_a_temporary_directory do + File.write(".gitmodules", gitmodules_file.content) + ParseConfig.new(".gitmodules").params.values.map { |p| p["path"] } + end, + T.nilable(T::Array[String]) + ) end + sig { params(submodule_path: String).returns(Dependabot::DependencyFile) } def fetch_submodule_ref_from_host(submodule_path) path = Pathname.new(File.join(directory, submodule_path)) .cleanpath.to_path.gsub(%r{^/*}, "") @@ -77,6 +93,7 @@ def fetch_submodule_ref_from_host(submodule_path) raise Dependabot::DependencyFileNotFound, path end + sig { params(path: String).returns(String) } def fetch_github_submodule_commit(path) content = T.unsafe(github_client).contents( repo, diff --git a/git_submodules/lib/dependabot/git_submodules/file_parser.rb b/git_submodules/lib/dependabot/git_submodules/file_parser.rb index 00495af838..e55bad0256 100644 --- a/git_submodules/lib/dependabot/git_submodules/file_parser.rb +++ b/git_submodules/lib/dependabot/git_submodules/file_parser.rb @@ -1,7 +1,9 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "parseconfig" +require "sorbet-runtime" + require "dependabot/dependency" require "dependabot/file_parsers" require "dependabot/file_parsers/base" @@ -10,6 +12,9 @@ module Dependabot module GitSubmodules class FileParser < Dependabot::FileParsers::Base + extend T::Sig + + sig { override.returns(T::Array[Dependabot::Dependency]) } def parse Dependabot::SharedHelpers.in_a_temporary_directory do File.write(".gitmodules", gitmodules_file.content) @@ -39,6 +44,7 @@ def parse private + sig { params(url: String).returns(String) } def absolute_url(url) # Submodules can be specified with a relative URL (e.g., ../repo.git) # which we want to expand out into a full URL if present. @@ -48,6 +54,7 @@ def absolute_url(url) "https://#{source&.hostname}/#{path.cleanpath}" end + sig { params(path: String).returns(T.nilable(String)) } def submodule_sha(path) submodule = dependency_files.find { |f| f.name == path } raise "Submodule not found #{path}" unless submodule @@ -55,10 +62,16 @@ def submodule_sha(path) submodule.content end + sig { returns(Dependabot::DependencyFile) } def gitmodules_file - @gitmodules_file ||= get_original_file(".gitmodules") + @gitmodules_file ||= + T.let( + T.must(get_original_file(".gitmodules")), + T.nilable(Dependabot::DependencyFile) + ) end + sig { override.void } def check_required_files %w(.gitmodules).each do |filename| raise "No #{filename}!" unless get_original_file(filename) diff --git a/git_submodules/lib/dependabot/git_submodules/file_updater.rb b/git_submodules/lib/dependabot/git_submodules/file_updater.rb index ed0c2f1b7d..355264577e 100644 --- a/git_submodules/lib/dependabot/git_submodules/file_updater.rb +++ b/git_submodules/lib/dependabot/git_submodules/file_updater.rb @@ -1,37 +1,52 @@ -# typed: true +# typed: strong # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/file_updaters" require "dependabot/file_updaters/base" module Dependabot module GitSubmodules class FileUpdater < Dependabot::FileUpdaters::Base + extend T::Sig + + sig { override.returns(T::Array[Regexp]) } def self.updated_files_regex [] end + sig { override.returns(T::Array[Dependabot::DependencyFile]) } def updated_dependency_files - [updated_file(file: submodule, content: dependency.version)] + [updated_file(file: submodule, content: T.must(dependency.version))] end private + sig { returns(Dependabot::Dependency) } def dependency # Git submodules will only ever be updating a single dependency - dependencies.first + T.must(dependencies.first) end + sig { override.void } def check_required_files %w(.gitmodules).each do |filename| raise "No #{filename}!" unless get_original_file(filename) end end + sig { returns(Dependabot::DependencyFile) } def submodule - @submodule ||= dependency_files.find do |file| - file.name == dependency.name - end + @submodule ||= + T.let( + T.must( + dependency_files.find do |file| + file.name == dependency.name + end + ), + T.nilable(Dependabot::DependencyFile) + ) end end end diff --git a/git_submodules/lib/dependabot/git_submodules/metadata_finder.rb b/git_submodules/lib/dependabot/git_submodules/metadata_finder.rb index dec406fae4..6e28a8a476 100644 --- a/git_submodules/lib/dependabot/git_submodules/metadata_finder.rb +++ b/git_submodules/lib/dependabot/git_submodules/metadata_finder.rb @@ -1,14 +1,19 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/metadata_finders" require "dependabot/metadata_finders/base" module Dependabot module GitSubmodules class MetadataFinder < Dependabot::MetadataFinders::Base + extend T::Sig + private + sig { override.returns(T.nilable(Dependabot::Source)) } def look_up_source url = dependency.requirements.first&.fetch(:source)&.fetch(:url) || dependency.requirements.first&.fetch(:source)&.fetch("url") diff --git a/git_submodules/lib/dependabot/git_submodules/requirement.rb b/git_submodules/lib/dependabot/git_submodules/requirement.rb index 00c6e2994e..6d7575245a 100644 --- a/git_submodules/lib/dependabot/git_submodules/requirement.rb +++ b/git_submodules/lib/dependabot/git_submodules/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "sorbet-runtime" @@ -21,9 +21,10 @@ def self.requirements_array(requirement_string) # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { params(requirements: T.nilable(String)).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| - req_string.split(",").map(&:strip) + req_string&.split(",")&.map(&:strip) end super(requirements) diff --git a/git_submodules/lib/dependabot/git_submodules/update_checker.rb b/git_submodules/lib/dependabot/git_submodules/update_checker.rb index eea6843eac..7c81c7cb82 100644 --- a/git_submodules/lib/dependabot/git_submodules/update_checker.rb +++ b/git_submodules/lib/dependabot/git_submodules/update_checker.rb @@ -1,6 +1,8 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/update_checkers" require "dependabot/update_checkers/base" require "dependabot/git_submodules/version" @@ -10,20 +12,30 @@ module Dependabot module GitSubmodules class UpdateChecker < Dependabot::UpdateCheckers::Base + extend T::Sig + + sig { override.returns(T.nilable(T.any(String, Gem::Version))) } def latest_version - @latest_version ||= fetch_latest_version + @latest_version ||= + T.let( + fetch_latest_version, + T.nilable(T.any(String, Gem::Version)) + ) end + sig { override.returns(T.nilable(T.any(String, Gem::Version))) } def latest_resolvable_version # Resolvability isn't an issue for submodules. latest_version end + sig { override.returns(T.nilable(T.any(String, Gem::Version))) } def latest_resolvable_version_with_no_unlock # No concept of "unlocking" for submodules latest_version end + sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) } def updated_requirements # Submodule requirements are the URL and branch to use for the # submodule. We never want to update either. @@ -32,15 +44,18 @@ def updated_requirements private + sig { override.returns(T::Boolean) } def latest_version_resolvable_with_full_unlock? # Full unlock checks aren't relevant for submodules false end + sig { override.returns(T::Array[Dependabot::Dependency]) } def updated_dependencies_after_full_unlock raise NotImplementedError end + sig { returns(T.nilable(String)) } def fetch_latest_version git_commit_checker = Dependabot::GitCommitChecker.new( dependency: dependency, From 2cf924bbff7989f3e3251815c6df5a6c0a408782 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Mon, 5 Feb 2024 10:56:43 -0800 Subject: [PATCH 02/49] Strict type `Dependabot::Devcontainers` (#8982) * Strict type `Dependabot::Devcontainers` * Migrate to `Dependabot::Credential` --------- Co-authored-by: AbdulFattaah Popoola --- devcontainers/.rubocop.yml | 3 ++ .../dependabot/devcontainers/file_fetcher.rb | 22 +++++++-- .../dependabot/devcontainers/file_parser.rb | 19 +++++-- .../file_parser/feature_dependency_parser.rb | 28 ++++++++++- .../dependabot/devcontainers/file_updater.rb | 37 +++++++++++--- .../file_updater/config_updater.rb | 49 ++++++++++++++++++- .../devcontainers/metadata_finder.rb | 7 ++- .../dependabot/devcontainers/requirement.rb | 9 ++-- .../devcontainers/update_checker.rb | 45 ++++++++++++++--- .../lib/dependabot/devcontainers/utils.rb | 9 +++- .../lib/dependabot/devcontainers/version.rb | 10 +++- 11 files changed, 205 insertions(+), 33 deletions(-) diff --git a/devcontainers/.rubocop.yml b/devcontainers/.rubocop.yml index fc2019d46a..b8168698e8 100644 --- a/devcontainers/.rubocop.yml +++ b/devcontainers/.rubocop.yml @@ -1 +1,4 @@ inherit_from: ../.rubocop.yml + +Sorbet/TrueSigil: + Enabled: true diff --git a/devcontainers/lib/dependabot/devcontainers/file_fetcher.rb b/devcontainers/lib/dependabot/devcontainers/file_fetcher.rb index 8b419744a8..2ea57409d2 100644 --- a/devcontainers/lib/dependabot/devcontainers/file_fetcher.rb +++ b/devcontainers/lib/dependabot/devcontainers/file_fetcher.rb @@ -1,6 +1,7 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" require "dependabot/file_fetchers" require "dependabot/file_fetchers/base" require "dependabot/devcontainers/utils" @@ -8,16 +9,21 @@ module Dependabot module Devcontainers class FileFetcher < Dependabot::FileFetchers::Base + extend T::Sig + + sig { override.params(filenames: T::Array[String]).returns(T::Boolean) } def self.required_files_in?(filenames) # There's several other places a devcontainer.json can be checked into # See: https://containers.dev/implementors/spec/#devcontainerjson filenames.any? { |f| f.end_with?("devcontainer.json") } end + sig { override.returns(String) } def self.required_files_message "Repo must contain a dev container configuration file." end + sig { override.returns(T::Array[Dependabot::DependencyFile]) } def fetch_files fetched_files = [] fetched_files += root_files @@ -34,16 +40,19 @@ def fetch_files private + sig { returns(T::Array[Dependabot::DependencyFile]) } def root_files fetch_config_and_lockfile_from(".") end + sig { returns(T::Array[Dependabot::DependencyFile]) } def scoped_files return [] unless devcontainer_directory fetch_config_and_lockfile_from(".devcontainer") end + sig { returns(T::Array[Dependabot::DependencyFile]) } def custom_directory_files return [] unless devcontainer_directory @@ -52,16 +61,21 @@ def custom_directory_files end end + sig { returns(T::Array[T.untyped]) } def custom_directories repo_contents(dir: ".devcontainer").select { |f| f.type == "dir" && f.name != ".devcontainer" } end + sig { returns(T.untyped) } def devcontainer_directory - return @devcontainer_directory if defined?(@devcontainer_directory) - - @devcontainer_directory = repo_contents.find { |f| f.type == "dir" && f.name == ".devcontainer" } + @devcontainer_directory ||= + T.let( + repo_contents.find { |f| f.type == "dir" && f.name == ".devcontainer" }, + T.untyped + ) end + sig { params(directory: String).returns(T::Array[Dependabot::DependencyFile]) } def fetch_config_and_lockfile_from(directory) files = [] diff --git a/devcontainers/lib/dependabot/devcontainers/file_parser.rb b/devcontainers/lib/dependabot/devcontainers/file_parser.rb index 9d2fcc7204..6bd5d64b4b 100644 --- a/devcontainers/lib/dependabot/devcontainers/file_parser.rb +++ b/devcontainers/lib/dependabot/devcontainers/file_parser.rb @@ -1,6 +1,8 @@ -# typed: true +# typed: strong # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/file_parsers" require "dependabot/file_parsers/base" require "dependabot/devcontainers/version" @@ -9,8 +11,11 @@ module Dependabot module Devcontainers class FileParser < Dependabot::FileParsers::Base + extend T::Sig + require "dependabot/file_parsers/base/dependency_set" + sig { override.returns(T::Array[Dependabot::Dependency]) } def parse dependency_set = DependencySet.new @@ -25,12 +30,14 @@ def parse private + sig { override.void } def check_required_files return if config_dependency_files.any? raise "No dev container configuration!" end + sig { params(config_dependency_file: Dependabot::DependencyFile).returns(T::Array[Dependabot::Dependency]) } def parse_features(config_dependency_file) FeatureDependencyParser.new( config_dependency_file: config_dependency_file, @@ -39,10 +46,14 @@ def parse_features(config_dependency_file) ).parse end + sig { returns(T::Array[Dependabot::DependencyFile]) } def config_dependency_files - @config_dependency_files ||= dependency_files.select do |f| - f.name.end_with?("devcontainer.json") - end + @config_dependency_files ||= T.let( + dependency_files.select do |f| + f.name.end_with?("devcontainer.json") + end, + T.nilable(T::Array[Dependabot::DependencyFile]) + ) end end end diff --git a/devcontainers/lib/dependabot/devcontainers/file_parser/feature_dependency_parser.rb b/devcontainers/lib/dependabot/devcontainers/file_parser/feature_dependency_parser.rb index 46dc04074d..a64eabbae5 100644 --- a/devcontainers/lib/dependabot/devcontainers/file_parser/feature_dependency_parser.rb +++ b/devcontainers/lib/dependabot/devcontainers/file_parser/feature_dependency_parser.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "dependabot/devcontainers/requirement" @@ -6,18 +6,30 @@ require "dependabot/shared_helpers" require "dependabot/dependency" require "json" +require "sorbet-runtime" require "uri" module Dependabot module Devcontainers class FileParser < Dependabot::FileParsers::Base class FeatureDependencyParser + extend T::Sig + + sig do + params( + config_dependency_file: Dependabot::DependencyFile, + repo_contents_path: T.nilable(String), + credentials: T::Array[Dependabot::Credential] + ) + .void + end def initialize(config_dependency_file:, repo_contents_path:, credentials:) @config_dependency_file = config_dependency_file @repo_contents_path = repo_contents_path @credentials = credentials end + sig { returns(T::Array[Dependabot::Dependency]) } def parse SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do SharedHelpers.with_git_configured(credentials: credentials) do @@ -28,19 +40,23 @@ def parse private + sig { returns(String) } def base_dir File.dirname(config_dependency_file.path) end + sig { returns(String) } def config_name File.basename(config_dependency_file.path) end + sig { returns(T.nilable(String)) } def config_contents config_dependency_file.content end # https://github.com/devcontainers/cli/blob/9444540283b236298c28f397dea879e7ec222ca1/src/spec-node/devContainersSpecCLI.ts#L1072 + sig { returns(T::Hash[String, T.untyped]) } def evaluate_with_cli raise "config_name must be a string" unless config_name.is_a?(String) && !config_name.empty? @@ -55,6 +71,7 @@ def evaluate_with_cli JSON.parse(json) end + sig { params(json: T::Hash[String, T.untyped]).returns(T::Array[Dependabot::Dependency]) } def parse_cli_json(json) dependencies = [] @@ -90,7 +107,14 @@ def parse_cli_json(json) dependencies end - attr_reader :config_dependency_file, :repo_contents_path, :credentials + sig { returns(Dependabot::DependencyFile) } + attr_reader :config_dependency_file + + sig { returns(T.nilable(String)) } + attr_reader :repo_contents_path + + sig { returns(T::Array[Dependabot::Credential]) } + attr_reader :credentials end end end diff --git a/devcontainers/lib/dependabot/devcontainers/file_updater.rb b/devcontainers/lib/dependabot/devcontainers/file_updater.rb index 30fb765cdf..e792014aec 100644 --- a/devcontainers/lib/dependabot/devcontainers/file_updater.rb +++ b/devcontainers/lib/dependabot/devcontainers/file_updater.rb @@ -1,6 +1,8 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/file_updaters" require "dependabot/file_updaters/base" require "dependabot/devcontainers/file_updater/config_updater" @@ -8,6 +10,9 @@ module Dependabot module Devcontainers class FileUpdater < Dependabot::FileUpdaters::Base + extend T::Sig + + sig { override.returns(T::Array[Regexp]) } def self.updated_files_regex [ /^\.?devcontainer\.json$/, @@ -15,6 +20,7 @@ def self.updated_files_regex ] end + sig { override.returns(T::Array[Dependabot::DependencyFile]) } def updated_dependency_files updated_files = [] @@ -24,7 +30,7 @@ def updated_dependency_files config_contents, lockfile_contents = update(manifest, requirement) - updated_files << updated_file(file: manifest, content: config_contents) if file_changed?(manifest) + updated_files << updated_file(file: manifest, content: T.must(config_contents)) if file_changed?(manifest) lockfile = lockfile_for(manifest) @@ -36,23 +42,30 @@ def updated_dependency_files private + sig { returns(Dependabot::Dependency) } def dependency # TODO: Handle one dependency at a time - dependencies.first + T.must(dependencies.first) end + sig { override.void } def check_required_files return if dependency_files.any? raise "No dev container configuration!" end + sig { returns(T::Array[Dependabot::DependencyFile]) } def manifests - @manifests ||= dependency_files.select do |f| - f.name.end_with?("devcontainer.json") - end + @manifests ||= T.let( + dependency_files.select do |f| + f.name.end_with?("devcontainer.json") + end, + T.nilable(T::Array[Dependabot::DependencyFile]) + ) end + sig { params(manifest: Dependabot::DependencyFile).returns(T.nilable(Dependabot::DependencyFile)) } def lockfile_for(manifest) lockfile_name = lockfile_name_for(manifest) @@ -61,6 +74,7 @@ def lockfile_for(manifest) end end + sig { params(manifest: Dependabot::DependencyFile).returns(String) } def lockfile_name_for(manifest) basename = File.basename(manifest.name) lockfile_name = Utils.expected_lockfile_name(basename) @@ -68,13 +82,20 @@ def lockfile_name_for(manifest) manifest.name.delete_suffix(basename).concat(lockfile_name) end + sig do + params( + manifest: Dependabot::DependencyFile, + requirement: T::Hash[Symbol, T.untyped] + ) + .returns(T::Array[String]) + end def update(manifest, requirement) ConfigUpdater.new( feature: dependency.name, requirement: requirement[:requirement], - version: dependency.version, + version: T.must(dependency.version), manifest: manifest, - repo_contents_path: repo_contents_path, + repo_contents_path: T.must(repo_contents_path), credentials: credentials ).update end diff --git a/devcontainers/lib/dependabot/devcontainers/file_updater/config_updater.rb b/devcontainers/lib/dependabot/devcontainers/file_updater/config_updater.rb index f24f5e7d8b..da2e5a10be 100644 --- a/devcontainers/lib/dependabot/devcontainers/file_updater/config_updater.rb +++ b/devcontainers/lib/dependabot/devcontainers/file_updater/config_updater.rb @@ -1,15 +1,31 @@ -# typed: true +# typed: strong # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/file_updaters/base" require "dependabot/shared_helpers" require "dependabot/logger" require "dependabot/devcontainers/utils" +require "dependabot/devcontainers/version" module Dependabot module Devcontainers class FileUpdater < Dependabot::FileUpdaters::Base class ConfigUpdater + extend T::Sig + + sig do + params( + feature: String, + requirement: T.any(String, Dependabot::Devcontainers::Version), + version: String, + manifest: Dependabot::DependencyFile, + repo_contents_path: String, + credentials: T::Array[Dependabot::Credential] + ) + .void + end def initialize(feature:, requirement:, version:, manifest:, repo_contents_path:, credentials:) @feature = feature @requirement = requirement @@ -19,6 +35,7 @@ def initialize(feature:, requirement:, version:, manifest:, repo_contents_path:, @credentials = credentials end + sig { returns(T::Array[String]) } def update SharedHelpers.in_a_temporary_repo_directory(base_dir, repo_contents_path) do SharedHelpers.with_git_configured(credentials: credentials) do @@ -34,18 +51,28 @@ def update private + sig { returns(String) } def base_dir File.dirname(manifest.path) end + sig { returns(String) } def manifest_name File.basename(manifest.path) end + sig { returns(String) } def lockfile_name Utils.expected_lockfile_name(manifest_name) end + sig do + params( + target_requirement: T.any(String, Dependabot::Devcontainers::Version), + target_version: String + ) + .void + end def update_manifests(target_requirement:, target_version:) # First force target version to upgrade lockfile. run_devcontainer_upgrade(target_version) @@ -55,10 +82,12 @@ def update_manifests(target_requirement:, target_version:) force_target_requirement(lockfile_name, from: target_version, to: target_requirement) end + sig { params(file_name: String, from: String, to: T.any(String, Dependabot::Devcontainers::Version)).void } def force_target_requirement(file_name, from:, to:) File.write(file_name, File.read(file_name).gsub("#{feature}:#{from}", "#{feature}:#{to}")) end + sig { params(target_version: String).void } def run_devcontainer_upgrade(target_version) cmd = "devcontainer upgrade " \ "--workspace-folder . " \ @@ -71,7 +100,23 @@ def run_devcontainer_upgrade(target_version) SharedHelpers.run_shell_command(cmd, stderr_to_stdout: false) end - attr_reader :feature, :requirement, :version, :manifest, :repo_contents_path, :credentials + sig { returns(String) } + attr_reader :feature + + sig { returns(T.any(String, Dependabot::Devcontainers::Version)) } + attr_reader :requirement + + sig { returns(String) } + attr_reader :version + + sig { returns(Dependabot::DependencyFile) } + attr_reader :manifest + + sig { returns(String) } + attr_reader :repo_contents_path + + sig { returns(T::Array[Dependabot::Credential]) } + attr_reader :credentials end end end diff --git a/devcontainers/lib/dependabot/devcontainers/metadata_finder.rb b/devcontainers/lib/dependabot/devcontainers/metadata_finder.rb index 2cc2b4b21d..ec7097483b 100644 --- a/devcontainers/lib/dependabot/devcontainers/metadata_finder.rb +++ b/devcontainers/lib/dependabot/devcontainers/metadata_finder.rb @@ -1,14 +1,19 @@ -# typed: true +# typed: strong # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/metadata_finders" require "dependabot/metadata_finders/base" module Dependabot module Devcontainers class MetadataFinder < Dependabot::MetadataFinders::Base + extend T::Sig + private + sig { override.returns(T.nilable(Dependabot::Source)) } def look_up_source # TODO: Make upstream changes to dev container CLI to point to docs. # Specifically, 'devcontainers features info' can be augmented to expose documentationUrl diff --git a/devcontainers/lib/dependabot/devcontainers/requirement.rb b/devcontainers/lib/dependabot/devcontainers/requirement.rb index 9983d788cd..dbceef44a5 100644 --- a/devcontainers/lib/dependabot/devcontainers/requirement.rb +++ b/devcontainers/lib/dependabot/devcontainers/requirement.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "sorbet-runtime" @@ -14,17 +14,18 @@ class Requirement < Dependabot::Requirement # For consistency with other languages, we define a requirements array. # Devcontainers don't have an `OR` separator for requirements, so it # always contains a single element. - sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Requirement]) } + sig { override.params(requirement_string: T.nilable(String)).returns(T::Array[Dependabot::Requirement]) } def self.requirements_array(requirement_string) [new(requirement_string)] end # Patches Gem::Requirement to make it accept requirement strings like # "~> 4.2.5, >= 4.2.5.1" without first needing to split them. + sig { params(requirements: T.nilable(String)).void } def initialize(*requirements) requirements = requirements.flatten.flat_map do |req_string| - req_string.split(",").map(&:strip) - end + req_string&.split(",")&.map(&:strip) + end.compact super(requirements) end diff --git a/devcontainers/lib/dependabot/devcontainers/update_checker.rb b/devcontainers/lib/dependabot/devcontainers/update_checker.rb index 08a1da621e..fa70e70ed4 100644 --- a/devcontainers/lib/dependabot/devcontainers/update_checker.rb +++ b/devcontainers/lib/dependabot/devcontainers/update_checker.rb @@ -1,6 +1,8 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/update_checkers" require "dependabot/update_checkers/base" require "dependabot/devcontainers/version" @@ -10,14 +12,19 @@ module Dependabot module Devcontainers class UpdateChecker < Dependabot::UpdateCheckers::Base + extend T::Sig + + sig { returns(T.nilable(Gem::Version)) } def latest_version - @latest_version ||= fetch_latest_version + @latest_version ||= T.let(fetch_latest_version, T.nilable(Gem::Version)) end + sig { returns(T.nilable(Gem::Version)) } def latest_resolvable_version latest_version # TODO end + sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) } def updated_requirements dependency.requirements.map do |requirement| required_version = version_class.new(requirement[:requirement]) @@ -32,41 +39,61 @@ def updated_requirements end end + sig { override.returns(T.nilable(Dependabot::Version)) } def latest_resolvable_version_with_no_unlock raise NotImplementedError end private + sig { returns(T::Array[Dependabot::Devcontainers::Version]) } def viable_candidates - @viable_candidates ||= fetch_viable_candidates + @viable_candidates ||= T.let( + fetch_viable_candidates, + T.nilable(T::Array[Dependabot::Devcontainers::Version]) + ) end + sig { returns(T::Array[Dependabot::Devcontainers::Version]) } def fetch_viable_candidates candidates = comparable_versions_from_registry candidates = filter_ignored(candidates) candidates.sort end + sig { returns(Dependabot::Devcontainers::Version) } def fetch_latest_version return current_version unless viable_candidates.any? - viable_candidates.last + T.must(viable_candidates.last) end + sig do + params( + versions: T::Array[Dependabot::Devcontainers::Version], + required_version: Dependabot::Devcontainers::Version + ) + .returns(T::Array[Dependabot::Devcontainers::Version]) + end def remove_precision_changes(versions, required_version) versions.select do |version| version.same_precision?(required_version) end end + sig do + params( + versions: T::Array[Dependabot::Devcontainers::Version] + ) + .returns(T::Array[Dependabot::Devcontainers::Version]) + end def filter_ignored(versions) filtered = versions.reject do |version| ignore_requirements.any? { |r| version.satisfies?(r) } end - if @raise_on_ignored && + if raise_on_ignored && filter_lower_versions(filtered).empty? && filter_lower_versions(versions).any? raise AllVersionsIgnored @@ -75,16 +102,19 @@ def filter_ignored(versions) filtered end + sig { returns(T::Array[Dependabot::Devcontainers::Version]) } def comparable_versions_from_registry tags_from_registry.filter_map do |tag| version_class.correct?(tag) && version_class.new(tag) end end + sig { returns(T::Array[String]) } def tags_from_registry - @tags_from_registry ||= fetch_tags_from_registry + @tags_from_registry ||= T.let(fetch_tags_from_registry, T.nilable(T::Array[String])) end + sig { returns(T::Array[String]) } def fetch_tags_from_registry cmd = "devcontainer features info tags #{dependency.name} --output-format json" @@ -95,16 +125,19 @@ def fetch_tags_from_registry JSON.parse(output).fetch("publishedTags") end + sig { params(versions: T::Array[Gem::Version]).returns(T::Array[Gem::Version]) } def filter_lower_versions(versions) versions.select do |version| version > current_version end end + sig { override.returns(T::Boolean) } def latest_version_resolvable_with_full_unlock? false # TODO end + sig { override.returns(T::Array[Dependabot::Dependency]) } def updated_dependencies_after_full_unlock raise NotImplementedError end diff --git a/devcontainers/lib/dependabot/devcontainers/utils.rb b/devcontainers/lib/dependabot/devcontainers/utils.rb index d0b5e6a213..713c21117c 100644 --- a/devcontainers/lib/dependabot/devcontainers/utils.rb +++ b/devcontainers/lib/dependabot/devcontainers/utils.rb @@ -1,17 +1,24 @@ -# typed: true +# typed: strong # frozen_string_literal: true +require "sorbet-runtime" + module Dependabot module Devcontainers module Utils + extend T::Sig + + sig { params(directory: String).returns(String) } def self.expected_config_basename(directory) root_directory?(directory) ? ".devcontainer.json" : "devcontainer.json" end + sig { params(directory: String).returns(T::Boolean) } def self.root_directory?(directory) Pathname.new(directory).cleanpath.to_path == Pathname.new(".").cleanpath.to_path end + sig { params(config_file_name: String).returns(String) } def self.expected_lockfile_name(config_file_name) if config_file_name.start_with?(".") ".devcontainer-lock.json" diff --git a/devcontainers/lib/dependabot/devcontainers/version.rb b/devcontainers/lib/dependabot/devcontainers/version.rb index 7077ef5bc4..158a864c6f 100644 --- a/devcontainers/lib/dependabot/devcontainers/version.rb +++ b/devcontainers/lib/dependabot/devcontainers/version.rb @@ -1,20 +1,27 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + require "dependabot/version" require "dependabot/utils" module Dependabot module Devcontainers class Version < Dependabot::Version + extend T::Sig + + sig { params(other: Dependabot::Devcontainers::Version).returns(T::Boolean) } def same_precision?(other) precision == other.precision end + sig { params(requirement: Dependabot::Requirement).returns(T::Boolean) } def satisfies?(requirement) requirement.satisfied_by?(self) end + sig { params(other: BasicObject).returns(T.nilable(Integer)) } def <=>(other) if self == other precision <=> other.precision @@ -25,6 +32,7 @@ def <=>(other) protected + sig { returns(Integer) } def precision segments.size end From 182ee68e45abea4567df6cce4cf12d3e39171204 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Mon, 5 Feb 2024 17:43:57 -0700 Subject: [PATCH 03/49] force set `Condition="false"` on Microsoft.WebApplication.targets (#8946) The file $(VSToolsPath)\WebApplications\Microsoft.WebApplications.targets can never be resolved in the updater as it runs in the Linux Docker container, so an always false condition is added to that import to make the minimal change necessary to allow the NuGet updater to successfully run. After completion, the condition attribute is restored. Co-authored-by: AbdulFattaah Popoola --- .../UpdateWorkerTests.PackagesConfig.cs | 169 ++++++++++++++++++ .../Updater/PackagesConfigUpdater.cs | 6 +- .../WebApplicationTargetsConditionPatcher.cs | 47 +++++ .../Updater/XmlFilePreAndPostProcessor.cs | 55 ++++++ .../Utilities/XmlExtensions.cs | 11 ++ 5 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs create mode 100644 nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs index 9c750eeb07..b50bcaf713 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs @@ -903,6 +903,175 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """); } + [Fact] + public async Task PackagesConfigUpdateIsNotThwartedBy_VSToolsPath_PropertyBeingSetInUserCode() + { + await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", + projectContents: """ + + + Debug + AnyCPU + + + 2.0 + 68ed3303-52a0-47b8-a687-3abbb07530da + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + TestProject + TestProject + v4.5 + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + C:\some\path\that\does\not\exist + + + + + + """, + packagesConfigContents: """ + + + + """, + expectedProjectContents: """ + + + Debug + AnyCPU + + + 2.0 + 68ed3303-52a0-47b8-a687-3abbb07530da + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + TestProject + TestProject + v4.5 + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + C:\some\path\that\does\not\exist + + + + + + """, + expectedPackagesConfigContents: """ + + + + + """); + } + protected static async Task TestUpdateForProject( string dependencyName, string oldVersion, diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs index 428cc3451d..acdb37c394 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs @@ -7,6 +7,7 @@ using System.Xml.Linq; using Microsoft.Language.Xml; +using NuGetUpdater.Core.Updater; namespace NuGetUpdater.Core; @@ -56,7 +57,10 @@ public static async Task UpdateDependencyAsync(string repoRootPath, string proje args.Add(msbuildDirectory); // e.g., /usr/share/dotnet/sdk/7.0.203 } - RunNuget(args, packagesDirectory, logger); + using (new WebApplicationTargetsConditionPatcher(projectPath)) + { + RunNuget(args, packagesDirectory, logger); + } projectBuildFile = ProjectBuildFile.Open(repoRootPath, projectPath); projectBuildFile.NormalizeDirectorySeparatorsInProject(); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs new file mode 100644 index 0000000000..a04746567e --- /dev/null +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs @@ -0,0 +1,47 @@ +using System; +using System.IO; +using System.Linq; + +using Microsoft.Language.Xml; + +namespace NuGetUpdater.Core.Updater +{ + internal class WebApplicationTargetsConditionPatcher : IDisposable + { + private string? _capturedCondition; + private readonly XmlFilePreAndPostProcessor _processor; + + public WebApplicationTargetsConditionPatcher(string projectFilePath) + { + _processor = new XmlFilePreAndPostProcessor( + getContent: () => File.ReadAllText(projectFilePath), + setContent: s => File.WriteAllText(projectFilePath, s), + nodeFinder: doc => doc.Descendants() + .FirstOrDefault(e => e.Name == "Import" && e.GetAttributeValue("Project") == @"$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets") + as XmlNodeSyntax, + preProcessor: n => + { + var element = (IXmlElementSyntax)n; + _capturedCondition = element.GetAttributeValue("Condition"); + return (XmlNodeSyntax)element.RemoveAttributeByName("Condition").WithAttribute("Condition", "false"); + }, + postProcessor: n => + { + var element = (IXmlElementSyntax)n; + var newElement = element.RemoveAttributeByName("Condition"); + if (_capturedCondition is not null) + { + newElement = newElement.WithAttribute("Condition", _capturedCondition); + } + + return (XmlNodeSyntax)newElement; + } + ); + } + + public void Dispose() + { + _processor.Dispose(); + } + } +} diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs new file mode 100644 index 0000000000..a728a919a5 --- /dev/null +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs @@ -0,0 +1,55 @@ +using System; + +using Microsoft.Language.Xml; + +namespace NuGetUpdater.Core.Updater +{ + internal class XmlFilePreAndPostProcessor : IDisposable + { + public Func GetContent { get; } + public Action SetContent { get; } + public Func NodeFinder { get; } + public Func PreProcessor { get; } + public Func PostProcessor { get; } + + public XmlFilePreAndPostProcessor(Func getContent, Action setContent, Func nodeFinder, Func preProcessor, Func postProcessor) + { + GetContent = getContent; + SetContent = setContent; + NodeFinder = nodeFinder; + PreProcessor = preProcessor; + PostProcessor = postProcessor; + PreProcess(); + } + + public void Dispose() + { + PostProcess(); + } + + private void PreProcess() => RunProcessor(PreProcessor); + + private void PostProcess() => RunProcessor(PostProcessor); + + private void RunProcessor(Func processor) + { + var content = GetContent(); + var xml = Parser.ParseText(content); + if (xml is null) + { + return; + } + + var node = NodeFinder(xml); + if (node is null) + { + return; + } + + var replacementElement = processor(node); + var replacementXml = xml.ReplaceNode(node, replacementElement); + var replacementString = replacementXml.ToFullString(); + SetContent(replacementString); + } + } +} diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs index e31bb65b94..1a38affa7d 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/XmlExtensions.cs @@ -30,6 +30,17 @@ public static IEnumerable GetElements(this IXmlElementSyntax return element.Attributes.FirstOrDefault(a => a.Name.Equals(name, comparisonType)); } + public static IXmlElementSyntax RemoveAttributeByName(this IXmlElementSyntax element, string attributeName, StringComparison comparisonType = StringComparison.Ordinal) + { + var attribute = element.GetAttribute(attributeName, comparisonType); + if (attribute is null) + { + return element; + } + + return element.RemoveAttribute(attribute); + } + public static string GetAttributeValue(this IXmlElementSyntax element, string name, StringComparison comparisonType) { return element.Attributes.First(a => a.Name.Equals(name, comparisonType)).Value; From 228d6f80df8b3ac3508a8f0a5f92cbc9b0ac6871 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Mon, 5 Feb 2024 18:05:48 -0700 Subject: [PATCH 04/49] escape nuget feed urls before querying (#8990) Co-authored-by: AbdulFattaah Popoola --- .../nuget/update_checker/repository_finder.rb | 3 ++ .../update_checker/repository_finder_spec.rb | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb b/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb index a43d0fdfd4..6f571c69ad 100644 --- a/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb +++ b/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb @@ -33,6 +33,9 @@ def known_repositories @known_repositories << { url: DEFAULT_REPOSITORY_URL, token: nil } if @known_repositories.empty? + @known_repositories = @known_repositories.map do |repo| + { url: URI::DEFAULT_PARSER.escape(repo[:url]), token: repo[:token] } + end @known_repositories.uniq end diff --git a/nuget/spec/dependabot/nuget/update_checker/repository_finder_spec.rb b/nuget/spec/dependabot/nuget/update_checker/repository_finder_spec.rb index 42d6d93301..99a1d1a10c 100644 --- a/nuget/spec/dependabot/nuget/update_checker/repository_finder_spec.rb +++ b/nuget/spec/dependabot/nuget/update_checker/repository_finder_spec.rb @@ -181,6 +181,36 @@ end end + context "that has URLs that need to be escaped" do + let(:custom_repo_url) { "https://www.myget.org/F/exceptionless/api with spaces/v3/index.json" } + before do + stub_request(:get, "https://www.myget.org/F/exceptionless/api%20with%20spaces/v3/index.json") + .to_return( + status: 200, + body: fixture("nuget_responses", "myget_base.json") + ) + end + + it "gets the right URL" do + expect(dependency_urls).to eq( + [{ + base_url: "https://www.myget.org/F/exceptionless/api/v3/flatcontainer/", + registration_url: "https://www.myget.org/F/exceptionless/api/v3/registration1/" \ + "microsoft.extensions.dependencymodel/index.json", + repository_url: "https://www.myget.org/F/exceptionless/api%20with%20spaces/v3/index.json", + versions_url: "https://www.myget.org/F/exceptionless/api/v3/" \ + "flatcontainer/microsoft.extensions." \ + "dependencymodel/index.json", + search_url: "https://www.myget.org/F/exceptionless/api/v3/" \ + "query?q=microsoft.extensions.dependencymodel" \ + "&prerelease=true&semVerLevel=2.0.0", + auth_header: { "Authorization" => "Basic bXk6cGFzc3cwcmQ=" }, + repository_type: "v3" + }] + ) + end + end + context "that 404s" do before { stub_request(:get, custom_repo_url).to_return(status: 404) } From a6583b0b90f2033545aed7fdb55f6b83e63aef19 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Tue, 6 Feb 2024 09:27:34 -0600 Subject: [PATCH 05/49] fix TypeError: no implicit conversion of Credential into Hash (#8995) --- common/lib/dependabot/credential.rb | 12 ++++- hex/lib/dependabot/hex/credential_helpers.rb | 4 +- .../dependabot/hex/credential_helpers_spec.rb | 37 ++++++++++++++ .../hex/file_updater/lockfile_updater_spec.rb | 4 +- .../dependabot/hex/update_checker_spec.rb | 48 +++++++++---------- 5 files changed, 76 insertions(+), 29 deletions(-) create mode 100644 hex/spec/dependabot/hex/credential_helpers_spec.rb diff --git a/common/lib/dependabot/credential.rb b/common/lib/dependabot/credential.rb index 2fabfc7038..8713cd006f 100644 --- a/common/lib/dependabot/credential.rb +++ b/common/lib/dependabot/credential.rb @@ -8,7 +8,7 @@ class Credential extend T::Sig extend Forwardable - def_delegators :@credential, :fetch, :keys, :[]=, :delete + def_delegators :@credential, :fetch, :keys, :[]=, :delete, :slice, :values, :entries sig { params(credential: T::Hash[String, T.any(T::Boolean, String)]).void } def initialize(credential) @@ -26,5 +26,15 @@ def replaces_base? def [](key) @credential[key] end + + sig { params(other: Credential).returns(Credential) } + def merge(other) + Credential.new(@credential.merge(other.to_h)) + end + + sig { returns(T::Hash[String, String]) } + def to_h + @credential + end end end diff --git a/hex/lib/dependabot/hex/credential_helpers.rb b/hex/lib/dependabot/hex/credential_helpers.rb index 07701fc9ae..d246e0036c 100644 --- a/hex/lib/dependabot/hex/credential_helpers.rb +++ b/hex/lib/dependabot/hex/credential_helpers.rb @@ -9,7 +9,7 @@ def self.hex_credentials(credentials) end def self.organization_credentials(credentials) - defaults = { "organization" => "", "token" => "" } + defaults = Dependabot::Credential.new({ "organization" => "", "token" => "" }) keys = %w(type organization token) credentials @@ -20,7 +20,7 @@ def self.organization_credentials(credentials) def self.repo_credentials(credentials) # Credentials are serialized as an array that may not have optional fields. Using a # default ensures that the array is always the same length, even if values are empty. - defaults = { "url" => "", "auth_key" => "", "public_key_fingerprint" => "" } + defaults = Dependabot::Credential.new({ "url" => "", "auth_key" => "", "public_key_fingerprint" => "" }) keys = %w(type repo url auth_key public_key_fingerprint) credentials diff --git a/hex/spec/dependabot/hex/credential_helpers_spec.rb b/hex/spec/dependabot/hex/credential_helpers_spec.rb new file mode 100644 index 0000000000..eaf3c986ff --- /dev/null +++ b/hex/spec/dependabot/hex/credential_helpers_spec.rb @@ -0,0 +1,37 @@ +# typed: false +# frozen_string_literal: true + +require "spec_helper" +require "dependabot/hex/credential_helpers" + +RSpec.describe Dependabot::Hex::CredentialHelpers do + describe ".organization_credentials" do + subject(:organization_credentials) { described_class.organization_credentials(credentials) } + + let(:credentials) do + [ + Dependabot::Credential.new({ "type" => "hex_organization", "organization" => "organization", + "token" => "token" }) + ] + end + + it "populates the credentials with default properties" do + expect(organization_credentials).to eq(%w(hex_organization organization token)) + end + end + + describe ".repo_credentials" do + subject(:repo_credentials) { described_class.repo_credentials(credentials) } + + let(:credentials) do + [ + Dependabot::Credential.new({ "type" => "hex_repository", "url" => "url", "auth_key" => "auth_key", + "public_key_fingerprint" => "public_key_fingerprint" }) + ] + end + + it "populates the credentials with default properties" do + expect(repo_credentials).to eq(%w(hex_repository url auth_key public_key_fingerprint)) + end + end +end diff --git a/hex/spec/dependabot/hex/file_updater/lockfile_updater_spec.rb b/hex/spec/dependabot/hex/file_updater/lockfile_updater_spec.rb index 5d8dde79d8..e0d9f5c9df 100644 --- a/hex/spec/dependabot/hex/file_updater/lockfile_updater_spec.rb +++ b/hex/spec/dependabot/hex/file_updater/lockfile_updater_spec.rb @@ -351,12 +351,12 @@ let(:lockfile_fixture_name) { "private_repo" } let(:credentials) do - { + Dependabot::Credential.new({ "type" => "hex_repository", "repo" => "dependabot", "auth_key" => "d6fc2b6n6h7katic6vuq6k5e2csahcm4", "url" => "https://dependabot-private.fly.dev" - } + }) end let(:dependency) do diff --git a/hex/spec/dependabot/hex/update_checker_spec.rb b/hex/spec/dependabot/hex/update_checker_spec.rb index ab75ff24ca..b687ad15cd 100644 --- a/hex/spec/dependabot/hex/update_checker_spec.rb +++ b/hex/spec/dependabot/hex/update_checker_spec.rb @@ -22,12 +22,12 @@ end let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }] + })] end let(:ignored_versions) { [] } let(:raise_on_ignored) { false } @@ -278,16 +278,16 @@ context "with good credentials" do let(:hex_pm_org_token) { ENV.fetch("HEX_PM_ORGANIZATION_TOKEN", nil) } let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }, { + }), Dependabot::Credential.new({ "type" => "hex_organization", "organization" => "dependabot", "token" => hex_pm_org_token - }] + })] end it "returns the expected version" do @@ -298,16 +298,16 @@ context "with bad credentials" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }, { + }), Dependabot::Credential.new({ "type" => "hex_organization", "organization" => "dependabot", "token" => "111f6cbeffc6e14c6a884f0111caff3e" - }] + })] end it "raises a helpful error" do @@ -321,15 +321,15 @@ context "with no token" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }, { + }), Dependabot::Credential.new({ "type" => "hex_organization", "organization" => "dependabot" - }] + })] end # This needs to changes to the Elixir helper @@ -344,12 +344,12 @@ context "with no credentials" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }] + })] end # The Elixir process hangs waiting for input in this case. This spec @@ -378,12 +378,12 @@ context "with good credentials" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "hex_repository", "repo" => "dependabot", "auth_key" => "d6fc2b6n6h7katic6vuq6k5e2csahcm4", "url" => "https://dependabot-private.fly.dev" - }] + })] end it { is_expected.to eq(Dependabot::Hex::Version.new("1.1.0")) } @@ -391,12 +391,12 @@ context "with bad credentials" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "hex_repository", "repo" => "dependabot", "auth_key" => "111f6cbeffc6e14c6a884f0111caff3e", "url" => "https://dependabot-private.fly.dev" - }] + })] end it "raises a helpful error" do @@ -411,13 +411,13 @@ context "with correct public key fingerprint verification" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "hex_repository", "repo" => "dependabot", "auth_key" => "d6fc2b6n6h7katic6vuq6k5e2csahcm4", "url" => "https://dependabot-private.fly.dev", "public_key_fingerprint" => "SHA256:jn36tNgSXuEljoob8fkejX9LIyXqCcwShjRGps7RVgw" - }] + })] end it { is_expected.to eq(Dependabot::Hex::Version.new("1.1.0")) } @@ -425,13 +425,13 @@ context "with incorrect public key fingerprint verification" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "hex_repository", "repo" => "dependabot", "auth_key" => "d6fc2b6n6h7katic6vuq6k5e2csahcm4", "url" => "https://dependabot-private.fly.dev", "public_key_fingerprint" => "SHA256:kejX9LIyXqCcwShjRGps7RVgjn36tNgSXuEljoob8fw" - }] + })] end it "raises a helpful error" do @@ -446,16 +446,16 @@ context "with dependencies on both a private organization and private repo" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "hex_organization", "organization" => "dependabot", "token" => "855f6cbeffc6e14c6a884f0111caff3e" - }, { + }), Dependabot::Credential.new({ "type" => "hex_repository", "repo" => "dependabot", "auth_key" => "d6fc2b6n6h7katic6vuq6k5e2csahcm4", "url" => "https://dependabot-private.fly.dev" - }] + })] end it { is_expected.to eq(Dependabot::Hex::Version.new("1.1.0")) } From 83997360f8f22b2f4827396b5409cec905168db9 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Tue, 6 Feb 2024 10:04:32 -0600 Subject: [PATCH 06/49] add types to DependencySnapshot (#8986) type DependencyGroupEngine (#8987) --- updater/lib/dependabot/api_client.rb | 2 +- .../lib/dependabot/dependency_group_engine.rb | 20 ++- updater/lib/dependabot/dependency_snapshot.rb | 117 ++++++++++++------ .../dependabot/dependency_snapshot_spec.rb | 3 + 4 files changed, 96 insertions(+), 46 deletions(-) diff --git a/updater/lib/dependabot/api_client.rb b/updater/lib/dependabot/api_client.rb index 959d7f75a9..a85a873d84 100644 --- a/updater/lib/dependabot/api_client.rb +++ b/updater/lib/dependabot/api_client.rb @@ -170,7 +170,7 @@ def mark_job_as_processed(base_commit_sha) span&.finish end - sig { params(dependencies: T::Array[T::Hash[Symbol, T.untyped]], dependency_files: T::Array[DependencyFile]).void } + sig { params(dependencies: T::Array[T::Hash[Symbol, T.untyped]], dependency_files: T::Array[String]).void } def update_dependency_list(dependencies, dependency_files) span = ::Dependabot::OpenTelemetry.tracer&.start_span("update_dependency_list", kind: :internal) span&.set_attribute(::Dependabot::OpenTelemetry::Attributes::JOB_ID, job_id) diff --git a/updater/lib/dependabot/dependency_group_engine.rb b/updater/lib/dependabot/dependency_group_engine.rb index 6dcbc8038a..5993bac7d8 100644 --- a/updater/lib/dependabot/dependency_group_engine.rb +++ b/updater/lib/dependabot/dependency_group_engine.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "dependabot/dependency_group" @@ -18,8 +18,11 @@ # module Dependabot class DependencyGroupEngine + extend T::Sig + class ConfigurationError < StandardError; end + sig { params(job: Dependabot::Job).returns(Dependabot::DependencyGroupEngine) } def self.from_job_config(job:) if job.security_updates_only? && job.source.directories && job.dependency_groups.empty? # The indication that this should be a grouped update is: @@ -44,12 +47,18 @@ def self.from_job_config(job:) new(dependency_groups: groups) end - attr_reader :dependency_groups, :groups_calculated, :ungrouped_dependencies + sig { returns(T::Array[Dependabot::DependencyGroup]) } + attr_reader :dependency_groups + + sig { returns(T::Array[Dependabot::Dependency]) } + attr_reader :ungrouped_dependencies + sig { params(name: String).returns(T.nilable(Dependabot::DependencyGroup)) } def find_group(name:) dependency_groups.find { |group| group.name == name } end + sig { params(dependencies: T::Array[Dependabot::Dependency]).void } def assign_to_groups!(dependencies:) raise ConfigurationError, "dependency groups have already been configured!" if @groups_calculated @@ -75,17 +84,20 @@ def assign_to_groups!(dependencies:) private + sig { params(dependency_groups: T::Array[Dependabot::DependencyGroup]).void } def initialize(dependency_groups:) @dependency_groups = dependency_groups - @ungrouped_dependencies = [] - @groups_calculated = false + @ungrouped_dependencies = T.let([], T::Array[Dependabot::Dependency]) + @groups_calculated = T.let(false, T::Boolean) end + sig { void } def validate_groups empty_groups = dependency_groups.select { |group| group.dependencies.empty? } warn_misconfigured_groups(empty_groups) if empty_groups.any? end + sig { params(groups: T::Array[Dependabot::DependencyGroup]).void } def warn_misconfigured_groups(groups) Dependabot.logger.warn <<~WARN Please check your configuration as there are groups where no dependencies match: diff --git a/updater/lib/dependabot/dependency_snapshot.rb b/updater/lib/dependabot/dependency_snapshot.rb index fa049b935c..914551bf58 100644 --- a/updater/lib/dependabot/dependency_snapshot.rb +++ b/updater/lib/dependabot/dependency_snapshot.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "base64" @@ -14,6 +14,9 @@ module Dependabot class DependencySnapshot extend T::Sig + sig do + params(job: Dependabot::Job, job_definition: T::Hash[String, T.untyped]).returns(Dependabot::DependencySnapshot) + end def self.create_from_job_definition(job:, job_definition:) decoded_dependency_files = job_definition.fetch("base64_dependency_files").map do |a| file = Dependabot::DependencyFile.new(**a.transform_keys(&:to_sym)) @@ -30,55 +33,38 @@ def self.create_from_job_definition(job:, job_definition:) ) end - attr_reader :base_commit_sha, :dependency_files, :dependencies, :handled_dependencies + sig { returns(String) } + attr_reader :base_commit_sha - def add_handled_dependencies(dependency_names) - @handled_dependencies += Array(dependency_names) - end + sig { returns(T::Array[Dependabot::DependencyFile]) } + attr_reader :dependency_files - # Returns the subset of all project dependencies which are permitted - # by the project configuration. - def allowed_dependencies - @allowed_dependencies ||= if job.security_updates_only? - dependencies.select { |d| T.must(job.dependencies).include?(d.name) } - else - dependencies.select { |d| job.allowed_update?(d) } - end - end + sig { returns(T::Array[Dependabot::Dependency]) } + attr_reader :dependencies - # Returns the subset of all project dependencies which are specifically - # requested to be updated by the job definition. - def job_dependencies - return [] unless job.dependencies&.any? - return @job_dependencies if defined? @job_dependencies + sig { returns(T::Set[String]) } + attr_reader :handled_dependencies - # Gradle, Maven and Nuget dependency names can be case-insensitive and - # the dependency name in the security advisory often doesn't match what - # users have specified in their manifest. - # - # It's technically possibly to publish case-sensitive npm packages to a - # private registry but shouldn't cause problems here as job.dependencies - # is set either from an existing PR rebase/recreate or a security - # advisory. - job_dependency_names = T.must(job.dependencies).map(&:downcase) - @job_dependencies = dependencies.select do |dep| - job_dependency_names.include?(dep.name.downcase) - end - end + sig { returns(T::Array[Dependabot::Dependency]) } + attr_reader :allowed_dependencies - # Returns just the group that is specifically requested to be updated by - # the job definition - def job_group - return nil unless job.dependency_group_to_refresh - return @job_group if defined?(@job_group) + sig { returns(T::Array[Dependabot::Dependency]) } + attr_reader :job_dependencies + + sig { returns(T.nilable(Dependabot::DependencyGroup)) } + attr_reader :job_group - @job_group = @dependency_group_engine.find_group(name: job.dependency_group_to_refresh) + sig { params(dependency_names: T.any(String, T::Array[String])).void } + def add_handled_dependencies(dependency_names) + @handled_dependencies += Array(dependency_names) end + sig { returns(T::Array[Dependabot::DependencyGroup]) } def groups @dependency_group_engine.dependency_groups end + sig { returns(T::Array[Dependabot::Dependency]) } def ungrouped_dependencies # If no groups are defined, all dependencies are ungrouped by default. return allowed_dependencies unless groups.any? @@ -89,25 +75,34 @@ def ungrouped_dependencies private + sig do + params(job: Dependabot::Job, base_commit_sha: String, dependency_files: T::Array[Dependabot::DependencyFile]).void + end def initialize(job:, base_commit_sha:, dependency_files:) @job = job @base_commit_sha = base_commit_sha @dependency_files = dependency_files - @handled_dependencies = Set.new + @handled_dependencies = T.let(Set.new, T::Set[String]) - @dependencies = parse_files! + @dependencies = T.let(parse_files!, T::Array[Dependabot::Dependency]) + @allowed_dependencies = T.let(calculate_allowed_dependencies, T::Array[Dependabot::Dependency]) + @job_dependencies = T.let(calculate_job_dependencies, T::Array[Dependabot::Dependency]) - @dependency_group_engine = DependencyGroupEngine.from_job_config(job: job) + @dependency_group_engine = T.let(DependencyGroupEngine.from_job_config(job: job), + Dependabot::DependencyGroupEngine) @dependency_group_engine.assign_to_groups!(dependencies: allowed_dependencies) + @job_group = T.let(calculate_job_group, T.nilable(Dependabot::DependencyGroup)) end sig { returns(Dependabot::Job) } attr_reader :job + sig { returns(T::Array[Dependabot::Dependency]) } def parse_files! dependency_file_parser.parse end + sig { returns(Dependabot::FileParsers::Base) } def dependency_file_parser Dependabot::FileParsers.for_package_manager(job.package_manager).new( dependency_files: dependency_files, @@ -118,5 +113,45 @@ def dependency_file_parser options: job.experiments ) end + + # Returns the subset of all project dependencies which are permitted + # by the project configuration. + sig { returns(T::Array[Dependabot::Dependency]) } + def calculate_allowed_dependencies + if job.security_updates_only? + dependencies.select { |d| T.must(job.dependencies).include?(d.name) } + else + dependencies.select { |d| job.allowed_update?(d) } + end + end + + # Returns the subset of all project dependencies which are specifically + # requested to be updated by the job definition. + sig { returns(T::Array[Dependabot::Dependency]) } + def calculate_job_dependencies + return [] unless job.dependencies&.any? + + # Gradle, Maven and Nuget dependency names can be case-insensitive and + # the dependency name in the security advisory often doesn't match what + # users have specified in their manifest. + # + # It's technically possibly to publish case-sensitive npm packages to a + # private registry but shouldn't cause problems here as job.dependencies + # is set either from an existing PR rebase/recreate or a security + # advisory. + job_dependency_names = T.must(job.dependencies).map(&:downcase) + dependencies.select do |dep| + job_dependency_names.include?(dep.name.downcase) + end + end + + # Returns just the group that is specifically requested to be updated by + # the job definition + sig { returns(T.nilable(Dependabot::DependencyGroup)) } + def calculate_job_group + return nil unless job.dependency_group_to_refresh + + @dependency_group_engine.find_group(name: T.must(job.dependency_group_to_refresh)) + end end end diff --git a/updater/spec/dependabot/dependency_snapshot_spec.rb b/updater/spec/dependabot/dependency_snapshot_spec.rb index e5f0a8138b..adaa91613d 100644 --- a/updater/spec/dependabot/dependency_snapshot_spec.rb +++ b/updater/spec/dependabot/dependency_snapshot_spec.rb @@ -36,6 +36,8 @@ source: source, dependency_groups: dependency_groups, allowed_update?: true, + dependency_group_to_refresh: nil, + dependencies: nil, experiments: { large_hadron_collider: true }) end @@ -150,6 +152,7 @@ dependency_groups: dependency_groups, dependencies: ["dummy-pkg-a"], allowed_update?: false, + dependency_group_to_refresh: nil, experiments: { large_hadron_collider: true }) end From b186765cc9a9b1bfb87ec2dc9f652b2a70889f44 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 6 Feb 2024 10:15:25 -0800 Subject: [PATCH 07/49] Allow `submodule_path` to be nilable (#8996) --- git_submodules/lib/dependabot/git_submodules/file_fetcher.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb b/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb index db6b852961..b001a116f4 100644 --- a/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb +++ b/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb @@ -66,7 +66,7 @@ def submodule_paths ) end - sig { params(submodule_path: String).returns(Dependabot::DependencyFile) } + sig { params(submodule_path: T.nilable(String)).returns(Dependabot::DependencyFile) } def fetch_submodule_ref_from_host(submodule_path) path = Pathname.new(File.join(directory, submodule_path)) .cleanpath.to_path.gsub(%r{^/*}, "") From e44fc6effc3bc23592393d908281372de945f870 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Tue, 6 Feb 2024 10:44:04 -0800 Subject: [PATCH 08/49] Expand Sorbet usage (#8958) * Sorbet types for nuget_client * Sorbet types for native_helpers * Sorbet types for Nuget version * PR cleanup * Fix Sorbet types * Sorbet errors * Fix return type * Fix return type * Promote to strong * Fix fallthrough * PR feedback * Fix sorbet errors * Sorbet T.must * Use less T.untyped * Promote to strong --------- Co-authored-by: AbdulFattaah Popoola --- nuget/lib/dependabot/nuget/file_parser.rb | 24 +++++- nuget/lib/dependabot/nuget/file_updater.rb | 6 +- nuget/lib/dependabot/nuget/native_helpers.rb | 16 +++- nuget/lib/dependabot/nuget/nuget_client.rb | 91 ++++++++++++++------ nuget/lib/dependabot/nuget/version.rb | 25 ++++-- 5 files changed, 124 insertions(+), 38 deletions(-) diff --git a/nuget/lib/dependabot/nuget/file_parser.rb b/nuget/lib/dependabot/nuget/file_parser.rb index 005825f113..98d65d06a9 100644 --- a/nuget/lib/dependabot/nuget/file_parser.rb +++ b/nuget/lib/dependabot/nuget/file_parser.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "nokogiri" @@ -6,12 +6,15 @@ require "dependabot/dependency" require "dependabot/file_parsers" require "dependabot/file_parsers/base" +require "sorbet-runtime" # For details on how dotnet handles version constraints, see: # https://docs.microsoft.com/en-us/nuget/reference/package-versioning module Dependabot module Nuget class FileParser < Dependabot::FileParsers::Base + extend T::Sig + require "dependabot/file_parsers/base/dependency_set" require_relative "file_parser/project_file_parser" require_relative "file_parser/packages_config_parser" @@ -20,6 +23,7 @@ class FileParser < Dependabot::FileParsers::Base PACKAGE_CONF_DEPENDENCY_SELECTOR = "packages > packages" + sig { override.returns(T::Array[Dependabot::Dependency]) } def parse dependency_set = DependencySet.new dependency_set += project_file_dependencies @@ -31,6 +35,7 @@ def parse private + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def project_file_dependencies dependency_set = DependencySet.new @@ -42,6 +47,7 @@ def project_file_dependencies dependency_set end + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def packages_config_dependencies dependency_set = DependencySet.new @@ -53,26 +59,32 @@ def packages_config_dependencies dependency_set end + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def global_json_dependencies return DependencySet.new unless global_json GlobalJsonParser.new(global_json: global_json).dependency_set end + sig { returns(Dependabot::FileParsers::Base::DependencySet) } def dotnet_tools_json_dependencies return DependencySet.new unless dotnet_tools_json DotNetToolsJsonParser.new(dotnet_tools_json: dotnet_tools_json).dependency_set end + sig { returns(Dependabot::Nuget::FileParser::ProjectFileParser) } def project_file_parser - @project_file_parser ||= + @project_file_parser ||= T.let( ProjectFileParser.new( dependency_files: dependency_files, credentials: credentials - ) + ), + T.nilable(Dependabot::Nuget::FileParser::ProjectFileParser) + ) end + sig { returns(T::Array[Dependabot::DependencyFile]) } def project_files projfile = /\.([a-z]{2})?proj$/ packageprops = /[Dd]irectory.[Pp]ackages.props/ @@ -83,12 +95,14 @@ def project_files end end + sig { returns(T::Array[Dependabot::DependencyFile]) } def packages_config_files dependency_files.select do |f| f.name.split("/").last&.casecmp("packages.config")&.zero? end end + sig { returns(T::Array[Dependabot::DependencyFile]) } def project_import_files dependency_files - project_files - @@ -98,18 +112,22 @@ def project_import_files [dotnet_tools_json] end + sig { returns(T::Array[Dependabot::DependencyFile]) } def nuget_configs dependency_files.select { |f| f.name.match?(/nuget\.config$/i) } end + sig { returns(T.nilable(Dependabot::DependencyFile)) } def global_json dependency_files.find { |f| f.name.casecmp("global.json")&.zero? } end + sig { returns(T.nilable(Dependabot::DependencyFile)) } def dotnet_tools_json dependency_files.find { |f| f.name.casecmp(".config/dotnet-tools.json")&.zero? } end + sig { override.void } def check_required_files return if project_files.any? || packages_config_files.any? diff --git a/nuget/lib/dependabot/nuget/file_updater.rb b/nuget/lib/dependabot/nuget/file_updater.rb index 14c42c7168..6c320719df 100644 --- a/nuget/lib/dependabot/nuget/file_updater.rb +++ b/nuget/lib/dependabot/nuget/file_updater.rb @@ -66,6 +66,8 @@ def try_update_projects(dependency) next unless project_dependencies.any? { |dep| dep.name.casecmp(dependency.name).zero? } + next unless repo_contents_path + checked_key = "#{project_file.name}-#{dependency.name}#{dependency.version}" call_nuget_updater_tool(dependency, proj_path) unless checked_files.include?(checked_key) @@ -89,6 +91,8 @@ def try_update_json(dependency) project_file = project_files.first proj_path = dependency_file_path(project_file) + return false unless repo_contents_path + call_nuget_updater_tool(dependency, proj_path) return true end @@ -98,7 +102,7 @@ def try_update_json(dependency) sig { params(dependency: Dependency, proj_path: String).void } def call_nuget_updater_tool(dependency, proj_path) - NativeHelpers.run_nuget_updater_tool(repo_root: repo_contents_path, proj_path: proj_path, + NativeHelpers.run_nuget_updater_tool(repo_root: T.must(repo_contents_path), proj_path: proj_path, dependency: dependency, is_transitive: !dependency.top_level?, credentials: credentials) diff --git a/nuget/lib/dependabot/nuget/native_helpers.rb b/nuget/lib/dependabot/nuget/native_helpers.rb index b1770dade2..1e98cc329f 100644 --- a/nuget/lib/dependabot/nuget/native_helpers.rb +++ b/nuget/lib/dependabot/nuget/native_helpers.rb @@ -1,13 +1,17 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "shellwords" +require "sorbet-runtime" require_relative "nuget_config_credential_helpers" module Dependabot module Nuget module NativeHelpers + extend T::Sig + + sig { returns(String) } def self.native_helpers_root helpers_root = ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", nil) return File.join(helpers_root, "nuget") unless helpers_root.nil? @@ -15,6 +19,7 @@ def self.native_helpers_root File.expand_path("../../../helpers", __dir__) end + sig { params(project_tfms: T::Array[String], package_tfms: T::Array[String]).returns(T::Boolean) } def self.run_nuget_framework_check(project_tfms, package_tfms) exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli") command_parts = [ @@ -51,6 +56,15 @@ def self.run_nuget_framework_check(project_tfms, package_tfms) end # rubocop:disable Metrics/MethodLength + sig do + params( + repo_root: String, + proj_path: String, + dependency: Dependency, + is_transitive: T::Boolean, + credentials: T::Array[T.untyped] + ).void + end def self.run_nuget_updater_tool(repo_root:, proj_path:, dependency:, is_transitive:, credentials:) exe_path = File.join(native_helpers_root, "NuGetUpdater", "NuGetUpdater.Cli") command_parts = [ diff --git a/nuget/lib/dependabot/nuget/nuget_client.rb b/nuget/lib/dependabot/nuget/nuget_client.rb index d337b843b7..e0e6f4238d 100644 --- a/nuget/lib/dependabot/nuget/nuget_client.rb +++ b/nuget/lib/dependabot/nuget/nuget_client.rb @@ -1,12 +1,19 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "dependabot/nuget/cache_manager" require "dependabot/nuget/update_checker/repository_finder" +require "sorbet-runtime" module Dependabot module Nuget class NugetClient + extend T::Sig + + sig do + params(dependency_name: String, repository_details: T::Hash[Symbol, String]) + .returns(T.nilable(T::Set[String])) + end def self.get_package_versions(dependency_name, repository_details) repository_type = repository_details.fetch(:repository_type) if repository_type == "v3" @@ -18,6 +25,10 @@ def self.get_package_versions(dependency_name, repository_details) end end + sig do + params(dependency_name: String, repository_details: T::Hash[Symbol, String]) + .returns(T.nilable(T::Set[String])) + end private_class_method def self.get_package_versions_v3(dependency_name, repository_details) # Use the registration URL if possible because it is fast and correct if repository_details[:registration_url] @@ -28,9 +39,15 @@ def self.get_package_versions(dependency_name, repository_details) # Otherwise, use the versions URL (fast but wrong because it includes unlisted versions) elsif repository_details[:versions_url] get_versions_from_versions_url_v3(repository_details) + else + raise "No version sources were available for #{dependency_name} in #{repository_details}" end end + sig do + params(dependency_name: String, repository_details: T::Hash[Symbol, String]) + .returns(T.nilable(T::Set[String])) + end private_class_method def self.get_package_versions_v2(dependency_name, repository_details) doc = execute_xml_nuget_request(repository_details.fetch(:versions_url), repository_details) return unless doc @@ -49,38 +66,33 @@ def self.get_package_versions(dependency_name, repository_details) matching_versions end + sig { params(repository_details: T::Hash[Symbol, String]).returns(T.nilable(T::Set[String])) } private_class_method def self.get_versions_from_versions_url_v3(repository_details) - body = execute_json_nuget_request(repository_details[:versions_url], repository_details) - body&.fetch("versions") + body = execute_json_nuget_request(repository_details.fetch(:versions_url), repository_details) + ver_array = T.let(body&.fetch("versions"), T.nilable(T::Array[String])) + ver_array&.to_set end + sig { params(repository_details: T::Hash[Symbol, String]).returns(T.nilable(T::Set[String])) } private_class_method def self.get_versions_from_registration_v3(repository_details) - url = repository_details[:registration_url] + url = repository_details.fetch(:registration_url) body = execute_json_nuget_request(url, repository_details) return unless body pages = body.fetch("items") - versions = Set.new + versions = T.let(Set.new, T::Set[String]) pages.each do |page| items = page["items"] if items # inlined entries - items.each do |item| - catalog_entry = item["catalogEntry"] - - # a package is considered listed if the `listed` property is either `true` or missing - listed_property = catalog_entry["listed"] - is_listed = listed_property.nil? || listed_property == true - if is_listed - vers = catalog_entry["version"] - versions << vers - end - end + get_versions_from_inline_page(items, versions) else # paged entries page_url = page["@id"] page_body = execute_json_nuget_request(page_url, repository_details) + next unless page_body + items = page_body.fetch("items") items.each do |item| catalog_entry = item.fetch("catalogEntry") @@ -92,8 +104,27 @@ def self.get_package_versions(dependency_name, repository_details) versions end + sig { params(items: T::Array[T::Hash[String, T.untyped]], versions: T::Set[String]).void } + private_class_method def self.get_versions_from_inline_page(items, versions) + items.each do |item| + catalog_entry = item["catalogEntry"] + + # a package is considered listed if the `listed` property is either `true` or missing + listed_property = catalog_entry["listed"] + is_listed = listed_property.nil? || listed_property == true + if is_listed + vers = catalog_entry["version"] + versions << vers + end + end + end + + sig do + params(repository_details: T::Hash[Symbol, String], dependency_name: String) + .returns(T.nilable(T::Set[String])) + end private_class_method def self.get_versions_from_search_url_v3(repository_details, dependency_name) - search_url = repository_details[:search_url] + search_url = repository_details.fetch(:search_url) body = execute_json_nuget_request(search_url, repository_details) body&.fetch("data") @@ -102,11 +133,14 @@ def self.get_package_versions(dependency_name, repository_details) &.map { |d| d.fetch("version") } end + sig do + params(url: String, repository_details: T::Hash[Symbol, T.untyped]).returns(T.nilable(Nokogiri::XML::Document)) + end private_class_method def self.execute_xml_nuget_request(url, repository_details) response = execute_nuget_request_internal( url: url, - auth_header: repository_details[:auth_header], - repository_url: repository_details[:repository_url] + auth_header: repository_details.fetch(:auth_header), + repository_url: repository_details.fetch(:repository_url) ) return unless response.status == 200 @@ -115,11 +149,16 @@ def self.get_package_versions(dependency_name, repository_details) doc end + sig do + params(url: String, + repository_details: T::Hash[Symbol, T.untyped]) + .returns(T.nilable(T::Hash[T.untyped, T.untyped])) + end private_class_method def self.execute_json_nuget_request(url, repository_details) response = execute_nuget_request_internal( url: url, - auth_header: repository_details[:auth_header], - repository_url: repository_details[:repository_url] + auth_header: repository_details.fetch(:auth_header), + repository_url: repository_details.fetch(:repository_url) ) return unless response.status == 200 @@ -127,11 +166,10 @@ def self.get_package_versions(dependency_name, repository_details) JSON.parse(body) end - private_class_method def self.execute_nuget_request_internal( - url: String, - auth_header: String, - repository_url: String - ) + sig do + params(url: String, auth_header: T::Hash[Symbol, T.untyped], repository_url: String).returns(Excon::Response) + end + private_class_method def self.execute_nuget_request_internal(url:, auth_header:, repository_url:) cache = CacheManager.cache("dependency_url_search_cache") if cache[url].nil? response = Dependabot::RegistryClient.get( @@ -156,6 +194,7 @@ def self.get_package_versions(dependency_name, repository_details) raise PrivateSourceTimedOut, repo_url end + sig { params(string: String).returns(String) } private_class_method def self.remove_wrapping_zero_width_chars(string) string.force_encoding("UTF-8").encode .gsub(/\A[\u200B-\u200D\uFEFF]/, "") diff --git a/nuget/lib/dependabot/nuget/version.rb b/nuget/lib/dependabot/nuget/version.rb index 74b43289a9..b2ff760f72 100644 --- a/nuget/lib/dependabot/nuget/version.rb +++ b/nuget/lib/dependabot/nuget/version.rb @@ -1,8 +1,9 @@ -# typed: true +# typed: strong # frozen_string_literal: true require "dependabot/version" require "dependabot/utils" +require "sorbet-runtime" # Dotnet pre-release versions use 1.0.1-rc1 syntax, which Gem::Version # converts into 1.0.1.pre.rc1. We override the `to_s` method to stop that @@ -11,30 +12,37 @@ module Dependabot module Nuget class Version < Dependabot::Version - VERSION_PATTERN = Gem::Version::VERSION_PATTERN + '(\+[0-9a-zA-Z\-.]+)?' + extend T::Sig + + VERSION_PATTERN = T.let(Gem::Version::VERSION_PATTERN + '(\+[0-9a-zA-Z\-.]+)?', String) ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})?\s*\z/ + sig { override.params(version: T.nilable(T.any(String, Integer, Float, Gem::Version))).returns(T::Boolean) } def self.correct?(version) return false if version.nil? version.to_s.match?(ANCHORED_VERSION_PATTERN) end + sig { override.params(version: T.nilable(T.any(String, Integer, Float, Gem::Version))).void } def initialize(version) version = version.to_s.split("+").first || "" - @version_string = version + @version_string = T.let(version, String) super end + sig { returns(String) } def to_s @version_string end + sig { returns(String) } def inspect # :nodoc: "#<#{self.class} #{@version_string}>" end + sig { params(other: Object).returns(Integer) } def <=>(other) version_comparison = compare_release(other) return version_comparison unless version_comparison.zero? @@ -42,14 +50,16 @@ def <=>(other) compare_prerelease_part(other) end + sig { params(other: Object).returns(Integer) } def compare_release(other) release_str = @version_string.split("-").first || "" other_release_str = other.to_s.split("-").first || "" - Gem::Version.new(release_str) <=> Gem::Version.new(other_release_str) + T.must(Gem::Version.new(release_str) <=> Gem::Version.new(other_release_str)) end # rubocop:disable Metrics/PerceivedComplexity + sig { params(other: Object).returns(Integer) } def compare_prerelease_part(other) release_str = @version_string.split("-").first || "" prerelease_string = @version_string @@ -67,8 +77,8 @@ def compare_prerelease_part(other) return 1 if !prerelease_string && other_prerelease_string return 0 if !prerelease_string && !other_prerelease_string - split_prerelease_string = prerelease_string.split(".") - other_split_prerelease_string = other_prerelease_string.split(".") + split_prerelease_string = T.must(prerelease_string).split(".") + other_split_prerelease_string = T.must(other_prerelease_string).split(".") length = [split_prerelease_string.length, other_split_prerelease_string.length].max - 1 (0..length).to_a.each do |index| @@ -82,13 +92,14 @@ def compare_prerelease_part(other) end # rubocop:enable Metrics/PerceivedComplexity + sig { params(lhs: T.nilable(String), rhs: T.nilable(String)).returns(Integer) } def compare_dot_separated_part(lhs, rhs) return -1 if lhs.nil? return 1 if rhs.nil? return lhs.to_i <=> rhs.to_i if lhs.match?(/^\d+$/) && rhs.match?(/^\d+$/) - lhs.upcase <=> rhs.upcase + T.must(lhs.upcase <=> rhs.upcase) end end end From e1f0f0595c722b1ecb711844facffb240a0e6cd0 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Fri, 2 Feb 2024 14:20:19 -0800 Subject: [PATCH 09/49] Update DevContainer --- .devcontainer/devcontainer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 1ae8105879..c5ee8530ff 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -19,10 +19,10 @@ // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. "remoteUser": "vscode", "features": { - "ghcr.io/devcontainers/features/docker-from-docker": "latest", + "ghcr.io/devcontainers/features/docker-outside-of-docker": "latest", "ghcr.io/devcontainers/features/github-cli": "latest", "ghcr.io/devcontainers/features/node": "lts", - "ghcr.io/devcontainers/features/golang": "latest", + "ghcr.io/devcontainers/features/go": "latest", "ghcr.io/devcontainers/features/ruby": "3.1.4", "ghcr.io/devcontainers/features/rust": "latest", "ghcr.io/devcontainers/features/dotnet": "latest", From 68df8608a4c467ae6d426fc55fb46d8db467dbe2 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 6 Feb 2024 16:51:11 -0800 Subject: [PATCH 10/49] True type `Dependabot::Python::Version` (#9002) * True type `Dependabot::Bundler::FileFetcher` to * True type `Dependabot::Python::Version` --- bundler/lib/dependabot/bundler/file_fetcher.rb | 5 +++-- python/lib/dependabot/python/version.rb | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bundler/lib/dependabot/bundler/file_fetcher.rb b/bundler/lib/dependabot/bundler/file_fetcher.rb index 8c1a4f8a91..b2daa0b68f 100644 --- a/bundler/lib/dependabot/bundler/file_fetcher.rb +++ b/bundler/lib/dependabot/bundler/file_fetcher.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: true # frozen_string_literal: true require "sorbet-runtime" @@ -105,7 +105,7 @@ def ruby_version_file end def path_gemspecs - gemspec_files = [] + gemspec_files = T.let([], T::Array[Dependabot::DependencyFile]) unfetchable_gems = [] path_gemspec_paths.each do |path| @@ -152,6 +152,7 @@ def require_relative_files(files) .tap { |req_files| req_files.each { |f| f.support_file = true } } end + sig { params(dir_path: T.any(String, Pathname)).returns(T::Array[DependencyFile]) } def fetch_gemspecs_from_directory(dir_path) repo_contents(dir: dir_path, fetch_submodules: true) .select { |f| f.name.end_with?(".gemspec", ".specification") } diff --git a/python/lib/dependabot/python/version.rb b/python/lib/dependabot/python/version.rb index f832d93198..d3ff1648e8 100644 --- a/python/lib/dependabot/python/version.rb +++ b/python/lib/dependabot/python/version.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: true # frozen_string_literal: true require "dependabot/version" @@ -59,7 +59,7 @@ def <=>(other) return epoch_comparison unless epoch_comparison.zero? version_comparison = super(other) - return version_comparison unless version_comparison.zero? + return version_comparison unless version_comparison&.zero? post_version_comparison = post_version_comparison(other) return post_version_comparison unless post_version_comparison.zero? @@ -96,7 +96,7 @@ def local_version_comparison(other) local_comparison = Gem::Version.new(lhs) <=> Gem::Version.new(rhs) - return local_comparison unless local_comparison.zero? + return local_comparison unless local_comparison&.zero? lhsegments.count <=> rhsegments.count end From 0554c121a125979ee4f66bb132fe40ab40fbde05 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 6 Feb 2024 17:04:38 -0800 Subject: [PATCH 11/49] True type `Dependabot::Bundler::FileFetcher` to (#8997) Co-authored-by: AbdulFattaah Popoola From 1e066546b40e22bc588b32443e826df4f0a5a3ce Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Wed, 7 Feb 2024 08:24:01 -0600 Subject: [PATCH 12/49] handle dependencies incidentally updated (#8803) --- silent/tests/testdata/vu-group-incidental.txt | 16 ++------ .../updater/dependency_group_change_batch.rb | 5 +++ .../updater/group_update_creation.rb | 41 ++++++++++++++++++- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/silent/tests/testdata/vu-group-incidental.txt b/silent/tests/testdata/vu-group-incidental.txt index 651c2255bb..0e2f6d7fd1 100644 --- a/silent/tests/testdata/vu-group-incidental.txt +++ b/silent/tests/testdata/vu-group-incidental.txt @@ -1,9 +1,7 @@ dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent -stdout -count=2 create_pull_request -stderr 'created \| dependency-a \( from 1.2.3 to 1.2.5 \)' -stderr 'created \| dependency-b \( from 1.2.3 to 1.2.5 \)' -pr-created expected-1.json -pr-created expected-2.json +stdout -count=1 create_pull_request +stderr 'created \| dependency-a \( from 1.2.3 to 1.2.5 \), dependency-b \( from 1.2.3 to 1.2.5 \)' +pr-created expected.json # When Dependabot goes to update dependency-a it will also bump dependency-b to the same version. # This test checks what the behavior is when using grouped updates. @@ -14,18 +12,12 @@ pr-created expected-2.json "dependency-b": { "version": "1.2.3" } } --- expected-1.json -- +-- expected.json -- { "dependency-a": { "version": "1.2.5", "depends-on": "dependency-b" }, "dependency-b": { "version": "1.2.5" } } --- expected-2.json -- -{ - "dependency-a": { "version": "1.2.3", "depends-on": "dependency-b" }, - "dependency-b": { "version": "1.2.5" } -} - -- dependency-a -- { "versions": [ diff --git a/updater/lib/dependabot/updater/dependency_group_change_batch.rb b/updater/lib/dependabot/updater/dependency_group_change_batch.rb index 826106a40a..96f542b70c 100644 --- a/updater/lib/dependabot/updater/dependency_group_change_batch.rb +++ b/updater/lib/dependabot/updater/dependency_group_change_batch.rb @@ -55,6 +55,11 @@ def merge(dependency_change) debug_current_file_state end + # add an updated dependency without changing any files, useful for incidental updates + def add_updated_dependency(dependency) + merge_dependency_changes([dependency]) + end + private # We should retain a list of all dependencies that we change, in future we may need to account for the folder diff --git a/updater/lib/dependabot/updater/group_update_creation.rb b/updater/lib/dependabot/updater/group_update_creation.rb index 27db6ee46b..dfb3b29d52 100644 --- a/updater/lib/dependabot/updater/group_update_creation.rb +++ b/updater/lib/dependabot/updater/group_update_creation.rb @@ -19,12 +19,21 @@ module GroupUpdateCreation # Returns a Dependabot::DependencyChange object that encapsulates the # outcome of attempting to update every dependency iteratively which # can be used for PR creation. - def compile_all_dependency_changes_for(group) # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/PerceivedComplexity + def compile_all_dependency_changes_for(group) prepare_workspace group_changes = Dependabot::Updater::DependencyGroupChangeBatch.new( initial_dependency_files: dependency_snapshot.dependency_files ) + # TODO: add directory to the dependencies to avoid reparsing? + job_directory = Pathname.new(job.source.directory).cleanpath + original_dependency_files = dependency_snapshot.dependency_files.select do |f| + Pathname.new(f.directory).cleanpath == job_directory + end + original_dependencies = dependency_file_parser(original_dependency_files).parse group.dependencies.each do |dependency| if dependency_snapshot.handled_dependencies.include?(dependency.name) @@ -45,8 +54,15 @@ def compile_all_dependency_changes_for(group) # rubocop:disable Metrics/AbcSize # dependency update next if dependency.nil? - updated_dependencies = compile_updates_for(dependency, dependency_files, group) + # If the dependency version changed, then we can deduce that the dependency was updated already. + original_dependency = original_dependencies.find { |d| d.name == dependency.name } + updated_dependency = deduce_updated_dependency(dependency, original_dependency) + unless updated_dependency.nil? + group_changes.add_updated_dependency(updated_dependency) + next + end + updated_dependencies = compile_updates_for(dependency, dependency_files, group) next unless updated_dependencies.any? lead_dependency = updated_dependencies.find do |dep| @@ -75,6 +91,9 @@ def compile_all_dependency_changes_for(group) # rubocop:disable Metrics/AbcSize ensure cleanup_workspace end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/PerceivedComplexity def dependency_file_parser(dependency_files) Dependabot::FileParsers.for_package_manager(job.package_manager).new( @@ -319,6 +338,24 @@ def dependencies_in_existing_pr_for_group(group) pr["dependency-group-name"] == group.name end.fetch("dependencies", []) end + + def deduce_updated_dependency(dependency, original_dependency) + return nil if original_dependency.version == dependency.version + + Dependabot.logger.info( + "Skipping #{dependency.name} as it has already been updated to #{dependency.version}" + ) + dependency_snapshot.handled_dependencies << dependency.name + + Dependabot::Dependency.new( + name: dependency.name, + version: dependency.version, + previous_version: original_dependency.version, + requirements: dependency.requirements, + previous_requirements: original_dependency.requirements, + package_manager: dependency.package_manager + ) + end end end end From a054d2af2817e230ebcd64c0df4ab0c89688d614 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 7 Feb 2024 08:44:42 -0600 Subject: [PATCH 13/49] Bump golang from 1.21.6-bookworm to 1.22.0-bookworm in /go_modules (#9008) Bumps golang from 1.21.6-bookworm to 1.22.0-bookworm. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Jake Coffman --- go_modules/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go_modules/Dockerfile b/go_modules/Dockerfile index 71309401af..03a32ded64 100644 --- a/go_modules/Dockerfile +++ b/go_modules/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21.6-bookworm as go +FROM golang:1.22.0-bookworm as go FROM ghcr.io/dependabot/dependabot-updater-core ARG TARGETARCH From 293b93dc6eba205a0ed821219e93e16c41cec6f2 Mon Sep 17 00:00:00 2001 From: THETCR Date: Wed, 7 Feb 2024 16:02:18 +0100 Subject: [PATCH 14/49] fix(gitlab): pr creator missing default for target_project_id (#8985) --- common/lib/dependabot/pull_request_creator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/dependabot/pull_request_creator.rb b/common/lib/dependabot/pull_request_creator.rb index f24bcc8ec5..94b6c27c05 100644 --- a/common/lib/dependabot/pull_request_creator.rb +++ b/common/lib/dependabot/pull_request_creator.rb @@ -295,7 +295,7 @@ def gitlab_creator approvers: reviewers, assignees: assignees, milestone: milestone, - target_project_id: provider_metadata&.fetch(:target_project_id) + target_project_id: provider_metadata&.fetch(:target_project_id, nil) ) end From 028de3aac5480dd6ade239d406fe9f7d4e1b4068 Mon Sep 17 00:00:00 2001 From: Nish Sinha Date: Tue, 6 Feb 2024 16:37:17 -0500 Subject: [PATCH 15/49] Add info on Docker tag support --- docker/README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docker/README.md b/docker/README.md index 79df0f6211..af783c168c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -16,3 +16,30 @@ Docker support for [`dependabot-core`][core-repo]. ``` [core-repo]: https://github.com/dependabot/dependabot-core + +### Supported tag schemas + +Dependabot supports updates for Docker tags that use semver versioning, dates, and build numbers. +The Docker tag class is located at: +https://github.com/dependabot/dependabot-core/blob/main/docker/lib/dependabot/docker/tag.rb + +#### Semver + +Dependabot will attempt to parse a semver version from a tag and will only update it to a tag with a matching prefix and suffix. + +As an example, `base-12.5.1` and `base-12.5.1-golden` would be parsed as `-` and `--` respectively. + +That means for `base-12.5.1` only another `-` tag would be a viable update, and for `base-12.5.1-golden`, only another `--` tag would be viable. The exception to this is if the suffix is a SHA, in which case it does not get compared and only the `` parts are considered in finding a viable tag. + +#### Dates + +Dependabot will parse dates in the `yyyy-mm`, `yyyy-mm-dd` formats (or with `.` instead of `-`) and update tags to the latest date. + +As an example, `2024-01` will get updated to `2024-02` and `2024.01.29` will get updated to `2024.03.15`. + +#### Build numbers + +Dependabot will recognize build numbers and will update to the highest build number available. + +As an example, `21-ea-32`, `22-ea-7`, and `22-ea-jdk-nanoserver-1809` are mapped to `-ea-`, `-ea-`, and `-ea-jdk-nanoserver-` respectively. +That means only "22-ea-7" will be considered as a viable update candidate for `21-ea-32`, since it's the only one that respects that format. From a9e814aa789cd19d7c485696c4be551439b0fbab Mon Sep 17 00:00:00 2001 From: Jan Trejbal Date: Wed, 7 Feb 2024 23:25:24 +0100 Subject: [PATCH 16/49] Nuget lint (#8930) * Drop unused keywords, use collection literals * Apply dotnet format * Add native_csharp_format * Use resharper_indent_raw_literal_string * Add resharper_outdent_statement_labels * Set max_line_length to 0 * Disable csharp_new_line_before_members_in_object_initializers * Ignore NuGetUpdater.sln.DotSettings.user * Make UpdateTopLevelDepdendency sync * Apply dotnet format * Apply Rider rules * Apply PR suggestion * Apply dotnet format --- nuget/helpers/lib/NuGetUpdater/.editorconfig | 65 +- nuget/helpers/lib/NuGetUpdater/.gitignore | 1 + .../AssemblyMetadataExtractor.cs | 1 + .../EntryPointTests.Update.cs | 354 ++++---- .../NuGetUpdater/NuGetUpdater.Cli/Program.cs | 4 +- .../Files/ProjectBuildFileTests.cs | 27 +- .../CompatibilityCheckerFacts.cs | 4 +- .../FrameworkCompatibilityServiceFacts.cs | 14 +- .../SupportedFrameworkFacts.cs | 2 +- .../Update/PackagesConfigUpdaterTests.cs | 18 +- .../Update/UpdateWorker.DirsProj.cs | 161 ++-- .../Update/UpdateWorkerTestBase.cs | 31 +- .../Update/UpdateWorkerTests.DotNetTools.cs | 238 +++--- .../Update/UpdateWorkerTests.GlobalJson.cs | 50 +- .../Update/UpdateWorkerTests.Mixed.cs | 17 +- .../UpdateWorkerTests.PackagesConfig.cs | 51 +- .../Update/UpdateWorkerTests.Sdk.cs | 800 +++++++++--------- .../Utilities/JsonHelperTests.cs | 32 +- .../Utilities/MSBuildHelperTests.cs | 111 ++- .../Utilities/SdkPackageUpdaterTests.cs | 190 +++-- .../NuGetUpdater.Core/Files/JsonBuildFile.cs | 3 +- .../Files/PackagesConfigBuildFile.cs | 1 + .../Files/ProjectBuildFile.cs | 9 +- .../FrameworkChecker/CompatabilityChecker.cs | 1 + .../FrameworkCompatibilityService.cs | 15 +- .../FrameworkChecker/SupportedFrameworks.cs | 28 +- .../Updater/BindingRedirectManager.cs | 35 +- .../Updater/BindingRedirectResolver.cs | 14 +- .../Updater/DotNetToolsJsonUpdater.cs | 14 +- .../Updater/GlobalJsonUpdater.cs | 12 +- .../Updater/PackagesConfigUpdater.cs | 42 +- .../Updater/SdkPackageUpdater.cs | 64 +- .../Updater/UpdaterWorker.cs | 29 +- .../WebApplicationTargetsConditionPatcher.cs | 2 +- .../Updater/XmlFilePreAndPostProcessor.cs | 2 +- .../NuGetUpdater.Core/Utilities/JsonHelper.cs | 17 +- .../Utilities/MSBuildHelper.cs | 88 +- .../NuGetUpdater.Core/Utilities/PathHelper.cs | 1 + .../Utilities/ProcessExtensions.cs | 12 +- .../dependabot/nuget/native_helpers_spec.rb | 31 + 40 files changed, 1391 insertions(+), 1200 deletions(-) diff --git a/nuget/helpers/lib/NuGetUpdater/.editorconfig b/nuget/helpers/lib/NuGetUpdater/.editorconfig index 9cf15a88d5..c5fa72bccd 100644 --- a/nuget/helpers/lib/NuGetUpdater/.editorconfig +++ b/nuget/helpers/lib/NuGetUpdater/.editorconfig @@ -24,6 +24,8 @@ insert_final_newline = true #### .NET Coding Conventions #### [*.{cs,vb}] +max_line_length = 0 + # Organize usings dotnet_separate_import_directive_groups = true dotnet_sort_system_directives_first = true @@ -131,7 +133,8 @@ csharp_new_line_before_catch = true csharp_new_line_before_else = true csharp_new_line_before_finally = true csharp_new_line_before_members_in_anonymous_types = true -csharp_new_line_before_members_in_object_initializers = true +# The following line is commented out to work around a Rider formatter bug. `true` is the default value. +# csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_open_brace = all csharp_new_line_between_query_expression_clauses = true @@ -143,6 +146,9 @@ csharp_indent_case_contents_when_block = true csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true +resharper_outdent_statement_labels = true +resharper_indent_raw_literal_string = indent + # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true @@ -256,31 +262,31 @@ dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase dotnet_naming_symbols.interfaces.applicable_kinds = interface dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interfaces.required_modifiers = +dotnet_naming_symbols.interfaces.required_modifiers = dotnet_naming_symbols.enums.applicable_kinds = enum dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.enums.required_modifiers = +dotnet_naming_symbols.enums.required_modifiers = dotnet_naming_symbols.events.applicable_kinds = event dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.events.required_modifiers = +dotnet_naming_symbols.events.required_modifiers = dotnet_naming_symbols.methods.applicable_kinds = method dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.methods.required_modifiers = +dotnet_naming_symbols.methods.required_modifiers = dotnet_naming_symbols.properties.applicable_kinds = property dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.properties.required_modifiers = +dotnet_naming_symbols.properties.required_modifiers = dotnet_naming_symbols.public_fields.applicable_kinds = field dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal -dotnet_naming_symbols.public_fields.required_modifiers = +dotnet_naming_symbols.public_fields.required_modifiers = dotnet_naming_symbols.private_fields.applicable_kinds = field dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected -dotnet_naming_symbols.private_fields.required_modifiers = +dotnet_naming_symbols.private_fields.required_modifiers = dotnet_naming_symbols.private_static_fields.applicable_kinds = field dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected @@ -288,15 +294,15 @@ dotnet_naming_symbols.private_static_fields.required_modifiers = static dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types_and_namespaces.required_modifiers = +dotnet_naming_symbols.types_and_namespaces.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = dotnet_naming_symbols.type_parameters.applicable_kinds = namespace dotnet_naming_symbols.type_parameters.applicable_accessibilities = * -dotnet_naming_symbols.type_parameters.required_modifiers = +dotnet_naming_symbols.type_parameters.required_modifiers = dotnet_naming_symbols.private_constant_fields.applicable_kinds = field dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected @@ -304,7 +310,7 @@ dotnet_naming_symbols.private_constant_fields.required_modifiers = const dotnet_naming_symbols.local_variables.applicable_kinds = local dotnet_naming_symbols.local_variables.applicable_accessibilities = local -dotnet_naming_symbols.local_variables.required_modifiers = +dotnet_naming_symbols.local_variables.required_modifiers = dotnet_naming_symbols.local_constants.applicable_kinds = local dotnet_naming_symbols.local_constants.applicable_accessibilities = local @@ -312,7 +318,7 @@ dotnet_naming_symbols.local_constants.required_modifiers = const dotnet_naming_symbols.parameters.applicable_kinds = parameter dotnet_naming_symbols.parameters.applicable_accessibilities = * -dotnet_naming_symbols.parameters.required_modifiers = +dotnet_naming_symbols.parameters.required_modifiers = dotnet_naming_symbols.public_constant_fields.applicable_kinds = field dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal @@ -328,37 +334,40 @@ dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readon dotnet_naming_symbols.local_functions.applicable_kinds = local_function dotnet_naming_symbols.local_functions.applicable_accessibilities = * -dotnet_naming_symbols.local_functions.required_modifiers = +dotnet_naming_symbols.local_functions.required_modifiers = # Naming styles -dotnet_naming_style.pascalcase.required_prefix = -dotnet_naming_style.pascalcase.required_suffix = -dotnet_naming_style.pascalcase.word_separator = +dotnet_naming_style.pascalcase.required_prefix = +dotnet_naming_style.pascalcase.required_suffix = +dotnet_naming_style.pascalcase.word_separator = dotnet_naming_style.pascalcase.capitalization = pascal_case dotnet_naming_style.ipascalcase.required_prefix = I -dotnet_naming_style.ipascalcase.required_suffix = -dotnet_naming_style.ipascalcase.word_separator = +dotnet_naming_style.ipascalcase.required_suffix = +dotnet_naming_style.ipascalcase.word_separator = dotnet_naming_style.ipascalcase.capitalization = pascal_case dotnet_naming_style.tpascalcase.required_prefix = T -dotnet_naming_style.tpascalcase.required_suffix = -dotnet_naming_style.tpascalcase.word_separator = +dotnet_naming_style.tpascalcase.required_suffix = +dotnet_naming_style.tpascalcase.word_separator = dotnet_naming_style.tpascalcase.capitalization = pascal_case dotnet_naming_style._camelcase.required_prefix = _ -dotnet_naming_style._camelcase.required_suffix = -dotnet_naming_style._camelcase.word_separator = +dotnet_naming_style._camelcase.required_suffix = +dotnet_naming_style._camelcase.word_separator = dotnet_naming_style._camelcase.capitalization = camel_case -dotnet_naming_style.camelcase.required_prefix = -dotnet_naming_style.camelcase.required_suffix = -dotnet_naming_style.camelcase.word_separator = +dotnet_naming_style.camelcase.required_prefix = +dotnet_naming_style.camelcase.required_suffix = +dotnet_naming_style.camelcase.word_separator = dotnet_naming_style.camelcase.capitalization = camel_case dotnet_naming_style.s_camelcase.required_prefix = s_ -dotnet_naming_style.s_camelcase.required_suffix = -dotnet_naming_style.s_camelcase.word_separator = +dotnet_naming_style.s_camelcase.required_suffix = +dotnet_naming_style.s_camelcase.word_separator = dotnet_naming_style.s_camelcase.capitalization = camel_case +[NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs] +generated_code = true +disable_formatter = true diff --git a/nuget/helpers/lib/NuGetUpdater/.gitignore b/nuget/helpers/lib/NuGetUpdater/.gitignore index 4c5efb30e9..ce73752685 100644 --- a/nuget/helpers/lib/NuGetUpdater/.gitignore +++ b/nuget/helpers/lib/NuGetUpdater/.gitignore @@ -3,3 +3,4 @@ artifacts/ bin/ obj/ Properties/launchSettings.json +NuGetUpdater.sln.DotSettings.user diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs b/nuget/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs index f90fa93895..72885b846d 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetProjects/NuGet.CommandLine/AssemblyMetadataExtractor.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using System.Reflection; + using NuGet.Common; using NuGet.Packaging; using NuGet.Packaging.Core; diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs index 9db66d7997..8f5d0850ef 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs @@ -16,186 +16,187 @@ public class Update : UpdateWorkerTestBase [Fact] public async Task WithSolution() { - await Run(path => new[] - { - "update", - "--repo-root", - path, - "--solution-or-project", - Path.Combine(path, "path/to/solution.sln"), - "--dependency", - "Newtonsoft.Json", - "--new-version", - "13.0.1", - "--previous-version", - "7.0.1", - }, - new[] - { - ("path/to/solution.sln", """ - Microsoft Visual Studio Solution File, Format Version 12.00 - # Visual Studio 14 - VisualStudioVersion = 14.0.22705.0 - MinimumVisualStudioVersion = 10.0.40219.1 - Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "my", "my.csproj", "{782E0C0A-10D3-444D-9640-263D03D2B20C}" - EndProject - Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {782E0C0A-10D3-444D-9640-263D03D2B20C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {782E0C0A-10D3-444D-9640-263D03D2B20C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {782E0C0A-10D3-444D-9640-263D03D2B20C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {782E0C0A-10D3-444D-9640-263D03D2B20C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - EndGlobal - """), - ("path/to/my.csproj", """ - - - - v4.5 - - - - - - - packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - True - - - - - """), - ("path/to/packages.config", """ - - - - """) - }, - new[] - { - ("path/to/my.csproj", """ - - - - v4.5 - - - - - - - packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - True - - - - - """), - ("path/to/packages.config", """ - - - - - """) - }); + await Run(path => + [ + "update", + "--repo-root", + path, + "--solution-or-project", + Path.Combine(path, "path/to/solution.sln"), + "--dependency", + "Newtonsoft.Json", + "--new-version", + "13.0.1", + "--previous-version", + "7.0.1", + ], + [ + ("path/to/solution.sln", """ + Microsoft Visual Studio Solution File, Format Version 12.00 + # Visual Studio 14 + VisualStudioVersion = 14.0.22705.0 + MinimumVisualStudioVersion = 10.0.40219.1 + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "my", "my.csproj", "{782E0C0A-10D3-444D-9640-263D03D2B20C}" + EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {782E0C0A-10D3-444D-9640-263D03D2B20C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {782E0C0A-10D3-444D-9640-263D03D2B20C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {782E0C0A-10D3-444D-9640-263D03D2B20C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {782E0C0A-10D3-444D-9640-263D03D2B20C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + EndGlobal + """), + ("path/to/my.csproj", """ + + + + v4.5 + + + + + + + packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + """), + ("path/to/packages.config", """ + + + + """) + ], + [ + ("path/to/my.csproj", """ + + + + v4.5 + + + + + + + packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + """), + ("path/to/packages.config", """ + + + + + """) + ]); } [Fact] public async Task WithProject() { - await Run(path => new[] - { - "update", - "--repo-root", - path, - "--solution-or-project", - Path.Combine(path, "path/to/my.csproj"), - "--dependency", - "Newtonsoft.Json", - "--new-version", - "13.0.1", - "--previous-version", - "7.0.1", - "--verbose" - }, - new[] - { - ("path/to/my.csproj", """ - - - - v4.5 - - - - - - - packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - True - - - - - """), - ("path/to/packages.config", """ - - - - """) - }, - new[] - { - ("path/to/my.csproj", """ - - - - v4.5 - - - - - - - packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll - True - - - - - """), - ("path/to/packages.config", """ - - - - - """) - }); + await Run(path => + [ + "update", + "--repo-root", + path, + "--solution-or-project", + Path.Combine(path, "path/to/my.csproj"), + "--dependency", + "Newtonsoft.Json", + "--new-version", + "13.0.1", + "--previous-version", + "7.0.1", + "--verbose" + ], + [ + ("path/to/my.csproj", """ + + + + v4.5 + + + + + + + packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + """), + ("path/to/packages.config", """ + + + + """) + ], + [ + ("path/to/my.csproj", """ + + + + v4.5 + + + + + + + packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + """), + ("path/to/packages.config", """ + + + + + """) + ]); } [Fact] public async Task WithDirsProjAndDirectoryBuildPropsThatIsOutOfDirectoryButStillMatchingThePackage() { - await Run(path => new[] - { + await Run(path => + [ "update", - "--repo-root", path, - "--solution-or-project", $"{path}/some-dir/dirs.proj", - "--dependency", "NuGet.Versioning", - "--new-version", "6.6.1", - "--previous-version", "6.1.0", + "--repo-root", + path, + "--solution-or-project", + $"{path}/some-dir/dirs.proj", + "--dependency", + "NuGet.Versioning", + "--new-version", + "6.6.1", + "--previous-version", + "6.1.0", "--verbose" - }, - initialFiles: new[] - { + ], + initialFiles: + [ ("some-dir/dirs.proj", """ @@ -232,16 +233,16 @@ await Run(path => new[] """), ("other-dir/Directory.Build.props", """ - + """) - }, - expectedFiles: new[] - { + ], + expectedFiles: + [ ("some-dir/dirs.proj", """ @@ -250,7 +251,8 @@ await Run(path => new[] """), - ("some-dir/project1/project.csproj", """ + ("some-dir/project1/project.csproj", + """ Exe @@ -278,20 +280,20 @@ await Run(path => new[] """), ("other-dir/Directory.Build.props", """ - + """) - } + ] ); } private static async Task Run(Func getArgs, (string Path, string Content)[] initialFiles, (string, string)[] expectedFiles) { - var actualFiles = await RunUpdate(initialFiles, async (path) => + var actualFiles = await RunUpdate(initialFiles, async path => { var sb = new StringBuilder(); var writer = new StringWriter(sb); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs index bfc5d3e0e5..6549831eba 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs @@ -11,9 +11,9 @@ internal sealed class Program internal static async Task Main(string[] args) { var exitCode = 0; - Action setExitCode = (int code) => exitCode = code; + Action setExitCode = code => exitCode = code; - var command = new RootCommand() + var command = new RootCommand { FrameworkCheckCommand.GetCommand(setExitCode), UpdateCommand.GetCommand(setExitCode), diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs index 3e66e3cc2f..f937725c41 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Files/ProjectBuildFileTests.cs @@ -133,17 +133,26 @@ public void EmptyProject_GetDependencies_ReturnsNoDependencies() } [Theory] - [InlineData( // no change made - @"path\to\file.dll", - @"path\to\file.dll" + // no change made + [InlineData( + // language=csproj + @"path\to\file.dll", + // language=csproj + @"path\to\file.dll" )] - [InlineData( // change from `/` to `\` - "path/to/file.dll", - @"path\to\file.dll" + // change from `/` to `\` + [InlineData( + // language=csproj + "path/to/file.dll", + // language=csproj + @"path\to\file.dll" )] - [InlineData( // multiple changes made - "path1/to1/file1.dllpath2/to2/file2.dll", - @"path1\to1\file1.dllpath2\to2\file2.dll" + // multiple changes made + [InlineData( + // language=csproj + "path1/to1/file1.dllpath2/to2/file2.dll", + // language=csproj + @"path1\to1\file1.dllpath2\to2\file2.dll" )] public void ReferenceHintPathsCanBeNormalized(string originalXml, string expectedXml) { diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs index 9aef2b7048..87378fe3e1 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/CompatibilityCheckerFacts.cs @@ -20,7 +20,7 @@ public class CompatibilityCheckerFacts [InlineData("net4.8", "netstandard1.3")] public void PackageContainsCompatibleFramework(string projectTfm, string packageTfm) { - var result = CompatibilityChecker.IsCompatible(new[] { projectTfm }, new[] { packageTfm }, new Logger(verbose: true)); + var result = CompatibilityChecker.IsCompatible([projectTfm], [packageTfm], new Logger(verbose: true)); Assert.True(result); } @@ -37,7 +37,7 @@ public void PackageContainsCompatibleFramework(string projectTfm, string package [InlineData("net7.0", "net48")] public void PackageContainsIncompatibleFramework(string projectTfm, string packageTfm) { - var result = CompatibilityChecker.IsCompatible(new[] { projectTfm }, new[] { packageTfm }, new Logger(verbose: true)); + var result = CompatibilityChecker.IsCompatible([projectTfm], [packageTfm], new Logger(verbose: true)); Assert.False(result); } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs index 507ea7e072..b77608420b 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/FrameworkCompatibilityServiceFacts.cs @@ -41,11 +41,11 @@ public void EmptyPackageFrameworksReturnsEmptySet() public void UnknownSupportedPackageReturnsSetWithSameFramework() { var framework = NuGetFramework.Parse("net45-client"); - var frameworks = new List() { framework }; + var frameworks = new List { framework }; var compatible = _service.GetCompatibleFrameworks(frameworks); Assert.False(framework.IsUnsupported); - Assert.Equal(expected: 1, compatible.Count); + Assert.Single(compatible); Assert.Contains(framework, compatible); } @@ -57,10 +57,10 @@ public void UnsupportedPackageFrameworksReturnsEmptySet(string unsupportedFramew { var unsupportedFramework = NuGetFramework.Parse(unsupportedFrameworkName); - var result = _service.GetCompatibleFrameworks(new List() { unsupportedFramework }); + var result = _service.GetCompatibleFrameworks([unsupportedFramework]); Assert.True(unsupportedFramework.IsUnsupported); - Assert.Equal(expected: 0, actual: result.Count); + Assert.Empty(result); } [Theory] @@ -71,10 +71,10 @@ public void PCLPackageFrameworksReturnsEmptySet(string pclFrameworkName) { var portableFramework = NuGetFramework.Parse(pclFrameworkName); - var result = _service.GetCompatibleFrameworks(new List() { portableFramework }); + var result = _service.GetCompatibleFrameworks([portableFramework]); Assert.True(portableFramework.IsPCL); - Assert.Equal(expected: 0, actual: result.Count); + Assert.Empty(result); } [Theory] @@ -113,7 +113,7 @@ public void WindowsPlatformVersionsShouldContainAllSpecifiedFrameworks(string wi projectFrameworks.Add(NuGetFramework.Parse(frameworkName)); } - var compatibleFrameworks = _service.GetCompatibleFrameworks(new HashSet() { packageFramework }); + var compatibleFrameworks = _service.GetCompatibleFrameworks([packageFramework]); Assert.Equal(windowsProjectFrameworks.Length, compatibleFrameworks.Count); var containsAllCompatibleFrameworks = compatibleFrameworks.All(cf => projectFrameworks.Contains(cf)); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs index bd72daeaad..c52c656384 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/FrameworkChecker/SupportedFrameworkFacts.cs @@ -49,7 +49,7 @@ public void SupportedFrameworksContainsCommonFrameworksWithNoDeprecatedFramework foreach (var field in fields) { - var framework = (NuGetFramework)field.GetValue(null); + var framework = (NuGetFramework)field.GetValue(null)!; if (DeprecatedFrameworks.Contains(framework)) { diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs index 3ba8115816..fdb2f0d74c 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackagesConfigUpdaterTests.cs @@ -23,8 +23,8 @@ public void PathToPackagesDirectoryCanBeDetermined(string projectContents, strin public static IEnumerable PackagesDirectoryPathTestData() { // project with namespace - yield return new object[] - { + yield return + [ """ @@ -38,11 +38,11 @@ public static IEnumerable PackagesDirectoryPathTestData() "Newtonsoft.Json", "7.0.1", @"..\packages" - }; + ]; // project without namespace - yield return new object[] - { + yield return + [ """ @@ -56,11 +56,11 @@ public static IEnumerable PackagesDirectoryPathTestData() "Newtonsoft.Json", "7.0.1", @"..\packages" - }; + ]; // project with non-standard packages path - yield return new object[] - { + yield return + [ """ @@ -74,6 +74,6 @@ public static IEnumerable PackagesDirectoryPathTestData() "Newtonsoft.Json", "7.0.1", @"..\not-a-path-you-would-expect" - }; + ]; } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs index 71cba8a306..d8f487199d 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -23,53 +24,53 @@ await TestUpdateForDirsProj("Newtonsoft.Json", "9.0.1", "13.0.1", // initial projectContents: """ - + """, - additionalFiles: new (string Path, string Content)[] - { + additionalFiles: + [ ("src/test-project.csproj", - """ - - - netstandard2.0 - - - - - - - """) - }, + """ + + + netstandard2.0 + + + + + + + """) + ], // expected expectedProjectContents: """ - + """, - additionalFilesExpected: new (string Path, string Content)[] - { + additionalFilesExpected: + [ ("src/test-project.csproj", - """ - - - netstandard2.0 - - - - - - - """) - }); + """ + + + netstandard2.0 + + + + + + + """) + ]); } [Fact] @@ -100,73 +101,73 @@ await TestUpdateForDirsProj("Newtonsoft.Json", "9.0.1", "13.0.1", // initial projectContents: """ - + """, - additionalFiles: new (string Path, string Content)[] - { + additionalFiles: + [ ("src/dirs.proj", - """ - - - - - - - - """), + """ + + + + + + + + """), ("src/test-project/test-project.csproj", - """ - - - netstandard2.0 - - - - - - - """) - }, + """ + + + netstandard2.0 + + + + + + + """) + ], // expected expectedProjectContents: """ - + """, - additionalFilesExpected: new (string Path, string Content)[] - { + additionalFilesExpected: + [ ("src/dirs.proj", - """ - - - - - - - - """), + """ + + + + + + + + """), ("src/test-project/test-project.csproj", - """ - - - netstandard2.0 - - - - - - - """) - }); + """ + + + netstandard2.0 + + + + + + + """) + ]); } static async Task TestUpdateForDirsProj( @@ -179,8 +180,8 @@ static async Task TestUpdateForDirsProj( (string Path, string Content)[]? additionalFiles = null, (string Path, string Content)[]? additionalFilesExpected = null) { - additionalFiles ??= Array.Empty<(string Path, string Content)>(); - additionalFilesExpected ??= Array.Empty<(string Path, string Content)>(); + additionalFiles ??= []; + additionalFilesExpected ??= []; var projectName = "dirs"; var projectFileName = $"{projectName}.proj"; diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs index 4af9edaaae..7dff9be25f 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs @@ -18,7 +18,15 @@ protected static Task TestNoChangeforProject( bool isTransitive = false, (string Path, string Content)[]? additionalFiles = null, string projectFilePath = "test-project.csproj") - => TestUpdateForProject(dependencyName, oldVersion, newVersion, (projectFilePath, projectContents), expectedProjectContents: projectContents, isTransitive, additionalFiles, additionalFilesExpected: additionalFiles); + => TestUpdateForProject( + dependencyName, + oldVersion, + newVersion, + (projectFilePath, projectContents), + expectedProjectContents: projectContents, + isTransitive, + additionalFiles, + additionalFilesExpected: additionalFiles); protected static Task TestUpdateForProject( string dependencyName, @@ -30,10 +38,15 @@ protected static Task TestUpdateForProject( (string Path, string Content)[]? additionalFiles = null, (string Path, string Content)[]? additionalFilesExpected = null, string projectFilePath = "test-project.csproj") - { - var projectFile = (Path: projectFilePath, Content: projectContents); - return TestUpdateForProject(dependencyName, oldVersion, newVersion, projectFile, expectedProjectContents, isTransitive, additionalFiles, additionalFilesExpected); - } + => TestUpdateForProject( + dependencyName, + oldVersion, + newVersion, + (Path: projectFilePath, Content: projectContents), + expectedProjectContents, + isTransitive, + additionalFiles, + additionalFilesExpected); protected static async Task TestUpdateForProject( string dependencyName, @@ -45,8 +58,8 @@ protected static async Task TestUpdateForProject( (string Path, string Content)[]? additionalFiles = null, (string Path, string Content)[]? additionalFilesExpected = null) { - additionalFiles ??= Array.Empty<(string Path, string Content)>(); - additionalFilesExpected ??= Array.Empty<(string Path, string Content)>(); + additionalFiles ??= []; + additionalFilesExpected ??= []; var projectFilePath = projectFile.Path; var projectName = Path.GetFileNameWithoutExtension(projectFilePath); @@ -76,7 +89,7 @@ protected static async Task TestUpdateForProject( """; var testFiles = new[] { (slnName, slnContent), projectFile }.Concat(additionalFiles).ToArray(); - var actualResult = await RunUpdate(testFiles, async (temporaryDirectory) => + var actualResult = await RunUpdate(testFiles, async temporaryDirectory => { var slnPath = Path.Combine(temporaryDirectory, slnName); var worker = new UpdaterWorker(new Logger(verbose: true)); @@ -94,7 +107,7 @@ protected static async Task TestUpdateForProject( using var tempDir = new TemporaryDirectory(); foreach (var file in files) { - var localPath = file.Path.StartsWith("/") ? file.Path[1..] : file.Path; // remove path rooting character + var localPath = file.Path.StartsWith('/') ? file.Path[1..] : file.Path; // remove path rooting character var filePath = Path.Combine(tempDir.DirectoryPath, localPath); var directoryPath = Path.GetDirectoryName(filePath); Directory.CreateDirectory(directoryPath!); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs index 4547ed8f97..f1880e12a2 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.DotNetTools.cs @@ -23,7 +23,7 @@ await TestNoChangeforProject("Microsoft.BotSay", "1.0.0", "1.1.0", netstandard2.0 - + @@ -41,29 +41,29 @@ await TestNoChangeforProject("Microsoft.BotSay", "1.0.0", "1.1.0", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ (".config/dotnet-tools.json", """ - { - "version": 1, - "isRoot": true, - "tools": { - "dotnetsay": { - "version": "2.1.3", - "commands": [ - "dotnetsay" - ] + { + "version": 1, + "isRoot": true, + "tools": { + "dotnetsay": { + "version": "2.1.3", + "commands": [ + "dotnetsay" + ] + } } } - } - """) - }); + """) + ]); } [Fact] @@ -77,29 +77,29 @@ await TestNoChangeforProject("Microsoft.BotSay", "1.0.0", "1.1.0", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("eng/.config/dotnet-tools.json", """ - { - "version": 1, - "isRoot": true, - "tools": { - "dotnetsay": { - "version": "2.1.3", - "commands": [ - "dotnetsay" - ] + { + "version": 1, + "isRoot": true, + "tools": { + "dotnetsay": { + "version": "2.1.3", + "commands": [ + "dotnetsay" + ] + } } } - } - """) - }); + """) + ]); } [Fact] @@ -112,70 +112,70 @@ await TestUpdateForProject("Microsoft.BotSay", "1.0.0", "1.1.0", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ (".config/dotnet-tools.json", """ - { - "version": 1, - "isRoot": true, - "tools": { - "microsoft.botsay": { - "version": "1.0.0", - "commands": [ - "botsay" - ] - }, - "dotnetsay": { - "version": "2.1.3", - "commands": [ - "dotnetsay" - ] + { + "version": 1, + "isRoot": true, + "tools": { + "microsoft.botsay": { + "version": "1.0.0", + "commands": [ + "botsay" + ] + }, + "dotnetsay": { + "version": "2.1.3", + "commands": [ + "dotnetsay" + ] + } } } - } - """) - }, + """) + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ (".config/dotnet-tools.json", """ - { - "version": 1, - "isRoot": true, - "tools": { - "microsoft.botsay": { - "version": "1.1.0", - "commands": [ - "botsay" - ] - }, - "dotnetsay": { - "version": "2.1.3", - "commands": [ - "dotnetsay" - ] + { + "version": 1, + "isRoot": true, + "tools": { + "microsoft.botsay": { + "version": "1.1.0", + "commands": [ + "botsay" + ] + }, + "dotnetsay": { + "version": "2.1.3", + "commands": [ + "dotnetsay" + ] + } } } - } - """) - }); + """) + ]); } [Fact] @@ -188,74 +188,74 @@ await TestUpdateForProject("Microsoft.BotSay", "1.0.0", "1.1.0", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ (".config/dotnet-tools.json", """ - { - // this is a comment - "version": 1, - "isRoot": true, - "tools": { - "microsoft.botsay": { - // this is a deep comment - "version": "1.0.0", - "commands": [ - "botsay" - ] - }, - "dotnetsay": { - "version": "2.1.3", - "commands": [ - "dotnetsay" - ] + { + // this is a comment + "version": 1, + "isRoot": true, + "tools": { + "microsoft.botsay": { + // this is a deep comment + "version": "1.0.0", + "commands": [ + "botsay" + ] + }, + "dotnetsay": { + "version": "2.1.3", + "commands": [ + "dotnetsay" + ] + } } } - } - """) - }, + """) + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ (".config/dotnet-tools.json", """ - { - // this is a comment - "version": 1, - "isRoot": true, - "tools": { - "microsoft.botsay": { - // this is a deep comment - "version": "1.1.0", - "commands": [ - "botsay" - ] - }, - "dotnetsay": { - "version": "2.1.3", - "commands": [ - "dotnetsay" - ] + { + // this is a comment + "version": 1, + "isRoot": true, + "tools": { + "microsoft.botsay": { + // this is a deep comment + "version": "1.1.0", + "commands": [ + "botsay" + ] + }, + "dotnetsay": { + "version": "2.1.3", + "commands": [ + "dotnetsay" + ] + } } } - } - """) - }); + """) + ]); } } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs index a26022147b..458a63d2fc 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.GlobalJson.cs @@ -23,7 +23,7 @@ await TestNoChangeforProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", netstandard2.0 - + @@ -41,14 +41,14 @@ await TestNoChangeforProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("global.json", """ { "sdk": { @@ -57,7 +57,7 @@ await TestNoChangeforProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", } } """) - }); + ]); } [Fact] @@ -71,14 +71,14 @@ await TestNoChangeforProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", netstandard2.0 - + > """, - additionalFiles: new[] - { + additionalFiles: + [ ("eng/global.json", """ { "sdk": { @@ -90,7 +90,7 @@ await TestNoChangeforProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", } } """) - }); + ]); } [Fact] @@ -104,14 +104,14 @@ await TestUpdateForProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("src/global.json", """ { "sdk": { @@ -123,21 +123,21 @@ await TestUpdateForProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", } } """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("src/global.json", """ { "sdk": { @@ -149,7 +149,7 @@ await TestUpdateForProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", } } """) - }); + ]); } [Fact] @@ -162,14 +162,14 @@ await TestUpdateForProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("global.json", """ { // this is a comment @@ -183,21 +183,21 @@ await TestUpdateForProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", } } """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("global.json", """ { // this is a comment @@ -211,7 +211,7 @@ await TestUpdateForProject("Microsoft.Build.Traversal", "3.2.0", "4.1.0", } } """) - }); + ]); } } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs index 04289c3611..4cd72ed88b 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -38,8 +37,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFiles: new[] - { + additionalFiles: + [ ("packages.config", """ @@ -52,8 +51,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", - """), - }, + """) + ], // expected expectedProjectContents: """ @@ -73,8 +72,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("packages.config", """ @@ -87,8 +86,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", - """), - }); + """) + ]); } } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs index b50bcaf713..f052c81854 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs @@ -278,8 +278,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFiles: new[] - { + additionalFiles: + [ ("app.config", """ @@ -292,7 +292,7 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """) - }, + ], expectedProjectContents: """ @@ -320,8 +320,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("app.config", """ @@ -334,7 +334,7 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -425,8 +425,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFiles: new[] - { + additionalFiles: + [ ("web.config", """ @@ -439,7 +439,7 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """) - }, + ], expectedProjectContents: """ @@ -525,8 +525,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("web.config", """ @@ -539,7 +539,7 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -630,15 +630,15 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFiles: new[] - { + additionalFiles: + [ ("web.config", """ """) - }, + ], expectedProjectContents: """ @@ -724,8 +724,8 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("web.config", """ @@ -738,7 +738,7 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -1072,7 +1072,7 @@ await TestUpdateForProject("Newtonsoft.Json", "7.0.1", "13.0.1", """); } - protected static async Task TestUpdateForProject( + protected static Task TestUpdateForProject( string dependencyName, string oldVersion, string newVersion, @@ -1083,7 +1083,7 @@ protected static async Task TestUpdateForProject( (string Path, string Content)[]? additionalFiles = null, (string Path, string Content)[]? additionalFilesExpected = null) { - var realizedAdditionalFiles = new List<(string Path, string Content)>() + var realizedAdditionalFiles = new List<(string Path, string Content)> { ("packages.config", packagesConfigContents), }; @@ -1092,7 +1092,7 @@ protected static async Task TestUpdateForProject( realizedAdditionalFiles.AddRange(additionalFiles); } - var realizedAdditionalFilesExpected = new List<(string Path, string Content)>() + var realizedAdditionalFilesExpected = new List<(string Path, string Content)> { ("packages.config", expectedPackagesConfigContents), }; @@ -1101,7 +1101,14 @@ protected static async Task TestUpdateForProject( realizedAdditionalFilesExpected.AddRange(additionalFilesExpected); } - await TestUpdateForProject(dependencyName, oldVersion, newVersion, projectContents, expectedProjectContents, additionalFiles: realizedAdditionalFiles.ToArray(), additionalFilesExpected: realizedAdditionalFilesExpected.ToArray()); + return TestUpdateForProject( + dependencyName, + oldVersion, + newVersion, + projectContents, + expectedProjectContents, + additionalFiles: realizedAdditionalFiles.ToArray(), + additionalFilesExpected: realizedAdditionalFilesExpected.ToArray()); } } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs index ec548689e4..4dea5d289c 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs @@ -30,7 +30,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", {tfm} - + @@ -42,7 +42,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", {tfm} - + @@ -76,12 +76,12 @@ await TestNoChangeforProject("Newtonsoft.Json", "9.0.1", "13.0.1", public async Task UpdateFindsNearestNugetConfig_AndSucceeds() { // Clean the cache to ensure we don't find a cached version of packages. - await ProcessEx.RunAsync("dotnet", $"nuget locals -c all"); + await ProcessEx.RunAsync("dotnet", "nuget locals -c all"); // If the Top-Level NugetConfig was found we would have failed. - var privateNugetContent = $""" + var privateNugetContent = """ - + @@ -90,16 +90,16 @@ public async Task UpdateFindsNearestNugetConfig_AndSucceeds() """; await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", projectFile: (Path: "Directory/Project.csproj", Content: """ - - - netstandard2.0 - - - - - - """), - $""" + + + netstandard2.0 + + + + + + """), + """ netstandard2.0 @@ -109,7 +109,8 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """, - additionalFiles: new (string Path, string Content)[] { + additionalFiles: + [ (Path: "NuGet.config", Content: $""" @@ -120,7 +121,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """), (Path: "Directory/NuGet.config", Content: privateNugetContent) - }); + ]); } [Fact] @@ -139,8 +140,9 @@ await TestNoChangeforProject("Newtonsoft.Json", "9.0.1", "13.0.1", """, - additionalFiles: new (string Path, string Content)[] { - (Path: "NuGet.config", Content: $""" + additionalFiles: + [ + (Path: "NuGet.config", Content: """ @@ -151,8 +153,8 @@ await TestNoChangeforProject("Newtonsoft.Json", "9.0.1", "13.0.1", - """), - }); + """) + ]); } [Fact] @@ -161,24 +163,24 @@ public async Task UpdateExactMatchVersionAttribute_InProjectFile_ForPackageRefer // update Newtonsoft.Json from 9.0.1 to 13.0.1 await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", // initial - projectContents: $""" + projectContents: """ net6.0 - + """, // expected - expectedProjectContents: $""" + expectedProjectContents: """ net6.0 - + @@ -194,11 +196,11 @@ await TestUpdateForProject("System.Text.Json", "5.0.1", "5.0.2", isTransitive: t // initial projectContents: """ - + netcoreapp3.1 - + @@ -208,11 +210,11 @@ await TestUpdateForProject("System.Text.Json", "5.0.1", "5.0.2", isTransitive: t // expected expectedProjectContents: """ - + netcoreapp3.1 - + @@ -233,7 +235,7 @@ await TestUpdateForProject("Microsoft.CodeAnalysis.Analyzers", "3.3.0", "3.3.4", netstandard2.0 - + all @@ -248,7 +250,7 @@ await TestUpdateForProject("Microsoft.CodeAnalysis.Analyzers", "3.3.0", "3.3.4", netstandard2.0 - + all @@ -270,7 +272,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + @@ -283,7 +285,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + @@ -303,7 +305,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + @@ -316,7 +318,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + @@ -336,52 +338,52 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ true - + """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ true - + """) - }); + ]); } [Fact] @@ -395,52 +397,52 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ true - + """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ true - + """) - }); + ]); } [Fact] @@ -454,7 +456,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 9.0.1 - + @@ -467,7 +469,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 13.0.1 - + @@ -486,7 +488,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 9.0.1 - + @@ -499,7 +501,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 13.0.1 - + @@ -518,7 +520,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 9.0.1 - + @@ -531,7 +533,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 13.0.1 - + @@ -550,7 +552,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 [9.0.1] - + @@ -563,7 +565,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 [13.0.1] - + @@ -582,7 +584,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 9.0.1 - + @@ -596,7 +598,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 13.0.1 - + @@ -616,7 +618,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 9.0.1 - + @@ -630,7 +632,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 13.0.1 - + @@ -650,54 +652,54 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ true 9.0.1 - + """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ true 13.0.1 - + """) - }); + ]); } [Fact] @@ -711,54 +713,54 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ true [9.0.1] - + """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ true [13.0.1] - + """) - }); + ]); } [Fact] @@ -772,54 +774,54 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ true 9.0.1 - + """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ true 13.0.1 - + """) - }); + ]); } [Fact] @@ -835,20 +837,20 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ true - + """) - }, + ], // expected expectedProjectContents: """ @@ -857,20 +859,20 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ true - + """) - }); + ]); } [Fact] @@ -886,21 +888,21 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ true 9.0.1 - + """) - }, + ], // expected expectedProjectContents: """ @@ -909,21 +911,21 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ true 13.0.1 - + """) - }); + ]); } [Fact] @@ -936,14 +938,14 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ // initial props file ("Directory.Build.props", """ @@ -952,21 +954,21 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }, + ], // expected project expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ // expected props file ("Directory.Build.props", """ @@ -975,7 +977,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -986,18 +988,18 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", projectContents: """ - + netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ // initial props file ("my-properties.props", """ @@ -1006,23 +1008,23 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }, + ], // expected project expectedProjectContents: """ - + netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ // expected props file ("my-properties.props", """ @@ -1031,7 +1033,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -1045,14 +1047,14 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ // initial props files ("Directory.Packages.props", """ @@ -1060,7 +1062,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", true - + @@ -1073,21 +1075,21 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ // expected props files ("Directory.Packages.props", """ @@ -1095,7 +1097,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", true - + @@ -1108,7 +1110,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -1122,14 +1124,14 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ // initial props files ("Directory.Packages.props", """ @@ -1138,7 +1140,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", true $(NewtonsoftJsonVersion) - + @@ -1151,21 +1153,21 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ // expected props files ("Directory.Packages.props", """ @@ -1174,7 +1176,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", true $(NewtonsoftJsonVersion) - + @@ -1187,7 +1189,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -1201,14 +1203,14 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", netstandard2.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ // initial props files ("Directory.Packages.props", """ @@ -1217,7 +1219,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", true $(NewtonsoftJsonVersion) - + @@ -1231,21 +1233,21 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }, + ], // expected expectedProjectContents: """ netstandard2.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ // expected props files ("Directory.Packages.props", """ @@ -1254,7 +1256,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", true $(NewtonsoftJsonVersion) - + @@ -1268,7 +1270,7 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """) - }); + ]); } [Fact] @@ -1347,8 +1349,8 @@ await TestUpdateForProject("Microsoft.Extensions.Http", "2.2.0", "7.0.0", """, - additionalFiles: new[] - { + additionalFiles: + [ ("Versions.props", """ @@ -1357,7 +1359,7 @@ await TestUpdateForProject("Microsoft.Extensions.Http", "2.2.0", "7.0.0", """) - }, + ], expectedProjectContents: """ @@ -1370,8 +1372,8 @@ await TestUpdateForProject("Microsoft.Extensions.Http", "2.2.0", "7.0.0", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Versions.props", """ @@ -1380,7 +1382,7 @@ await TestUpdateForProject("Microsoft.Extensions.Http", "2.2.0", "7.0.0", """) - }); + ]); } [Fact] @@ -1428,8 +1430,8 @@ await TestUpdateForProject("Microsoft.Identity.Web", "2.13.0", "2.13.2", """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ @@ -1444,7 +1446,7 @@ await TestUpdateForProject("Microsoft.Identity.Web", "2.13.0", "2.13.2", """) - }, + ], expectedProjectContents: """ @@ -1458,8 +1460,8 @@ await TestUpdateForProject("Microsoft.Identity.Web", "2.13.0", "2.13.2", """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ @@ -1474,7 +1476,7 @@ await TestUpdateForProject("Microsoft.Identity.Web", "2.13.0", "2.13.2", """) - }); + ]); } [Fact] @@ -1563,13 +1565,13 @@ public async Task AvoidPackageDowngradeWhenUpdatingDependency() await TestUpdateForProject("Microsoft.VisualStudio.Sdk.TestFramework.Xunit", "17.2.7", "17.6.16", projectContents: """ - + $(PreferredTargetFramework) false - + @@ -1581,8 +1583,8 @@ await TestUpdateForProject("Microsoft.VisualStudio.Sdk.TestFramework.Xunit", "17 """, - additionalFiles: new[] - { + additionalFiles: + [ ("Directory.Packages.props", """ @@ -1608,16 +1610,16 @@ await TestUpdateForProject("Microsoft.VisualStudio.Sdk.TestFramework.Xunit", "17 """) - }, + ], expectedProjectContents: """ - + $(PreferredTargetFramework) false - + @@ -1629,8 +1631,8 @@ await TestUpdateForProject("Microsoft.VisualStudio.Sdk.TestFramework.Xunit", "17 """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ ("Directory.Packages.props", """ @@ -1656,7 +1658,7 @@ await TestUpdateForProject("Microsoft.VisualStudio.Sdk.TestFramework.Xunit", "17 """) - }); + ]); } [Fact] @@ -1666,19 +1668,19 @@ await TestUpdateForProject("System.Text.Json", "5.0.0", "5.0.2", isTransitive: t // initial projectContents: """ - + net5.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ // initial props files ("Directory.Packages.props", """ @@ -1690,15 +1692,15 @@ await TestUpdateForProject("System.Text.Json", "5.0.0", "5.0.2", isTransitive: t """) - }, + ], // expected expectedProjectContents: """ - + net5.0 - + @@ -1706,8 +1708,8 @@ await TestUpdateForProject("System.Text.Json", "5.0.0", "5.0.2", isTransitive: t """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ // expected props files ("Directory.Packages.props", """ @@ -1720,7 +1722,7 @@ await TestUpdateForProject("System.Text.Json", "5.0.0", "5.0.2", isTransitive: t """) - }); + ]); } [Fact] @@ -1730,20 +1732,20 @@ await TestUpdateForProject("System.Text.Json", "5.0.0", "5.0.2", isTransitive: t // initial projectContents: """ - + $(NoWarn);NETSDK1138 net5.0 - + """, - additionalFiles: new[] - { + additionalFiles: + [ // initial props files ("Directory.Packages.props", """ @@ -1756,24 +1758,24 @@ await TestUpdateForProject("System.Text.Json", "5.0.0", "5.0.2", isTransitive: t """) - }, + ], // expected expectedProjectContents: """ - + $(NoWarn);NETSDK1138 net5.0 - + """, - additionalFilesExpected: new[] - { + additionalFilesExpected: + [ // expected props files ("Directory.Packages.props", """ @@ -1787,7 +1789,7 @@ await TestUpdateForProject("System.Text.Json", "5.0.0", "5.0.2", isTransitive: t """) - }); + ]); } [Fact] @@ -1795,17 +1797,17 @@ public async Task PropsFileNameWithDifferentCasing() { await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", projectContents: """ - - - net7.0 - - - - - - """, - additionalFiles: new[] - { + + + net7.0 + + + + + + """, + additionalFiles: + [ ("Directory.Build.props", """ @@ -1819,20 +1821,20 @@ await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", """) - }, + ], // no change expectedProjectContents: """ - - - net7.0 - - - - - - """, - additionalFilesExpected: new[] - { + + + net7.0 + + + + + + """, + additionalFilesExpected: + [ // no change ("Directory.Build.props", """ @@ -1847,7 +1849,7 @@ await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", """) - } + ] ); } @@ -1857,25 +1859,25 @@ public async Task VersionAttributeWithDifferentCasing_VersionNumberInline() // the version attribute in the project has an all lowercase name await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", projectContents: """ - - - net7.0 - - - - - - """, + + + net7.0 + + + + + + """, expectedProjectContents: """ - - - net7.0 - - - - - - """ + + + net7.0 + + + + + + """ ); } @@ -1885,17 +1887,17 @@ public async Task VersionAttributeWithDifferentCasing_VersionNumberInProperty() // the version attribute in the project has an all lowercase name await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", projectContents: """ - - - net7.0 - - - - - - """, - additionalFiles: new[] - { + + + net7.0 + + + + + + """, + additionalFiles: + [ ("Directory.Build.props", """ @@ -1908,20 +1910,20 @@ await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", """) - }, + ], // no change expectedProjectContents: """ - - - net7.0 - - - - - - """, - additionalFilesExpected: new[] - { + + + net7.0 + + + + + + """, + additionalFilesExpected: + [ // no change ("Directory.Build.props", """ @@ -1936,7 +1938,7 @@ await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", """) - } + ] ); } @@ -1946,17 +1948,17 @@ public async Task DirectoryPackagesPropsDoesCentralPackagePinningGetsUpdatedIfTr await TestUpdateForProject("xunit.assert", "2.5.2", "2.5.3", isTransitive: true, projectContents: """ - - - net7.0 - - - - - - """, - additionalFiles: new[] - { + + + net7.0 + + + + + + """, + additionalFiles: + [ ("Directory.Packages.props", """ @@ -1969,19 +1971,19 @@ await TestUpdateForProject("xunit.assert", "2.5.2", "2.5.3", """) - }, + ], expectedProjectContents: """ - - - net7.0 - - - - - - """, - additionalFilesExpected: new[] - { + + + net7.0 + + + + + + """, + additionalFilesExpected: + [ ("Directory.Packages.props", """ @@ -1994,7 +1996,7 @@ await TestUpdateForProject("xunit.assert", "2.5.2", "2.5.3", """) - } + ] ); } @@ -2004,17 +2006,17 @@ public async Task DirectoryPackagesPropsDoesNotGetDuplicateEntryIfCentralTransit await TestUpdateForProject("xunit.assert", "2.5.2", "2.5.3", isTransitive: true, projectContents: """ - - - net7.0 - - - - - - """, - additionalFiles: new[] - { + + + net7.0 + + + + + + """, + additionalFiles: + [ ("Directory.Packages.props", """ @@ -2027,19 +2029,19 @@ await TestUpdateForProject("xunit.assert", "2.5.2", "2.5.3", """) - }, + ], expectedProjectContents: """ - - - net7.0 - - - - - - """, - additionalFilesExpected: new[] - { + + + net7.0 + + + + + + """, + additionalFilesExpected: + [ ("Directory.Packages.props", """ @@ -2052,7 +2054,7 @@ await TestUpdateForProject("xunit.assert", "2.5.2", "2.5.3", """) - } + ] ); } @@ -2061,25 +2063,25 @@ public async Task PackageWithFourPartVersionCanBeUpdated() { await TestUpdateForProject("AWSSDK.Core", "3.7.204.13", "3.7.204.14", projectContents: """ - - - net7.0 - - - - - - """, + + + net7.0 + + + + + + """, expectedProjectContents: """ - - - net7.0 - - - - - - """ + + + net7.0 + + + + + + """ ); } @@ -2088,25 +2090,25 @@ public async Task PackageWithOnlyBuildTargetsCanBeUpdated() { await TestUpdateForProject("Microsoft.Windows.Compatibility", "7.0.0", "8.0.0", projectContents: """ - - - net5.0 - - - - - - """, + + + net5.0 + + + + + + """, expectedProjectContents: """ - - - net5.0 - - - - - - """ + + + net5.0 + + + + + + """ ); } @@ -2115,31 +2117,31 @@ public async Task UpdatePackageVersionFromPropertiesWithAndWithoutConditions() { await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", projectContents: """ - - - netstandard2.0 - 7.0.1 - 12.0.1 - 9.0.1 - - - - - - """, + + + netstandard2.0 + 7.0.1 + 12.0.1 + 9.0.1 + + + + + + """, expectedProjectContents: """ - - - netstandard2.0 - 7.0.1 - 13.0.1 - 9.0.1 - - - - - - """ + + + netstandard2.0 + 7.0.1 + 13.0.1 + 9.0.1 + + + + + + """ ); } @@ -2148,29 +2150,29 @@ public async Task UpdatePackageVersionFromPropertyWithConditionCheckingForEmptyS { await TestUpdateForProject("Newtonsoft.Json", "12.0.1", "13.0.1", projectContents: """ - - - netstandard2.0 - 12.0.1 - 9.0.1 - - - - - - """, + + + netstandard2.0 + 12.0.1 + 9.0.1 + + + + + + """, expectedProjectContents: """ - - - netstandard2.0 - 13.0.1 - 9.0.1 - - - - - - """ + + + netstandard2.0 + 13.0.1 + 9.0.1 + + + + + + """ ); } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs index 7268509aa5..87737f4982 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/JsonHelperTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; + using NuGetUpdater.Core.Utilities; + using Xunit; namespace NuGetUpdater.Core.Test.Utilities; @@ -18,8 +20,8 @@ public void UpdateJsonPreservingComments(string json, string[] propertyPath, str public static IEnumerable JsonUpdaterTestData() { - yield return new object[] - { + yield return + [ // json """ { @@ -75,10 +77,10 @@ public static IEnumerable JsonUpdaterTestData() } } """ - }; + ]; - yield return new object[] - { + yield return + [ // json """ { @@ -119,11 +121,11 @@ public static IEnumerable JsonUpdaterTestData() } } """ - }; + ]; // differing case between `propertyPath` and the actual property values - yield return new object[] - { + yield return + [ // json """ { @@ -177,11 +179,11 @@ public static IEnumerable JsonUpdaterTestData() } } """ - }; + ]; // shallow property path - yield return new object[] - { + yield return + [ // original json """ { @@ -209,11 +211,11 @@ public static IEnumerable JsonUpdaterTestData() "path2": "new-value" } """ - }; + ]; // line comment after comma - yield return new object[] - { + yield return + [ // original json """ { @@ -234,6 +236,6 @@ public static IEnumerable JsonUpdaterTestData() "property2": "updated-value" } """ - }; + ]; } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs index 839ba1f23b..7fe941c2bb 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs @@ -167,7 +167,11 @@ public async Task AllPackageDependenciesCanBeTraversed() new("System.Threading.Tasks.Extensions", "4.5.4", DependencyType.Unknown), new("NETStandard.Library", "2.0.3", DependencyType.Unknown), }; - var actualDependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(temp.DirectoryPath, temp.DirectoryPath, "netstandard2.0", new[] { new Dependency("Microsoft.Extensions.Http", "7.0.0", DependencyType.Unknown) }); + var actualDependencies = await MSBuildHelper.GetAllPackageDependenciesAsync( + temp.DirectoryPath, + temp.DirectoryPath, + "netstandard2.0", + [new Dependency("Microsoft.Extensions.Http", "7.0.0", DependencyType.Unknown)]); Assert.Equal(expectedDependencies, actualDependencies); } @@ -251,7 +255,8 @@ public async Task AllPackageDependencies_DoNotTruncateLongDependencyLists() new("MSTest.TestAdapter", "2.1.0", DependencyType.Unknown), new("NETStandard.Library", "2.0.3", DependencyType.Unknown), }; - var packages = new[] { + var packages = new[] + { new Dependency("System", "4.1.311.2", DependencyType.Unknown), new Dependency("System.Core", "3.5.21022.801", DependencyType.Unknown), new Dependency("Moq", "4.16.1", DependencyType.Unknown), @@ -269,12 +274,13 @@ public async Task AllPackageDependencies_DoNotTruncateLongDependencyLists() new Dependency("Newtonsoft.Json", "12.0.1", DependencyType.Unknown) }; var actualDependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(temp.DirectoryPath, temp.DirectoryPath, "netstandard2.0", packages); - for(int i = 0; i < actualDependencies.Length; i++) + for (int i = 0; i < actualDependencies.Length; i++) { var ad = actualDependencies[i]; var ed = expectedDependencies[i]; Assert.Equal(ed, ad); } + Assert.Equal(expectedDependencies, actualDependencies); } @@ -301,7 +307,8 @@ public async Task AllPackageDependencies_DoNotIncludeUpdateOnlyPackages() new("System.Threading.Tasks.Extensions", "4.5.4", DependencyType.Unknown), new("NETStandard.Library", "2.0.3", DependencyType.Unknown), }; - var packages = new[] { + var packages = new[] + { new Dependency("Microsoft.Extensions.Http", "7.0.0", DependencyType.Unknown), new Dependency("Newtonsoft.Json", "12.0.1", DependencyType.Unknown, IsUpdate: true) }; @@ -326,11 +333,16 @@ public async Task AllPackageDependenciesCanBeFoundWithNuGetConfig() Environment.SetEnvironmentVariable("NUGET_HTTP_CACHE_PATH", tempNuGetHttpCacheDirectory); // First validate that we are unable to find dependencies for the package version without a NuGet.config. - var dependenciesNoNuGetConfig = await MSBuildHelper.GetAllPackageDependenciesAsync(temp.DirectoryPath, temp.DirectoryPath, "netstandard2.0", new[] { new Dependency("Microsoft.CodeAnalysis.Common", "4.8.0-3.23457.5", DependencyType.Unknown) }); - Assert.Equal(Array.Empty(), dependenciesNoNuGetConfig); + var dependenciesNoNuGetConfig = await MSBuildHelper.GetAllPackageDependenciesAsync( + temp.DirectoryPath, + temp.DirectoryPath, + "netstandard2.0", + [new Dependency("Microsoft.CodeAnalysis.Common", "4.8.0-3.23457.5", DependencyType.Unknown)]); + Assert.Equal([], dependenciesNoNuGetConfig); // Write the NuGet.config and try again. - await File.WriteAllTextAsync(Path.Combine(temp.DirectoryPath, "NuGet.Config"), """ + await File.WriteAllTextAsync( + Path.Combine(temp.DirectoryPath, "NuGet.Config"), """ @@ -355,7 +367,12 @@ await File.WriteAllTextAsync(Path.Combine(temp.DirectoryPath, "NuGet.Config"), " new("Microsoft.CodeAnalysis.Analyzers", "3.3.4", DependencyType.Unknown), new("NETStandard.Library", "2.0.3", DependencyType.Unknown), }; - var actualDependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(temp.DirectoryPath, temp.DirectoryPath, "netstandard2.0", new[] { new Dependency("Microsoft.CodeAnalysis.Common", "4.8.0-3.23457.5", DependencyType.Unknown) }); + var actualDependencies = await MSBuildHelper.GetAllPackageDependenciesAsync( + temp.DirectoryPath, + temp.DirectoryPath, + "netstandard2.0", + [new Dependency("Microsoft.CodeAnalysis.Common", "4.8.0-3.23457.5", DependencyType.Unknown)] + ); Assert.Equal(expectedDependencies, actualDependencies); } finally @@ -369,8 +386,8 @@ await File.WriteAllTextAsync(Path.Combine(temp.DirectoryPath, "NuGet.Config"), " public static IEnumerable GetTopLevelPackageDependenyInfosTestData() { // simple case - yield return new object[] - { + yield return + [ // build file contents new[] { @@ -387,11 +404,11 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() { new("Newtonsoft.Json", "12.0.1", DependencyType.Unknown) } - }; + ]; // version is a child-node of the package reference - yield return new object[] - { + yield return + [ // build file contents new[] { @@ -410,11 +427,11 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() { new("Newtonsoft.Json", "12.0.1", DependencyType.Unknown) } - }; + ]; // version is in property in same file - yield return new object[] - { + yield return + [ // build file contents new[] { @@ -434,11 +451,11 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() { new("Newtonsoft.Json", "12.0.1", DependencyType.Unknown) } - }; + ]; // version is a property not triggered by a condition - yield return new object[] - { + yield return + [ // build file contents new[] { @@ -460,7 +477,7 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() { new("Newtonsoft.Json", "12.0.1", DependencyType.Unknown) } - }; + ]; // version is a property not triggered by a quoted condition yield return new object[] @@ -489,8 +506,8 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() }; // version is a property with a condition checking for an empty string - yield return new object[] - { + yield return + [ // build file contents new[] { @@ -512,7 +529,7 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() { new("Newtonsoft.Json", "12.0.1", DependencyType.Unknown) } - }; + ]; // version is a property with a quoted condition checking for an empty string yield return new object[] @@ -541,19 +558,19 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() }; // version is set in one file, used in another - yield return new object[] - { + yield return + [ // build file contents new[] { ("Packages.props", """ - - - - - - - """), + + + + + + + """), ("project.csproj", """ @@ -571,11 +588,11 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() new("Azure.Identity", "1.6.0", DependencyType.Unknown), new("Microsoft.Data.SqlClient", "5.1.4", DependencyType.Unknown, IsUpdate: true) } - }; + ]; // version is set in one file, used in another - yield return new object[] - { + yield return + [ // build file contents new[] { @@ -590,13 +607,13 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() """), ("Packages.props", """ - - - - - - - """) + + + + + + + """) }, // expected dependencies new Dependency[] @@ -604,13 +621,13 @@ public static IEnumerable GetTopLevelPackageDependenyInfosTestData() new("Azure.Identity", "1.6.0", DependencyType.Unknown), new("Microsoft.Data.SqlClient", "5.1.4", DependencyType.Unknown, IsUpdate: true) } - }; + ]; } public static IEnumerable SolutionProjectPathTestData() { - yield return new object[] - { + yield return + [ """ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 @@ -644,7 +661,7 @@ public static IEnumerable SolutionProjectPathTestData() { "src/Some.Project/SomeProject.csproj", "src/Some.Project.Test/Some.Project.Test.csproj", - }, - }; + } + ]; } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs index 8b96a52d8a..c0f2048995 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/SdkPackageUpdaterTests.cs @@ -31,7 +31,7 @@ public static IEnumerable GetDependencyUpdates() // Simple case yield return new object[] { - new [] + new[] { (Path: "src/Project.csproj", Content: """ @@ -44,7 +44,7 @@ public static IEnumerable GetDependencyUpdates() """) }, // starting contents - new [] + new[] { (Path: "src/Project.csproj", Content: """ @@ -61,98 +61,104 @@ public static IEnumerable GetDependencyUpdates() }; // Dependency package has version constraint - yield return new object[] - { + yield return + [ new[] { (Path: "src/Project/Project.csproj", Content: """ - - - netstandard2.0 - - - - - - - """), + + + netstandard2.0 + + + + + + + """), }, // starting contents new[] { // If a dependency has a version constraint outside of our new-version, we don't update anything (Path: "src/Project/Project.csproj", Content: """ - - - netstandard2.0 - - - - - - - """), - },// expected contents - "AWSSDK.Core", "3.3.21.19", "3.7.300.20", false // isTransitive - }; + + + netstandard2.0 + + + + + + + """), + }, // expected contents + "AWSSDK.Core", + "3.3.21.19", + "3.7.300.20", + false // isTransitive + ]; // Dependency project has version constraint - yield return new object[] - { + yield return + [ new[] { (Path: "src/Project2/Project2.csproj", Content: """ - - - netstandard2.0 - - - - - - - """), + + + netstandard2.0 + + + + + + + """), (Path: "src/Project/Project.csproj", Content: """ - - - netstandard2.0 - - - - - - """), + + + netstandard2.0 + + + + + + """), }, // starting contents new[] { (Path: "src/Project2/Project2.csproj", Content: """ - - - netstandard2.0 - - - - - - - """), // starting contents + + + netstandard2.0 + + + + + + + """), // starting contents (Path: "src/Project/Project.csproj", Content: """ - - - netstandard2.0 - - - - - - """), - },// expected contents - "Newtonsoft.Json", "12.0.1", "13.0.1", false // isTransitive - }; + + + netstandard2.0 + + + + + + """), + }, // expected contents + "Newtonsoft.Json", + "12.0.1", + "13.0.1", + false // isTransitive + ]; // Multiple references - yield return new object[] - { - new [] + yield return + [ + new[] { (Path: "src/Project.csproj", Content: """ @@ -168,7 +174,7 @@ public static IEnumerable GetDependencyUpdates() """) }, // starting contents - new [] + new[] { (Path: "src/Project.csproj", Content: """ @@ -184,12 +190,16 @@ public static IEnumerable GetDependencyUpdates() """) }, // expected contents - "Newtonsoft.Json", "12.0.1", "13.0.1", false // isTransitive - }; + "Newtonsoft.Json", + "12.0.1", + "13.0.1", + false // isTransitive + ]; // Make sure we don't update if there are incoherent versions - yield return new object[] { - new [] + yield return + [ + new[] { (Path: "src/Project.csproj", Content: """ @@ -218,7 +228,7 @@ public static IEnumerable GetDependencyUpdates() """) }, // starting contents - new [] + new[] { (Path: "src/Project.csproj", Content: """ @@ -247,13 +257,16 @@ public static IEnumerable GetDependencyUpdates() """) }, // expected contents - "Microsoft.EntityFrameworkCore.SqlServer", "2.1.0", "2.2.0", false // isTransitive - }; + "Microsoft.EntityFrameworkCore.SqlServer", + "2.1.0", + "2.2.0", + false // isTransitive + ]; // PackageReference with Version as child element - yield return new object[] - { - new [] + yield return + [ + new[] { (Path: "src/Project.csproj", Content: """ @@ -268,7 +281,7 @@ public static IEnumerable GetDependencyUpdates() """) }, // starting contents - new [] + new[] { (Path: "src/Project.csproj", Content: """ @@ -283,8 +296,11 @@ public static IEnumerable GetDependencyUpdates() """) }, // expected contents - "Newtonsoft.Json", "12.0.1", "13.0.1", false // isTransitive - }; + "Newtonsoft.Json", + "12.0.1", + "13.0.1", + false // isTransitive + ]; } private static void AssertContentsEqual((string Path, string Contents)[] expectedContents, TemporaryDirectory directory) diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs index b057265670..009692c7bf 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/JsonBuildFile.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json; using System.Text.Json.Nodes; using NuGetUpdater.Core.Utilities; @@ -35,7 +36,7 @@ private void ResetNode() { return JsonHelper.ParseNode(Contents); } - catch (System.Text.Json.JsonException ex) + catch (JsonException ex) { // We can't police that people have legal JSON files. // If they don't, we just return null. diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs index c9c3794098..2f4b892141 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/PackagesConfigBuildFile.cs @@ -11,6 +11,7 @@ internal sealed class PackagesConfigBuildFile : XmlBuildFile { public static PackagesConfigBuildFile Open(string repoRootPath, string path) => Parse(repoRootPath, path, File.ReadAllText(path)); + public static PackagesConfigBuildFile Parse(string repoRootPath, string path, string xml) => new(repoRootPath, path, Parser.ParseText(xml)); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs index e20602da46..cb4e79df0d 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs @@ -11,6 +11,7 @@ internal sealed class ProjectBuildFile : XmlBuildFile { public static ProjectBuildFile Open(string repoRootPath, string path) => Parse(repoRootPath, path, File.ReadAllText(path)); + public static ProjectBuildFile Parse(string repoRootPath, string path, string xml) => new(repoRootPath, path, Parser.ParseText(xml)); @@ -31,9 +32,9 @@ public IEnumerable> GetProperties() => PropertyNode .SelectMany(e => e.Elements); public IEnumerable PackageItemNodes => ItemNodes.Where(e => - e.Name.Equals("PackageReference", StringComparison.OrdinalIgnoreCase) || - e.Name.Equals("GlobalPackageReference", StringComparison.OrdinalIgnoreCase) || - e.Name.Equals("PackageVersion", StringComparison.OrdinalIgnoreCase)); + e.Name.Equals("PackageReference", StringComparison.OrdinalIgnoreCase) || + e.Name.Equals("GlobalPackageReference", StringComparison.OrdinalIgnoreCase) || + e.Name.Equals("PackageVersion", StringComparison.OrdinalIgnoreCase)); public IEnumerable GetDependencies() => PackageItemNodes .Select(GetDependency) @@ -42,7 +43,7 @@ public IEnumerable GetDependencies() => PackageItemNodes private static Dependency? GetDependency(IXmlElementSyntax element) { var name = element.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) - ?? element.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase); + ?? element.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase); if (name is null || name.StartsWith("@(")) { return null; diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs index 3dc1b8b4d4..e49f4c9faf 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/CompatabilityChecker.cs @@ -33,6 +33,7 @@ static NuGetFramework ParseFramework(string tfm) // effort by including just the platform. framework = new NuGetFramework(framework.Framework, framework.Version, framework.Platform, FrameworkConstants.EmptyVersion); } + return framework; } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs index adca1b8b79..e3e6642e15 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/FrameworkCompatibilityService.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using NuGet.Frameworks; + using NuGetGallery.Frameworks; namespace NuGetUpdater.Core.FrameworkChecker; @@ -15,7 +16,7 @@ public class FrameworkCompatibilityService private static readonly IReadOnlyList AllSupportedFrameworks = SupportedFrameworks.AllSupportedNuGetFrameworks; private static readonly IReadOnlyDictionary> CompatibilityMatrix = GetCompatibilityMatrix(); - public ISet GetCompatibleFrameworks(IEnumerable packageFrameworks) + public ISet GetCompatibleFrameworks(IEnumerable? packageFrameworks) { if (packageFrameworks == null) { @@ -63,10 +64,14 @@ private static IReadOnlyDictionary> GetComp } } - matrix.Add(SupportedFrameworks.Net60Windows7, - new HashSet() { - SupportedFrameworks.Net60Windows, SupportedFrameworks.Net60Windows7, - SupportedFrameworks.Net70Windows, SupportedFrameworks.Net70Windows7 }); + matrix.Add( + SupportedFrameworks.Net60Windows7, + new HashSet + { + SupportedFrameworks.Net60Windows, SupportedFrameworks.Net60Windows7, + SupportedFrameworks.Net70Windows, SupportedFrameworks.Net70Windows7 + } + ); return matrix; } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs index 6482212558..9ded2bf4f2 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/FrameworkChecker/SupportedFrameworks.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; + using NuGet.Frameworks; + using static NuGet.Frameworks.FrameworkConstants; using static NuGet.Frameworks.FrameworkConstants.CommonFrameworks; @@ -42,7 +44,9 @@ public static class SupportedFrameworks public static readonly NuGetFramework Net70MacCatalyst = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version7, "maccatalyst", EmptyVersion); public static readonly NuGetFramework Net70TvOs = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version7, "tvos", EmptyVersion); public static readonly NuGetFramework Net70Windows = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version7, "windows", EmptyVersion); + public static readonly NuGetFramework Net80 = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version8); // https://github.com/NuGet/Engineering/issues/5112 + public static readonly NuGetFramework Net80Android = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version8, "android", EmptyVersion); public static readonly NuGetFramework Net80Ios = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version8, "ios", EmptyVersion); public static readonly NuGetFramework Net80MacOs = new NuGetFramework(FrameworkIdentifiers.NetCoreApp, Version8, "macos", EmptyVersion); @@ -91,16 +95,16 @@ static SupportedFrameworks() /// public static class TfmFilters { - public static readonly List NetTfms = new List - { + public static readonly List NetTfms = + [ Net80, Net70, Net60, Net50 - }; + ]; - public static readonly List NetCoreAppTfms = new List - { + public static readonly List NetCoreAppTfms = + [ NetCoreApp31, NetCoreApp30, NetCoreApp22, @@ -108,10 +112,10 @@ public static class TfmFilters NetCoreApp20, NetCoreApp11, NetCoreApp10 - }; + ]; - public static readonly List NetStandardTfms = new List - { + public static readonly List NetStandardTfms = + [ NetStandard21, NetStandard20, NetStandard16, @@ -121,10 +125,10 @@ public static class TfmFilters NetStandard12, NetStandard11, NetStandard10 - }; + ]; - public static readonly List NetFrameworkTfms = new List - { + public static readonly List NetFrameworkTfms = + [ Net481, Net48, Net472, @@ -140,7 +144,7 @@ public static class TfmFilters Net35, Net3, Net2 - }; + ]; } } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs index eadec691ff..81fde97a04 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs @@ -7,11 +7,13 @@ using System.Threading.Tasks; using System.Xml.Linq; +using CoreV2::NuGet.Runtime; + using Microsoft.Language.Xml; using NuGet.ProjectManagement; -using AssemblyBinding = CoreV2::NuGet.Runtime.AssemblyBinding; +using Runtime_AssemblyBinding = CoreV2::NuGet.Runtime.AssemblyBinding; namespace NuGetUpdater.Core; @@ -150,7 +152,7 @@ static bool IsConfigFile(IXmlElementSyntax element) var path = Path.GetFileName(content); return (element.Name == "None" && string.Equals(path, "app.config", StringComparison.OrdinalIgnoreCase)) - || (element.Name == "Content" && string.Equals(path, "web.config", StringComparison.OrdinalIgnoreCase)); + || (element.Name == "Content" && string.Equals(path, "web.config", StringComparison.OrdinalIgnoreCase)); } static string GetConfigFileName(XmlDocumentSyntax document) @@ -171,13 +173,13 @@ static string GenerateDefaultAppConfig(XmlDocumentSyntax document) { var frameworkVersion = GetFrameworkVersion(document); return $""" - - - - - - - """; + + + + + + + """; } static string? GetFrameworkVersion(XmlDocumentSyntax document) @@ -190,7 +192,7 @@ static string GenerateDefaultAppConfig(XmlDocumentSyntax document) } } - private static string AddBindingRedirects(ConfigurationFile configFile, IEnumerable bindingRedirects) + private static string AddBindingRedirects(ConfigurationFile configFile, IEnumerable bindingRedirects) { // Do nothing if there are no binding redirects to add, bail out if (!bindingRedirects.Any()) @@ -261,7 +263,7 @@ static void RemoveElement(XElement element) static void UpdateBindingRedirectElement( XElement existingDependentAssemblyElement, - AssemblyBinding newBindingRedirect) + Runtime_AssemblyBinding newBindingRedirect) { var existingBindingRedirectElement = existingDependentAssemblyElement.Element(BindingRedirectName); // Since we've successfully parsed this node, it has to be valid and this child must exist. @@ -287,12 +289,11 @@ static void UpdateBindingRedirectElement( .Elements(DependentAssemblyName); // We're going to need to know which element is associated with what binding for removal - var assemblyElementPairs = from dependentAssemblyElement in dependencyAssemblyElements - select new - { - Binding = AssemblyBinding.Parse(dependentAssemblyElement), - Element = dependentAssemblyElement - }; + var assemblyElementPairs = dependencyAssemblyElements.Select(dependentAssemblyElement => new + { + Binding = Runtime_AssemblyBinding.Parse(dependentAssemblyElement), + Element = dependentAssemblyElement + }); // Return a mapping from binding to element return assemblyElementPairs.ToDictionary(p => (p.Binding.Name, p.Binding.PublicKeyToken), p => p.Element); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs index 12682aa502..79c5764686 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectResolver.cs @@ -8,14 +8,14 @@ using System.Reflection; using System.Text.RegularExpressions; -using AssemblyBinding = CoreV2::NuGet.Runtime.AssemblyBinding; -using IAssembly = CoreV2::NuGet.Runtime.IAssembly; +using Runtime_AssemblyBinding = CoreV2::NuGet.Runtime.AssemblyBinding; +using Runtime_IAssembly = CoreV2::NuGet.Runtime.IAssembly; namespace NuGetUpdater.Core; public static partial class BindingRedirectResolver { - public static IEnumerable GetBindingRedirects(string projectPath, IEnumerable includes) + public static IEnumerable GetBindingRedirects(string projectPath, IEnumerable includes) { var directoryPath = Path.GetDirectoryName(projectPath); if (directoryPath is null) @@ -27,7 +27,7 @@ public static IEnumerable GetBindingRedirects(string projectPat { if (TryParseIncludesString(include, out var assemblyInfo)) { - yield return new AssemblyBinding(assemblyInfo); + yield return new Runtime_AssemblyBinding(assemblyInfo); } } @@ -63,9 +63,9 @@ static bool TryParseIncludesString(string include, [NotNullWhen(true)] out Assem private static readonly Regex IncludesRegex = IncludesPattern(); /// - /// Wraps system type in the nuget interface to interop with nuget apis + /// Wraps system type in the nuget interface to interop with nuget apis /// - private class AssemblyWrapper : IAssembly + private class AssemblyWrapper : Runtime_IAssembly { public AssemblyWrapper(string name, Version version, string? publicKeyToken = null, string? culture = null) { @@ -79,7 +79,7 @@ public AssemblyWrapper(string name, Version version, string? publicKeyToken = nu public Version Version { get; } public string? PublicKeyToken { get; } public string? Culture { get; } - public IEnumerable ReferencedAssemblies { get; } = Enumerable.Empty(); + public IEnumerable ReferencedAssemblies { get; } = Enumerable.Empty(); } [GeneratedRegex("(?\\w+)=(?[^,]+)")] diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs index 9cccf5b2b2..a7aa673a6b 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/DotNetToolsJsonUpdater.cs @@ -1,27 +1,27 @@ using System; using System.Collections.Immutable; -using System.IO; using System.Linq; using System.Threading.Tasks; namespace NuGetUpdater.Core; -internal static partial class DotNetToolsJsonUpdater +internal static class DotNetToolsJsonUpdater { - public static async Task UpdateDependencyAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, Logger logger) + public static async Task UpdateDependencyAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, + Logger logger) { var buildFiles = LoadBuildFiles(repoRootPath, workspacePath, logger); if (buildFiles.Length == 0) { - logger.Log($" No dotnet-tools.json files found."); + logger.Log(" No dotnet-tools.json files found."); return; } - logger.Log($" Updating dotnet-tools.json files."); + logger.Log(" Updating dotnet-tools.json files."); var filesToUpdate = buildFiles.Where(f => - f.GetDependencies().Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))) + f.GetDependencies().Any(d => d.Name.Equals(dependencyName, StringComparison.OrdinalIgnoreCase))) .ToImmutableArray(); if (filesToUpdate.Length == 0) { @@ -39,7 +39,7 @@ public static async Task UpdateDependencyAsync(string repoRootPath, string works if (toolObject is not null && toolObject["version"]?.GetValue() == previousDependencyVersion) { - buildFile.UpdateProperty(new[] { "tools", dependencyName, "version" }, newDependencyVersion); + buildFile.UpdateProperty(["tools", dependencyName, "version"], newDependencyVersion); if (await buildFile.SaveAsync()) { diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs index a507c0fe0f..fa8d9aa99e 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/GlobalJsonUpdater.cs @@ -5,9 +5,15 @@ namespace NuGetUpdater.Core; -internal static partial class GlobalJsonUpdater +internal static class GlobalJsonUpdater { - public static async Task UpdateDependencyAsync(string repoRootPath, string globalJsonPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, Logger logger) + public static async Task UpdateDependencyAsync( + string repoRootPath, + string globalJsonPath, + string dependencyName, + string previousDependencyVersion, + string newDependencyVersion, + Logger logger) { if (!File.Exists(globalJsonPath)) { @@ -29,7 +35,7 @@ public static async Task UpdateDependencyAsync(string repoRootPath, string globa if (globalJsonFile.MSBuildSdks?.TryGetPropertyValue(dependencyName, out var version) != true || version?.GetValue() is not string versionString) { - logger.Log($" Unable to determine dependency version."); + logger.Log(" Unable to determine dependency version."); return; } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs index acdb37c394..28bff1c020 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs @@ -4,16 +4,28 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Xml.Linq; using Microsoft.Language.Xml; + +using NuGet.CommandLine; + using NuGetUpdater.Core.Updater; +using Console = System.Console; + namespace NuGetUpdater.Core; internal static class PackagesConfigUpdater { - public static async Task UpdateDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive, Logger logger) + public static async Task UpdateDependencyAsync( + string repoRootPath, + string projectPath, + string dependencyName, + string previousDependencyVersion, + string newDependencyVersion, + bool isTransitive, + Logger logger + ) { logger.Log($" Found {NuGetHelper.PackagesConfigFileName}; running with NuGet.exe"); @@ -36,18 +48,18 @@ public static async Task UpdateDependencyAsync(string repoRootPath, string proje var packagesDirectory = PathHelper.JoinPath(projectDirectory, packagesSubDirectory); Directory.CreateDirectory(packagesDirectory); - var args = new List() - { - "update", - packagesConfigPath, - "-Id", - dependencyName, - "-Version", - newDependencyVersion, - "-RepositoryPath", - packagesDirectory, - "-NonInteractive", - }; + var args = new List + { + "update", + packagesConfigPath, + "-Id", + dependencyName, + "-Version", + newDependencyVersion, + "-RepositoryPath", + packagesDirectory, + "-NonInteractive", + }; logger.Log(" Finding MSBuild..."); var msbuildDirectory = MSBuildHelper.MSBuildPath; @@ -88,7 +100,7 @@ private static void RunNuget(List args, string packagesDirectory, Logger logger.Log($" Running NuGet.exe with args: {string.Join(" ", args)}"); Environment.CurrentDirectory = packagesDirectory; - var result = NuGet.CommandLine.Program.Main(args.ToArray()); + var result = Program.Main(args.ToArray()); var fullOutput = outputBuilder.ToString(); logger.Log($" Result: {result}"); logger.Log($" Output:\n{fullOutput}"); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs index 61686e0910..4258592fb0 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs @@ -5,16 +5,23 @@ using System.Linq; using System.Threading.Tasks; -using Microsoft.Build.Evaluation; using Microsoft.Language.Xml; using NuGet.Versioning; namespace NuGetUpdater.Core; -internal static partial class SdkPackageUpdater +internal static class SdkPackageUpdater { - public static async Task UpdateDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive, Logger logger) + public static async Task UpdateDependencyAsync( + string repoRootPath, + string projectPath, + string dependencyName, + string previousDependencyVersion, + string newDependencyVersion, + bool isTransitive, + Logger logger + ) { // SDK-style project, modify the XML directly logger.Log(" Running for SDK-style project"); @@ -74,7 +81,7 @@ public static async Task UpdateDependencyAsync(string repoRootPath, string proje // stop update process if we find conflicting package versions var conflictingPackageVersionsFound = false; var packagesAndVersions = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (var (tfm, dependencies) in tfmsAndDependencies) + foreach (var (_, dependencies) in tfmsAndDependencies) { foreach (var (packageName, packageVersion, _, _, _, _) in dependencies) { @@ -118,7 +125,7 @@ public static async Task UpdateDependencyAsync(string repoRootPath, string proje } else { - await UpdateTopLevelDepdendencyAsync(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, packagesAndVersions, logger); + UpdateTopLevelDepdendency(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, packagesAndVersions, logger); } var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependenyInfos(buildFiles); @@ -239,7 +246,14 @@ private static async Task AddTransitiveDependencyAsync(string projectPath, strin } } - private static async Task UpdateTopLevelDepdendencyAsync(ImmutableArray buildFiles, string dependencyName, string previousDependencyVersion, string newDependencyVersion, Dictionary packagesAndVersions, Logger logger) + private static void UpdateTopLevelDepdendency( + ImmutableArray buildFiles, + string dependencyName, + string previousDependencyVersion, + string newDependencyVersion, + IDictionary packagesAndVersions, + Logger logger + ) { var result = TryUpdateDependencyVersion(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, logger); if (result == UpdateResult.NotFound) @@ -254,7 +268,12 @@ private static async Task UpdateTopLevelDepdendencyAsync(ImmutableArray buildFiles, string dependencyName, string? previousDependencyVersion, string newDependencyVersion, Logger logger) + private static UpdateResult TryUpdateDependencyVersion( + ImmutableArray buildFiles, + string dependencyName, + string? previousDependencyVersion, + string newDependencyVersion, + Logger logger) { var foundCorrect = false; var foundUnsupported = false; @@ -275,9 +294,9 @@ private static UpdateResult TryUpdateDependencyVersion(ImmutableArray e.Name.Equals("Version", StringComparison.OrdinalIgnoreCase)) - ?? packageNode.Elements.FirstOrDefault(e => e.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase)); + ?? packageNode.Elements.FirstOrDefault(e => e.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase)); if (versionAttribute is not null) { // Is this the case where version is specified with property substitution? @@ -373,31 +392,29 @@ private static UpdateResult TryUpdateDependencyVersion(ImmutableArray 0) { var updatedXml = buildFile.Contents - .ReplaceNodes(updateNodes, (o, n) => + .ReplaceNodes(updateNodes, (_, n) => { if (n is XmlAttributeSyntax attributeSyntax) { return attributeSyntax.WithValue(attributeSyntax.Value.Replace(previousPackageVersion!, newDependencyVersion)); } - else if (n is XmlElementSyntax elementsSyntax) + + if (n is XmlElementSyntax elementsSyntax) { var modifiedContent = elementsSyntax.GetContentValue().Replace(previousPackageVersion!, newDependencyVersion); var textSyntax = SyntaxFactory.XmlText(SyntaxFactory.Token(null, SyntaxKind.XmlTextLiteralToken, null, modifiedContent)); return elementsSyntax.WithContent(SyntaxFactory.SingletonList(textSyntax)); } - else - { - throw new InvalidDataException($"Unsupported SyntaxType {n.GetType().Name} marked for update"); - } + + throw new InvalidDataException($"Unsupported SyntaxType {n.GetType().Name} marked for update"); }); buildFile.Update(updatedXml); updateWasPerformed = true; @@ -489,10 +506,13 @@ private static UpdateResult TryUpdateDependencyVersion(ImmutableArray FindPackageNodes(ProjectBuildFile buildFile, string packageName) - { - return buildFile.PackageItemNodes.Where(e => - string.Equals(e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase), packageName, StringComparison.OrdinalIgnoreCase) && + private static IEnumerable FindPackageNodes( + ProjectBuildFile buildFile, + string packageName) + => buildFile.PackageItemNodes.Where(e => + string.Equals( + e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase), + packageName, + StringComparison.OrdinalIgnoreCase) && (e.GetAttributeOrSubElementValue("Version", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("VersionOverride", StringComparison.OrdinalIgnoreCase)) is not null); - } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs index 23b7302e22..76d4ac3257 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs @@ -5,7 +5,7 @@ namespace NuGetUpdater.Core; -public partial class UpdaterWorker +public class UpdaterWorker { private readonly Logger _logger; private readonly HashSet _processedGlobalJsonPaths = new(StringComparer.OrdinalIgnoreCase); @@ -51,7 +51,13 @@ public async Task RunAsync(string repoRootPath, string workspacePath, string dep _processedGlobalJsonPaths.Clear(); } - private async Task RunForSolutionAsync(string repoRootPath, string solutionPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive) + private async Task RunForSolutionAsync( + string repoRootPath, + string solutionPath, + string dependencyName, + string previousDependencyVersion, + string newDependencyVersion, + bool isTransitive) { _logger.Log($"Running for solution [{Path.GetRelativePath(repoRootPath, solutionPath)}]"); var projectPaths = MSBuildHelper.GetProjectPathsFromSolution(solutionPath); @@ -61,7 +67,13 @@ private async Task RunForSolutionAsync(string repoRootPath, string solutionPath, } } - private async Task RunForProjFileAsync(string repoRootPath, string projFilePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive) + private async Task RunForProjFileAsync( + string repoRootPath, + string projFilePath, + string dependencyName, + string previousDependencyVersion, + string newDependencyVersion, + bool isTransitive) { _logger.Log($"Running for proj file [{Path.GetRelativePath(repoRootPath, projFilePath)}]"); if (!File.Exists(projFilePath)) @@ -69,6 +81,7 @@ private async Task RunForProjFileAsync(string repoRootPath, string projFilePath, _logger.Log($"File [{projFilePath}] does not exist."); return; } + var projectFilePaths = MSBuildHelper.GetProjectPathsFromProject(projFilePath); foreach (var projectFullPath in projectFilePaths) { @@ -80,12 +93,18 @@ private async Task RunForProjFileAsync(string repoRootPath, string projFilePath, } } - private async Task RunForProjectAsync(string repoRootPath, string projectPath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive) + private async Task RunForProjectAsync( + string repoRootPath, + string projectPath, + string dependencyName, + string previousDependencyVersion, + string newDependencyVersion, + bool isTransitive) { _logger.Log($"Running for project [{projectPath}]"); if (!isTransitive - && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is string globalJsonPath + && MSBuildHelper.GetGlobalJsonPath(repoRootPath, projectPath) is { } globalJsonPath && !_processedGlobalJsonPaths.Contains(globalJsonPath)) { _processedGlobalJsonPaths.Add(globalJsonPath); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs index a04746567e..8468cdb8c5 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/WebApplicationTargetsConditionPatcher.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs index a728a919a5..5b9af19eda 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/XmlFilePreAndPostProcessor.cs @@ -1,4 +1,4 @@ -using System; +using System; using Microsoft.Language.Xml; diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs index 73940bc2d3..1c5683ddd6 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/JsonHelper.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; @@ -11,7 +12,7 @@ namespace NuGetUpdater.Core.Utilities { internal static class JsonHelper { - public static JsonDocumentOptions DocumentOptions { get; } = new JsonDocumentOptions() + public static JsonDocumentOptions DocumentOptions { get; } = new JsonDocumentOptions { CommentHandling = JsonCommentHandling.Skip, }; @@ -24,16 +25,16 @@ internal static class JsonHelper public static string UpdateJsonProperty(string json, string[] propertyPath, string newValue, StringComparison comparisonType = StringComparison.Ordinal) { - var readerOptions = new JsonReaderOptions() + var readerOptions = new JsonReaderOptions { CommentHandling = JsonCommentHandling.Allow, }; var bytes = Encoding.UTF8.GetBytes(json); var reader = new Utf8JsonReader(bytes, readerOptions); using var ms = new MemoryStream(); - var writerOptions = new JsonWriterOptions() + var writerOptions = new JsonWriterOptions { - Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Indented = true, }; var writer = new Utf8JsonWriter(ms, writerOptions); @@ -70,6 +71,7 @@ public static string UpdateJsonProperty(string json, string[] propertyPath, stri // let the default block comment writer handle it writer.WriteCommentValue(reader.GetComment()); } + break; case JsonTokenType.EndArray: writer.WriteEndArray(); @@ -126,7 +128,7 @@ public static string UpdateJsonProperty(string json, string[] propertyPath, stri { currentPath.RemoveAt(currentPath.Count - 1); } - + currentPath[pathDepth] = pathValue; if (IsPathMatch(currentPath, propertyPath, comparisonType)) { @@ -199,6 +201,7 @@ private static string GetCurrentTokenTriviaPrefix(int tokenStartIndex, string or { prefixStart--; } + goto done; default: // found regular character; move forward one and quit @@ -215,8 +218,8 @@ private static string GetCurrentTokenTriviaPrefix(int tokenStartIndex, string or private static bool IsPreceedingCharacterEqual(string originalText, int currentIndex, char expectedCharacter) { return currentIndex > 0 - && currentIndex < originalText.Length - && originalText[currentIndex - 1] == expectedCharacter; + && currentIndex < originalText.Length + && originalText[currentIndex - 1] == expectedCharacter; } } } diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs index 0d302053eb..d0da89e50c 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs @@ -145,11 +145,11 @@ public static IEnumerable GetTopLevelPackageDependenyInfos(Immutable var projectRoot = CreateProjectRootElement(buildFile); foreach (var packageItem in projectRoot.Items - .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference"))) + .Where(i => (i.ItemType == "PackageReference" || i.ItemType == "GlobalPackageReference"))) { var versionSpecification = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value - ?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value - ?? string.Empty; + ?? packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("VersionOverride", StringComparison.OrdinalIgnoreCase))?.Value + ?? string.Empty; foreach (var attributeValue in new[] { packageItem.Include, packageItem.Update }) { if (!string.IsNullOrWhiteSpace(attributeValue)) @@ -175,10 +175,10 @@ public static IEnumerable GetTopLevelPackageDependenyInfos(Immutable } foreach (var packageItem in projectRoot.Items - .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include))) + .Where(i => i.ItemType == "PackageVersion" && !string.IsNullOrEmpty(i.Include))) { packageVersionInfo[packageItem.Include] = packageItem.Metadata.FirstOrDefault(m => m.Name.Equals("Version", StringComparison.OrdinalIgnoreCase))?.Value - ?? string.Empty; + ?? string.Empty; } foreach (var property in projectRoot.Properties) @@ -286,7 +286,12 @@ private static ProjectRootElement CreateProjectRootElement(ProjectBuildFile buil return projectRoot; } - private static async Task CreateTempProjectAsync(DirectoryInfo tempDir, string repoRoot, string projectPath, string targetFramework, Dependency[] packages) + private static async Task CreateTempProjectAsync( + DirectoryInfo tempDir, + string repoRoot, + string projectPath, + string targetFramework, + IReadOnlyCollection packages) { var projectDirectory = Path.GetDirectoryName(projectPath); projectDirectory ??= repoRoot; @@ -300,39 +305,40 @@ private static async Task CreateTempProjectAsync(DirectoryInfo tempDir, var packageReferences = string.Join( Environment.NewLine, packages - .Where(p => !string.IsNullOrWhiteSpace(p.Version)) // empty `Version` attributes will cause the temporary project to not build + // empty `Version` attributes will cause the temporary project to not build + .Where(p => !string.IsNullOrWhiteSpace(p.Version)) // If all PackageReferences for a package are update-only mark it as such, otherwise it can cause package incoherence errors which do not exist in the repo. .Select(static p => $"")); var projectContents = $""" - - - {targetFramework} - true - false - - - {packageReferences} - - - - <_NuGetPackageData Include="@(NativeCopyLocalItems)" /> - <_NuGetPackageData Include="@(ResourceCopyLocalItems)" /> - <_NuGetPackageData Include="@(RuntimeCopyLocalItems)" /> - <_NuGetPackageData Include="@(ResolvedAnalyzers)" /> - <_NuGetPackageData Include="@(_PackageDependenciesDesignTime)"> - %(_PackageDependenciesDesignTime.Name) - %(_PackageDependenciesDesignTime.Version) - - - - - - - - """; + + + {targetFramework} + true + false + + + {packageReferences} + + + + <_NuGetPackageData Include="@(NativeCopyLocalItems)" /> + <_NuGetPackageData Include="@(ResourceCopyLocalItems)" /> + <_NuGetPackageData Include="@(RuntimeCopyLocalItems)" /> + <_NuGetPackageData Include="@(ResolvedAnalyzers)" /> + <_NuGetPackageData Include="@(_PackageDependenciesDesignTime)"> + %(_PackageDependenciesDesignTime.Name) + %(_PackageDependenciesDesignTime.Version) + + + + + + + + """; var tempProjectPath = Path.Combine(tempDir.FullName, "Project.csproj"); await File.WriteAllTextAsync(tempProjectPath, projectContents); @@ -345,7 +351,7 @@ private static async Task CreateTempProjectAsync(DirectoryInfo tempDir, } internal static async Task GetAllPackageDependenciesAsync( - string repoRoot, string projectPath, string targetFramework, Dependency[] packages, Logger? logger = null) + string repoRoot, string projectPath, string targetFramework, IReadOnlyCollection packages, Logger? logger = null) { var tempDirectory = Directory.CreateTempSubdirectory("package-dependency-resolution_"); try @@ -369,7 +375,7 @@ internal static async Task GetAllPackageDependenciesAsync( else { logger?.Log($"dotnet build in {nameof(GetAllPackageDependenciesAsync)} failed. STDOUT: {stdout} STDERR: {stderr}"); - return Array.Empty(); + return []; } } finally @@ -391,7 +397,7 @@ internal static async Task GetAllPackageDependenciesAsync( internal static async Task> LoadBuildFiles(string repoRootPath, string projectPath) { - var buildFileList = new List() + var buildFileList = new List { projectPath.NormalizePathToUnix() // always include the starting project }; @@ -410,12 +416,12 @@ internal static async Task> LoadBuildFiles(stri // create a safe version with only certain top-level keys var globalJsonContent = await File.ReadAllTextAsync(safeGlobalJsonName); var json = JsonHelper.ParseNode(globalJsonContent); - var sdks = json["msbuild-sdks"]; + var sdks = json?["msbuild-sdks"]; if (sdks is not null) { var newObject = new Dictionary() { - { "msbuild-sdks", sdks } + ["msbuild-sdks"] = sdks, }; var newContent = JsonSerializer.Serialize(newObject); await File.WriteAllTextAsync(globalJsonPath, newContent); @@ -427,7 +433,7 @@ internal static async Task> LoadBuildFiles(stri // load the project even if it imports a file that doesn't exist (e.g. a file that's generated at restore // or build time). using var projectCollection = new ProjectCollection(); // do this in a one-off instance and don't pollute the global collection - var project = Project.FromFile(projectPath, new ProjectOptions() + var project = Project.FromFile(projectPath, new ProjectOptions { LoadSettings = ProjectLoadSettings.IgnoreMissingImports, ProjectCollection = projectCollection, diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs index 6056a45aa1..1a0bf60fed 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs @@ -10,6 +10,7 @@ internal static class PathHelper { MatchCasing = MatchCasing.CaseInsensitive, }; + private static readonly EnumerationOptions _caseSensitiveEnumerationOptions = new() { MatchCasing = MatchCasing.CaseSensitive, diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs index 39fccb0d33..c7b5da8758 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs @@ -45,14 +45,14 @@ public static class ProcessEx // than enter right back into the Process type and start a wait which isn't guaranteed to be safe. Task.Run(() => { - redirectInitiated.Wait(); - redirectInitiated.Dispose(); - redirectInitiated = null; + redirectInitiated.Wait(); + redirectInitiated.Dispose(); + redirectInitiated = null; - process.WaitForExit(); + process.WaitForExit(); - tcs.TrySetResult((process.ExitCode, stdout.ToString(), stderr.ToString())); - process.Dispose(); + tcs.TrySetResult((process.ExitCode, stdout.ToString(), stderr.ToString())); + process.Dispose(); }); }; diff --git a/nuget/spec/dependabot/nuget/native_helpers_spec.rb b/nuget/spec/dependabot/nuget/native_helpers_spec.rb index 3e85a02f03..1939fb039f 100644 --- a/nuget/spec/dependabot/nuget/native_helpers_spec.rb +++ b/nuget/spec/dependabot/nuget/native_helpers_spec.rb @@ -45,4 +45,35 @@ end end end + + describe "#native_csharp_format" do + let(:command) do + [ + "dotnet", + "format", + lib_path, + "--exclude", + except_path, + "--verify-no-changes", + "-v", + "diag" + ].join(" ") + end + + subject(:dotnet_test) do + Dependabot::SharedHelpers.run_shell_command(command) + end + + context "`dotnet format NuGetUpdater` output" do + let(:lib_path) do + File.absolute_path(File.join("helpers", "lib", "NuGetUpdater")) + end + + let(:except_path) { "helpers/lib/NuGet.Client" } + + it "contains the expected output" do + expect(dotnet_test).to include("Format complete") + end + end + end end From 3aa1e0cfdb71e46ab33915830e46f078b6bbfed0 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Wed, 7 Feb 2024 15:15:22 -0800 Subject: [PATCH 17/49] Filter out NuGet feeds which don't have URLs (#9011) * Filter out NuGet feeds which don't have URLs or tokens * Update nuget/lib/dependabot/nuget/update_checker/repository_finder.rb Co-authored-by: Brett V. Forsgren --------- Co-authored-by: Brett V. Forsgren --- nuget/lib/dependabot/nuget/update_checker/repository_finder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb b/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb index 6f571c69ad..ea1ca79055 100644 --- a/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb +++ b/nuget/lib/dependabot/nuget/update_checker/repository_finder.rb @@ -195,7 +195,7 @@ def handle_timeout(repo_metadata_url:) def credential_repositories @credential_repositories ||= credentials - .select { |cred| cred["type"] == "nuget_feed" } + .select { |cred| cred["type"] == "nuget_feed" && cred["url"] } .map { |c| { url: c.fetch("url"), token: c["token"] } } end From f87f1a7109c0183d526eb9d79df54d2c44cbed98 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Thu, 8 Feb 2024 10:47:32 -0700 Subject: [PATCH 18/49] only consider a package a development dependency if it doesn't have any other regular dependencies (#9017) Co-authored-by: AbdulFattaah Popoola --- .../update_checker/compatibility_checker.rb | 20 +++- .../compatibility_checker_spec.rb | 112 ++++++++++++++---- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/nuget/lib/dependabot/nuget/update_checker/compatibility_checker.rb b/nuget/lib/dependabot/nuget/update_checker/compatibility_checker.rb index fbd8c7e917..60252c755c 100644 --- a/nuget/lib/dependabot/nuget/update_checker/compatibility_checker.rb +++ b/nuget/lib/dependabot/nuget/update_checker/compatibility_checker.rb @@ -21,9 +21,10 @@ def compatible?(version) nuspec_xml = NuspecFetcher.fetch_nuspec(dependency_urls, dependency.name, version) return false unless nuspec_xml - # development dependencies are packages such as analyzers which need to be - # compatible with the compiler not the project itself. - return true if development_dependency?(nuspec_xml) + # development dependencies are packages such as analyzers which need to be compatible with the compiler not the + # project itself, but some packages that report themselves as development dependencies still contain target + # framework dependencies and should be checked for compatibility through the regular means + return true if pure_development_dependency?(nuspec_xml) package_tfms = parse_package_tfms(nuspec_xml) package_tfms = fetch_package_tfms(version) if package_tfms.empty? @@ -40,11 +41,18 @@ def compatible?(version) attr_reader :dependency_urls, :dependency, :tfm_finder - def development_dependency?(nuspec_xml) + def pure_development_dependency?(nuspec_xml) contents = nuspec_xml.at_xpath("package/metadata/developmentDependency")&.content&.strip - return false unless contents + return false unless contents # no `developmentDependency` element - contents.casecmp("true").zero? + self_reports_as_development_dependency = contents.casecmp?("true") + return false unless self_reports_as_development_dependency + + # even though a package self-reports as a development dependency, it might not be if it has dependency groups + # with a target framework + dependency_groups_with_target_framework = + nuspec_xml.at_xpath("/package/metadata/dependencies/group[@targetFramework]") + dependency_groups_with_target_framework.to_a.empty? end def parse_package_tfms(nuspec_xml) diff --git a/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb b/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb index 0e0e8a0d0e..3e9ef7609f 100644 --- a/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb +++ b/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb @@ -74,41 +74,109 @@ context "#compatible?" do subject(:compatible) { checker.compatible?(version) } - context "when the `.nuspec` has groups without a `targetFramework` attribute" do - let(:version) { "5.0.3" } + before do + stub_request(:get, "https://api.nuget.org/v3/registration5-gz-semver2/microsoft.appcenter.crashes/index.json") + .to_return( + status: 200, + body: { + items: [ + items: [ + { + catalogEntry: { + listed: true, + version: "5.0.2" + } + }, + { + catalogEntry: { + listed: true, + version: "5.0.3" + } + } + ] + ] + }.to_json + ) + end + + context "when the `.nuspec` reports itself as a development dependency, but still has regular dependencies" do + let(:csproj_body) do + <<~XML + + + net6.0 + + + + + + XML + end before do + nuspec502 = + <<~XML + + + Microsoft.AppCenter.Crashes + 5.0.2 + true + + + + + + + XML + nuspec503 = nuspec502.gsub("5.0.2", "5.0.3") + nuspec601 = nuspec502.gsub("5.0.2", "6.0.1").gsub("net6.0", "net8.0") stub_request(:get, "https://api.nuget.org/v3-flatcontainer/microsoft.appcenter.crashes/5.0.2/microsoft.appcenter.crashes.nuspec") .to_return( status: 200, - body: fixture("nuspecs", "Microsoft.AppCenter.Crashes_faked.nuspec") + body: nuspec502 ) stub_request(:get, "https://api.nuget.org/v3-flatcontainer/microsoft.appcenter.crashes/5.0.3/microsoft.appcenter.crashes.nuspec") + .to_return( + status: 200, + body: nuspec503 + ) + stub_request(:get, "https://api.nuget.org/v3-flatcontainer/microsoft.appcenter.crashes/6.0.1/microsoft.appcenter.crashes.nuspec") + .to_return( + status: 200, + body: nuspec601 + ) + end + + context "with a targetFramework compatible version" do + let(:version) { "5.0.3" } + + it "returns the correct data" do + expect(compatible).to be_truthy + end + end + + context "with a targetFramework non-compatible version" do + let(:version) { "6.0.1" } + + it "returns the correct data" do + expect(compatible).to be_falsey + end + end + end + + context "when the `.nuspec` has groups without a `targetFramework` attribute" do + let(:version) { "5.0.3" } + + before do + stub_request(:get, "https://api.nuget.org/v3-flatcontainer/microsoft.appcenter.crashes/5.0.2/microsoft.appcenter.crashes.nuspec") .to_return( status: 200, body: fixture("nuspecs", "Microsoft.AppCenter.Crashes_faked.nuspec") ) - stub_request(:get, "https://api.nuget.org/v3/registration5-gz-semver2/microsoft.appcenter.crashes/index.json") + stub_request(:get, "https://api.nuget.org/v3-flatcontainer/microsoft.appcenter.crashes/5.0.3/microsoft.appcenter.crashes.nuspec") .to_return( status: 200, - body: { - items: [ - items: [ - { - catalogEntry: { - listed: true, - version: "5.0.2" - } - }, - { - catalogEntry: { - listed: true, - version: "5.0.3" - } - } - ] - ] - }.to_json + body: fixture("nuspecs", "Microsoft.AppCenter.Crashes_faked.nuspec") ) end From 4d664bbc090cc962454a56e26a87a0974cf9d168 Mon Sep 17 00:00:00 2001 From: "Brett V. Forsgren" Date: Thu, 8 Feb 2024 18:07:23 -0700 Subject: [PATCH 19/49] allow folllowing HTTP 307 when resolving `.nupkg` contents (#9022) --- .../nuget/update_checker/nupkg_fetcher.rb | 2 +- .../update_checker/nupkg_fetcher_spec.rb | 39 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/nuget/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb b/nuget/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb index 7b8503da6a..2f937e088e 100644 --- a/nuget/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb +++ b/nuget/lib/dependabot/nuget/update_checker/nupkg_fetcher.rb @@ -73,7 +73,7 @@ def self.fetch_stream(stream_url, auth_header, max_redirects = 5) response_block: response_block ) - if response.status == 303 + if response.status == 303 || response.status == 307 current_redirects += 1 return nil if current_redirects > max_redirects diff --git a/nuget/spec/dependabot/nuget/update_checker/nupkg_fetcher_spec.rb b/nuget/spec/dependabot/nuget/update_checker/nupkg_fetcher_spec.rb index 8e44452e25..f631503e8b 100644 --- a/nuget/spec/dependabot/nuget/update_checker/nupkg_fetcher_spec.rb +++ b/nuget/spec/dependabot/nuget/update_checker/nupkg_fetcher_spec.rb @@ -5,6 +5,7 @@ require "dependabot/dependency" require "dependabot/dependency_file" require "dependabot/nuget/update_checker/nupkg_fetcher" +require "dependabot/nuget/update_checker/repository_finder" RSpec.describe Dependabot::Nuget::NupkgFetcher do describe "#fetch_nupkg_url_from_repository" do @@ -81,4 +82,42 @@ it { is_expected.to eq("https://nuget.pkg.github.com/some-namespace/download/newtonsoft.json/13.0.1/newtonsoft.json.13.0.1.nupkg") } end end + + describe "#fetch_nupkg_buffer" do + let(:package_id) { "Newtonsoft.Json" } + let(:package_version) { "13.0.1" } + let(:repository_details) { Dependabot::Nuget::RepositoryFinder.get_default_repository_details(package_id) } + let(:dependency_urls) { [repository_details] } + subject(:nupkg_buffer) do + described_class.fetch_nupkg_buffer(dependency_urls, package_id, package_version) + end + + before do + stub_request(:get, "https://api.nuget.org/v3-flatcontainer/newtonsoft.json/13.0.1/newtonsoft.json.13.0.1.nupkg") + .to_return( + status: 303, + headers: { + "Location" => "https://api.nuget.org/redirect-on-303" + }, + body: "not the final contents" + ) + stub_request(:get, "https://api.nuget.org/redirect-on-303") + .to_return( + status: 307, + headers: { + "Location" => "https://api.nuget.org/redirect-on-307" + }, + body: "almost final contents" + ) + stub_request(:get, "https://api.nuget.org/redirect-on-307") + .to_return( + status: 200, + body: "the final contents" + ) + end + + it "fetches the nupkg after multiple redirects" do + expect(nupkg_buffer.string).to eq("the final contents") + end + end end From 18b1f54d21b45248e44a8871daa6cea5c5b7f5c8 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Fri, 9 Feb 2024 15:14:15 -0600 Subject: [PATCH 20/49] add types to DependencyChange (#8999) --- updater/lib/dependabot/api_client.rb | 2 - updater/lib/dependabot/dependency_change.rb | 62 ++++++++++++++----- .../dependabot/dependency_change_builder.rb | 58 ++++++++++++++--- updater/lib/dependabot/service.rb | 4 +- updater/spec/dependabot/api_client_spec.rb | 10 ++- .../spec/dependabot/dependency_change_spec.rb | 13 +++- updater/spec/dependabot/service_spec.rb | 37 ++++++++++- 7 files changed, 149 insertions(+), 37 deletions(-) diff --git a/updater/lib/dependabot/api_client.rb b/updater/lib/dependabot/api_client.rb index a85a873d84..3e68699c56 100644 --- a/updater/lib/dependabot/api_client.rb +++ b/updater/lib/dependabot/api_client.rb @@ -290,8 +290,6 @@ def create_pull_request_data(dependency_change, base_commit_sha) "base-commit-sha": base_commit_sha }.merge(dependency_group_hash(dependency_change)) - return data unless dependency_change.pr_message - data["commit-message"] = dependency_change.pr_message.commit_message data["pr-title"] = dependency_change.pr_message.pr_name data["pr-body"] = dependency_change.pr_message.pr_message diff --git a/updater/lib/dependabot/dependency_change.rb b/updater/lib/dependabot/dependency_change.rb index bba8ffba09..529e83e88d 100644 --- a/updater/lib/dependabot/dependency_change.rb +++ b/updater/lib/dependabot/dependency_change.rb @@ -1,6 +1,8 @@ -# typed: true +# typed: strict # frozen_string_literal: true +require "sorbet-runtime" + # This class describes a change to the project's Dependencies which has been # determined by a Dependabot operation. # @@ -12,19 +14,42 @@ # by adapters to create a Pull Request, apply the changes on disk, etc. module Dependabot class DependencyChange - attr_reader :job, :updated_dependencies, :updated_dependency_files, :dependency_group + extend T::Sig + + sig { returns(Dependabot::Job) } + attr_reader :job + + sig { returns(T::Array[Dependabot::Dependency]) } + attr_reader :updated_dependencies + + sig { returns(T::Array[Dependabot::DependencyFile]) } + attr_reader :updated_dependency_files + sig { returns(T.nilable(Dependabot::DependencyGroup)) } + attr_reader :dependency_group + + sig do + params( + job: Dependabot::Job, + updated_dependencies: T::Array[Dependabot::Dependency], + updated_dependency_files: T::Array[Dependabot::DependencyFile], + dependency_group: T.nilable(Dependabot::DependencyGroup) + ).void + end def initialize(job:, updated_dependencies:, updated_dependency_files:, dependency_group: nil) @job = job @updated_dependencies = updated_dependencies @updated_dependency_files = updated_dependency_files @dependency_group = dependency_group + + @pr_message = T.let(nil, T.nilable(Dependabot::PullRequestCreator::Message)) end + sig { returns(Dependabot::PullRequestCreator::Message) } def pr_message - return @pr_message if defined?(@pr_message) + return @pr_message unless @pr_message.nil? - case job.source&.provider + case job.source.provider when "github" pr_message_max_length = Dependabot::PullRequestCreator::Github::PR_DESCRIPTION_MAX_LENGTH when "azure" @@ -38,7 +63,7 @@ def pr_message pr_message_max_length = Dependabot::PullRequestCreator::Github::PR_DESCRIPTION_MAX_LENGTH end - @pr_message = Dependabot::PullRequestCreator::MessageBuilder.new( + message = Dependabot::PullRequestCreator::MessageBuilder.new( source: job.source, dependencies: updated_dependencies, files: updated_dependency_files, @@ -49,18 +74,23 @@ def pr_message pr_message_encoding: pr_message_encoding, ignore_conditions: job.ignore_conditions ).message + + @pr_message = message end + sig { returns(String) } def humanized updated_dependencies.map do |dependency| "#{dependency.name} ( from #{dependency.humanized_previous_version} to #{dependency.humanized_version} )" end.join(", ") end + sig { returns(T::Array[T::Hash[String, T.untyped]]) } def updated_dependency_files_hash updated_dependency_files.map(&:to_h) end + sig { returns(T::Boolean) } def grouped_update? !!dependency_group end @@ -72,19 +102,17 @@ def grouped_update? # rather than supersede it as the new changes don't necessarily follow # from the previous ones; dependencies could have been removed from the # project, or pinned by other changes. + sig { returns(T::Boolean) } def should_replace_existing_pr? return false unless job.updating_a_pull_request? # NOTE: Gradle, Maven and Nuget dependency names can be case-insensitive # and the dependency name injected from a security advisory often doesn't # match what users have specified in their manifest. - updated_dependencies.map { |x| x.name.downcase }.uniq.sort != job.dependencies.map(&:downcase).uniq.sort - end - - def matches_existing_pr? - !!existing_pull_request + updated_dependencies.map { |x| x.name.downcase }.uniq.sort != T.must(job.dependencies).map(&:downcase).uniq.sort end + sig { params(dependency_changes: T::Array[DependencyChange]).void } def merge_changes!(dependency_changes) dependency_changes.each do |dependency_change| updated_dependencies.concat(dependency_change.updated_dependencies) @@ -94,20 +122,22 @@ def merge_changes!(dependency_changes) updated_dependency_files.compact! end - private - - def existing_pull_request + sig { returns(T::Boolean) } + def matches_existing_pr? if grouped_update? # We only want PRs for the same group that have the same versions - job.existing_group_pull_requests.find do |pr| - pr["dependency-group-name"] == dependency_group.name && + job.existing_group_pull_requests.any? do |pr| + pr["dependency-group-name"] == dependency_group&.name && Set.new(pr["dependencies"]) == updated_dependencies_set end else - job.existing_pull_requests.find { |pr| Set.new(pr) == updated_dependencies_set } + job.existing_pull_requests.any? { |pr| Set.new(pr) == updated_dependencies_set } end end + private + + sig { returns(T::Set[T::Hash[String, T.any(String, T::Boolean)]]) } def updated_dependencies_set Set.new( updated_dependencies.map do |dep| diff --git a/updater/lib/dependabot/dependency_change_builder.rb b/updater/lib/dependabot/dependency_change_builder.rb index 620fe9b3cc..6e09dcf217 100644 --- a/updater/lib/dependabot/dependency_change_builder.rb +++ b/updater/lib/dependabot/dependency_change_builder.rb @@ -1,6 +1,7 @@ -# typed: false +# typed: strong # frozen_string_literal: true +require "sorbet-runtime" require "dependabot/dependency" require "dependabot/dependency_change" require "dependabot/file_updaters" @@ -22,15 +23,39 @@ # a DependencyGroup module Dependabot class DependencyChangeBuilder - def self.create_from(**kwargs) - new(**kwargs).run + extend T::Sig + + sig do + params( + job: Dependabot::Job, + dependency_files: T::Array[Dependabot::DependencyFile], + updated_dependencies: T::Array[Dependabot::Dependency], + change_source: T.any(Dependabot::Dependency, Dependabot::DependencyGroup) + ).returns(Dependabot::DependencyChange) + end + def self.create_from(job:, dependency_files:, updated_dependencies:, change_source:) + new( + job: job, + dependency_files: dependency_files, + updated_dependencies: updated_dependencies, + change_source: change_source + ).run end + sig do + params( + job: Dependabot::Job, + dependency_files: T::Array[Dependabot::DependencyFile], + updated_dependencies: T::Array[Dependabot::Dependency], + change_source: T.any(Dependabot::Dependency, Dependabot::DependencyGroup) + ).void + end def initialize(job:, dependency_files:, updated_dependencies:, change_source:) @job = job dir = Pathname.new(job.source.directory).cleanpath - @dependency_files = dependency_files.select { |f| Pathname.new(f.directory).cleanpath == dir } + @dependency_files = T.let(dependency_files.select { |f| Pathname.new(f.directory).cleanpath == dir }, + T::Array[Dependabot::DependencyFile]) raise "Missing directory in dependency files: #{dir}" unless @dependency_files.any? @@ -38,6 +63,7 @@ def initialize(job:, dependency_files:, updated_dependencies:, change_source:) @change_source = change_source end + sig { returns(Dependabot::DependencyChange) } def run updated_files = generate_dependency_files raise DependabotError, "FileUpdater failed" unless updated_files.any? @@ -52,7 +78,7 @@ def run d.version == d.previous_version end - updated_deps.each { |d| d.metadata[:directory] = job.source.directory } if job.source&.directory + updated_deps.each { |d| d.metadata[:directory] = job.source.directory } if job.source.directory Dependabot::DependencyChange.new( job: job, @@ -64,23 +90,36 @@ def run private - attr_reader :job, :dependency_files, :updated_dependencies, :change_source + sig { returns(Dependabot::Job) } + attr_reader :job + + sig { returns(T::Array[Dependabot::DependencyFile]) } + attr_reader :dependency_files + + sig { returns(T::Array[Dependabot::Dependency]) } + attr_reader :updated_dependencies + + sig { returns(T.any(Dependabot::Dependency, Dependabot::DependencyGroup)) } + attr_reader :change_source + sig { returns(T.nilable(String)) } def source_dependency_name return nil unless change_source.is_a? Dependabot::Dependency - change_source.name + T.cast(change_source, Dependabot::Dependency).name end + sig { returns(T.nilable(Dependabot::DependencyGroup)) } def source_dependency_group return nil unless change_source.is_a? Dependabot::DependencyGroup - change_source + T.cast(change_source, Dependabot::DependencyGroup) end + sig { returns(T::Array[Dependabot::DependencyFile]) } def generate_dependency_files if updated_dependencies.count == 1 - updated_dependency = updated_dependencies.first + updated_dependency = T.must(updated_dependencies.first) Dependabot.logger.info("Updating #{updated_dependency.name} from " \ "#{updated_dependency.previous_version} to " \ "#{updated_dependency.version}") @@ -96,6 +135,7 @@ def generate_dependency_files file_updater_for(relevant_dependencies).updated_dependency_files end + sig { params(dependencies: T::Array[Dependabot::Dependency]).returns(Dependabot::FileUpdaters::Base) } def file_updater_for(dependencies) Dependabot::FileUpdaters.for_package_manager(job.package_manager).new( dependencies: dependencies, diff --git a/updater/lib/dependabot/service.rb b/updater/lib/dependabot/service.rb index 7de1f7ab56..2bdf9c98f9 100644 --- a/updater/lib/dependabot/service.rb +++ b/updater/lib/dependabot/service.rb @@ -19,7 +19,7 @@ class Service extend T::Sig extend Forwardable - sig { returns(T::Array[T::Array[String]]) } + sig { returns(T::Array[T.untyped]) } attr_reader :pull_requests sig { returns(T::Array[T::Array[T.untyped]]) } @@ -162,7 +162,7 @@ def pull_request_summary T.unsafe(Terminal::Table).new do |t| t.title = "Changes to Dependabot Pull Requests" - t.rows = pull_requests.map { |deps, action| [action, truncate(T.must(deps))] } + t.rows = pull_requests.map { |deps, action| [action, truncate(deps)] } end end diff --git a/updater/spec/dependabot/api_client_spec.rb b/updater/spec/dependabot/api_client_spec.rb index 4e8219e253..9f99ae7c46 100644 --- a/updater/spec/dependabot/api_client_spec.rb +++ b/updater/spec/dependabot/api_client_spec.rb @@ -20,9 +20,12 @@ updated_dependency_files: dependency_files ) end + let(:source) do + instance_double(Dependabot::Source, provider: "github", repo: "gocardless/bump", directory: "/") + end let(:job) do instance_double(Dependabot::Job, - source: nil, + source: source, credentials: [], commit_message_options: [], updating_a_pull_request?: false, @@ -219,9 +222,12 @@ updated_dependency_files: dependency_files ) end + let(:source) do + instance_double(Dependabot::Source, provider: "github", repo: "gocardless/bump", directory: "/") + end let(:job) do instance_double(Dependabot::Job, - source: nil, + source: source, credentials: [], commit_message_options: [], updating_a_pull_request?: true) diff --git a/updater/spec/dependabot/dependency_change_spec.rb b/updater/spec/dependabot/dependency_change_spec.rb index d615e36369..e8c26f850f 100644 --- a/updater/spec/dependabot/dependency_change_spec.rb +++ b/updater/spec/dependabot/dependency_change_spec.rb @@ -86,7 +86,14 @@ end let(:message_builder_mock) do - instance_double(Dependabot::PullRequestCreator::MessageBuilder, message: "Hello World!") + instance_double( + Dependabot::PullRequestCreator::MessageBuilder, + message: Dependabot::PullRequestCreator::Message.new( + pr_name: "Title", + pr_message: "Hello World!", + commit_message: "Commit message" + ) + ) end before do @@ -110,7 +117,7 @@ ignore_conditions: [] ) - expect(dependency_change.pr_message).to eql("Hello World!") + expect(dependency_change.pr_message.pr_message).to eql("Hello World!") end context "when a dependency group is assigned" do @@ -137,7 +144,7 @@ ignore_conditions: [] ) - expect(dependency_change.pr_message).to eql("Hello World!") + expect(dependency_change.pr_message&.pr_message).to eql("Hello World!") end end end diff --git a/updater/spec/dependabot/service_spec.rb b/updater/spec/dependabot/service_spec.rb index 283e096216..a487d450fb 100644 --- a/updater/spec/dependabot/service_spec.rb +++ b/updater/spec/dependabot/service_spec.rb @@ -25,12 +25,25 @@ allow(api_client).to receive(:is_a?).with(Dependabot::ApiClient).and_return(true) api_client end + subject(:service) { described_class.new(client: mock_client) } shared_context :a_pr_was_created do + let(:source) do + instance_double(Dependabot::Source, provider: "github", repo: "dependabot/dependabot-core", directory: "/") + end + + let(:job) do + instance_double(Dependabot::Job, + source: source, + credentials: [], + commit_message_options: [], + ignore_conditions: []) + end + let(:dependency_change) do Dependabot::DependencyChange.new( - job: instance_double(Dependabot::Job, source: nil, credentials: [], commit_message_options: []), + job: job, updated_dependencies: dependencies, updated_dependency_files: dependency_files ) @@ -74,16 +87,34 @@ before do allow(Dependabot::PullRequestCreator::MessageBuilder) - .to receive_message_chain(:new, :message).and_return(pr_message) + .to receive_message_chain(:new, :message).and_return( + Dependabot::PullRequestCreator::Message.new( + pr_name: "Test PR", + pr_message: pr_message, + commit_message: "Commit message" + ) + ) service.create_pull_request(dependency_change, base_sha) end end shared_context :a_pr_was_updated do + let(:source) do + instance_double(Dependabot::Source, provider: "github", repo: "dependabot/dependabot-core", directory: "/") + end + + let(:job) do + instance_double(Dependabot::Job, + source: source, + credentials: [], + commit_message_options: [], + ignore_conditions: []) + end + let(:dependency_change) do Dependabot::DependencyChange.new( - job: anything, + job: job, updated_dependencies: dependencies, updated_dependency_files: dependency_files ) From 43204b1ae6fb5f3483d1806b7e8fbd5b1cedf81b Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Fri, 9 Feb 2024 15:57:16 -0600 Subject: [PATCH 21/49] fix directories in use for non-grouped updates (#9026) --- .../tests/testdata/vu-basic-directories.txt | 54 +++++++++++++++++++ .../refresh_version_update_pull_request.rb | 4 ++ .../updater/operations/update_all_versions.rb | 4 ++ 3 files changed, 62 insertions(+) create mode 100644 silent/tests/testdata/vu-basic-directories.txt diff --git a/silent/tests/testdata/vu-basic-directories.txt b/silent/tests/testdata/vu-basic-directories.txt new file mode 100644 index 0000000000..5f0836360b --- /dev/null +++ b/silent/tests/testdata/vu-basic-directories.txt @@ -0,0 +1,54 @@ +dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +stderr 'created \| dependency-a \( from 1.2.3 to 1.2.5 \)' +pr-created expected.json + +dependabot update -f input-2.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +stderr 'updated \| dependency-a \( from 1.2.3 to 1.2.5 \)' +pr-updated expected.json + +-- manifest.json -- +{ + "dependency-a": { "version": "1.2.3" } +} + +-- expected.json -- +{ + "dependency-a": { "version": "1.2.5" } +} + +-- dependency-a -- +{ + "versions": [ + "1.2.3", + "1.2.4", + "1.2.5" + ] +} + +-- input.yml -- +job: + package-manager: "silent" + source: + directories: + - "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests + +-- input-2.yml -- +job: + package-manager: "silent" + source: + directories: + - "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests + dependencies: + - dependency-a + updating-a-pull-request: true + existing-pull-requests: + - - dependency-name: dependency-a + dependency-version: 1.2.5 diff --git a/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb b/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb index 3d4e9c0d6f..f2321824c3 100644 --- a/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb +++ b/updater/lib/dependabot/updater/operations/refresh_version_update_pull_request.rb @@ -30,6 +30,10 @@ def initialize(service:, job:, dependency_snapshot:, error_handler:) @job = job @dependency_snapshot = dependency_snapshot @error_handler = error_handler + + return unless job.source.directory.nil? && job.source.directories.count == 1 + + job.source.directory = job.source.directories.first end def perform diff --git a/updater/lib/dependabot/updater/operations/update_all_versions.rb b/updater/lib/dependabot/updater/operations/update_all_versions.rb index eda72b5033..96f3a39297 100644 --- a/updater/lib/dependabot/updater/operations/update_all_versions.rb +++ b/updater/lib/dependabot/updater/operations/update_all_versions.rb @@ -27,6 +27,10 @@ def initialize(service:, job:, dependency_snapshot:, error_handler:) @error_handler = error_handler # TODO: Collect @created_pull_requests on the Job object? @created_pull_requests = [] + + return unless job.source.directory.nil? && job.source.directories.count == 1 + + job.source.directory = job.source.directories.first end def perform From edfc38d709dec3ff026a36953de4a2ce53462141 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Mon, 12 Feb 2024 09:10:03 -0800 Subject: [PATCH 22/49] Strict type `Dependabot::MetadataFinders::Base::ChangelogFinder` (#9029) --- .../metadata_finders/base/changelog_finder.rb | 207 +++++++++++++----- 1 file changed, 151 insertions(+), 56 deletions(-) diff --git a/common/lib/dependabot/metadata_finders/base/changelog_finder.rb b/common/lib/dependabot/metadata_finders/base/changelog_finder.rb index 9277ce967f..1aa59df7f9 100644 --- a/common/lib/dependabot/metadata_finders/base/changelog_finder.rb +++ b/common/lib/dependabot/metadata_finders/base/changelog_finder.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "excon" @@ -9,9 +9,11 @@ require "dependabot/clients/bitbucket_with_retries" require "dependabot/shared_helpers" require "dependabot/metadata_finders/base" + module Dependabot module MetadataFinders class Base + # rubocop:disable Metrics/ClassLength class ChangelogFinder extend T::Sig @@ -19,24 +21,49 @@ class ChangelogFinder require_relative "commits_finder" # Earlier entries are preferred - CHANGELOG_NAMES = %w( - changelog news changes history release whatsnew releases - ).freeze + CHANGELOG_NAMES = T.let( + %w(changelog news changes history release whatsnew releases).freeze, + T::Array[String] + ) + + sig { returns(T.nilable(Dependabot::Source)) } + attr_reader :source + + sig { returns(Dependabot::Dependency) } + attr_reader :dependency + + sig { returns(T::Array[Dependabot::Credential]) } + attr_reader :credentials - attr_reader :source, :dependency, :credentials, :suggested_changelog_url + sig { returns(T.nilable(String)) } + attr_reader :suggested_changelog_url + sig do + params( + source: T.nilable(Dependabot::Source), + dependency: Dependabot::Dependency, + credentials: T::Array[Dependabot::Credential], + suggested_changelog_url: T.nilable(String) + ) + .void + end def initialize(source:, dependency:, credentials:, suggested_changelog_url: nil) @source = source @dependency = dependency @credentials = credentials @suggested_changelog_url = suggested_changelog_url + + @new_version = T.let(nil, T.nilable(String)) + @changelog_from_suggested_url = T.let(nil, T.untyped) end + sig { returns(T.nilable(String)) } def changelog_url changelog&.html_url end + sig { returns(T.nilable(String)) } def changelog_text return unless full_changelog_text @@ -46,19 +73,25 @@ def changelog_text ).pruned_text end + sig { returns(T.nilable(String)) } def upgrade_guide_url upgrade_guide&.html_url end + sig { returns(T.nilable(String)) } def upgrade_guide_text return unless upgrade_guide - @upgrade_guide_text ||= fetch_file_text(upgrade_guide) + @upgrade_guide_text ||= T.let( + fetch_file_text(upgrade_guide), + T.nilable(String) + ) end private # rubocop:disable Metrics/PerceivedComplexity + sig { returns(T.untyped) } def changelog return unless changelog_from_suggested_url || source return if git_source? && !ref_changed? @@ -66,13 +99,13 @@ def changelog # If there is a changelog, and it includes the new version, return it if new_version && default_branch_changelog && - fetch_file_text(default_branch_changelog)&.include?(new_version) + fetch_file_text(default_branch_changelog)&.include?(T.must(new_version)) return default_branch_changelog end # Otherwise, look for a changelog at the tag for this version if new_version && relevant_tag_changelog && - fetch_file_text(relevant_tag_changelog)&.include?(new_version) + fetch_file_text(relevant_tag_changelog)&.include?(T.must(new_version)) return relevant_tag_changelog end @@ -81,8 +114,9 @@ def changelog end # rubocop:enable Metrics/PerceivedComplexity + sig { returns(T.nilable(Sawyer::Resource)) } def changelog_from_suggested_url - return @changelog_from_suggested_url if defined?(@changelog_from_suggested_url) + return @changelog_from_suggested_url unless @changelog_from_suggested_url.nil? return unless suggested_changelog_url # TODO: Support other providers @@ -90,29 +124,40 @@ def changelog_from_suggested_url return unless suggested_source&.provider == "github" opts = { path: suggested_source&.directory, ref: suggested_source&.branch }.compact - suggested_source_client = github_client_for_source(suggested_source) - tmp_files = suggested_source_client.contents(suggested_source&.repo, opts) + suggested_source_client = github_client_for_source(T.must(suggested_source)) + tmp_files = T.unsafe(suggested_source_client).contents(suggested_source&.repo, opts) - filename = suggested_changelog_url.split("/").last.split("#").first + filename = T.must(T.must(suggested_changelog_url).split("/").last).split("#").first @changelog_from_suggested_url = tmp_files.find { |f| f.name == filename } rescue Octokit::NotFound, Octokit::UnavailableForLegalReasons @changelog_from_suggested_url = nil end + sig { returns(T.nilable(T.any(OpenStruct, Sawyer::Resource))) } def default_branch_changelog return unless source - @default_branch_changelog ||= changelog_from_ref(nil) + @default_branch_changelog ||= + T.let( + changelog_from_ref(nil), + T.nilable(T.any(OpenStruct, Sawyer::Resource)) + ) end + sig { returns(T.nilable(T.any(OpenStruct, Sawyer::Resource))) } def relevant_tag_changelog return unless source return unless tag_for_new_version - @relevant_tag_changelog ||= changelog_from_ref(tag_for_new_version) + @relevant_tag_changelog ||= + T.let( + changelog_from_ref(tag_for_new_version), + T.nilable(T.any(OpenStruct, Sawyer::Resource)) + ) end + sig { params(ref: T.nilable(String)).returns(T.nilable(T.any(OpenStruct, Sawyer::Resource))) } def changelog_from_ref(ref) files = dependency_file_list(ref) @@ -125,6 +170,7 @@ def changelog_from_ref(ref) end # rubocop:disable Metrics/PerceivedComplexity + sig { params(files: T::Array[T.untyped]).returns(T.untyped) } def select_best_changelog(files) CHANGELOG_NAMES.each do |name| candidates = files.select { |f| f.name =~ /#{name}/i } @@ -150,15 +196,20 @@ def select_best_changelog(files) end # rubocop:enable Metrics/PerceivedComplexity + sig { returns(T.nilable(String)) } def tag_for_new_version @tag_for_new_version ||= - CommitsFinder.new( - dependency: dependency, - source: source, - credentials: credentials - ).new_tag + T.let( + CommitsFinder.new( + dependency: dependency, + source: source, + credentials: credentials + ).new_tag, + T.nilable(String) + ) end + sig { returns(T.nilable(String)) } def full_changelog_text return unless changelog @@ -167,7 +218,7 @@ def full_changelog_text sig { params(file: T.untyped).returns(T.nilable(String)) } def fetch_file_text(file) - @file_text ||= {} + @file_text ||= T.let({}, T.nilable(T::Hash[String, T.untyped])) unless @file_text.key?(file.download_url) file_source = T.must(Source.from_url(file.html_url)) @@ -187,12 +238,14 @@ def fetch_file_text(file) @file_text[file.download_url].rstrip end + sig { params(file_source: Dependabot::Source, file: T.untyped).returns(String) } def fetch_github_file(file_source, file) # Hitting the download URL directly causes encoding problems - raw_content = github_client_for_source(file_source).get(file.url).content + raw_content = T.unsafe(github_client_for_source(file_source)).get(file.url).content Base64.decode64(raw_content).force_encoding("UTF-8").encode end + sig { params(file: T.untyped).returns(String) } def fetch_gitlab_file(file) Excon.get( file.download_url, @@ -201,16 +254,19 @@ def fetch_gitlab_file(file) ).body.force_encoding("UTF-8").encode end + sig { params(file: T.untyped).returns(String) } def fetch_bitbucket_file(file) - bitbucket_client.get(file.download_url).body - .force_encoding("UTF-8").encode + T.unsafe(bitbucket_client).get(file.download_url).body + .force_encoding("UTF-8").encode end + sig { params(file: T.untyped).returns(String) } def fetch_azure_file(file) azure_client.get(file.download_url).body .force_encoding("UTF-8").encode end + sig { returns(T.untyped) } def upgrade_guide return unless source @@ -225,49 +281,55 @@ def upgrade_guide .max_by(&:size) end + sig { params(ref: T.nilable(String)).returns(T.untyped) } def dependency_file_list(ref = nil) - @dependency_file_list ||= {} + @dependency_file_list ||= T.let({}, T.nilable(T::Hash[T.nilable(String), T.untyped])) @dependency_file_list[ref] ||= fetch_dependency_file_list(ref) end + sig { params(ref: T.nilable(String)).returns(T::Array[T.untyped,]) } def fetch_dependency_file_list(ref) - case source.provider + case T.must(source).provider when "github" then fetch_github_file_list(ref) when "bitbucket" then fetch_bitbucket_file_list when "gitlab" then fetch_gitlab_file_list when "azure" then fetch_azure_file_list when "codecommit" then [] # TODO: Fetch Files from Codecommit - else raise "Unexpected repo provider '#{source.provider}'" + else raise "Unexpected repo provider '#{T.must(source).provider}'" end end + # rubocop:disable Metrics/AbcSize + sig { params(ref: T.nilable(String)).returns(T::Array[T.untyped]) } def fetch_github_file_list(ref) files = [] - if source.directory - opts = { path: source.directory, ref: ref }.compact - tmp_files = github_client.contents(source.repo, opts) + if T.must(source).directory + opts = { path: T.must(source).directory, ref: ref }.compact + tmp_files = T.unsafe(github_client).contents(T.must(source).repo, opts) files += tmp_files if tmp_files.is_a?(Array) end opts = { ref: ref }.compact - files += github_client.contents(source.repo, opts) + files += T.unsafe(github_client).contents(T.must(source).repo, opts) files.uniq.each do |f| next unless f.type == "dir" && f.name.match?(/docs?/o) opts = { path: f.path, ref: ref }.compact - files += github_client.contents(source.repo, opts) + files += T.unsafe(github_client).contents(T.must(source).repo, opts) end files rescue Octokit::NotFound, Octokit::UnavailableForLegalReasons [] end + # rubocop:enable Metrics/AbcSize + sig { returns(T.untyped) } def fetch_bitbucket_file_list branch = default_bitbucket_branch - bitbucket_client.fetch_repo_contents(source.repo).map do |file| + T.unsafe(bitbucket_client).fetch_repo_contents(T.must(source).repo).map do |file| type = case file.fetch("type") when "commit_file" then "file" when "commit_directory" then "dir" @@ -277,8 +339,8 @@ def fetch_bitbucket_file_list name: file.fetch("path").split("/").last, type: type, size: file.fetch("size", 100), - html_url: "#{source.url}/src/#{branch}/#{file['path']}", - download_url: "#{source.url}/raw/#{branch}/#{file['path']}" + html_url: "#{T.must(source).url}/src/#{branch}/#{file['path']}", + download_url: "#{T.must(source).url}/raw/#{branch}/#{file['path']}" ) end rescue Dependabot::Clients::Bitbucket::NotFound, @@ -287,9 +349,10 @@ def fetch_bitbucket_file_list [] end + sig { returns(T.untyped) } def fetch_gitlab_file_list branch = default_gitlab_branch - gitlab_client.repo_tree(source.repo).map do |file| + T.unsafe(gitlab_client).repo_tree(T.must(source).repo).map do |file| type = case file.type when "blob" then "file" when "tree" then "dir" @@ -299,14 +362,15 @@ def fetch_gitlab_file_list name: file.name, type: type, size: 100, # GitLab doesn't return file size - html_url: "#{source.url}/blob/#{branch}/#{file.path}", - download_url: "#{source.url}/raw/#{branch}/#{file.path}" + html_url: "#{T.must(source).url}/blob/#{branch}/#{file.path}", + download_url: "#{T.must(source).url}/raw/#{branch}/#{file.path}" ) end rescue Gitlab::Error::NotFound [] end + sig { returns(T.untyped) } def fetch_azure_file_list azure_client.fetch_repo_contents.map do |entry| type = case entry.fetch("gitObjectType") @@ -320,7 +384,7 @@ def fetch_azure_file_list type: type, size: entry.fetch("size"), path: entry.fetch("relativePath"), - html_url: "#{source.url}?path=/#{entry.fetch('relativePath')}", + html_url: "#{T.must(source).url}?path=/#{entry.fetch('relativePath')}", download_url: entry.fetch("url") ) end @@ -330,20 +394,23 @@ def fetch_azure_file_list [] end + sig { returns(T.nilable(String)) } def new_version - return @new_version if defined?(@new_version) + return @new_version unless @new_version.nil? new_version = git_source? && new_ref ? new_ref : dependency.version @new_version = new_version&.gsub(/^v/, "") end + sig { returns(T.nilable(String)) } def previous_ref - previous_refs = dependency.previous_requirements.filter_map do |r| + previous_refs = dependency.previous_requirements&.filter_map do |r| r.dig(:source, "ref") || r.dig(:source, :ref) - end.uniq - previous_refs.first if previous_refs.count == 1 + end&.uniq + previous_refs&.first if previous_refs&.count == 1 end + sig { returns(T.nilable(String)) } def new_ref new_refs = dependency.requirements.filter_map do |r| r.dig(:source, "ref") || r.dig(:source, :ref) @@ -351,12 +418,14 @@ def new_ref new_refs.first if new_refs.count == 1 end + sig { returns(T::Boolean) } def ref_changed? # We could go from multiple previous refs (nil) to a single new ref previous_ref != new_ref end # TODO: Refactor me so that Composer doesn't need to be special cased + sig { returns(T::Boolean) } def git_source? # Special case Composer, which uses git as a source but handles tags # internally @@ -369,51 +438,77 @@ def git_source? sources.all? { |s| s[:type] == "git" || s["type"] == "git" } end + sig { returns(T::Boolean) } def major_version_upgrade? return false unless dependency.version&.match?(/^\d/) return false unless dependency.previous_version&.match?(/^\d/) - dependency.version.split(".").first.to_i - - dependency.previous_version.split(".").first.to_i >= 1 + T.must(dependency.version).split(".").first.to_i - + T.must(dependency.previous_version).split(".").first.to_i >= 1 end + sig { returns(Dependabot::Clients::GitlabWithRetries) } def gitlab_client - @gitlab_client ||= Dependabot::Clients::GitlabWithRetries - .for_gitlab_dot_com(credentials: credentials) + @gitlab_client ||= + T.let( + Dependabot::Clients::GitlabWithRetries.for_gitlab_dot_com(credentials: credentials), + T.nilable(Dependabot::Clients::GitlabWithRetries) + ) end + sig { returns(Dependabot::Clients::GithubWithRetries) } def github_client - @github_client ||= Dependabot::Clients::GithubWithRetries - .for_source(source: source, credentials: credentials) + @github_client ||= + T.let( + Dependabot::Clients::GithubWithRetries.for_source(source: source, credentials: credentials), + T.nilable(Dependabot::Clients::GithubWithRetries) + ) end + sig { returns(Dependabot::Clients::Azure) } def azure_client - @azure_client ||= Dependabot::Clients::Azure - .for_source(source: source, credentials: credentials) + @azure_client ||= + T.let( + Dependabot::Clients::Azure.for_source(source: source, credentials: credentials), + T.nilable(Dependabot::Clients::Azure) + ) end + sig { params(client_source: Dependabot::Source).returns(Dependabot::Clients::GithubWithRetries) } def github_client_for_source(client_source) return github_client if client_source == source - Dependabot::Clients::GithubWithRetries - .for_source(source: client_source, credentials: credentials) + Dependabot::Clients::GithubWithRetries.for_source(source: client_source, credentials: credentials) end + sig { returns(Dependabot::Clients::BitbucketWithRetries) } def bitbucket_client - @bitbucket_client ||= Dependabot::Clients::BitbucketWithRetries - .for_bitbucket_dot_org(credentials: credentials) + @bitbucket_client ||= + T.let( + Dependabot::Clients::BitbucketWithRetries.for_bitbucket_dot_org(credentials: credentials), + T.nilable(Dependabot::Clients::BitbucketWithRetries) + ) end + sig { returns(String) } def default_bitbucket_branch @default_bitbucket_branch ||= - bitbucket_client.fetch_default_branch(source.repo) + T.let( + T.unsafe(bitbucket_client).fetch_default_branch(T.must(source).repo), + T.nilable(String) + ) end + sig { returns(String) } def default_gitlab_branch @default_gitlab_branch ||= - gitlab_client.fetch_default_branch(source.repo) + T.let( + gitlab_client.fetch_default_branch(T.must(source).repo), + T.nilable(String) + ) end end + # rubocop:enable Metrics/ClassLength end end end From 7e9d3950fd99a9b1482ae69efe3d011b2cfe8eaa Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Mon, 12 Feb 2024 13:14:21 -0600 Subject: [PATCH 23/49] add close up-to-date updater test (#9025) --- silent/tests/testdata/vs-close-up-to-date.txt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 silent/tests/testdata/vs-close-up-to-date.txt diff --git a/silent/tests/testdata/vs-close-up-to-date.txt b/silent/tests/testdata/vs-close-up-to-date.txt new file mode 100644 index 0000000000..3e7fed940e --- /dev/null +++ b/silent/tests/testdata/vs-close-up-to-date.txt @@ -0,0 +1,37 @@ +dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +stderr 'closed: up_to_date \| dependency-a' +stdout '{"data":{"dependency-names":\["dependency-a"\],"reason":"up_to_date"},"type":"close_pull_request"}' +! stdout 'create_pull_request' +! stdout 'update_pull_request' + +# This tests the scenario where a manifest was updated and the existing pull request should be closed. + +-- manifest.json -- +{ + "dependency-a": { "version": "1.2.5" } +} + +-- dependency-a -- +{ + "versions": [ + "1.2.3", + "1.2.4", + "1.2.5" + ] +} + +-- input.yml -- +job: + package-manager: "silent" + source: + directory: "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests + dependencies: + - dependency-a + updating-a-pull-request: true + existing-pull-requests: + - - dependency-name: dependency-a + dependency-version: 1.2.5 From d63dd7e7b5eb6c849fd6ba92bd21635f09d4a3fc Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Mon, 12 Feb 2024 13:23:49 -0600 Subject: [PATCH 24/49] test more of the security error scenarios (#9039) --- .../testdata/su-err-dependency-not-found.txt | 37 +++++++++++++++++ silent/tests/testdata/su-err-not-needed.txt | 37 +++++++++++++++++ .../testdata/su-err-pr-exists-latest.txt | 41 +++++++++++++++++++ .../testdata/su-err-pr-exists-security.txt | 41 +++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 silent/tests/testdata/su-err-dependency-not-found.txt create mode 100644 silent/tests/testdata/su-err-not-needed.txt create mode 100644 silent/tests/testdata/su-err-pr-exists-latest.txt create mode 100644 silent/tests/testdata/su-err-pr-exists-security.txt diff --git a/silent/tests/testdata/su-err-dependency-not-found.txt b/silent/tests/testdata/su-err-dependency-not-found.txt new file mode 100644 index 0000000000..b9d589c4fc --- /dev/null +++ b/silent/tests/testdata/su-err-dependency-not-found.txt @@ -0,0 +1,37 @@ +! dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +stderr security_update_dependency_not_found +stdout '{"data":{"error-type":"security_update_dependency_not_found","error-details":{}},"type":"record_update_job_error"}' +! stdout create_pull_request + +# Since 'not-found' is not in the manifest, it errors with security_update_dependency_not_found. + +-- manifest.json -- +{ + "dependency-a": { "version": "1.2.3" } +} + +-- dependency-a -- +{ + "versions": [ + "1.2.3" + ] +} + +-- input.yml -- +job: + package-manager: "silent" + dependencies: + - not-found + source: + directory: "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests + security-advisories: + - dependency-name: not-found + affected-versions: + - <= 1.2.3 + patched-versions: [] + unaffected-versions: [] + security-updates-only: true diff --git a/silent/tests/testdata/su-err-not-needed.txt b/silent/tests/testdata/su-err-not-needed.txt new file mode 100644 index 0000000000..ae8cc327e2 --- /dev/null +++ b/silent/tests/testdata/su-err-not-needed.txt @@ -0,0 +1,37 @@ +! dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +stderr security_update_not_needed +stdout '{"data":{"error-type":"security_update_not_needed","error-details":{"dependency-name":"dependency-a"}},"type":"record_update_job_error"}' + +# The security update is not needed because 1.2.3 is not vulnerable according to the advisory given. + +-- manifest.json -- +{ + "dependency-a": { "version": "1.2.3" } +} + +-- dependency-a -- +{ + "versions": [ + "1.2.3", + "1.2.4" + ] +} + +-- input.yml -- +job: + package-manager: "silent" + dependencies: + - dependency-a + source: + directory: "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests + security-advisories: + - dependency-name: dependency-a + affected-versions: + - < 1.0.0 + patched-versions: [] + unaffected-versions: [] + security-updates-only: true diff --git a/silent/tests/testdata/su-err-pr-exists-latest.txt b/silent/tests/testdata/su-err-pr-exists-latest.txt new file mode 100644 index 0000000000..9cad9f2ddb --- /dev/null +++ b/silent/tests/testdata/su-err-pr-exists-latest.txt @@ -0,0 +1,41 @@ +! dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +stderr pull_request_exists_for_latest_version +stdout '{"data":{"error-type":"pull_request_exists_for_latest_version","error-details":{"dependency-name":"dependency-a","dependency-version":"1.2.5"}},"type":"record_update_job_error"}' + +# An existing pull request exists for 1.2.5 which is the latest version of dependency-a. + +-- manifest.json -- +{ + "dependency-a": { "version": "1.2.3" } +} + +-- dependency-a -- +{ + "versions": [ + "1.2.3", + "1.2.4", + "1.2.5" + ] +} + +-- input.yml -- +job: + package-manager: "silent" + dependencies: + - dependency-a + source: + directory: "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests + security-advisories: + - dependency-name: dependency-a + affected-versions: + - <= 1.2.3 + patched-versions: [] + unaffected-versions: [] + security-updates-only: true + existing-pull-requests: + - - dependency-name: dependency-a + dependency-version: 1.2.5 diff --git a/silent/tests/testdata/su-err-pr-exists-security.txt b/silent/tests/testdata/su-err-pr-exists-security.txt new file mode 100644 index 0000000000..fc63996996 --- /dev/null +++ b/silent/tests/testdata/su-err-pr-exists-security.txt @@ -0,0 +1,41 @@ +! dependabot update -f input.yml --local . --updater-image ghcr.io/dependabot/dependabot-updater-silent +stderr pull_request_exists_for_security_update +stdout '{"data":{"error-type":"pull_request_exists_for_security_update","error-details":{"updated-dependencies":\[{"dependency-name":"dependency-a","dependency-version":"1.2.4"}\]}},"type":"record_update_job_error"}' + +# An existing pull request exists for 1.2.4, which is the security version required, but not the latest. + +-- manifest.json -- +{ + "dependency-a": { "version": "1.2.3" } +} + +-- dependency-a -- +{ + "versions": [ + "1.2.3", + "1.2.4", + "1.2.5" + ] +} + +-- input.yml -- +job: + package-manager: "silent" + dependencies: + - dependency-a + source: + directory: "/" + provider: example + hostname: example.com + api-endpoint: https://example.com/api/v3 + repo: dependabot/smoke-tests + security-advisories: + - dependency-name: dependency-a + affected-versions: + - <= 1.2.3 + patched-versions: [] + unaffected-versions: [] + security-updates-only: true + existing-pull-requests: + - - dependency-name: dependency-a + dependency-version: 1.2.4 From 9fc744ed90b28fb0d1381d6310700dab265787a9 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Tue, 13 Feb 2024 08:22:34 -0600 Subject: [PATCH 25/49] support group configs specifically for security updates or version updates (#9040) --- common/lib/dependabot/dependency_group.rb | 10 +++- silent/tests/testdata/su-group-pattern.txt | 1 + silent/tests/testdata/su-group-semver.txt | 1 + silent/tests/testdata/su-group-type.txt | 2 + .../lib/dependabot/dependency_group_engine.rb | 12 ++++- .../dependency_group_engine_spec.rb | 52 ++++++++++++++++++- 6 files changed, 72 insertions(+), 6 deletions(-) diff --git a/common/lib/dependabot/dependency_group.rb b/common/lib/dependabot/dependency_group.rb index c89c752c08..008e98fc2a 100644 --- a/common/lib/dependabot/dependency_group.rb +++ b/common/lib/dependabot/dependency_group.rb @@ -22,15 +22,21 @@ class DependencyGroup sig { returns(T::Array[Dependabot::Dependency]) } attr_reader :dependencies + sig { returns(String) } + attr_reader :applies_to + sig do params( name: String, - rules: T::Hash[String, T.untyped] + rules: T::Hash[String, T.untyped], + applies_to: T.nilable(String) ) .void end - def initialize(name:, rules:) + def initialize(name:, rules:, applies_to: "version-updates") @name = name + # For backwards compatibility, if no applies_to is provided, default to "version-updates" + @applies_to = T.let(applies_to || "version-updates", String) @rules = rules @dependencies = T.let([], T::Array[Dependabot::Dependency]) end diff --git a/silent/tests/testdata/su-group-pattern.txt b/silent/tests/testdata/su-group-pattern.txt index 29379c9a5e..4b05958ad9 100644 --- a/silent/tests/testdata/su-group-pattern.txt +++ b/silent/tests/testdata/su-group-pattern.txt @@ -83,6 +83,7 @@ job: security-updates-only: true dependency-groups: - name: related + applies-to: "security-updates" rules: patterns: - "related-*" diff --git a/silent/tests/testdata/su-group-semver.txt b/silent/tests/testdata/su-group-semver.txt index b429767399..3205109b34 100644 --- a/silent/tests/testdata/su-group-semver.txt +++ b/silent/tests/testdata/su-group-semver.txt @@ -83,6 +83,7 @@ job: security-updates-only: true dependency-groups: - name: dev + applies-to: security-updates rules: update-types: - minor diff --git a/silent/tests/testdata/su-group-type.txt b/silent/tests/testdata/su-group-type.txt index 0bf11c9166..e8109038a5 100644 --- a/silent/tests/testdata/su-group-type.txt +++ b/silent/tests/testdata/su-group-type.txt @@ -85,8 +85,10 @@ job: grouped-update: true dependency-groups: - name: dev + applies-to: security-updates rules: dependency-type: development - name: prod + applies-to: security-updates rules: dependency-type: production diff --git a/updater/lib/dependabot/dependency_group_engine.rb b/updater/lib/dependabot/dependency_group_engine.rb index 5993bac7d8..9cd9fcafee 100644 --- a/updater/lib/dependabot/dependency_group_engine.rb +++ b/updater/lib/dependabot/dependency_group_engine.rb @@ -31,7 +31,8 @@ def self.from_job_config(job:) # Since there are no groups, the default behavior is to group all dependencies, so create a fake group. job.dependency_groups << { "name" => "#{job.package_manager} group", - "rules" => { "patterns" => ["*"] } + "rules" => { "patterns" => ["*"] }, + "applies-to" => "security-updates" } # This ensures refreshes work for these dynamic groups. @@ -41,9 +42,16 @@ def self.from_job_config(job:) end groups = job.dependency_groups.map do |group| - Dependabot::DependencyGroup.new(name: group["name"], rules: group["rules"]) + Dependabot::DependencyGroup.new(name: group["name"], rules: group["rules"], applies_to: group["applies-to"]) end + # Filter out version updates when doing security updates and visa versa + groups = if job.security_updates_only? + groups.select { |group| group.applies_to == "security-updates" } + else + groups.select { |group| group.applies_to == "version-updates" } + end + new(dependency_groups: groups) end diff --git a/updater/spec/dependabot/dependency_group_engine_spec.rb b/updater/spec/dependabot/dependency_group_engine_spec.rb index 034d7f0c6d..c180b6c6cd 100644 --- a/updater/spec/dependabot/dependency_group_engine_spec.rb +++ b/updater/spec/dependabot/dependency_group_engine_spec.rb @@ -13,11 +13,20 @@ include DependencyFileHelpers let(:dependency_group_engine) { described_class.from_job_config(job: job) } - + let(:source) do + Dependabot::Source.new( + provider: "github", + repo: "gocardless/bump", + directory: "/", + branch: "master" + ) + end + let(:security_updates_only) { false } let(:job) do instance_double(Dependabot::Job, dependency_groups: dependency_groups_config, - security_updates_only?: false) + source: source, + security_updates_only?: security_updates_only) end let(:dummy_pkg_a) do @@ -108,6 +117,45 @@ end end + context "when a job has grouped configured, and it's a version update" do + let(:dependency_groups_config) do + [ + { + "name" => "group-a", + "rules" => { + "patterns" => ["dummy-pkg-*"], + "exclude-patterns" => ["dummy-pkg-b"] + } + }, + { + "name" => "group-b", + "applies-to" => "security-updates", + "rules" => { + "patterns" => %w(dummy-pkg-b dummy-pkg-c) + } + } + ] + end + + describe "::from_job_config" do + it "filters out the security update" do + expect(dependency_group_engine.dependency_groups.length).to eql(1) + expect(dependency_group_engine.dependency_groups.map(&:name)).to eql(%w(group-a)) + end + end + + context "when it's a security update" do + let(:security_updates_only) { true } + + describe "::from_job_config" do + it "filters out the version update" do + expect(dependency_group_engine.dependency_groups.length).to eql(1) + expect(dependency_group_engine.dependency_groups.map(&:name)).to eql(%w(group-b)) + end + end + end + end + context "when a job has groups configured" do let(:dependency_groups_config) do [ From 4784ec83995bb8f89e2a31a727c4b1c63f17789a Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 13 Feb 2024 09:47:09 -0800 Subject: [PATCH 26/49] Strict type `Dependabot::Clients::Azure` (#9042) --- common/lib/dependabot/clients/azure.rb | 136 ++++++++++++++---- .../metadata_finders/base/changelog_finder.rb | 2 +- .../metadata_finders/base/commits_finder.rb | 2 +- .../dependabot/pull_request_creator/azure.rb | 2 +- common/spec/dependabot/clients/azure_spec.rb | 18 +-- .../base/changelog_finder_spec.rb | 26 ++-- .../pull_request_creator/azure_spec.rb | 5 +- .../pull_request_updater/azure_spec.rb | 5 +- .../dependabot/git_submodules/file_fetcher.rb | 2 +- sorbet/rbi/todo.rbi | 1 - 10 files changed, 146 insertions(+), 53 deletions(-) diff --git a/common/lib/dependabot/clients/azure.rb b/common/lib/dependabot/clients/azure.rb index f00813ff86..14ea75b861 100644 --- a/common/lib/dependabot/clients/azure.rb +++ b/common/lib/dependabot/clients/azure.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "dependabot/shared_helpers" @@ -7,6 +7,7 @@ module Dependabot module Clients + # rubocop:disable Metrics/ClassLength class Azure extend T::Sig @@ -24,12 +25,16 @@ class Forbidden < StandardError; end class TagsCreationForbidden < StandardError; end - RETRYABLE_ERRORS = [InternalServerError, BadGateway, ServiceNotAvailable].freeze + RETRYABLE_ERRORS = T.let( + [InternalServerError, BadGateway, ServiceNotAvailable].freeze, + T::Array[T.class_of(StandardError)] + ) ####################### # Constructor methods # ####################### + sig { params(source: Dependabot::Source, credentials: T::Array[Dependabot::Credential]).returns(Azure) } def self.for_source(source:, credentials:) credential = credentials @@ -43,15 +48,24 @@ def self.for_source(source:, credentials:) # Client # ########## + sig do + params( + source: Dependabot::Source, + credentials: T.nilable(Dependabot::Credential), + max_retries: T.nilable(Integer) + ) + .void + end def initialize(source, credentials, max_retries: 3) @source = source @credentials = credentials - @auth_header = auth_header_for(credentials&.fetch("token", nil)) - @max_retries = max_retries || 3 + @auth_header = T.let(auth_header_for(credentials&.fetch("token", nil)), T::Hash[String, String]) + @max_retries = T.let(max_retries || 3, Integer) end + sig { params(_repo: T.nilable(String), branch: String).returns(String) } def fetch_commit(_repo, branch) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/stats/branches?name=" + branch) @@ -61,18 +75,26 @@ def fetch_commit(_repo, branch) JSON.parse(response.body).fetch("commit").fetch("commitId") end + sig { params(_repo: String).returns(String) } def fetch_default_branch(_repo) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo) JSON.parse(response.body).fetch("defaultBranch").gsub("refs/heads/", "") end + sig do + params( + commit: T.nilable(String), + path: T.nilable(String) + ) + .returns(T::Array[T::Hash[String, T.untyped]]) + end def fetch_repo_contents(commit = nil, path = nil) tree = fetch_repo_contents_treeroot(commit, path) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/trees/" + tree + "?recursive=false") @@ -80,18 +102,19 @@ def fetch_repo_contents(commit = nil, path = nil) JSON.parse(response.body).fetch("treeEntries") end + sig { params(commit: T.nilable(String), path: T.nilable(String)).returns(String) } def fetch_repo_contents_treeroot(commit = nil, path = nil) actual_path = path actual_path = "/" if path.to_s.empty? - tree_url = source.api_endpoint + + tree_url = T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + - "/items?path=" + actual_path + "/items?path=" + T.must(actual_path) unless commit.to_s.empty? tree_url += "&versionDescriptor.versionType=commit" \ - "&versionDescriptor.version=" + commit + "&versionDescriptor.version=" + T.must(commit) end tree_response = get(tree_url) @@ -99,8 +122,9 @@ def fetch_repo_contents_treeroot(commit = nil, path = nil) JSON.parse(tree_response.body).fetch("objectId") end + sig { params(commit: String, path: String).returns(String) } def fetch_file_contents(commit, path) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/items?path=" + path + @@ -110,21 +134,23 @@ def fetch_file_contents(commit, path) response.body end + sig { params(branch_name: T.nilable(String)).returns(T::Array[T::Hash[String, T.untyped]]) } def commits(branch_name = nil) - commits_url = source.api_endpoint + + commits_url = T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/commits" - commits_url += "?searchCriteria.itemVersion.version=" + branch_name unless branch_name.to_s.empty? + commits_url += "?searchCriteria.itemVersion.version=" + T.must(branch_name) unless branch_name.to_s.empty? response = get(commits_url) JSON.parse(response.body).fetch("value") end + sig { params(branch_name: String).returns(T.nilable(T::Hash[String, T.untyped])) } def branch(branch_name) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/refs?filter=heads/" + branch_name) @@ -132,8 +158,9 @@ def branch(branch_name) JSON.parse(response.body).fetch("value").first end + sig { params(source_branch: String, target_branch: String).returns(T::Array[T::Hash[String, T.untyped]]) } def pull_requests(source_branch, target_branch) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/pullrequests?searchCriteria.status=all" \ @@ -143,6 +170,16 @@ def pull_requests(source_branch, target_branch) JSON.parse(response.body).fetch("value") end + sig do + params( + branch_name: String, + base_commit: String, + commit_message: String, + files: T::Array[Dependabot::DependencyFile], + author_details: T.nilable(T::Hash[String, String]) + ) + .returns(T.untyped) + end def create_commit(branch_name, base_commit, commit_message, files, author_details) content = { @@ -158,7 +195,7 @@ def create_commit(branch_name, base_commit, commit_message, files, changeType: "edit", item: { path: file.path }, newContent: { - content: Base64.encode64(file.content), + content: Base64.encode64(T.must(file.content)), contentType: "base64encoded" } } @@ -167,12 +204,25 @@ def create_commit(branch_name, base_commit, commit_message, files, ] } - post(source.api_endpoint + source.organization + "/" + source.project + + post(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/pushes?api-version=5.0", content.to_json) end # rubocop:disable Metrics/ParameterLists + sig do + params( + pr_name: String, + source_branch: String, + target_branch: String, + pr_description: String, + labels: T::Array[String], + reviewers: T.nilable(T::Array[String]), + assignees: T.nilable(T::Array[String]), + work_item: T.nilable(Integer) + ) + .returns(T.untyped) + end def create_pull_request(pr_name, source_branch, target_branch, pr_description, labels, reviewers = nil, assignees = nil, work_item = nil) @@ -187,12 +237,25 @@ def create_pull_request(pr_name, source_branch, target_branch, workItemRefs: [{ id: work_item }] } - post(source.api_endpoint + + post(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/pullrequests?api-version=5.0", content.to_json) end + sig do + params( + pull_request_id: Integer, + auto_complete_set_by: String, + merge_commit_message: String, + delete_source_branch: T::Boolean, + squash_merge: T::Boolean, + merge_strategy: String, + trans_work_items: T::Boolean, + ignore_config_ids: T::Array[String] + ) + .returns(T.untyped) + end def autocomplete_pull_request(pull_request_id, auto_complete_set_by, merge_commit_message, delete_source_branch = true, squash_merge = true, merge_strategy = "squash", trans_work_items = true, ignore_config_ids = []) @@ -211,7 +274,7 @@ def autocomplete_pull_request(pull_request_id, auto_complete_set_by, merge_commi } } - response = patch(source.api_endpoint + + response = patch(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/pullrequests/" + pull_request_id.to_s + "?api-version=5.1", content.to_json) @@ -219,14 +282,16 @@ def autocomplete_pull_request(pull_request_id, auto_complete_set_by, merge_commi JSON.parse(response.body) end + sig { params(pull_request_id: String).returns(T::Hash[String, T.untyped]) } def pull_request(pull_request_id) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/pullrequests/" + pull_request_id) JSON.parse(response.body) end + sig { params(branch_name: String, old_commit: String, new_commit: String).returns(T::Hash[String, T.untyped]) } def update_ref(branch_name, old_commit, new_commit) content = [ { @@ -236,7 +301,7 @@ def update_ref(branch_name, old_commit, new_commit) } ] - response = post(source.api_endpoint + source.organization + "/" + source.project + + response = post(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/refs?api-version=5.0", content.to_json) @@ -244,8 +309,15 @@ def update_ref(branch_name, old_commit, new_commit) end # rubocop:enable Metrics/ParameterLists + sig do + params( + previous_tag: T.nilable(String), new_tag: T.nilable(String), + type: String + ) + .returns(T::Array[T::Hash[String, T.untyped]]) + end def compare(previous_tag, new_tag, type) - response = get(source.api_endpoint + + response = get(T.must(source.api_endpoint) + source.organization + "/" + source.project + "/_apis/git/repositories/" + source.unscoped_repo + "/commits?searchCriteria.itemVersion.versionType=#{type}" \ @@ -311,7 +383,7 @@ def post(url, json) # rubocop:disable Metrics/PerceivedComplexity raise Unauthorized if response&.status == 401 if response&.status == 403 - raise TagsCreationForbidden if tags_creation_forbidden?(response) + raise TagsCreationForbidden if tags_creation_forbidden?(T.must(response)) raise Forbidden end @@ -354,7 +426,8 @@ def patch(url, json) private - def retry_connection_failures + sig { params(blk: T.proc.void).void } + def retry_connection_failures(&blk) # rubocop:disable Lint/UnusedMethodArgument retry_attempt = 0 begin @@ -365,6 +438,7 @@ def retry_connection_failures end end + sig { params(token: T.nilable(String)).returns(T::Hash[String, String]) } def auth_header_for(token) return {} unless token @@ -379,6 +453,7 @@ def auth_header_for(token) end end + sig { params(response: Excon::Response).returns(T::Boolean) } def tags_creation_forbidden?(response) return false if response.body.empty? @@ -386,6 +461,13 @@ def tags_creation_forbidden?(response) message&.include?("TF401289") end + sig do + params( + reviewers: T.nilable(T::Array[String]), + assignees: T.nilable(T::Array[String]) + ) + .returns(T::Array[T::Hash[Symbol, T.untyped]]) + end def pr_reviewers(reviewers, assignees) return [] unless reviewers || assignees @@ -393,9 +475,15 @@ def pr_reviewers(reviewers, assignees) pr_reviewers + (assignees&.map { |r_id| { id: r_id, isRequired: false } } || []) end + sig { returns(T::Hash[String, String]) } attr_reader :auth_header + + sig { returns(T.nilable(Dependabot::Credential)) } attr_reader :credentials + + sig { returns(Dependabot::Source) } attr_reader :source end + # rubocop:enable Metrics/ClassLength end end diff --git a/common/lib/dependabot/metadata_finders/base/changelog_finder.rb b/common/lib/dependabot/metadata_finders/base/changelog_finder.rb index 1aa59df7f9..9b9d2ff418 100644 --- a/common/lib/dependabot/metadata_finders/base/changelog_finder.rb +++ b/common/lib/dependabot/metadata_finders/base/changelog_finder.rb @@ -469,7 +469,7 @@ def github_client def azure_client @azure_client ||= T.let( - Dependabot::Clients::Azure.for_source(source: source, credentials: credentials), + Dependabot::Clients::Azure.for_source(source: T.must(source), credentials: credentials), T.nilable(Dependabot::Clients::Azure) ) end diff --git a/common/lib/dependabot/metadata_finders/base/commits_finder.rb b/common/lib/dependabot/metadata_finders/base/commits_finder.rb index 97b2c59d6d..1b66fd37c2 100644 --- a/common/lib/dependabot/metadata_finders/base/commits_finder.rb +++ b/common/lib/dependabot/metadata_finders/base/commits_finder.rb @@ -382,7 +382,7 @@ def github_client def azure_client @azure_client ||= T.let( - Dependabot::Clients::Azure.for_source(source: source, credentials: credentials), + Dependabot::Clients::Azure.for_source(source: T.must(source), credentials: credentials), T.nilable(Dependabot::Clients::Azure) ) end diff --git a/common/lib/dependabot/pull_request_creator/azure.rb b/common/lib/dependabot/pull_request_creator/azure.rb index bbf2a52783..07b49c564b 100644 --- a/common/lib/dependabot/pull_request_creator/azure.rb +++ b/common/lib/dependabot/pull_request_creator/azure.rb @@ -56,7 +56,7 @@ def azure_client_for_source def branch_exists? azure_client_for_source.branch(branch_name) - rescue ::Azure::Error::NotFound + rescue ::Dependabot::Clients::Azure::NotFound false end diff --git a/common/spec/dependabot/clients/azure_spec.rb b/common/spec/dependabot/clients/azure_spec.rb index da24bd1ee2..c5b6e0ba16 100644 --- a/common/spec/dependabot/clients/azure_spec.rb +++ b/common/spec/dependabot/clients/azure_spec.rb @@ -14,7 +14,7 @@ it "Using #{credential['token_type']} token in credentials" do client = described_class.for_source( source: source, - credentials: credential["credentials"] + credentials: [credential["credentials"]] ) response = JSON.parse(client.get(base_url).body) expect(response["result"]).to eq("Success") @@ -25,12 +25,12 @@ let(:username) { "username" } let(:password) { "password" } let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "dev.azure.com", "username" => username, "password" => password - }] + })] end let(:branch) { "master" } let(:base_url) { "https://dev.azure.com/org/gocardless" } @@ -378,37 +378,37 @@ basic_non_encoded_token_data = { "token_type" => "basic non encoded", - "credentials" => [ + "credentials" => Dependabot::Credential.new( { "type" => "git_source", "host" => "dev.azure.com", "token" => token } - ], + ), "headers" => { "Authorization" => "Basic #{encoded_token}" } } basic_encoded_token_data = { "token_type" => "basic encoded", - "credentials" => [ + "credentials" => Dependabot::Credential.new( { "type" => "git_source", "host" => "dev.azure.com", "token" => encoded_token.to_s } - ], + ), "headers" => { "Authorization" => "Basic #{encoded_token}" } } bearer_token_data = { "token_type" => "bearer", - "credentials" => [ + "credentials" => Dependabot::Credential.new( { "type" => "git_source", "host" => "dev.azure.com", "token" => bearer_token } - ], + ), "headers" => { "Authorization" => "Bearer #{bearer_token}" } } diff --git a/common/spec/dependabot/metadata_finders/base/changelog_finder_spec.rb b/common/spec/dependabot/metadata_finders/base/changelog_finder_spec.rb index 38453db920..f67af07d02 100644 --- a/common/spec/dependabot/metadata_finders/base/changelog_finder_spec.rb +++ b/common/spec/dependabot/metadata_finders/base/changelog_finder_spec.rb @@ -4,6 +4,7 @@ require "octokit" require "gitlab" require "spec_helper" +require "dependabot/credential" require "dependabot/dependency" require "dependabot/source" require "dependabot/metadata_finders/base/changelog_finder" @@ -588,17 +589,20 @@ context "with credentials" do let(:credentials) do - [{ - "type" => "git_source", - "host" => "github.com", - "username" => "x-access-token", - "password" => "token" - }, { - "type" => "git_source", - "host" => "dev.azure.com", - "username" => "greysteil", - "password" => "secret_token" - }] + [ + Dependabot::Credential.new({ + "type" => "git_source", + "host" => "github.com", + "username" => "x-access-token", + "password" => "token" + }), + Dependabot::Credential.new({ + "type" => "git_source", + "host" => "dev.azure.com", + "username" => "greysteil", + "password" => "secret_token" + }) + ] end it "uses the credentials" do diff --git a/common/spec/dependabot/pull_request_creator/azure_spec.rb b/common/spec/dependabot/pull_request_creator/azure_spec.rb index d2ade7b7f3..2ca6ce7926 100644 --- a/common/spec/dependabot/pull_request_creator/azure_spec.rb +++ b/common/spec/dependabot/pull_request_creator/azure_spec.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "spec_helper" +require "dependabot/credential" require "dependabot/dependency" require "dependabot/dependency_file" require "dependabot/pull_request_creator/azure" @@ -31,12 +32,12 @@ let(:branch_name) { "dependabot/bundler/business-1.5.0" } let(:base_commit) { "basecommitsha" } let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "dev.azure.com", "username" => "x-access-token", "password" => "token" - }] + })] end let(:files) { [gemfile, gemfile_lock] } let(:commit_message) { "Commit msg" } diff --git a/common/spec/dependabot/pull_request_updater/azure_spec.rb b/common/spec/dependabot/pull_request_updater/azure_spec.rb index 8d081aadf4..2c09c739b1 100644 --- a/common/spec/dependabot/pull_request_updater/azure_spec.rb +++ b/common/spec/dependabot/pull_request_updater/azure_spec.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "spec_helper" +require "dependabot/credential" require "dependabot/dependency_file" require "dependabot/pull_request_updater/azure" @@ -36,12 +37,12 @@ let(:temp_branch) { source_branch + "-temp" } let(:path) { "files/are/here" } let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "dev.azure.com", "username" => "x-access-token", "password" => "token" - }] + })] end let(:gemfile) do diff --git a/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb b/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb index b001a116f4..379207ccbb 100644 --- a/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb +++ b/git_submodules/lib/dependabot/git_submodules/file_fetcher.rb @@ -77,7 +77,7 @@ def fetch_submodule_ref_from_host(submodule_path) tmp_path = path.gsub(%r{^/*}, "") T.unsafe(gitlab_client).get_file(repo, tmp_path, commit).blob_id when "azure" - azure_client.fetch_file_contents(commit, path) + azure_client.fetch_file_contents(T.must(commit), path) else raise "Unsupported provider '#{source.provider}'." end diff --git a/sorbet/rbi/todo.rbi b/sorbet/rbi/todo.rbi index 8d40db2d08..b63008ebcd 100644 --- a/sorbet/rbi/todo.rbi +++ b/sorbet/rbi/todo.rbi @@ -4,7 +4,6 @@ # typed: false -module ::Azure::Error::NotFound; end module Bundler::CompactIndexClient::Updater; end module Bundler::SolveFailure; end module Dependabot::NpmAndYarn::FileFetcher::Pysch::SyntaxError; end From 5afddae401d4feaf369b92383fc9a8798eabb8f3 Mon Sep 17 00:00:00 2001 From: "Brandyn Bayes (Microsoft)" Date: Tue, 13 Feb 2024 10:01:54 -0800 Subject: [PATCH 27/49] Fix docker variant with matching digests not updating correctly (#9043) * Add test to show the existing bug * Adjust the spec to repro the bug * Add file_updater changes * Try to consolidate update logic * Update digest only spec * Update dependency for spec that still needs a tag --- docker/lib/dependabot/docker/file_updater.rb | 40 ++++--- .../dependabot/docker/file_updater_spec.rb | 104 +++++++++++++++++- ...ulti_stage_different_variants_with_digests | 11 ++ 3 files changed, 132 insertions(+), 23 deletions(-) create mode 100644 docker/spec/fixtures/docker/dockerfiles/multi_stage_different_variants_with_digests diff --git a/docker/lib/dependabot/docker/file_updater.rb b/docker/lib/dependabot/docker/file_updater.rb index 0aaa455e1b..c442c69f41 100644 --- a/docker/lib/dependabot/docker/file_updater.rb +++ b/docker/lib/dependabot/docker/file_updater.rb @@ -66,12 +66,7 @@ def updated_dockerfile_content(file) updated_content = file.content old_sources.zip(new_sources).each do |old_source, new_source| - updated_content = - if specified_with_digest?(old_source) - update_digest_and_tag(updated_content, old_source, new_source) - else - update_tag(updated_content, old_source, new_source) - end + updated_content = update_digest_and_tag(updated_content, old_source, new_source) end raise "Expected content to change!" if updated_content == file.content @@ -86,35 +81,38 @@ def update_digest_and_tag(previous_content, old_source, new_source) old_tag = old_source[:tag] new_tag = new_source[:tag] - old_declaration_regex = /^#{FROM_REGEX}\s+.*@sha256:#{old_digest}/ - - previous_content.gsub(old_declaration_regex) do |old_dec| - old_dec - .gsub("@sha256:#{old_digest}", "@sha256:#{new_digest}") - .gsub(":#{old_tag}", ":#{new_tag}") - end - end - - def update_tag(previous_content, old_source, new_source) - old_tag = old_source[:tag] - new_tag = new_source[:tag] - old_declaration = if private_registry_url(old_source) then "#{private_registry_url(old_source)}/" else "" end - old_declaration += "#{dependency.name}:#{old_tag}" + old_declaration += dependency.name + old_declaration += + if specified_with_tag?(old_source) then ":#{old_tag}" + else + "" + end + old_declaration += + if specified_with_digest?(old_source) then "@sha256:#{old_digest}" + else + "" + end escaped_declaration = Regexp.escape(old_declaration) old_declaration_regex = %r{^#{FROM_REGEX}\s+(docker\.io/)?#{escaped_declaration}(?=\s|$)} previous_content.gsub(old_declaration_regex) do |old_dec| - old_dec.gsub(":#{old_tag}", ":#{new_tag}") + old_dec + .gsub("@sha256:#{old_digest}", "@sha256:#{new_digest}") + .gsub(":#{old_tag}", ":#{new_tag}") end end + def specified_with_tag?(source) + source[:tag] + end + def specified_with_digest?(source) source[:digest] end diff --git a/docker/spec/dependabot/docker/file_updater_spec.rb b/docker/spec/dependabot/docker/file_updater_spec.rb index aa12fa2817..3229f3299f 100644 --- a/docker/spec/dependabot/docker/file_updater_spec.rb +++ b/docker/spec/dependabot/docker/file_updater_spec.rb @@ -211,6 +211,77 @@ end end + context "when multiple identical named dependencies with same tag, but different variants with digests" do + let(:dockerfile_body) do + fixture("docker", "dockerfiles", "multi_stage_different_variants_with_digests") + end + let(:dependency) do + Dependabot::Dependency.new( + name: "python", + version: "3.10.6", + previous_version: "3.10.5", + requirements: [{ + requirement: nil, + groups: [], + file: "Dockerfile", + source: { + tag: "3.10.6", + digest: "8d1f943ceaaf3b3ce05df5c0926e7958836b048b70" \ + "0176bf9c56d8f37ac13fca" + } + }, { + requirement: nil, + groups: [], + file: "Dockerfile", + source: { + tag: "3.10.6-slim", + digest: "c8ef926b002a8371fff6b4f40142dcc6d6f7e217f7" \ + "afce2c2d1ed2e6c28e2b7c" + } + }], + previous_requirements: [ + { + requirement: nil, + groups: [], + file: "Dockerfile", + source: { + tag: "3.10.5", + digest: "bdf0079de4094afdb26b94d9f89b716499436282c9" \ + "72461d945a87899c015c23" + } + }, + { + requirement: nil, + groups: [], + file: "Dockerfile", + source: { + tag: "3.10.5-slim", + digest: "bdf0079de4094afdb26b94d9f89b716499436282c9" \ + "72461d945a87899c015c23" + } + } + ], + package_manager: "docker" + ) + end + + describe "the updated Dockerfile" do + subject(:updated_dockerfile) do + updated_files.find { |f| f.name == "Dockerfile" } + end + + its(:content) do + is_expected.to include "FROM python:3.10.6@sha256:8d1f943ceaaf3b3ce05df5c0926e7958836b048b" \ + "700176bf9c56d8f37ac13fca AS base\n" + end + its(:content) do + is_expected.to include "FROM python:3.10.6-slim@sha256:c8ef926b002a8371fff6b4f40142dcc6d6f" \ + "7e217f7afce2c2d1ed2e6c28e2b7c AS production\n" + end + its(:content) { is_expected.to include "ENV PIP_NO_CACHE_DIR=off \\\n" } + end + end + context "when the dependency has a namespace" do let(:dockerfile_body) { fixture("docker", "dockerfiles", "namespace") } let(:dependency) do @@ -340,7 +411,7 @@ groups: [], file: "Dockerfile", source: { - tag: "17.10", + # corresponds to the tag "17.10" digest: "3ea1ca1aa8483a38081750953ad75046e6cc9f6b86" \ "ca97eba880ebf600d68608" } @@ -350,7 +421,7 @@ groups: [], file: "Dockerfile", source: { - tag: "12.04.5", + # corresponds to the tag "12.04.5" digest: "18305429afa14ea462f810146ba44d4363ae76e4c8" \ "dfc38288cf73aa07485005" } @@ -374,6 +445,35 @@ fixture("docker", "dockerfiles", "digest_and_tag") end + let(:dependency) do + Dependabot::Dependency.new( + name: "ubuntu", + version: "17.10", + previous_version: "12.04.5", + requirements: [{ + requirement: nil, + groups: [], + file: "Dockerfile", + source: { + tag: "17.10", + digest: "3ea1ca1aa8483a38081750953ad75046e6cc9f6b86" \ + "ca97eba880ebf600d68608" + } + }], + previous_requirements: [{ + requirement: nil, + groups: [], + file: "Dockerfile", + source: { + tag: "12.04.5", + digest: "18305429afa14ea462f810146ba44d4363ae76e4c8" \ + "dfc38288cf73aa07485005" + } + }], + package_manager: "docker" + ) + end + its(:content) do is_expected.to include "FROM ubuntu:17.10@sha256:3ea1ca1aa" end diff --git a/docker/spec/fixtures/docker/dockerfiles/multi_stage_different_variants_with_digests b/docker/spec/fixtures/docker/dockerfiles/multi_stage_different_variants_with_digests new file mode 100644 index 0000000000..2ee0afecc0 --- /dev/null +++ b/docker/spec/fixtures/docker/dockerfiles/multi_stage_different_variants_with_digests @@ -0,0 +1,11 @@ +FROM python:3.10.5@sha256:bdf0079de4094afdb26b94d9f89b716499436282c972461d945a87899c015c23 AS base + +ENV PIP_NO_CACHE_DIR=off \ + PIP_DEFAULT_TIMEOUT=100 \ + PIP_DISABLE_PIP_VERSION_CHECK=on + +FROM python:3.10.5-slim@sha256:bdf0079de4094afdb26b94d9f89b716499436282c972461d945a87899c015c23 AS production + +ENV PORT=8000 \ + PYTHONUNBUFFERED=1 \ + PYTHONDONTWRITEBYTECODE=1 From b8156165392ae991a4fe15244625dcbf8f5401e8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 18:39:52 +0000 Subject: [PATCH 28/49] v0.243.0 Release notes: https://github.com/dependabot/dependabot-core/releases/tag/v0.243.0 --- Gemfile.lock | 80 ++++++++++++++++++++-------------------- common/lib/dependabot.rb | 2 +- updater/Gemfile.lock | 80 ++++++++++++++++++++-------------------- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index b67a7efee7..1fe84527e2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,19 @@ PATH remote: bundler specs: - dependabot-bundler (0.242.1) - dependabot-common (= 0.242.1) + dependabot-bundler (0.243.0) + dependabot-common (= 0.243.0) PATH remote: cargo specs: - dependabot-cargo (0.242.1) - dependabot-common (= 0.242.1) + dependabot-cargo (0.243.0) + dependabot-common (= 0.243.0) PATH remote: common specs: - dependabot-common (0.242.1) + dependabot-common (0.243.0) aws-sdk-codecommit (~> 1.28) aws-sdk-ecr (~> 1.5) bundler (>= 1.16, < 3.0.0) @@ -35,107 +35,107 @@ PATH PATH remote: composer specs: - dependabot-composer (0.242.1) - dependabot-common (= 0.242.1) + dependabot-composer (0.243.0) + dependabot-common (= 0.243.0) PATH remote: devcontainers specs: - dependabot-devcontainers (0.242.1) - dependabot-common (= 0.242.1) + dependabot-devcontainers (0.243.0) + dependabot-common (= 0.243.0) PATH remote: docker specs: - dependabot-docker (0.242.1) - dependabot-common (= 0.242.1) + dependabot-docker (0.243.0) + dependabot-common (= 0.243.0) PATH remote: elm specs: - dependabot-elm (0.242.1) - dependabot-common (= 0.242.1) + dependabot-elm (0.243.0) + dependabot-common (= 0.243.0) PATH remote: git_submodules specs: - dependabot-git_submodules (0.242.1) - dependabot-common (= 0.242.1) + dependabot-git_submodules (0.243.0) + dependabot-common (= 0.243.0) parseconfig (~> 1.0, < 1.1.0) PATH remote: github_actions specs: - dependabot-github_actions (0.242.1) - dependabot-common (= 0.242.1) + dependabot-github_actions (0.243.0) + dependabot-common (= 0.243.0) PATH remote: go_modules specs: - dependabot-go_modules (0.242.1) - dependabot-common (= 0.242.1) + dependabot-go_modules (0.243.0) + dependabot-common (= 0.243.0) PATH remote: gradle specs: - dependabot-gradle (0.242.1) - dependabot-common (= 0.242.1) - dependabot-maven (= 0.242.1) + dependabot-gradle (0.243.0) + dependabot-common (= 0.243.0) + dependabot-maven (= 0.243.0) PATH remote: hex specs: - dependabot-hex (0.242.1) - dependabot-common (= 0.242.1) + dependabot-hex (0.243.0) + dependabot-common (= 0.243.0) PATH remote: maven specs: - dependabot-maven (0.242.1) - dependabot-common (= 0.242.1) + dependabot-maven (0.243.0) + dependabot-common (= 0.243.0) PATH remote: npm_and_yarn specs: - dependabot-npm_and_yarn (0.242.1) - dependabot-common (= 0.242.1) + dependabot-npm_and_yarn (0.243.0) + dependabot-common (= 0.243.0) PATH remote: nuget specs: - dependabot-nuget (0.242.1) - dependabot-common (= 0.242.1) + dependabot-nuget (0.243.0) + dependabot-common (= 0.243.0) rubyzip (>= 2.3.2, < 3.0) PATH remote: pub specs: - dependabot-pub (0.242.1) - dependabot-common (= 0.242.1) + dependabot-pub (0.243.0) + dependabot-common (= 0.243.0) PATH remote: python specs: - dependabot-python (0.242.1) - dependabot-common (= 0.242.1) + dependabot-python (0.243.0) + dependabot-common (= 0.243.0) PATH remote: silent specs: - dependabot-silent (0.242.1) - dependabot-common (= 0.242.1) + dependabot-silent (0.243.0) + dependabot-common (= 0.243.0) PATH remote: swift specs: - dependabot-swift (0.242.1) - dependabot-common (= 0.242.1) + dependabot-swift (0.243.0) + dependabot-common (= 0.243.0) PATH remote: terraform specs: - dependabot-terraform (0.242.1) - dependabot-common (= 0.242.1) + dependabot-terraform (0.243.0) + dependabot-common (= 0.243.0) GEM remote: https://rubygems.org/ diff --git a/common/lib/dependabot.rb b/common/lib/dependabot.rb index 29fad7bbc9..007e8f1b41 100644 --- a/common/lib/dependabot.rb +++ b/common/lib/dependabot.rb @@ -2,5 +2,5 @@ # frozen_string_literal: true module Dependabot - VERSION = "0.242.1" + VERSION = "0.243.0" end diff --git a/updater/Gemfile.lock b/updater/Gemfile.lock index 6372f13052..18362ae92f 100644 --- a/updater/Gemfile.lock +++ b/updater/Gemfile.lock @@ -1,19 +1,19 @@ PATH remote: ../bundler specs: - dependabot-bundler (0.242.1) - dependabot-common (= 0.242.1) + dependabot-bundler (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../cargo specs: - dependabot-cargo (0.242.1) - dependabot-common (= 0.242.1) + dependabot-cargo (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../common specs: - dependabot-common (0.242.1) + dependabot-common (0.243.0) aws-sdk-codecommit (~> 1.28) aws-sdk-ecr (~> 1.5) bundler (>= 1.16, < 3.0.0) @@ -35,107 +35,107 @@ PATH PATH remote: ../composer specs: - dependabot-composer (0.242.1) - dependabot-common (= 0.242.1) + dependabot-composer (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../devcontainers specs: - dependabot-devcontainers (0.242.1) - dependabot-common (= 0.242.1) + dependabot-devcontainers (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../docker specs: - dependabot-docker (0.242.1) - dependabot-common (= 0.242.1) + dependabot-docker (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../elm specs: - dependabot-elm (0.242.1) - dependabot-common (= 0.242.1) + dependabot-elm (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../git_submodules specs: - dependabot-git_submodules (0.242.1) - dependabot-common (= 0.242.1) + dependabot-git_submodules (0.243.0) + dependabot-common (= 0.243.0) parseconfig (~> 1.0, < 1.1.0) PATH remote: ../github_actions specs: - dependabot-github_actions (0.242.1) - dependabot-common (= 0.242.1) + dependabot-github_actions (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../go_modules specs: - dependabot-go_modules (0.242.1) - dependabot-common (= 0.242.1) + dependabot-go_modules (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../gradle specs: - dependabot-gradle (0.242.1) - dependabot-common (= 0.242.1) - dependabot-maven (= 0.242.1) + dependabot-gradle (0.243.0) + dependabot-common (= 0.243.0) + dependabot-maven (= 0.243.0) PATH remote: ../hex specs: - dependabot-hex (0.242.1) - dependabot-common (= 0.242.1) + dependabot-hex (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../maven specs: - dependabot-maven (0.242.1) - dependabot-common (= 0.242.1) + dependabot-maven (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../npm_and_yarn specs: - dependabot-npm_and_yarn (0.242.1) - dependabot-common (= 0.242.1) + dependabot-npm_and_yarn (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../nuget specs: - dependabot-nuget (0.242.1) - dependabot-common (= 0.242.1) + dependabot-nuget (0.243.0) + dependabot-common (= 0.243.0) rubyzip (>= 2.3.2, < 3.0) PATH remote: ../pub specs: - dependabot-pub (0.242.1) - dependabot-common (= 0.242.1) + dependabot-pub (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../python specs: - dependabot-python (0.242.1) - dependabot-common (= 0.242.1) + dependabot-python (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../silent specs: - dependabot-silent (0.242.1) - dependabot-common (= 0.242.1) + dependabot-silent (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../swift specs: - dependabot-swift (0.242.1) - dependabot-common (= 0.242.1) + dependabot-swift (0.243.0) + dependabot-common (= 0.243.0) PATH remote: ../terraform specs: - dependabot-terraform (0.242.1) - dependabot-common (= 0.242.1) + dependabot-terraform (0.243.0) + dependabot-common (= 0.243.0) GEM remote: https://rubygems.org/ From e23d5e627cdb1c63ecafe13fcc6e19ee2f0c3a1c Mon Sep 17 00:00:00 2001 From: Sebastian Gomez Date: Tue, 13 Feb 2024 14:27:04 -0500 Subject: [PATCH 29/49] Expand wildcards in nuget project references (#8956) * Expand wildcards in nuget project references * Address feedback. * remove prefix from expanded path * Resolve merge conflicts. * Add repo_contents_path. * Draft solution to MSBuildHelper * MSBuildHelper clean-up * Update Unit test to test wildcard expansion * Remove unnecesary using statement. * Make repo_contents_path required. * Normalize path. Remove unused var * Update unit tests. * Pass repo_contents_path in tests * Fix tests. * Fix last failing unit test. * Remove whitespace * Add unit tests to UpdaterWorker --------- Co-authored-by: Joey Robichaud Co-authored-by: Ankit Honey --- .../Update/UpdateWorker.DirsProj.cs | 152 ++++++++++++++++++ .../Utilities/MSBuildHelper.cs | 39 +++-- nuget/lib/dependabot/nuget/file_parser.rb | 3 +- .../nuget/file_parser/project_file_parser.rb | 88 +++++++--- nuget/lib/dependabot/nuget/file_updater.rb | 3 +- nuget/lib/dependabot/nuget/update_checker.rb | 9 +- .../nuget/update_checker/dependency_finder.rb | 8 +- .../nuget/update_checker/property_updater.rb | 11 +- .../nuget/update_checker/tfm_finder.rb | 8 +- .../nuget/update_checker/version_finder.rb | 9 +- .../file_parser/project_file_parser_spec.rb | 48 ++++-- .../dependabot/nuget/file_updater_spec.rb | 24 +++ .../compatibility_checker_spec.rb | 3 +- .../update_checker/dependency_finder_spec.rb | 3 +- .../nuget/update_checker/tfm_finder_spec.rb | 3 +- .../update_checker/version_finder_spec.rb | 3 +- .../Proj1/Proj1/Proj1.csproj | 8 + .../dirsproj_wildcards/Proj1/dirs.proj | 5 + .../dirsproj_wildcards/Proj2/Proj2.csproj | 8 + .../dirsproj_wildcards/Proj2/dirs.proj | 4 + .../projects/dirsproj_wildcards/dirs.proj | 6 + 21 files changed, 380 insertions(+), 65 deletions(-) create mode 100644 nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/Proj1/Proj1.csproj create mode 100644 nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/dirs.proj create mode 100644 nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/Proj2.csproj create mode 100644 nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/dirs.proj create mode 100644 nuget/spec/fixtures/projects/dirsproj_wildcards/dirs.proj diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs index d8f487199d..c74025d214 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorker.DirsProj.cs @@ -170,6 +170,158 @@ await TestUpdateForDirsProj("Newtonsoft.Json", "9.0.1", "13.0.1", ]); } + [Fact] + public async Task UpdateSingleDependencyInNestedDirsProjUsingWildcard() + { + await TestUpdateForDirsProj("Newtonsoft.Json", "9.0.1", "13.0.1", + // initial + projectContents: """ + + + + + + + + """, + additionalFiles: + [ + ("src/dirs.proj", + """ + + + + + + + + """), + ("src/test-project/test-project.csproj", + """ + + + netstandard2.0 + + + + + + + """) + ], + // expected + expectedProjectContents: """ + + + + + + + + """, + additionalFilesExpected: + [ + ("src/dirs.proj", + """ + + + + + + + + """), + ("src/test-project/test-project.csproj", + """ + + + netstandard2.0 + + + + + + + """) + ]); + } + + [Fact] + public async Task UpdateSingleDependencyInNestedDirsProjUsingRecursiveWildcard() + { + await TestUpdateForDirsProj("Newtonsoft.Json", "9.0.1", "13.0.1", + // initial + projectContents: """ + + + + + + + + """, + additionalFiles: + [ + ("src/dirs.proj", + """ + + + + + + + + """), + ("src/test-project/test-project.csproj", + """ + + + netstandard2.0 + + + + + + + """) + ], + // expected + expectedProjectContents: """ + + + + + + + + """, + additionalFilesExpected: + [ + ("src/dirs.proj", + """ + + + + + + + + """), + ("src/test-project/test-project.csproj", + """ + + + netstandard2.0 + + + + + + + """) + ]); + } + static async Task TestUpdateForDirsProj( string dependencyName, string oldVersion, diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs index d0da89e50c..a9ee01457d 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs @@ -15,6 +15,7 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; using Microsoft.Build.Locator; +using Microsoft.Extensions.FileSystemGlobbing; using NuGetUpdater.Core.Utilities; @@ -101,6 +102,7 @@ public static IEnumerable GetProjectPathsFromProject(string projFilePath { var projectStack = new Stack<(string folderPath, ProjectRootElement)>(); var projectRootElement = ProjectRootElement.Open(projFilePath); + var processedProjectFiles = new HashSet(StringComparer.OrdinalIgnoreCase); projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projFilePath)!), projectRootElement)); @@ -114,21 +116,36 @@ public static IEnumerable GetProjectPathsFromProject(string projFilePath continue; } - projectPath = PathHelper.GetFullPathFromRelative(folderPath, projectPath); + Matcher matcher = new Matcher(); + matcher.AddInclude(PathHelper.NormalizePathToUnix(projectReference.Include)); - var projectExtension = Path.GetExtension(projectPath).ToLowerInvariant(); - if (projectExtension == ".proj") + string searchDirectory = PathHelper.NormalizePathToUnix(folderPath); + + IEnumerable files = matcher.GetResultsInFullPath(searchDirectory); + + foreach (var file in files) { - // If there is some MSBuild logic that needs to run to fully resolve the path skip the project - if (File.Exists(projectPath)) + // Check that we haven't already processed this file + if (processedProjectFiles.Contains(file)) { - var additionalProjectRootElement = ProjectRootElement.Open(projectPath); - projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(projectPath)!), additionalProjectRootElement)); + continue; + } + + var projectExtension = Path.GetExtension(file).ToLowerInvariant(); + if (projectExtension == ".proj") + { + // If there is some MSBuild logic that needs to run to fully resolve the path skip the project + if (File.Exists(file)) + { + var additionalProjectRootElement = ProjectRootElement.Open(file); + projectStack.Push((Path.GetFullPath(Path.GetDirectoryName(file)!), additionalProjectRootElement)); + processedProjectFiles.Add(file); + } + } + else if (projectExtension == ".csproj" || projectExtension == ".vbproj" || projectExtension == ".fsproj") + { + yield return file; } - } - else if (projectExtension == ".csproj" || projectExtension == ".vbproj" || projectExtension == ".fsproj") - { - yield return projectPath; } } } diff --git a/nuget/lib/dependabot/nuget/file_parser.rb b/nuget/lib/dependabot/nuget/file_parser.rb index 98d65d06a9..430238a9ee 100644 --- a/nuget/lib/dependabot/nuget/file_parser.rb +++ b/nuget/lib/dependabot/nuget/file_parser.rb @@ -78,7 +78,8 @@ def project_file_parser @project_file_parser ||= T.let( ProjectFileParser.new( dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: @repo_contents_path ), T.nilable(Dependabot::Nuget::FileParser::ProjectFileParser) ) diff --git a/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb b/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb index 8f7e7accfd..9c2dfd4824 100644 --- a/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb +++ b/nuget/lib/dependabot/nuget/file_parser/project_file_parser.rb @@ -14,7 +14,9 @@ module Dependabot module Nuget class FileParser - class ProjectFileParser + class ProjectFileParser # rubocop:disable Metrics/ClassLength + extend T::Sig + require "dependabot/file_parsers/base/dependency_set" require_relative "property_value_finder" require_relative "../update_checker/repository_finder" @@ -46,16 +48,20 @@ def self.dependency_url_search_cache CacheManager.cache("dependency_url_search_cache") end - def initialize(dependency_files:, credentials:) + def initialize(dependency_files:, credentials:, repo_contents_path:) @dependency_files = dependency_files @credentials = credentials + @repo_contents_path = repo_contents_path end - def dependency_set(project_file:) + def dependency_set(project_file:, visited_project_files: Set.new) key = "#{project_file.name.downcase}::#{project_file.content.hash}" cache = ProjectFileParser.dependency_set_cache - cache[key] ||= parse_dependencies(project_file) + visited_project_files.add(cache[key]) + + # Pass the visited_project_files set to parse_dependencies + cache[key] ||= parse_dependencies(project_file, visited_project_files) end def downstream_file_references(project_file:) @@ -70,7 +76,10 @@ def downstream_file_references(project_file:) dep_file = get_attribute_value(project_reference_node, "Include") full_project_path = full_path(project_file, dep_file) full_project_path = full_project_path[1..-1] if full_project_path.start_with?("/") - file_set << full_project_path if full_project_path + full_project_paths = expand_wildcards_in_project_reference_path(full_project_path) + full_project_paths.each do |full_project_path_expanded| + file_set << full_project_path_expanded if full_project_path_expanded + end end file_set @@ -115,7 +124,7 @@ def full_path(project_file, ref_path) result end - def parse_dependencies(project_file) + def parse_dependencies(project_file, visited_project_files) dependency_set = Dependabot::FileParsers::Base::DependencySet.new doc = Nokogiri::XML(project_file.content) @@ -134,7 +143,7 @@ def parse_dependencies(project_file) add_global_package_references(dependency_set) - add_transitive_dependencies(project_file, doc, dependency_set) + add_transitive_dependencies(project_file, doc, dependency_set, visited_project_files) # Look for SDK references; see: # https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk @@ -160,12 +169,16 @@ def add_global_package_references(dependency_set) end end - def add_transitive_dependencies(project_file, doc, dependency_set) + def add_transitive_dependencies(project_file, doc, dependency_set, visited_project_files) add_transitive_dependencies_from_packages(dependency_set) - add_transitive_dependencies_from_project_references(project_file, doc, dependency_set) + add_transitive_dependencies_from_project_references(project_file, doc, dependency_set, visited_project_files) end - def add_transitive_dependencies_from_project_references(project_file, doc, dependency_set) + def add_transitive_dependencies_from_project_references(project_file, doc, dependency_set, + visited_project_files) + + # if visited_project_files is an empty set then new up a new set + visited_project_files = Set.new if visited_project_files.nil? # Look for regular project references project_refs = doc.css(PROJECT_REFERENCE_SELECTOR) # Look for ProjectFile references (dirs.proj) @@ -179,21 +192,51 @@ def add_transitive_dependencies_from_project_references(project_file, doc, depen full_project_path = full_path(project_file, relative_path) - referenced_file = dependency_files.find { |f| f.name == full_project_path } - next unless referenced_file - - dependency_set(project_file: referenced_file).dependencies.each do |dep| - dependency = Dependency.new( - name: dep.name, - version: dep.version, - package_manager: dep.package_manager, - requirements: [] - ) - dependency_set << dependency + full_project_paths = expand_wildcards_in_project_reference_path(full_project_path) + + full_project_paths.each do |path| + # Check if we've already visited this project file + next if visited_project_files.include?(path) + + visited_project_files.add(path) + referenced_file = dependency_files.find { |f| f.name == path } + next unless referenced_file + + dependency_set(project_file: referenced_file, + visited_project_files: visited_project_files).dependencies.each do |dep| + dependency = Dependency.new( + name: dep.name, + version: dep.version, + package_manager: dep.package_manager, + requirements: [] + ) + dependency_set << dependency + end end end end + sig { params(full_path: T.untyped).returns(T::Array[T.nilable(String)]) } + def expand_wildcards_in_project_reference_path(full_path) + full_path = T.let(File.join(@repo_contents_path, full_path), T.nilable(String)) + expanded_wildcard = Dir.glob(T.must(full_path)) + + filtered_paths = [] + + # For each expanded path, remove the @repo_contents_path prefix and leading slash + expanded_wildcard.map do |path| + # Remove @repo_contents_path prefix + path = path.sub(@repo_contents_path, "") + # Remove leading slash + path = path[1..-1] if path.start_with?("/") + filtered_paths << path + path # Return the modified path + end + + # If the wildcard didn't match anything, strip the @repo_contents_path prefix and return the original path. + filtered_paths.any? ? filtered_paths : [T.must(full_path).sub(@repo_contents_path, "")[1..-1]] + end + def add_transitive_dependencies_from_packages(dependency_set) transitive_dependencies_from_packages(dependency_set.dependencies).each { |dep| dependency_set << dep } end @@ -205,7 +248,8 @@ def transitive_dependencies_from_packages(dependencies) UpdateChecker::DependencyFinder.new( dependency: dependency, dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: @repo_contents_path ).transitive_dependencies.each do |transitive_dep| visited_dep = transitive_dependencies[transitive_dep.name.downcase] next if !visited_dep.nil? && visited_dep.numeric_version > transitive_dep.numeric_version diff --git a/nuget/lib/dependabot/nuget/file_updater.rb b/nuget/lib/dependabot/nuget/file_updater.rb index 6c320719df..45599be2be 100644 --- a/nuget/lib/dependabot/nuget/file_updater.rb +++ b/nuget/lib/dependabot/nuget/file_updater.rb @@ -144,7 +144,8 @@ def project_file_parser @project_file_parser ||= FileParser::ProjectFileParser.new( dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: repo_contents_path ) end diff --git a/nuget/lib/dependabot/nuget/update_checker.rb b/nuget/lib/dependabot/nuget/update_checker.rb index 5e98b0700a..541b8ae752 100644 --- a/nuget/lib/dependabot/nuget/update_checker.rb +++ b/nuget/lib/dependabot/nuget/update_checker.rb @@ -107,7 +107,8 @@ def updated_dependencies_after_full_unlock updated_dependencies += DependencyFinder.new( dependency: updated_dependency, dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: @repo_contents_path ).updated_peer_dependencies updated_dependencies end @@ -135,7 +136,8 @@ def version_finder credentials: credentials, ignored_versions: ignored_versions, raise_on_ignored: @raise_on_ignored, - security_advisories: security_advisories + security_advisories: security_advisories, + repo_contents_path: @repo_contents_path ) end @@ -147,7 +149,8 @@ def property_updater target_version_details: latest_version_details, credentials: credentials, ignored_versions: ignored_versions, - raise_on_ignored: @raise_on_ignored + raise_on_ignored: @raise_on_ignored, + repo_contents_path: @repo_contents_path ) end diff --git a/nuget/lib/dependabot/nuget/update_checker/dependency_finder.rb b/nuget/lib/dependabot/nuget/update_checker/dependency_finder.rb index fc57382055..0fdc51402f 100644 --- a/nuget/lib/dependabot/nuget/update_checker/dependency_finder.rb +++ b/nuget/lib/dependabot/nuget/update_checker/dependency_finder.rb @@ -26,10 +26,11 @@ def self.fetch_dependencies_cache CacheManager.cache("dependency_finder_fetch_dependencies") end - def initialize(dependency:, dependency_files:, credentials:) + def initialize(dependency:, dependency_files:, credentials:, repo_contents_path:) @dependency = dependency @dependency_files = dependency_files @credentials = credentials + @repo_contents_path = repo_contents_path end def transitive_dependencies @@ -93,7 +94,7 @@ def updated_peer_dependencies private - attr_reader :dependency, :dependency_files, :credentials + attr_reader :dependency, :dependency_files, :credentials, :repo_contents_path def updated_requirements(dep, target_version_details) @updated_requirements ||= {} @@ -219,7 +220,8 @@ def version_finder(dep) credentials: credentials, ignored_versions: [], raise_on_ignored: false, - security_advisories: [] + security_advisories: [], + repo_contents_path: repo_contents_path ) end end diff --git a/nuget/lib/dependabot/nuget/update_checker/property_updater.rb b/nuget/lib/dependabot/nuget/update_checker/property_updater.rb index c905a5a54a..f589390df4 100644 --- a/nuget/lib/dependabot/nuget/update_checker/property_updater.rb +++ b/nuget/lib/dependabot/nuget/update_checker/property_updater.rb @@ -14,7 +14,7 @@ class PropertyUpdater def initialize(dependency:, dependency_files:, credentials:, target_version_details:, ignored_versions:, - raise_on_ignored: false) + raise_on_ignored: false, repo_contents_path:) @dependency = dependency @dependency_files = dependency_files @credentials = credentials @@ -23,6 +23,7 @@ def initialize(dependency:, dependency_files:, credentials:, @target_version = target_version_details&.fetch(:version) @source_details = target_version_details &.slice(:nuspec_url, :repo_url, :source_url) + @repo_contents_path = repo_contents_path end def update_possible? @@ -36,7 +37,8 @@ def update_possible? credentials: credentials, ignored_versions: ignored_versions, raise_on_ignored: @raise_on_ignored, - security_advisories: [] + security_advisories: [], + repo_contents_path: repo_contents_path ).versions.map { |v| v.fetch(:version) } versions.include?(target_version) || versions.none? @@ -74,13 +76,14 @@ def updated_dependencies private attr_reader :dependency, :dependency_files, :target_version, - :source_details, :credentials, :ignored_versions + :source_details, :credentials, :ignored_versions, :repo_contents_path def process_updated_peer_dependencies(dependency, dependencies) DependencyFinder.new( dependency: dependency, dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: repo_contents_path ).updated_peer_dependencies.each do |peer_dependency| # Only keep one copy of each dependency, the one with the highest target version. visited_dependency = dependencies[peer_dependency.name.downcase] diff --git a/nuget/lib/dependabot/nuget/update_checker/tfm_finder.rb b/nuget/lib/dependabot/nuget/update_checker/tfm_finder.rb index 2089856747..5547f8b0e2 100644 --- a/nuget/lib/dependabot/nuget/update_checker/tfm_finder.rb +++ b/nuget/lib/dependabot/nuget/update_checker/tfm_finder.rb @@ -16,9 +16,10 @@ class TfmFinder require "dependabot/nuget/file_parser/packages_config_parser" require "dependabot/nuget/file_parser/project_file_parser" - def initialize(dependency_files:, credentials:) + def initialize(dependency_files:, credentials:, repo_contents_path:) @dependency_files = dependency_files @credentials = credentials + @repo_contents_path = repo_contents_path end def frameworks(dependency) @@ -30,7 +31,7 @@ def frameworks(dependency) private - attr_reader :dependency_files, :credentials + attr_reader :dependency_files, :credentials, :repo_contents_path def project_file_tfms(dependency) project_files_with_dependency(dependency).flat_map do |file| @@ -80,7 +81,8 @@ def project_file_parser @project_file_parser ||= FileParser::ProjectFileParser.new( dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: repo_contents_path ) end diff --git a/nuget/lib/dependabot/nuget/update_checker/version_finder.rb b/nuget/lib/dependabot/nuget/update_checker/version_finder.rb index 980b16e068..75c5e70d8f 100644 --- a/nuget/lib/dependabot/nuget/update_checker/version_finder.rb +++ b/nuget/lib/dependabot/nuget/update_checker/version_finder.rb @@ -18,13 +18,15 @@ class VersionFinder def initialize(dependency:, dependency_files:, credentials:, ignored_versions:, raise_on_ignored: false, - security_advisories:) + security_advisories:, + repo_contents_path:) @dependency = dependency @dependency_files = dependency_files @credentials = credentials @ignored_versions = ignored_versions @raise_on_ignored = raise_on_ignored @security_advisories = security_advisories + @repo_contents_path = repo_contents_path end def latest_version_details @@ -58,7 +60,7 @@ def versions end attr_reader :dependency, :dependency_files, :credentials, - :ignored_versions, :security_advisories + :ignored_versions, :security_advisories, :repo_contents_path private @@ -101,7 +103,8 @@ def compatibility_checker dependency: dependency, tfm_finder: TfmFinder.new( dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: repo_contents_path ) ) end diff --git a/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb b/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb index ea02ba4052..105d7a75e6 100644 --- a/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb +++ b/nuget/spec/dependabot/nuget/file_parser/project_file_parser_spec.rb @@ -17,7 +17,9 @@ Dependabot::DependencyFile.new(name: "my.csproj", content: file_body) end let(:file_body) { fixture("csproj", "basic.csproj") } - let(:parser) { described_class.new(dependency_files: [file], credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: [file], credentials: credentials, repo_contents_path: "/test/repo") + end let(:credentials) do [{ "type" => "git_source", @@ -68,7 +70,9 @@ ) ] end - let(:parser) { described_class.new(dependency_files: files, credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: files, credentials: credentials, repo_contents_path: "/test/repo") + end let(:dependencies) { dependency_set.dependencies } subject(:transitive_dependencies) { dependencies.reject(&:top_level?) } @@ -374,7 +378,10 @@ def dependencies_from(dep_info) ] end - let(:parser) { described_class.new(dependency_files: files, credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: files, credentials: credentials, + repo_contents_path: "/test/repo") + end subject(:dependency) do top_level_dependencies.find { |d| d.name == "Newtonsoft.Json" } @@ -427,7 +434,10 @@ def dependencies_from(dep_info) ] end - let(:parser) { described_class.new(dependency_files: files, credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: files, credentials: credentials, + repo_contents_path: "/test/repo") + end subject(:dependency) do top_level_dependencies.find { |d| d.name == "Newtonsoft.Json" } @@ -488,7 +498,10 @@ def dependencies_from(dep_info) ] end - let(:parser) { described_class.new(dependency_files: files, credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: files, credentials: credentials, + repo_contents_path: "/test/repo") + end subject(:dependency) do top_level_dependencies.find { |d| d.name == "Newtonsoft.Json" } @@ -623,7 +636,10 @@ def dependencies_from(dep_info) let(:nuget_config_file) do Dependabot::DependencyFile.new(name: "NuGet.config", content: nuget_config_body) end - let(:parser) { described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials, + repo_contents_path: "/test/repo") + end before do # no results @@ -694,7 +710,10 @@ def dependencies_from(dep_info) let(:nuget_config_file) do Dependabot::DependencyFile.new(name: "NuGet.config", content: nuget_config_body) end - let(:parser) { described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials, + repo_contents_path: "/test/repo") + end before do stub_request(:get, "https://www.nuget.org/api/v2/") @@ -729,7 +748,10 @@ def dependencies_from(dep_info) let(:nuget_config_file) do Dependabot::DependencyFile.new(name: "../../NuGet.Config", content: nuget_config_body) end - let(:parser) { described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials, + repo_contents_path: "/test/repo") + end it "finds the config file up several directories" do nuget_configs = parser.nuget_configs @@ -748,7 +770,10 @@ def dependencies_from(dep_info) let(:nuget_config_file) do Dependabot::DependencyFile.new(name: "../../not-NuGet.Config", content: nuget_config_body) end - let(:parser) { described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: [file, nuget_config_file], credentials: credentials, + repo_contents_path: "/test/repo") + end it "does not return a name with a partial match" do nuget_configs = parser.nuget_configs @@ -788,7 +813,10 @@ def dependencies_from(dep_info) let(:file2) do Dependabot::DependencyFile.new(name: "my2.csproj", content: file_2_body) end - let(:parser) { described_class.new(dependency_files: [file, file2], credentials: credentials) } + let(:parser) do + described_class.new(dependency_files: [file, file2], credentials: credentials, + repo_contents_path: "/test/repo") + end before do stub_no_search_results("this.dependency.does.not.exist") diff --git a/nuget/spec/dependabot/nuget/file_updater_spec.rb b/nuget/spec/dependabot/nuget/file_updater_spec.rb index 51f2141601..364703dc4e 100644 --- a/nuget/spec/dependabot/nuget/file_updater_spec.rb +++ b/nuget/spec/dependabot/nuget/file_updater_spec.rb @@ -81,4 +81,28 @@ end end end + + describe "#updated_dependency_files_with_wildcard" do + subject(:updated_files) { file_updater_instance.updated_dependency_files } + + let(:project_name) { "dirsproj_wildcards" } + let(:dependency_files) { nuget_project_dependency_files(project_name, directory: directory).reverse } + let(:dependency_name) { "Microsoft.Extensions.DependencyModel" } + let(:dependency_version) { "1.1.1" } + let(:dependency_previous_version) { "1.0.0" } + + it "updates the wildcard project" do + expect(updated_files.map(&:name)).to match_array([ + "Proj1/Proj1/Proj1.csproj", + "Proj2/Proj2.csproj" + ]) + + expect(file_updater_instance.send(:testonly_update_tooling_calls)).to eq( + { + "#{repo_contents_path}/dirs.projMicrosoft.Extensions.DependencyModel" => 1, + "#{repo_contents_path}/Proj2/Proj2.csprojMicrosoft.Extensions.DependencyModel" => 1 + } + ) + end + end end diff --git a/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb b/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb index 3e9ef7609f..10376286ce 100644 --- a/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb +++ b/nuget/spec/dependabot/nuget/update_checker/compatibility_checker_spec.rb @@ -50,7 +50,8 @@ let(:tfm_finder) do Dependabot::Nuget::TfmFinder.new( dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: "test/repo" ) end diff --git a/nuget/spec/dependabot/nuget/update_checker/dependency_finder_spec.rb b/nuget/spec/dependabot/nuget/update_checker/dependency_finder_spec.rb index ed56ee5e43..ba0431fcd5 100644 --- a/nuget/spec/dependabot/nuget/update_checker/dependency_finder_spec.rb +++ b/nuget/spec/dependabot/nuget/update_checker/dependency_finder_spec.rb @@ -11,7 +11,8 @@ described_class.new( dependency: dependency, dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: "test/repo" ) end let(:dependency) do diff --git a/nuget/spec/dependabot/nuget/update_checker/tfm_finder_spec.rb b/nuget/spec/dependabot/nuget/update_checker/tfm_finder_spec.rb index dfb2a7cae1..d6533e46d0 100644 --- a/nuget/spec/dependabot/nuget/update_checker/tfm_finder_spec.rb +++ b/nuget/spec/dependabot/nuget/update_checker/tfm_finder_spec.rb @@ -10,7 +10,8 @@ subject(:finder) do described_class.new( dependency_files: dependency_files, - credentials: credentials + credentials: credentials, + repo_contents_path: "test/repo" ) end diff --git a/nuget/spec/dependabot/nuget/update_checker/version_finder_spec.rb b/nuget/spec/dependabot/nuget/update_checker/version_finder_spec.rb index 0bf8fdafb6..0f2b6b854c 100644 --- a/nuget/spec/dependabot/nuget/update_checker/version_finder_spec.rb +++ b/nuget/spec/dependabot/nuget/update_checker/version_finder_spec.rb @@ -15,7 +15,8 @@ credentials: credentials, ignored_versions: ignored_versions, raise_on_ignored: raise_on_ignored, - security_advisories: security_advisories + security_advisories: security_advisories, + repo_contents_path: "test/repo" ) end diff --git a/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/Proj1/Proj1.csproj b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/Proj1/Proj1.csproj new file mode 100644 index 0000000000..d2f2db4218 --- /dev/null +++ b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/Proj1/Proj1.csproj @@ -0,0 +1,8 @@ + + + net461 + + + + + diff --git a/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/dirs.proj b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/dirs.proj new file mode 100644 index 0000000000..044d2a884c --- /dev/null +++ b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj1/dirs.proj @@ -0,0 +1,5 @@ + + + + + diff --git a/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/Proj2.csproj b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/Proj2.csproj new file mode 100644 index 0000000000..d2f2db4218 --- /dev/null +++ b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/Proj2.csproj @@ -0,0 +1,8 @@ + + + net461 + + + + + diff --git a/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/dirs.proj b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/dirs.proj new file mode 100644 index 0000000000..5c108193eb --- /dev/null +++ b/nuget/spec/fixtures/projects/dirsproj_wildcards/Proj2/dirs.proj @@ -0,0 +1,4 @@ + + + + diff --git a/nuget/spec/fixtures/projects/dirsproj_wildcards/dirs.proj b/nuget/spec/fixtures/projects/dirsproj_wildcards/dirs.proj new file mode 100644 index 0000000000..25bf99004d --- /dev/null +++ b/nuget/spec/fixtures/projects/dirsproj_wildcards/dirs.proj @@ -0,0 +1,6 @@ + + + + + + From 5c637ec2e50bc8eaf138450a5d9cfc372d6f9d12 Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Tue, 13 Feb 2024 17:17:05 -0800 Subject: [PATCH 30/49] Check credentials for required properties (#9052) --- maven/lib/dependabot/maven/update_checker/version_finder.rb | 2 +- npm_and_yarn/lib/dependabot/npm_and_yarn/registry_parser.rb | 4 +++- python/lib/dependabot/python/authed_url_builder.rb | 2 +- python/lib/dependabot/python/file_updater/pipfile_preparer.rb | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/maven/lib/dependabot/maven/update_checker/version_finder.rb b/maven/lib/dependabot/maven/update_checker/version_finder.rb index 4716529c92..f8a7380264 100644 --- a/maven/lib/dependabot/maven/update_checker/version_finder.rb +++ b/maven/lib/dependabot/maven/update_checker/version_finder.rb @@ -217,7 +217,7 @@ def pom_repository_details def credentials_repository_details credentials - .select { |cred| cred["type"] == "maven_repository" } + .select { |cred| cred["type"] == "maven_repository" && cred["url"] } .map do |cred| { "url" => cred.fetch("url").gsub(%r{/+$}, ""), diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/registry_parser.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/registry_parser.rb index 525ba0abfa..a78be58545 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/registry_parser.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/registry_parser.rb @@ -45,12 +45,13 @@ def dependency_name attr_reader :resolved_url, :credentials + # rubocop:disable Metrics/PerceivedComplexity def url_for_relevant_cred resolved_url_host = URI(resolved_url).host credential_matching_url = credentials - .select { |cred| cred["type"] == "npm_registry" } + .select { |cred| cred["type"] == "npm_registry" && cred["registry"] } .sort_by { |cred| cred["registry"].length } .find do |details| next true if resolved_url_host == details["registry"] @@ -70,6 +71,7 @@ def url_for_relevant_cred reg = credential_matching_url["registry"] resolved_url.gsub(/#{Regexp.quote(reg)}.*/, "") + reg end + # rubocop:enable Metrics/PerceivedComplexity end end end diff --git a/python/lib/dependabot/python/authed_url_builder.rb b/python/lib/dependabot/python/authed_url_builder.rb index d349ac8a31..37c10eb560 100644 --- a/python/lib/dependabot/python/authed_url_builder.rb +++ b/python/lib/dependabot/python/authed_url_builder.rb @@ -6,7 +6,7 @@ module Python class AuthedUrlBuilder def self.authed_url(credential:) token = credential.fetch("token", nil) - url = credential.fetch("index-url") + url = credential.fetch("index-url", nil) return url unless token basic_auth_details = diff --git a/python/lib/dependabot/python/file_updater/pipfile_preparer.rb b/python/lib/dependabot/python/file_updater/pipfile_preparer.rb index 1cc66d8c5e..a7732b385b 100644 --- a/python/lib/dependabot/python/file_updater/pipfile_preparer.rb +++ b/python/lib/dependabot/python/file_updater/pipfile_preparer.rb @@ -52,7 +52,7 @@ def sub_auth_url(source, credentials) base_url = source["url"].sub(/\${.*}@/, "") source_cred = credentials - .select { |cred| cred["type"] == "python_index" } + .select { |cred| cred["type"] == "python_index" && cred["index-url"] } .find { |c| c["index-url"].sub(/\${.*}@/, "") == base_url } return nil if source_cred.nil? From 935ab99a13cbc29184d92c3e91fa5dbf508c4ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Barri=C3=A9?= Date: Wed, 14 Feb 2024 22:39:04 +0100 Subject: [PATCH 31/49] Properly parse .ruby-version file (#9012) In ccb965e7dc66aa2a46375c6d06316e775b4e49ac, I used Bundler::RubyVersion.from_string, but this method is used to parse the Ruby version present in the Gemfile, of the form `ruby 3.3.0p0`, while we want to parse `3.3.0` or `ruby-3.3.0`. The code is lifted from `Bundler::RubyDsl#normalize_ruby_file`, so it only supports either `ruby-{version}` or `ruby {version}` or a plain version. That's what `ruby file: ".ruby-version"` supports. Co-authored-by: Jenny Shen --- .../definition_ruby_version_patch.rb | 9 ++++- bundler/helpers/v2/spec/ruby_version_spec.rb | 40 +++++++++++++++++++ .../ruby_version_implied/.ruby-version | 1 + .../bundler2/ruby_version_implied/Gemfile | 4 ++ .../ruby_version_implied/Gemfile.lock | 15 +++++++ 5 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 bundler/helpers/v2/spec/ruby_version_spec.rb create mode 100644 bundler/spec/fixtures/projects/bundler2/ruby_version_implied/.ruby-version create mode 100644 bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile create mode 100644 bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile.lock diff --git a/bundler/helpers/v2/monkey_patches/definition_ruby_version_patch.rb b/bundler/helpers/v2/monkey_patches/definition_ruby_version_patch.rb index de9bf03b6b..35857a9509 100644 --- a/bundler/helpers/v2/monkey_patches/definition_ruby_version_patch.rb +++ b/bundler/helpers/v2/monkey_patches/definition_ruby_version_patch.rb @@ -6,7 +6,14 @@ module BundlerDefinitionRubyVersionPatch def ruby_version super || begin - Bundler::RubyVersion.from_string(File.read(".ruby-version", chomp: true)) + file_content = Bundler.read_file(".ruby-version") + ruby_version = + if /^ruby(-|\s+)([^\s#]+)/ =~ file_content + ::Regexp.last_match(2) + else + file_content.strip + end + Bundler::RubyVersion.new(ruby_version, nil, nil, nil) if ruby_version rescue SystemCallError # .ruby-version doesn't exist, fallback to the Ruby Dependabot runs end diff --git a/bundler/helpers/v2/spec/ruby_version_spec.rb b/bundler/helpers/v2/spec/ruby_version_spec.rb new file mode 100644 index 0000000000..bacf092f03 --- /dev/null +++ b/bundler/helpers/v2/spec/ruby_version_spec.rb @@ -0,0 +1,40 @@ +# typed: false +# frozen_string_literal: true + +require "native_spec_helper" +require "shared_contexts" + +RSpec.describe BundlerDefinitionRubyVersionPatch do + include_context "in a temporary bundler directory" + include_context "stub rubygems compact index" + + let(:project_name) { "ruby_version_implied" } + before do + @ui = Bundler.ui + Bundler.ui = Bundler::UI::Silent.new + end + after { Bundler.ui = @ui } + + it "updates to the most recent version" do + in_tmp_folder do + File.delete(".ruby-version") + definition = Bundler::Definition.build("Gemfile", "Gemfile.lock", gems: ["statesman"]) + definition.resolve_remotely! + specs = definition.resolve["statesman"] + expect(specs.size).to eq(1) + spec = specs.first + expect(spec.version).to eq("7.2.0") + end + end + + it "doesn't update to a version that is not compatible with the Ruby version implied by .ruby-version" do + in_tmp_folder do + definition = Bundler::Definition.build("Gemfile", "Gemfile.lock", gems: ["statesman"]) + definition.resolve_remotely! + specs = definition.resolve["statesman"] + expect(specs.size).to eq(1) + spec = specs.first + expect(spec.version).to eq("2.0.1") + end + end +end diff --git a/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/.ruby-version b/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/.ruby-version new file mode 100644 index 0000000000..8dbb0f26ba --- /dev/null +++ b/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/.ruby-version @@ -0,0 +1 @@ +2.1.10 diff --git a/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile b/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile new file mode 100644 index 0000000000..3a258a2ac6 --- /dev/null +++ b/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile @@ -0,0 +1,4 @@ +source "https://rubygems.org" + +gem "business" +gem "statesman" diff --git a/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile.lock b/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile.lock new file mode 100644 index 0000000000..5bc9e7117a --- /dev/null +++ b/bundler/spec/fixtures/projects/bundler2/ruby_version_implied/Gemfile.lock @@ -0,0 +1,15 @@ +GEM + remote: https://rubygems.org/ + specs: + business (1.12.0) + statesman (2.0.1) + +PLATFORMS + ruby + +DEPENDENCIES + business + statesman + +BUNDLED WITH + 2.5.3 From be5dab53453777aee54437e9f0280e996f3b3db3 Mon Sep 17 00:00:00 2001 From: Yeikel Date: Wed, 14 Feb 2024 17:04:39 -0500 Subject: [PATCH 32/49] build(deps): bump pNPM to 8.15.2 (#8925) Release notes : https://github.com/pnpm/pnpm/releases/tag/v8.15.0 https://github.com/pnpm/pnpm/releases/tag/v8.15.1 https://github.com/pnpm/pnpm/releases/tag/v8.15.2 Co-authored-by: AbdulFattaah Popoola --- npm_and_yarn/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm_and_yarn/Dockerfile b/npm_and_yarn/Dockerfile index b76c08c752..6d4dc70b9a 100644 --- a/npm_and_yarn/Dockerfile +++ b/npm_and_yarn/Dockerfile @@ -4,7 +4,7 @@ FROM ghcr.io/dependabot/dependabot-updater-core ARG COREPACK_VERSION=0.24.0 # Check for updates at https://github.com/pnpm/pnpm/releases -ARG PNPM_VERSION=8.14.3 +ARG PNPM_VERSION=8.15.2 # Check for updates at https://github.com/yarnpkg/berry/releases ARG YARN_VERSION=3.7.0 From c7d1da267d272b18b0f3c99d603e6ac0da19659e Mon Sep 17 00:00:00 2001 From: Jamie Magee Date: Wed, 14 Feb 2024 14:31:27 -0800 Subject: [PATCH 33/49] Make container image references explicit (#9044) Co-authored-by: AbdulFattaah Popoola --- Dockerfile.updater-core | 4 ++-- cargo/Dockerfile | 2 +- go_modules/Dockerfile | 2 +- python/Dockerfile | 10 +++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Dockerfile.updater-core b/Dockerfile.updater-core index 4d5a66b861..9abeeaf1cb 100644 --- a/Dockerfile.updater-core +++ b/Dockerfile.updater-core @@ -1,4 +1,4 @@ -FROM ubuntu:22.04 +FROM docker.io/library/ubuntu:22.04 LABEL org.opencontainers.image.source="https://github.com/dependabot/dependabot-core" @@ -52,7 +52,7 @@ COPY --chown=dependabot:dependabot LICENSE $DEPENDABOT_HOME # Install Ruby from official Docker image # When bumping Ruby minor, need to also add the previous version to `bundler/helpers/v{1,2}/monkey_patches/definition_ruby_version_patch.rb` -COPY --from=ruby:3.1.4-bookworm --chown=dependabot:dependabot /usr/local /usr/local +COPY --from=docker.io/library/ruby:3.1.4-bookworm --chown=dependabot:dependabot /usr/local /usr/local # We had to explicitly bump this as the bundled version `0.2.2` in ubuntu 22.04 has a bug. # Once Ubuntu base image pulls in a new enough yaml version, we may not need to diff --git a/cargo/Dockerfile b/cargo/Dockerfile index 62f43c0599..8cb768d4eb 100644 --- a/cargo/Dockerfile +++ b/cargo/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.75.0-bookworm as rust +FROM docker.io/library/rust:1.75.0-bookworm as rust FROM ghcr.io/dependabot/dependabot-updater-core diff --git a/go_modules/Dockerfile b/go_modules/Dockerfile index 03a32ded64..c2de4de3f9 100644 --- a/go_modules/Dockerfile +++ b/go_modules/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.22.0-bookworm as go +FROM docker.io/library/golang:1.22.0-bookworm as go FROM ghcr.io/dependabot/dependabot-updater-core ARG TARGETARCH diff --git a/python/Dockerfile b/python/Dockerfile index 360519be77..b4c12e0437 100644 --- a/python/Dockerfile +++ b/python/Dockerfile @@ -37,7 +37,7 @@ RUN mkdir "${PYENV_ROOT}/versions" ## 3.8 # Docker doesn't support parametrizing `COPY --from:python:$PY_1_23-bookworm`, so work around it using an alias. # TODO: If upstream adds support for Ubuntu, use that instead of Debian as the base suffix: https://github.com/docker-library/python/pull/791 -FROM python:$PY_3_8-bookworm as upstream-python-3.8 +FROM docker.io/library/python:$PY_3_8-bookworm as upstream-python-3.8 FROM python-core as python-3.8 ARG PYTHON_INSTALL_LOCATION="$PYENV_ROOT/versions/$PY_3_8" COPY --from=upstream-python-3.8 --chown=dependabot:dependabot /usr/local/bin $PYTHON_INSTALL_LOCATION/bin @@ -55,7 +55,7 @@ RUN cd $PYENV_ROOT/versions \ ## 3.9 # Docker doesn't support parametrizing `COPY --from:python:$PY_1_23-bookworm`, so work around it using an alias. # TODO: If upstream adds support for Ubuntu, use that instead of Debian as the base suffix: https://github.com/docker-library/python/pull/791 -FROM python:$PY_3_9-bookworm as upstream-python-3.9 +FROM docker.io/library/python:$PY_3_9-bookworm as upstream-python-3.9 FROM python-core as python-3.9 ARG PYTHON_INSTALL_LOCATION="$PYENV_ROOT/versions/$PY_3_9" COPY --from=upstream-python-3.9 --chown=dependabot:dependabot /usr/local/bin $PYTHON_INSTALL_LOCATION/bin @@ -73,7 +73,7 @@ RUN cd $PYENV_ROOT/versions \ ## 3.10 # Docker doesn't support parametrizing `COPY --from:python:$PY_1_23-bookworm`, so work around it using an alias. # TODO: If upstream adds support for Ubuntu, use that instead of Debian as the base suffix: https://github.com/docker-library/python/pull/791 -FROM python:$PY_3_10-bookworm as upstream-python-3.10 +FROM docker.io/library/python:$PY_3_10-bookworm as upstream-python-3.10 FROM python-core as python-3.10 ARG PYTHON_INSTALL_LOCATION="$PYENV_ROOT/versions/$PY_3_10" COPY --from=upstream-python-3.10 --chown=dependabot:dependabot /usr/local/bin $PYTHON_INSTALL_LOCATION/bin @@ -91,7 +91,7 @@ RUN cd $PYENV_ROOT/versions \ ## 3.11 # Docker doesn't support parametrizing `COPY --from:python:$PY_1_23-bookworm`, so work around it using an alias. # TODO: If upstream adds support for Ubuntu, use that instead of Debian as the base suffix: https://github.com/docker-library/python/pull/791 -FROM python:$PY_3_11-bookworm as upstream-python-3.11 +FROM docker.io/library/python:$PY_3_11-bookworm as upstream-python-3.11 FROM python-core as python-3.11 ARG PYTHON_INSTALL_LOCATION="$PYENV_ROOT/versions/$PY_3_11" COPY --from=upstream-python-3.11 --chown=dependabot:dependabot /usr/local/bin $PYTHON_INSTALL_LOCATION/bin @@ -109,7 +109,7 @@ RUN cd $PYENV_ROOT/versions \ ## 3.12 # Docker doesn't support parametrizing `COPY --from:python:$PY_1_23-bookworm`, so work around it using an alias. # TODO: If upstream adds support for Ubuntu, use that instead of Debian as the base suffix: https://github.com/docker-library/python/pull/791 -FROM python:$PY_3_12-bookworm as upstream-python-3.12 +FROM docker.io/library/python:$PY_3_12-bookworm as upstream-python-3.12 FROM python-core as python-3.12 ARG PYTHON_INSTALL_LOCATION="$PYENV_ROOT/versions/$PY_3_12" COPY --from=upstream-python-3.12 --chown=dependabot:dependabot /usr/local/bin $PYTHON_INSTALL_LOCATION/bin From e13bfd23f76afe306253973121fd012740eb3358 Mon Sep 17 00:00:00 2001 From: fnoGematik <157806765+fnoGematik@users.noreply.github.com> Date: Thu, 15 Feb 2024 01:32:10 +0100 Subject: [PATCH 34/49] add missing require statement in credential.rb (#9054) Co-authored-by: AbdulFattaah Popoola --- common/lib/dependabot/credential.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/common/lib/dependabot/credential.rb b/common/lib/dependabot/credential.rb index 8713cd006f..4398acb5dd 100644 --- a/common/lib/dependabot/credential.rb +++ b/common/lib/dependabot/credential.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "sorbet-runtime" +require "forwardable" module Dependabot class Credential From db57663e17e824f25fad253f72c0239486d443f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Boyer?= Date: Wed, 14 Feb 2024 20:19:38 -0500 Subject: [PATCH 35/49] Fix dependency typo (#9049) --- .../NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs | 6 +++--- .../NuGetUpdater.Core/Updater/SdkPackageUpdater.cs | 4 ++-- .../NuGetUpdater.Core/Utilities/MSBuildHelper.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs index 7fe941c2bb..441b67a070 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs @@ -128,7 +128,7 @@ public void TfmsCanBeDeterminedFromProjectContents(string projectContents, strin } [Theory] - [MemberData(nameof(GetTopLevelPackageDependenyInfosTestData))] + [MemberData(nameof(GetTopLevelPackageDependencyInfosTestData))] public async Task TopLevelPackageDependenciesCanBeDetermined((string Path, string Content)[] buildFileContents, Dependency[] expectedTopLevelDependencies) { using var testDirectory = new TemporaryDirectory(); @@ -140,7 +140,7 @@ public async Task TopLevelPackageDependenciesCanBeDetermined((string Path, strin buildFiles.Add(ProjectBuildFile.Parse(testDirectory.DirectoryPath, fullPath, content)); } - var actualTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependenyInfos(buildFiles.ToImmutableArray()); + var actualTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles.ToImmutableArray()); Assert.Equal(expectedTopLevelDependencies, actualTopLevelDependencies); } @@ -383,7 +383,7 @@ [new Dependency("Microsoft.CodeAnalysis.Common", "4.8.0-3.23457.5", DependencyTy } } - public static IEnumerable GetTopLevelPackageDependenyInfosTestData() + public static IEnumerable GetTopLevelPackageDependencyInfosTestData() { // simple case yield return diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs index 4258592fb0..fe51270c32 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs @@ -33,7 +33,7 @@ Logger logger var tfms = MSBuildHelper.GetTargetFrameworkMonikers(buildFiles); // Get the set of all top-level dependencies in the current project - var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependenyInfos(buildFiles).ToArray(); + var topLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles).ToArray(); var packageFoundInDependencies = false; var packageNeedsUpdating = false; @@ -128,7 +128,7 @@ Logger logger UpdateTopLevelDepdendency(buildFiles, dependencyName, previousDependencyVersion, newDependencyVersion, packagesAndVersions, logger); } - var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependenyInfos(buildFiles); + var updatedTopLevelDependencies = MSBuildHelper.GetTopLevelPackageDependencyInfos(buildFiles); foreach (var tfm in tfms) { var updatedPackages = await MSBuildHelper.GetAllPackageDependenciesAsync(repoRootPath, projectPath, tfm, updatedTopLevelDependencies.ToArray(), logger); diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs index a9ee01457d..2fb64bfc5b 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs @@ -151,7 +151,7 @@ public static IEnumerable GetProjectPathsFromProject(string projFilePath } } - public static IEnumerable GetTopLevelPackageDependenyInfos(ImmutableArray buildFiles) + public static IEnumerable GetTopLevelPackageDependencyInfos(ImmutableArray buildFiles) { Dictionary packageInfo = new(StringComparer.OrdinalIgnoreCase); Dictionary packageVersionInfo = new(StringComparer.OrdinalIgnoreCase); From a0392fa7fc4da9fdde2fed2935822a25ffbf9293 Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Tue, 6 Feb 2024 20:14:32 +0100 Subject: [PATCH 36/49] Report Sorbet issue to sentry without raising Previously if Sorbet found runtime typecheck failures, these would result in an exception, halting further job execution. While we should be informed of those failures, there is no reason why this should end up failing the job for our customers. This PR configures Sorbet to report the exceptions to Sentry but continue processing. --- updater/lib/dependabot/setup.rb | 2 ++ updater/lib/dependabot/sorbet/runtime.rb | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 updater/lib/dependabot/sorbet/runtime.rb diff --git a/updater/lib/dependabot/setup.rb b/updater/lib/dependabot/setup.rb index 187f4e8184..7bac4b9260 100644 --- a/updater/lib/dependabot/setup.rb +++ b/updater/lib/dependabot/setup.rb @@ -8,6 +8,7 @@ require "dependabot/logger/formats" require "dependabot/opentelemetry" require "dependabot/sentry" +require "dependabot/sorbet/runtime" Dependabot.logger = Logger.new($stdout).tap do |logger| logger.level = Dependabot::Environment.log_level @@ -50,6 +51,7 @@ end Dependabot::OpenTelemetry.configure +Dependabot::Sorbet::Runtime.silently_report_errors! # Ecosystems require "dependabot/python" diff --git a/updater/lib/dependabot/sorbet/runtime.rb b/updater/lib/dependabot/sorbet/runtime.rb new file mode 100644 index 0000000000..e146a11a9e --- /dev/null +++ b/updater/lib/dependabot/sorbet/runtime.rb @@ -0,0 +1,21 @@ +# typed: true +# frozen_string_literal: true +# +require "sorbet-runtime" + +module Dependabot + module Sorbet + module Runtime + class InformationalError < StandardError; end + + def self.silently_report_errors! + T::Configuration.call_validation_error_handler = lambda do |sig, opts| + error = InformationalError.new(opts[:pretty_message]) + error.set_backtrace(caller.dup) + + Sentry.capture_exception(error) + end + end + end + end +end From 63305d4db6e124c148c332ec5e68f0ddbc4414cb Mon Sep 17 00:00:00 2001 From: Jurre Date: Thu, 8 Feb 2024 18:55:04 +0100 Subject: [PATCH 37/49] Strict typing for `dependabot/sorbet/runtime.rb` Co-authored-by: Jamie Magee --- updater/lib/dependabot/sorbet/runtime.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/updater/lib/dependabot/sorbet/runtime.rb b/updater/lib/dependabot/sorbet/runtime.rb index e146a11a9e..09db4c295e 100644 --- a/updater/lib/dependabot/sorbet/runtime.rb +++ b/updater/lib/dependabot/sorbet/runtime.rb @@ -1,13 +1,15 @@ -# typed: true +# typed: strict # frozen_string_literal: true -# + require "sorbet-runtime" module Dependabot module Sorbet module Runtime class InformationalError < StandardError; end + extend T::Sig + sig { void } def self.silently_report_errors! T::Configuration.call_validation_error_handler = lambda do |sig, opts| error = InformationalError.new(opts[:pretty_message]) From 7161f22e7c2a86b4a77d3457f98050af9503b0a7 Mon Sep 17 00:00:00 2001 From: Jurre Date: Thu, 8 Feb 2024 18:55:39 +0100 Subject: [PATCH 38/49] Use correct Sentry Co-authored-by: Jamie Magee --- updater/lib/dependabot/sorbet/runtime.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updater/lib/dependabot/sorbet/runtime.rb b/updater/lib/dependabot/sorbet/runtime.rb index 09db4c295e..cddf48c845 100644 --- a/updater/lib/dependabot/sorbet/runtime.rb +++ b/updater/lib/dependabot/sorbet/runtime.rb @@ -15,7 +15,7 @@ def self.silently_report_errors! error = InformationalError.new(opts[:pretty_message]) error.set_backtrace(caller.dup) - Sentry.capture_exception(error) + ::Sentry.capture_exception(error) end end end From c00a883c80c41c4321fd5f8dac5c0f898d773765 Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Thu, 8 Feb 2024 19:06:13 +0100 Subject: [PATCH 39/49] Rubocop --- README.md | 1 + updater/lib/dependabot/sorbet/runtime.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8dd3e64ec7..8e129f59c8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@

+ diff --git a/updater/lib/dependabot/sorbet/runtime.rb b/updater/lib/dependabot/sorbet/runtime.rb index cddf48c845..b5664b139c 100644 --- a/updater/lib/dependabot/sorbet/runtime.rb +++ b/updater/lib/dependabot/sorbet/runtime.rb @@ -11,7 +11,7 @@ class InformationalError < StandardError; end sig { void } def self.silently_report_errors! - T::Configuration.call_validation_error_handler = lambda do |sig, opts| + T::Configuration.call_validation_error_handler = lambda do |_sig, opts| error = InformationalError.new(opts[:pretty_message]) error.set_backtrace(caller.dup) From 13e521cbe5ea108ac499fa22b9c2f773ed31830c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 15 Feb 2024 20:19:00 +0100 Subject: [PATCH 40/49] If there's no latest tag, we can't update sha pinned images (#8070) Co-authored-by: AbdulFattaah Popoola --- docker/lib/dependabot/docker/update_checker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/lib/dependabot/docker/update_checker.rb b/docker/lib/dependabot/docker/update_checker.rb index b03f080158..f2675f27b0 100644 --- a/docker/lib/dependabot/docker/update_checker.rb +++ b/docker/lib/dependabot/docker/update_checker.rb @@ -108,7 +108,7 @@ def latest_tag_from(version) # NOTE: It's important that this *always* returns a tag (even if # it's the existing one) as it is what we later check the digest of. def fetch_latest_tag(version_tag) - return Tag.new(latest_digest) if version_tag.digest? + return Tag.new(latest_digest) if version_tag.digest? && latest_digest return version_tag unless version_tag.comparable? # Prune out any downgrade tags before checking for pre-releases From 9b7f6ba7d18957f0e08bf5e9304e1339c99f4660 Mon Sep 17 00:00:00 2001 From: Bryan Dragon <25506+bdragon@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:33:33 +0000 Subject: [PATCH 41/49] Prevent attempt to create empty commit Previously #store_change would attempt to create a git commit with no staged changes if there were changes to ignored files. As of this change, the method returns early if there are no changes (to un-ignored files) to commit. Fixes: https://github.com/dependabot/dependabot-core/issues/9060 --- common/lib/dependabot/workspace/git.rb | 4 ++-- common/spec/dependabot/workspace/git_spec.rb | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/common/lib/dependabot/workspace/git.rb b/common/lib/dependabot/workspace/git.rb index f58a55484b..bd9411bf83 100644 --- a/common/lib/dependabot/workspace/git.rb +++ b/common/lib/dependabot/workspace/git.rb @@ -26,7 +26,7 @@ def initialize(path) sig { returns(T::Boolean) } def changed? - changes.any? || !changed_files.empty? + changes.any? || !changed_files(ignored_mode: "no").empty? end sig { override.returns(String) } @@ -53,7 +53,7 @@ def reset! .returns(T.nilable(T::Array[Dependabot::Workspace::ChangeAttempt])) end def store_change(memo = nil) - return nil if changed_files.empty? + return nil if changed_files(ignored_mode: "no").empty? debug("store_change - before: #{current_commit}") sha, diff = commit(memo) diff --git a/common/spec/dependabot/workspace/git_spec.rb b/common/spec/dependabot/workspace/git_spec.rb index edac3bf4f2..3473f01b6b 100644 --- a/common/spec/dependabot/workspace/git_spec.rb +++ b/common/spec/dependabot/workspace/git_spec.rb @@ -210,8 +210,22 @@ end end + context "when there are changes to ignored files" do + # See: common/spec/fixtures/projects/simple/.gitignore + + it "returns nil and doesn't add any changes to the workspace" do + workspace.change { `echo "ignore me" >> ignored-file.txt` } + workspace.store_change("modify ignored file") + workspace.change { `mkdir -p ignored-dir && echo "ignore me" >> ignored-dir/file.txt` } + workspace.store_change("modify file in ignored directory") + + expect(workspace).not_to be_changed + expect(workspace.changes).to be_empty + end + end + context "when there are changes to store" do - it "captures the stores the changes correctly" do + it "stores the changes correctly" do workspace.change("timecop") do `echo 'gem "timecop", "~> 0.9.6", group: :test' >> Gemfile` end From 87ca0f32a4f10a75ad348f498a74989309e11660 Mon Sep 17 00:00:00 2001 From: "dependabot-core-action-automation[bot]" <98560086+dependabot-core-action-automation[bot]@users.noreply.github.com> Date: Thu, 15 Feb 2024 22:58:27 +0000 Subject: [PATCH 42/49] v0.244.0 (#9056) Release notes: https://github.com/dependabot/dependabot-core/releases/tag/v0.244.0 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Gemfile.lock | 80 ++++++++++++++++++++-------------------- common/lib/dependabot.rb | 2 +- updater/Gemfile.lock | 80 ++++++++++++++++++++-------------------- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1fe84527e2..1c045acf33 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,19 +1,19 @@ PATH remote: bundler specs: - dependabot-bundler (0.243.0) - dependabot-common (= 0.243.0) + dependabot-bundler (0.244.0) + dependabot-common (= 0.244.0) PATH remote: cargo specs: - dependabot-cargo (0.243.0) - dependabot-common (= 0.243.0) + dependabot-cargo (0.244.0) + dependabot-common (= 0.244.0) PATH remote: common specs: - dependabot-common (0.243.0) + dependabot-common (0.244.0) aws-sdk-codecommit (~> 1.28) aws-sdk-ecr (~> 1.5) bundler (>= 1.16, < 3.0.0) @@ -35,107 +35,107 @@ PATH PATH remote: composer specs: - dependabot-composer (0.243.0) - dependabot-common (= 0.243.0) + dependabot-composer (0.244.0) + dependabot-common (= 0.244.0) PATH remote: devcontainers specs: - dependabot-devcontainers (0.243.0) - dependabot-common (= 0.243.0) + dependabot-devcontainers (0.244.0) + dependabot-common (= 0.244.0) PATH remote: docker specs: - dependabot-docker (0.243.0) - dependabot-common (= 0.243.0) + dependabot-docker (0.244.0) + dependabot-common (= 0.244.0) PATH remote: elm specs: - dependabot-elm (0.243.0) - dependabot-common (= 0.243.0) + dependabot-elm (0.244.0) + dependabot-common (= 0.244.0) PATH remote: git_submodules specs: - dependabot-git_submodules (0.243.0) - dependabot-common (= 0.243.0) + dependabot-git_submodules (0.244.0) + dependabot-common (= 0.244.0) parseconfig (~> 1.0, < 1.1.0) PATH remote: github_actions specs: - dependabot-github_actions (0.243.0) - dependabot-common (= 0.243.0) + dependabot-github_actions (0.244.0) + dependabot-common (= 0.244.0) PATH remote: go_modules specs: - dependabot-go_modules (0.243.0) - dependabot-common (= 0.243.0) + dependabot-go_modules (0.244.0) + dependabot-common (= 0.244.0) PATH remote: gradle specs: - dependabot-gradle (0.243.0) - dependabot-common (= 0.243.0) - dependabot-maven (= 0.243.0) + dependabot-gradle (0.244.0) + dependabot-common (= 0.244.0) + dependabot-maven (= 0.244.0) PATH remote: hex specs: - dependabot-hex (0.243.0) - dependabot-common (= 0.243.0) + dependabot-hex (0.244.0) + dependabot-common (= 0.244.0) PATH remote: maven specs: - dependabot-maven (0.243.0) - dependabot-common (= 0.243.0) + dependabot-maven (0.244.0) + dependabot-common (= 0.244.0) PATH remote: npm_and_yarn specs: - dependabot-npm_and_yarn (0.243.0) - dependabot-common (= 0.243.0) + dependabot-npm_and_yarn (0.244.0) + dependabot-common (= 0.244.0) PATH remote: nuget specs: - dependabot-nuget (0.243.0) - dependabot-common (= 0.243.0) + dependabot-nuget (0.244.0) + dependabot-common (= 0.244.0) rubyzip (>= 2.3.2, < 3.0) PATH remote: pub specs: - dependabot-pub (0.243.0) - dependabot-common (= 0.243.0) + dependabot-pub (0.244.0) + dependabot-common (= 0.244.0) PATH remote: python specs: - dependabot-python (0.243.0) - dependabot-common (= 0.243.0) + dependabot-python (0.244.0) + dependabot-common (= 0.244.0) PATH remote: silent specs: - dependabot-silent (0.243.0) - dependabot-common (= 0.243.0) + dependabot-silent (0.244.0) + dependabot-common (= 0.244.0) PATH remote: swift specs: - dependabot-swift (0.243.0) - dependabot-common (= 0.243.0) + dependabot-swift (0.244.0) + dependabot-common (= 0.244.0) PATH remote: terraform specs: - dependabot-terraform (0.243.0) - dependabot-common (= 0.243.0) + dependabot-terraform (0.244.0) + dependabot-common (= 0.244.0) GEM remote: https://rubygems.org/ diff --git a/common/lib/dependabot.rb b/common/lib/dependabot.rb index 007e8f1b41..3cb3b68ed0 100644 --- a/common/lib/dependabot.rb +++ b/common/lib/dependabot.rb @@ -2,5 +2,5 @@ # frozen_string_literal: true module Dependabot - VERSION = "0.243.0" + VERSION = "0.244.0" end diff --git a/updater/Gemfile.lock b/updater/Gemfile.lock index 18362ae92f..747345337f 100644 --- a/updater/Gemfile.lock +++ b/updater/Gemfile.lock @@ -1,19 +1,19 @@ PATH remote: ../bundler specs: - dependabot-bundler (0.243.0) - dependabot-common (= 0.243.0) + dependabot-bundler (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../cargo specs: - dependabot-cargo (0.243.0) - dependabot-common (= 0.243.0) + dependabot-cargo (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../common specs: - dependabot-common (0.243.0) + dependabot-common (0.244.0) aws-sdk-codecommit (~> 1.28) aws-sdk-ecr (~> 1.5) bundler (>= 1.16, < 3.0.0) @@ -35,107 +35,107 @@ PATH PATH remote: ../composer specs: - dependabot-composer (0.243.0) - dependabot-common (= 0.243.0) + dependabot-composer (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../devcontainers specs: - dependabot-devcontainers (0.243.0) - dependabot-common (= 0.243.0) + dependabot-devcontainers (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../docker specs: - dependabot-docker (0.243.0) - dependabot-common (= 0.243.0) + dependabot-docker (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../elm specs: - dependabot-elm (0.243.0) - dependabot-common (= 0.243.0) + dependabot-elm (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../git_submodules specs: - dependabot-git_submodules (0.243.0) - dependabot-common (= 0.243.0) + dependabot-git_submodules (0.244.0) + dependabot-common (= 0.244.0) parseconfig (~> 1.0, < 1.1.0) PATH remote: ../github_actions specs: - dependabot-github_actions (0.243.0) - dependabot-common (= 0.243.0) + dependabot-github_actions (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../go_modules specs: - dependabot-go_modules (0.243.0) - dependabot-common (= 0.243.0) + dependabot-go_modules (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../gradle specs: - dependabot-gradle (0.243.0) - dependabot-common (= 0.243.0) - dependabot-maven (= 0.243.0) + dependabot-gradle (0.244.0) + dependabot-common (= 0.244.0) + dependabot-maven (= 0.244.0) PATH remote: ../hex specs: - dependabot-hex (0.243.0) - dependabot-common (= 0.243.0) + dependabot-hex (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../maven specs: - dependabot-maven (0.243.0) - dependabot-common (= 0.243.0) + dependabot-maven (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../npm_and_yarn specs: - dependabot-npm_and_yarn (0.243.0) - dependabot-common (= 0.243.0) + dependabot-npm_and_yarn (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../nuget specs: - dependabot-nuget (0.243.0) - dependabot-common (= 0.243.0) + dependabot-nuget (0.244.0) + dependabot-common (= 0.244.0) rubyzip (>= 2.3.2, < 3.0) PATH remote: ../pub specs: - dependabot-pub (0.243.0) - dependabot-common (= 0.243.0) + dependabot-pub (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../python specs: - dependabot-python (0.243.0) - dependabot-common (= 0.243.0) + dependabot-python (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../silent specs: - dependabot-silent (0.243.0) - dependabot-common (= 0.243.0) + dependabot-silent (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../swift specs: - dependabot-swift (0.243.0) - dependabot-common (= 0.243.0) + dependabot-swift (0.244.0) + dependabot-common (= 0.244.0) PATH remote: ../terraform specs: - dependabot-terraform (0.243.0) - dependabot-common (= 0.243.0) + dependabot-terraform (0.244.0) + dependabot-common (= 0.244.0) GEM remote: https://rubygems.org/ From 9e7396e57afb45ca83556fe0b968e62ca9d6937e Mon Sep 17 00:00:00 2001 From: Eike Send Date: Fri, 16 Feb 2024 00:02:54 +0100 Subject: [PATCH 43/49] Finds gradle repositories nested in dependencyResolutionManagement blocks, fixes #6888 (#7260) Co-authored-by: AbdulFattaah Popoola --- .../gradle/file_parser/repositories_finder.rb | 23 +++++++++++++++++++ .../file_parser/repositories_finder_spec.rb | 22 ++++++++++++++++++ .../dependency_resolution_management.gradle | 7 ++++++ 3 files changed, 52 insertions(+) create mode 100644 gradle/spec/fixtures/settings_files/dependency_resolution_management.gradle diff --git a/gradle/lib/dependabot/gradle/file_parser/repositories_finder.rb b/gradle/lib/dependabot/gradle/file_parser/repositories_finder.rb index df5e9fca22..43925d2ff7 100644 --- a/gradle/lib/dependabot/gradle/file_parser/repositories_finder.rb +++ b/gradle/lib/dependabot/gradle/file_parser/repositories_finder.rb @@ -8,6 +8,7 @@ module Gradle class FileParser class RepositoriesFinder SUPPORTED_BUILD_FILE_NAMES = %w(build.gradle build.gradle.kts).freeze + SUPPORTED_SETTINGS_FILE_NAMES = %w(settings.gradle settings.gradle.kts).freeze # The Central Repo doesn't have special status for Gradle, but until # we're confident we're selecting repos correctly it's wise to include @@ -37,6 +38,7 @@ def repository_urls repository_urls += inherited_repository_urls(dependency_file) end repository_urls += own_buildfile_repository_urls + repository_urls += settings_file_repository_urls(top_level_settings_file) repository_urls = repository_urls.uniq return repository_urls unless repository_urls.empty? @@ -91,6 +93,21 @@ def own_buildfile_repository_urls own_buildfile_urls end + def settings_file_repository_urls(settings_file) + return [] unless settings_file + + settings_file_content = comment_free_content(settings_file) + dependency_resolution_management_repositories = [] + + settings_file_content.scan(/(?:^|\s)dependencyResolutionManagement\s*\{/) do + mtch = Regexp.last_match + dependency_resolution_management_repositories << + mtch.post_match[0..closing_bracket_index(mtch.post_match)] + end + + repository_urls_from(dependency_resolution_management_repositories.join("\n")) + end + def repository_urls_from(buildfile_content) repository_urls = [] @@ -154,6 +171,12 @@ def top_level_buildfile SUPPORTED_BUILD_FILE_NAMES.include?(f.name) end end + + def top_level_settings_file + @top_level_settings_file ||= dependency_files.find do |f| + SUPPORTED_SETTINGS_FILE_NAMES.include?(f.name) + end + end end end end diff --git a/gradle/spec/dependabot/gradle/file_parser/repositories_finder_spec.rb b/gradle/spec/dependabot/gradle/file_parser/repositories_finder_spec.rb index c5cc46296a..afcce1a8d8 100644 --- a/gradle/spec/dependabot/gradle/file_parser/repositories_finder_spec.rb +++ b/gradle/spec/dependabot/gradle/file_parser/repositories_finder_spec.rb @@ -116,6 +116,28 @@ end end + context "with dependencyResolutionManagement in the settings file" do + let(:dependency_files) { [buildfile, settings_file] } + let(:settings_file) do + Dependabot::DependencyFile.new( + name: "settings.gradle", + content: fixture("settings_files", settings_file_fixture_name) + ) + end + let(:settings_file_fixture_name) { "dependency_resolution_management.gradle" } + + it "finds the repositories" do + expect(repository_urls).to match_array( + %w( + https://dependency_resolution_management.example.com + https://jcenter.bintray.com + https://dl.bintray.com/magnusja/maven + https://maven.google.com + ) + ) + end + end + context "that get URLs from a variable" do let(:buildfile_fixture_name) { "variable_repos_build.gradle" } diff --git a/gradle/spec/fixtures/settings_files/dependency_resolution_management.gradle b/gradle/spec/fixtures/settings_files/dependency_resolution_management.gradle new file mode 100644 index 0000000000..44b8210b31 --- /dev/null +++ b/gradle/spec/fixtures/settings_files/dependency_resolution_management.gradle @@ -0,0 +1,7 @@ +include ':app' + +dependencyResolutionManagement { + repositories { + maven { url 'https://dependency_resolution_management.example.com/' } + } +} From dfe50fe71cab4523af0aa1945e483470f639ca15 Mon Sep 17 00:00:00 2001 From: Andrejs Date: Fri, 16 Feb 2024 15:20:50 +0100 Subject: [PATCH 44/49] Fix hardcoded amd64 arch for git-shim (#9067) --- Dockerfile.updater-core | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile.updater-core b/Dockerfile.updater-core index 9abeeaf1cb..81b3419b37 100644 --- a/Dockerfile.updater-core +++ b/Dockerfile.updater-core @@ -1,5 +1,7 @@ FROM docker.io/library/ubuntu:22.04 +ARG TARGETARCH + LABEL org.opencontainers.image.source="https://github.com/dependabot/dependabot-core" SHELL ["/bin/bash", "-o", "pipefail", "-c"] @@ -72,7 +74,7 @@ ENV DEPENDABOT=true ENV GIT_LFS_SKIP_SMUDGE=1 # Place a git shim ahead of git on the path to rewrite git arguments to use HTTPS. -ARG SHIM="https://github.com/dependabot/git-shim/releases/download/v1.4.0/git-v1.4.0-linux-amd64.tar.gz" +ARG SHIM="https://github.com/dependabot/git-shim/releases/download/v1.4.0/git-v1.4.0-linux-${TARGETARCH}.tar.gz" RUN curl -sL $SHIM -o git-shim.tar.gz && mkdir -p ~/bin && tar -xvf git-shim.tar.gz -C ~/bin && rm git-shim.tar.gz COPY --chown=dependabot:dependabot omnibus omnibus From c676f5980bcd905fbb1367b3de5e9181b8f89b7d Mon Sep 17 00:00:00 2001 From: Ankit Honey Date: Fri, 16 Feb 2024 16:15:14 -0800 Subject: [PATCH 45/49] Surface out of disk/memory error message for easier visibility (#9064) * added out of disk error message to the go module updater * surface out of memory error for yarn lock file parser operation * Add test for memory error * fixed failed test cases * added out of disk and out of memory error to the workspace * added out of memory/disk error to correct layer (run_shell_command) --- common/lib/dependabot/shared_helpers.rb | 20 +++++++++- common/spec/dependabot/shared_helpers_spec.rb | 20 ++++++++++ common/spec/helpers/test/error_bash | 6 +++ .../go_modules/file_updater/go_mod_updater.rb | 3 +- .../file_updater/go_mod_updater_spec.rb | 11 +++++ .../npm_and_yarn/file_parser/yarn_lock.rb | 6 ++- .../file_parser/lockfile_parser_spec.rb | 40 +++++++++++++++++++ 7 files changed, 103 insertions(+), 3 deletions(-) diff --git a/common/lib/dependabot/shared_helpers.rb b/common/lib/dependabot/shared_helpers.rb index 02139fcebf..fb4cd64c59 100644 --- a/common/lib/dependabot/shared_helpers.rb +++ b/common/lib/dependabot/shared_helpers.rb @@ -405,7 +405,6 @@ def self.run_shell_command(command, stderr_to_stdout: true) start = Time.now cmd = allow_unsafe_shell_command ? command : escape_command(command) - if stderr_to_stdout stdout, process = Open3.capture2e(env || {}, cmd) else @@ -425,12 +424,31 @@ def self.run_shell_command(command, process_exit_value: process.to_s } + check_out_of_disk_memory_error(stderr, error_context) + raise SharedHelpers::HelperSubprocessFailed.new( message: stderr_to_stdout ? stdout : "#{stderr}\n#{stdout}", error_context: error_context ) end + sig { params(stderr: T.nilable(String), error_context: T::Hash[Symbol, String]).void } + def self.check_out_of_disk_memory_error(stderr, error_context) + if stderr&.include?("No space left on device") || stderr&.include?("Out of diskspace") + raise HelperSubprocessFailed.new( + message: "No space left on device", + error_class: "Dependabot::OutOfDisk", + error_context: error_context + ) + elsif stderr&.include?("MemoryError") + raise HelperSubprocessFailed.new( + message: "MemoryError", + error_class: "Dependabot::OutOfMemory", + error_context: error_context + ) + end + end + sig { params(command: String, stdin_data: String, env: T.nilable(T::Hash[String, String])).returns(String) } def self.helper_subprocess_bash_command(command:, stdin_data:, env:) escaped_stdin_data = stdin_data.gsub("\"", "\\\"") diff --git a/common/spec/dependabot/shared_helpers_spec.rb b/common/spec/dependabot/shared_helpers_spec.rb index 0c522cd6eb..0f2b03aedd 100644 --- a/common/spec/dependabot/shared_helpers_spec.rb +++ b/common/spec/dependabot/shared_helpers_spec.rb @@ -268,6 +268,26 @@ def existing_tmp_folders .to raise_error(Dependabot::SharedHelpers::HelperSubprocessFailed) end end + + context "when the subprocess exits with out of disk error" do + let(:command) { File.join(spec_root, "helpers/test/error_bash disk") } + it "raises a HelperSubprocessFailed out of disk error" do + expect { run_shell_command } + .to raise_error(Dependabot::SharedHelpers::HelperSubprocessFailed) do |error| + expect(error.message).to include("No space left on device") + end + end + + context "when the subprocess exits with out of memory error" do + let(:command) { File.join(spec_root, "helpers/test/error_bash memory") } + it "raises a HelperSubprocessFailed out of memory error" do + expect { run_shell_command } + .to raise_error(Dependabot::SharedHelpers::HelperSubprocessFailed) do |error| + expect(error.message).to include("MemoryError") + end + end + end + end end describe ".escape_command" do diff --git a/common/spec/helpers/test/error_bash b/common/spec/helpers/test/error_bash index 6320c2e1cd..076b5b9baf 100755 --- a/common/spec/helpers/test/error_bash +++ b/common/spec/helpers/test/error_bash @@ -2,4 +2,10 @@ set -e +if [ "$1" = "disk" ]; then + echo "No space left on device" >&2 +elif [ "$1" = "memory" ]; then + echo "MemoryError" >&2 +fi + exit 1 diff --git a/go_modules/lib/dependabot/go_modules/file_updater/go_mod_updater.rb b/go_modules/lib/dependabot/go_modules/file_updater/go_mod_updater.rb index 922a68295a..22462caab8 100644 --- a/go_modules/lib/dependabot/go_modules/file_updater/go_mod_updater.rb +++ b/go_modules/lib/dependabot/go_modules/file_updater/go_mod_updater.rb @@ -58,7 +58,8 @@ class GoModUpdater OUT_OF_DISK_REGEXES = [ %r{input/output error}, - /no space left on device/ + /no space left on device/, + /Out of diskspace/ ].freeze GO_MOD_VERSION = /^go 1\.\d+(\.\d+)?$/ diff --git a/go_modules/spec/dependabot/go_modules/file_updater/go_mod_updater_spec.rb b/go_modules/spec/dependabot/go_modules/file_updater/go_mod_updater_spec.rb index 323d3b64a5..a8d1c46e4b 100644 --- a/go_modules/spec/dependabot/go_modules/file_updater/go_mod_updater_spec.rb +++ b/go_modules/spec/dependabot/go_modules/file_updater/go_mod_updater_spec.rb @@ -962,6 +962,17 @@ expect(error.message).to include("info/attributes: no space left on device") end end + + it "detects 'Out of diskspace'" do + stderr = <<~ERROR + rsc.io/sampler imports + write fatal: sha1 file '/home/dependabot/dependabot-updater/repo/.git/index.lock' write error. Out of diskspace + ERROR + + expect { updater.send(:handle_subprocess_error, stderr) }.to raise_error(Dependabot::OutOfDisk) do |error| + expect(error.message).to include("write error. Out of diskspace") + end + end end end end diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb index 3d755304cd..a822032807 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/file_parser/yarn_lock.rb @@ -22,7 +22,11 @@ def parsed function: "yarn:parseLockfile", args: [Dir.pwd] ) - rescue SharedHelpers::HelperSubprocessFailed + rescue SharedHelpers::HelperSubprocessFailed => e + raise Dependabot::OutOfDisk, e.message if e.message.end_with?("No space left on device") + raise Dependabot::OutOfDisk, e.message if e.message.end_with?("Out of diskspace") + raise Dependabot::OutOfMemory, e.message if e.message.end_with?("MemoryError") + raise Dependabot::DependencyFileNotParseable, @dependency_file.path end end diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb index 7aaa17d3f4..0822e8c095 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_parser/lockfile_parser_spec.rb @@ -69,6 +69,46 @@ end end end + + context "that contain out of disk/memory error" do + let(:dependency_files) { project_dependency_files("yarn/broken_lockfile") } + + context "because it ran out of disk space" do + before do + allow(Dependabot::SharedHelpers) + .to receive(:run_helper_subprocess) + .and_raise( + Dependabot::SharedHelpers::HelperSubprocessFailed.new( + message: "No space left on device", + error_context: {} + ) + ) + end + + it "raises a helpful error" do + expect { subject } + .to raise_error(Dependabot::OutOfDisk) + end + end + + context "because it ran out of memory" do + before do + allow(Dependabot::SharedHelpers) + .to receive(:run_helper_subprocess) + .and_raise( + Dependabot::SharedHelpers::HelperSubprocessFailed.new( + message: "MemoryError", + error_context: {} + ) + ) + end + + it "raises a helpful error" do + expect { subject } + .to raise_error(Dependabot::OutOfMemory) + end + end + end end context "for pnpm lockfiles" do From 12a7b8d03c9440e69d6a8a51f4fd152fdf497d07 Mon Sep 17 00:00:00 2001 From: Jake Coffman Date: Wed, 21 Feb 2024 15:05:27 -0600 Subject: [PATCH 46/49] fix docker credential type errors (#9091) Co-authored-by: Noorul Islam K M --- .../docker/utils/credentials_finder.rb | 8 ++++- .../dependabot/docker/update_checker_spec.rb | 16 +++++----- .../docker/utils/credentials_finder_spec.rb | 30 +++++++++---------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/docker/lib/dependabot/docker/utils/credentials_finder.rb b/docker/lib/dependabot/docker/utils/credentials_finder.rb index 26b38eaa8b..e945739faf 100644 --- a/docker/lib/dependabot/docker/utils/credentials_finder.rb +++ b/docker/lib/dependabot/docker/utils/credentials_finder.rb @@ -4,12 +4,15 @@ require "aws-sdk-ecr" require "base64" +require "dependabot/credential" require "dependabot/errors" module Dependabot module Docker module Utils class CredentialsFinder + extend T::Sig + AWS_ECR_URL = /dkr\.ecr\.(?[^.]+)\.amazonaws\.com/ DEFAULT_DOCKER_HUB_REGISTRY = "registry.hub.docker.com" @@ -17,6 +20,7 @@ def initialize(credentials) @credentials = credentials end + sig { params(registry_hostname: String).returns(T.nilable(Dependabot::Credential)) } def credentials_for_registry(registry_hostname) registry_details = credentials @@ -42,8 +46,10 @@ def using_dockerhub?(registry) private + sig { returns(T::Array[Dependabot::Credential]) } attr_reader :credentials + sig { params(registry_details: Dependabot::Credential).returns(Dependabot::Credential) } def build_aws_credentials(registry_details) # If credentials have been generated from AWS we can just return them return registry_details if registry_details["username"] == "AWS" @@ -75,7 +81,7 @@ def build_aws_credentials(registry_details) ecr_client.get_authorization_token.authorization_data.first.authorization_token username, password = Base64.decode64(@authorization_tokens[registry_hostname]).split(":") - registry_details.merge("username" => username, "password" => password) + registry_details.merge(Dependabot::Credential.new({ "username" => username, "password" => password })) rescue Aws::Errors::MissingCredentialsError, Aws::ECR::Errors::UnrecognizedClientException, Aws::ECR::Errors::InvalidSignatureException diff --git a/docker/spec/dependabot/docker/update_checker_spec.rb b/docker/spec/dependabot/docker/update_checker_spec.rb index 194ba27d84..41a04b0734 100644 --- a/docker/spec/dependabot/docker/update_checker_spec.rb +++ b/docker/spec/dependabot/docker/update_checker_spec.rb @@ -24,12 +24,12 @@ let(:ignored_versions) { [] } let(:raise_on_ignored) { false } let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }] + })] end let(:dependency) do @@ -1107,17 +1107,17 @@ def stub_tag_with_no_digest(tag) context "with authentication credentials" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }, { + }), Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "registry-host.io:5000", "username" => "grey", "password" => "pa55word" - }] + })] end before do @@ -1130,15 +1130,15 @@ def stub_tag_with_no_digest(tag) context "that don't have a username or password" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "git_source", "host" => "github.com", "username" => "x-access-token", "password" => "token" - }, { + }), Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "registry-host.io:5000" - }] + })] end it { is_expected.to eq("17.10") } diff --git a/docker/spec/dependabot/docker/utils/credentials_finder_spec.rb b/docker/spec/dependabot/docker/utils/credentials_finder_spec.rb index d88eab770e..1161b405ba 100644 --- a/docker/spec/dependabot/docker/utils/credentials_finder_spec.rb +++ b/docker/spec/dependabot/docker/utils/credentials_finder_spec.rb @@ -10,12 +10,12 @@ RSpec.describe Dependabot::Docker::Utils::CredentialsFinder do subject(:finder) { described_class.new(credentials) } let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com", "username" => "grey", "password" => "pa55word" - }] + })] end describe "#credentials_for_registry" do @@ -30,12 +30,12 @@ context "with a non-AWS registry" do let(:registry) { "my.registry.com" } let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "my.registry.com", "username" => "grey", "password" => "pa55word" - }] + })] end it { is_expected.to eq(credentials.first) } @@ -46,12 +46,12 @@ context "with 'AWS' as the username" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com", "username" => "AWS", "password" => "pa55word" - }] + })] end it { is_expected.to eq(credentials.first) } @@ -59,10 +59,10 @@ context "without a username or password" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com" - }] + })] end context "and a valid AWS response (via proxying)" do @@ -75,7 +75,7 @@ end it "returns details without credentials" do - expect(found_credentials).to eq( + expect(found_credentials.to_h).to eq( "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com" ) @@ -85,12 +85,12 @@ context "with as AKID as the username" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com", "username" => "AKIAIHYCC4QXL4X2OTCQ", "password" => "pa55word" - }] + })] end context "and an invalid secret key as the password" do @@ -145,7 +145,7 @@ end it "returns an updated set of credentials" do - expect(found_credentials).to eq( + expect(found_credentials.to_h).to eq( "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com", "username" => "AWS", @@ -157,10 +157,10 @@ context "using the default credentials provider" do let(:credentials) do - [{ + [Dependabot::Credential.new({ "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com" - }] + })] end context "and a valid AWS response" do @@ -175,7 +175,7 @@ end it "returns updated, valid credentials" do - expect(found_credentials).to eq( + expect(found_credentials.to_h).to eq( "type" => "docker_registry", "registry" => "695729449481.dkr.ecr.eu-west-2.amazonaws.com", "username" => "foo", From c0ef96f752887aa472bbd38377441ba0d6296429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 21 Feb 2024 23:39:03 +0100 Subject: [PATCH 47/49] Report release to sentry (#8885) Report release to sentry --------- Co-authored-by: Nish Sinha --- .github/workflows/images-branch.yml | 14 +++++++------- Dockerfile.updater-core | 2 ++ script/_common | 1 + sorbet/rbi/shims/sentry-ruby.rbi | 3 +++ updater/lib/dependabot/setup.rb | 1 + 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/images-branch.yml b/.github/workflows/images-branch.yml index f3223130cc..ebd5e74dfb 100644 --- a/.github/workflows/images-branch.yml +++ b/.github/workflows/images-branch.yml @@ -73,7 +73,7 @@ jobs: contents: read packages: write env: - TAG: ${{ github.sha }} + DEPENDABOT_UPDATER_VERSION: ${{ github.sha }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -81,7 +81,7 @@ jobs: submodules: recursive - name: Prepare tag - run: echo "TAG=${{ github.sha }}" >> $GITHUB_ENV + run: echo "DEPENDABOT_UPDATER_VERSION=${{ github.sha }}" >> $GITHUB_ENV if: github.event_name == 'pull_request' - name: Prepare tag (forks) @@ -90,7 +90,7 @@ jobs: git fetch origin main git merge origin/main --ff-only || exit 1 git submodule update --init --recursive - echo "TAG=$(git rev-parse HEAD)" >> $GITHUB_ENV + echo "DEPENDABOT_UPDATER_VERSION=$(git rev-parse HEAD)" >> $GITHUB_ENV if: github.event_name == 'workflow_dispatch' - name: Log in to GHCR @@ -102,12 +102,12 @@ jobs: - name: Push branch image run: | - docker tag "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}" "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}:$TAG" - docker push "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}:$TAG" + docker tag "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}" "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}:$DEPENDABOT_UPDATER_VERSION" + docker push "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}:$DEPENDABOT_UPDATER_VERSION" - name: Set summary run: | - echo "updater uploaded with tag \`$TAG\`" >> $GITHUB_STEP_SUMMARY + echo "updater uploaded with tag \`$DEPENDABOT_UPDATER_VERSION\`" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}:$TAG" >> $GITHUB_STEP_SUMMARY + echo "ghcr.io/dependabot/dependabot-updater-${{ matrix.suite.ecosystem }}:$DEPENDABOT_UPDATER_VERSION" >> $GITHUB_STEP_SUMMARY echo "\`\`\`" >> $GITHUB_STEP_SUMMARY diff --git a/Dockerfile.updater-core b/Dockerfile.updater-core index 81b3419b37..de950c6968 100644 --- a/Dockerfile.updater-core +++ b/Dockerfile.updater-core @@ -128,6 +128,8 @@ RUN gem install bundler -v $BUNDLER_V2_VERSION --no-document && \ ENV PATH="$DEPENDABOT_HOME/bin:$PATH" ENV DEPENDABOT_NATIVE_HELPERS_PATH="/opt" +ENV DEPENDABOT_UPDATER_VERSION=${DEPENDABOT_UPDATER_VERSION:-development} + USER root CMD ["bin/run"] diff --git a/script/_common b/script/_common index e4cf21e978..ae2e13f826 100755 --- a/script/_common +++ b/script/_common @@ -47,6 +47,7 @@ function docker_build() { --build-arg BUILDKIT_INLINE_CACHE=1 \ --build-arg USER_UID=$DEPENDABOT_USER_UID \ --build-arg USER_GID=$DEPENDABOT_USER_GID \ + --build-arg DEPENDABOT_UPDATER_VERSION=$DEPENDABOT_UPDATER_VERSION \ --cache-from "$UPDATER_CORE_IMAGE" \ -t "$UPDATER_CORE_IMAGE" \ -f Dockerfile.updater-core \ diff --git a/sorbet/rbi/shims/sentry-ruby.rbi b/sorbet/rbi/shims/sentry-ruby.rbi index 4e0cc189ba..fcf185aa27 100644 --- a/sorbet/rbi/shims/sentry-ruby.rbi +++ b/sorbet/rbi/shims/sentry-ruby.rbi @@ -11,6 +11,9 @@ module Sentry end class Configuration + sig { returns(T.nilable(String)) } + attr_accessor :release + sig { returns(T.nilable(::Logger)) } attr_accessor :logger diff --git a/updater/lib/dependabot/setup.rb b/updater/lib/dependabot/setup.rb index 7bac4b9260..2e06316da7 100644 --- a/updater/lib/dependabot/setup.rb +++ b/updater/lib/dependabot/setup.rb @@ -16,6 +16,7 @@ end Sentry.init do |config| + config.release = ENV.fetch("DEPENDABOT_UPDATER_VERSION") config.logger = Dependabot.logger config.project_root = File.expand_path("../../..", __dir__) From 97e8329a88ba92bd0ad8879efaf569ec56a80e0a Mon Sep 17 00:00:00 2001 From: Bluehill / Jonghyo Lee Date: Thu, 22 Feb 2024 08:33:07 +0900 Subject: [PATCH 48/49] NuGet: Set EnableWindowsTargeting as true (#9082) * Set EnableWindowsTargeting * Revert "Set EnableWindowsTargeting" This reverts commit b45ddf5ce16dc56a059aecd0e09cdcf2a433ecb4. * Add EnableWindowsTargeting in temp project * Add Test case --------- Co-authored-by: AbdulFattaah Popoola Co-authored-by: Nish Sinha --- .../Update/UpdateWorkerTests.Sdk.cs | 32 +++++++++++++++++++ .../Utilities/MSBuildHelper.cs | 12 ++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs index 4dea5d289c..220a62a867 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs @@ -50,6 +50,38 @@ await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", """); } + [Fact] + public async Task UpdateVersionAttribute_InProjectFile_ForPackageReferenceInclude_Windows() + { + // update Newtonsoft.Json from 9.0.1 to 13.0.1 + await TestUpdateForProject("Newtonsoft.Json", "9.0.1", "13.0.1", + // initial + projectContents: $""" + + + net8.0-windows10.0.19041.0 + win-x64 + + + + + + + """, + // expected + expectedProjectContents: $""" + + + net8.0-windows10.0.19041.0 + win-x64 + + + + + + + """); + } [Theory] [InlineData("$(NewtonsoftJsonVersion")] diff --git a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs index 2fb64bfc5b..e6e0fd241c 100644 --- a/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +++ b/nuget/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs @@ -360,7 +360,17 @@ private static async Task CreateTempProjectAsync( await File.WriteAllTextAsync(tempProjectPath, projectContents); // prevent directory crawling - await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Build.props"), ""); + await File.WriteAllTextAsync( + Path.Combine(tempDir.FullName, "Directory.Build.props"), + """ + + + + true + + + """); + await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Build.targets"), ""); await File.WriteAllTextAsync(Path.Combine(tempDir.FullName, "Directory.Packages.props"), ""); From 4718098a2e8a68addfb95ab4e0f75443ea449be9 Mon Sep 17 00:00:00 2001 From: David Stosik Date: Thu, 22 Feb 2024 10:41:23 +0900 Subject: [PATCH 49/49] Fix README file The block of HTML code that displays a picture header at the top of the README file is indented with 4 spaces after an empty line. GitHub Markdown sees that as a code block (see [Indented code blocks](https://github.github.com/gfm/#indented-code-blocks) in the GitHub Flavored Markdown Spec). Removing the blank line should fix it as the HTML code won't be seen as a code block anymore and will instead be unescaped and inserted as [raw HTML](https://github.github.com/gfm/#raw-html). --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 8e129f59c8..8dd3e64ec7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@

-