Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Ensure Corepack Usage for npm, pnpm, and yarn Command Execution #10944

Merged
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

Copy link
Contributor Author

@kbukum1 kbukum1 Nov 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved from PackageManagerHelper into Helpers.

# 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] }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix Sorbet Issue:

Dependabot::Sorbet::Runtime::InformationalError
Parameter 'name': Expected type T.nilable(String), got type Hash with value {"npm"=>Dependabot::NpmAndY...pmAndYarn::PNPMPackageManager}
Caller: /home/dependabot/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb:221
Definition: /home/dependabot/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb:282 (Dependabot::NpmAndYarn::PackageManagerHelper#package_manager_by_name)

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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix Sorbet Issue:

Dependabot::Sorbet::Runtime::InformationalError
Parameter 'name': Expected type T.nilable(String), got type Hash with value {"npm"=>Dependabot::NpmAndY...pmAndYarn::PNPMPackageManager}
Caller: /home/dependabot/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb:221
Definition: /home/dependabot/npm_and_yarn/lib/dependabot/npm_and_yarn/package_manager.rb:282 (Dependabot::NpmAndYarn::PackageManagerHelper#package_manager_by_name)

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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This else is going to be removed in clean-up process of enable_corepack_for_npm_and_yarn feature flag after all checks.

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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to install npm, and yarn versions as well. We removed the condition that only install for pnpm when version is guessed.


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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate!

.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 @@ -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
Loading