diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb index e5e20fdd6d..2fb082d475 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb @@ -145,7 +145,7 @@ def self.yarn_berry?(yarn_lock) false end - sig { returns(Integer) } + sig { returns(T.any(Integer, T.noreturn)) } def self.yarn_major_version retries = 0 output = run_single_yarn_command("--version") @@ -171,6 +171,7 @@ def self.yarn_major_version handle_subprocess_failure(e) end + sig { params(error: StandardError).returns(T.noreturn) } def self.handle_subprocess_failure(error) message = error.message if YARN_PATH_NOT_FOUND.match?(message) @@ -224,6 +225,7 @@ def self.yarn_4_or_higher? yarn_major_version >= 4 end + sig { returns(T.nilable(String)) } def self.setup_yarn_berry # Always disable immutable installs so yarn's CI detection doesn't prevent updates. run_single_yarn_command("config set enableImmutableInstalls false") @@ -260,24 +262,92 @@ def self.run_yarn_commands(*commands) # NOTE: Needs to be explicitly run through corepack to respect the # `packageManager` setting in `package.json`, because corepack does not # add shims for NPM. + sig { params(command: String, fingerprint: T.nilable(String)).returns(String) } def self.run_npm_command(command, fingerprint: command) - SharedHelpers.run_shell_command("corepack npm #{command}", fingerprint: "corepack npm #{fingerprint}") + if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) + package_manager_run_command(NpmPackageManager::NAME, command, fingerprint: fingerprint) + else + SharedHelpers.run_shell_command("corepack npm #{command}", fingerprint: "corepack npm #{fingerprint}") + end end # Setup yarn and run a single yarn command returning stdout/stderr + sig { params(command: String, fingerprint: T.nilable(String)).returns(String) } def self.run_yarn_command(command, fingerprint: nil) setup_yarn_berry run_single_yarn_command(command, fingerprint: fingerprint) end # Run single pnpm command returning stdout/stderr + sig { params(command: String, fingerprint: T.nilable(String)).returns(String) } def self.run_pnpm_command(command, fingerprint: nil) - SharedHelpers.run_shell_command("pnpm #{command}", fingerprint: "pnpm #{fingerprint || command}") + if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) + package_manager_run_command(PNPMPackageManager::NAME, command, fingerprint: fingerprint) + else + SharedHelpers.run_shell_command("pnpm #{command}", fingerprint: "pnpm #{fingerprint || command}") + end end # Run single yarn command returning stdout/stderr + sig { params(command: String, fingerprint: T.nilable(String)).returns(String) } def self.run_single_yarn_command(command, fingerprint: nil) - SharedHelpers.run_shell_command("yarn #{command}", fingerprint: "yarn #{fingerprint || command}") + if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) + package_manager_run_command(YarnPackageManager::NAME, command, fingerprint: fingerprint) + else + SharedHelpers.run_shell_command("yarn #{command}", fingerprint: "yarn #{fingerprint || command}") + end + end + + # Install the package manager for specified version by using corepack + # and prepare it for use by using corepack + sig { params(name: String, version: String).void } + def self.install(name, version) + Dependabot.logger.info("Installing \"#{name}@#{version}\"") + + package_manager_install(name, version) + package_manager_activate(name, version) + installed_version = package_manager_version(name) + + Dependabot.logger.info("Installed version of #{name}: #{installed_version}") + end + + # Install the package manager for specified version by using corepack + sig { params(name: String, version: String).void } + def self.package_manager_install(name, version) + SharedHelpers.run_shell_command( + "corepack install #{name}@#{version} --global --cache-only", + fingerprint: "corepack install @ --global --cache-only" + ) + end + + # Prepare the package manager for use by using corepack + sig { params(name: String, version: String).void } + def self.package_manager_activate(name, version) + SharedHelpers.run_shell_command( + "corepack prepare #{name}@#{version} --activate", + fingerprint: "corepack prepare --activate" + ) + end + + # Get the version of the package manager by using corepack + sig { params(name: String).returns(String) } + def self.package_manager_version(name) + package_manager_run_command(name, "-v") + end + + # Run single command on package manager returning stdout/stderr + sig do + params( + name: String, + command: String, + fingerprint: T.nilable(String) + ).returns(String) + end + def self.package_manager_run_command(name, command, fingerprint: nil) + SharedHelpers.run_shell_command( + "corepack #{name} #{command}", + fingerprint: "corepack #{name} #{fingerprint || command}" + ) end private_class_method :run_single_yarn_command diff --git a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb index 1d8026425a..6dcd8ccb4f 100644 --- a/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb +++ b/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb @@ -172,18 +172,15 @@ def detect_package_manager sig { returns(T.nilable(String)) } def name_from_lockfiles - PACKAGE_MANAGER_CLASSES.each_key do |manager_name| # iterates keys in order as defined in the hash - return manager_name.to_s if @lockfiles[manager_name.to_sym] - end - nil + PACKAGE_MANAGER_CLASSES.keys.map(&:to_s).find { |manager_name| @lockfiles[manager_name.to_sym] } end sig { returns(T.nilable(String)) } def name_from_package_manager_attr return unless @manifest_package_manager - PACKAGE_MANAGER_CLASSES.each_key do |manager_name| # iterates keys in order as defined in the hash - return manager_name.to_s if @manifest_package_manager.start_with?("#{manager_name}@") + PACKAGE_MANAGER_CLASSES.keys.map(&:to_s).find do |manager_name| + @manifest_package_manager.start_with?("#{manager_name}@") end end @@ -255,22 +252,30 @@ def setup(name) ) end - version ||= requested_version(name) - - if version - raise_if_unsupported!(name, version) + if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) + version ||= requested_version(name) || guessed_version(name) - install(name, version) + if version + raise_if_unsupported!(name, version.to_s) + install(name, version) + end else - version = guessed_version(name) + version ||= requested_version(name) if version - raise_if_unsupported!(name, version.to_s) + raise_if_unsupported!(name, version) + + install(name, version) + else + version = guessed_version(name) + + if version + raise_if_unsupported!(name, version.to_s) - install(name, version) if name == PNPMPackageManager::NAME + install(name, version) if name == PNPMPackageManager::NAME + end end end - version end # rubocop:enable Metrics/CyclomaticComplexity @@ -299,6 +304,10 @@ def raise_if_unsupported!(name, version) end def install(name, version) + if Dependabot::Experiments.enabled?(:enable_corepack_for_npm_and_yarn) + return Helpers.install(name, version.to_s) + end + Dependabot.logger.info("Installing \"#{name}@#{version}\"") SharedHelpers.run_shell_command( diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb index 5a03421770..c0f8f842d1 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/npm_lockfile_updater_spec.rb @@ -63,10 +63,15 @@ let(:tmp_path) { Dependabot::Utils::BUMP_TMP_DIR_PATH } + # Variable to control the enabling feature flag for the corepack fix + let(:enable_corepack_for_npm_and_yarn) { true } + before do FileUtils.mkdir_p(tmp_path) allow(Dependabot::Experiments).to receive(:enabled?) .with(:npm_fallback_version_above_v6).and_return(npm_fallback_version_above_v6_enabled) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:enable_corepack_for_npm_and_yarn).and_return(enable_corepack_for_npm_and_yarn) end after do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater_spec.rb index 05608b328d..4f86ad0ec8 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/pnpm_lockfile_updater_spec.rb @@ -63,7 +63,18 @@ let(:repo_contents_path) { build_tmp_repo(project_name, path: "projects") } - before { FileUtils.mkdir_p(tmp_path) } + # Variable to control the enabling feature flag for the corepack fix + let(:enable_corepack_for_npm_and_yarn) { true } + + before do + FileUtils.mkdir_p(tmp_path) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:enable_corepack_for_npm_and_yarn).and_return(enable_corepack_for_npm_and_yarn) + end + + after do + Dependabot::Experiments.reset! + end describe "errors" do context "with a dependency version that can't be found" do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater_spec.rb index 36d79adba8..aa4c55324e 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater/yarn_lockfile_updater_spec.rb @@ -58,7 +58,18 @@ let(:tmp_path) { Dependabot::Utils::BUMP_TMP_DIR_PATH } - before { FileUtils.mkdir_p(tmp_path) } + # Variable to control the enabling feature flag for the corepack fix + let(:enable_corepack_for_npm_and_yarn) { true } + + before do + FileUtils.mkdir_p(tmp_path) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:enable_corepack_for_npm_and_yarn).and_return(enable_corepack_for_npm_and_yarn) + end + + after do + Dependabot::Experiments.reset! + end describe "errors" do context "with a dependency version that can't be found" do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb index 56879b9353..ab362f490f 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/file_updater_spec.rb @@ -59,11 +59,15 @@ # Variable to control the npm fallback version feature flag let(:npm_fallback_version_above_v6_enabled) { true } + # Variable to control the enabling feature flag for the corepack fix + let(:enable_corepack_for_npm_and_yarn) { true } before do FileUtils.mkdir_p(tmp_path) allow(Dependabot::Experiments).to receive(:enabled?) .with(:npm_fallback_version_above_v6).and_return(npm_fallback_version_above_v6_enabled) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:enable_corepack_for_npm_and_yarn).and_return(enable_corepack_for_npm_and_yarn) end after do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver_spec.rb index 91057eacf1..40d32e0bc1 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/subdependency_version_resolver_spec.rb @@ -33,9 +33,14 @@ # Variable to control the npm fallback version feature flag let(:npm_fallback_version_above_v6_enabled) { true } + # Variable to control the enabling feature flag for the corepack fix + let(:enable_corepack_for_npm_and_yarn) { true } + before do allow(Dependabot::Experiments).to receive(:enabled?) .with(:npm_fallback_version_above_v6).and_return(npm_fallback_version_above_v6_enabled) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:enable_corepack_for_npm_and_yarn).and_return(enable_corepack_for_npm_and_yarn) end after do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/version_resolver_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/version_resolver_spec.rb index 9a7995fe7c..c0a864611c 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/version_resolver_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker/version_resolver_spec.rb @@ -63,6 +63,9 @@ # Variable to control the npm fallback version feature flag let(:npm_fallback_version_above_v6_enabled) { true } + # Variable to control the enabling feature flag for the corepack fix + let(:enable_corepack_for_npm_and_yarn) { true } + before do stub_request(:get, react_dom_registry_listing_url) .to_return(status: 200, body: react_dom_registry_response) @@ -79,7 +82,7 @@ allow(Dependabot::Experiments).to receive(:enabled?) .with(:npm_fallback_version_above_v6).and_return(npm_fallback_version_above_v6_enabled) allow(Dependabot::Experiments).to receive(:enabled?) - .with(:npm_fallback_version_above_v6).and_return(npm_fallback_version_above_v6_enabled) + .with(:enable_corepack_for_npm_and_yarn).and_return(enable_corepack_for_npm_and_yarn) end after do diff --git a/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker_spec.rb b/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker_spec.rb index c395cab87f..93208bf669 100644 --- a/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker_spec.rb +++ b/npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker_spec.rb @@ -56,11 +56,25 @@ let(:registry_listing_url) { "#{registry_base}/#{escaped_dependency_name}" } let(:registry_base) { "https://registry.npmjs.org" } + # Variable to control the npm fallback version feature flag + let(:npm_fallback_version_above_v6_enabled) { false } + + # Variable to control the enabling feature flag for the corepack fix + let(:enable_corepack_for_npm_and_yarn) { true } + before do stub_request(:get, registry_listing_url) .to_return(status: 200, body: registry_response) stub_request(:head, "#{registry_base}/#{dependency_name}/-/#{unscoped_dependency_name}-#{target_version}.tgz") .to_return(status: 200) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:enable_corepack_for_npm_and_yarn).and_return(enable_corepack_for_npm_and_yarn) + allow(Dependabot::Experiments).to receive(:enabled?) + .with(:npm_fallback_version_above_v6).and_return(npm_fallback_version_above_v6_enabled) + end + + after do + Dependabot::Experiments.reset! end it_behaves_like "an update checker"