Skip to content

Commit

Permalink
Ensure Corepack Usage for npm, pnpm, and yarn Command Execution (#10944)
Browse files Browse the repository at this point in the history
* fix corepack package manager installation for npm, pnpm and yarn
* fix finding package manager name issue
* fix installing pnpm version issue
  • Loading branch information
kbukum1 authored Nov 18, 2024
1 parent 4159f9d commit 8f037cf
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 22 deletions.
78 changes: 74 additions & 4 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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 <name>@<version> --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

Expand Down
39 changes: 24 additions & 15 deletions npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions npm_and_yarn/spec/dependabot/npm_and_yarn/update_checker_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit 8f037cf

Please sign in to comment.