Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 16 additions & 21 deletions e2e/backend/test_backend_missing_deps
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

# Test that package manager backends show helpful errors when their dependencies are missing
# Test that package manager backends show helpful warnings when their dependencies are missing

# Create a PATH that has mise but not package managers
# ROOT is set by run_test script
Expand Down Expand Up @@ -38,44 +38,39 @@ test_error_message() {
fi
}

# Helper function to test a command that should fail with expected error
test_backend_error() {
# Helper function to test a command that should show a warning
test_backend_warning() {
local backend_name=$1
local command=$2
local expected_error=$3
local expected_warning=$3

local output
if output=$(eval "$command" 2>&1); then
echo "ERROR: $backend_name command should have failed but succeeded"
TEST_FAILED=1
return 1
else
output=$(eval "$command" 2>&1 || true)
test_error_message "$backend_name shows correct error" "$output" "$expected_error"
fi
# Commands will succeed but show warnings, capture stderr
output=$(MISE_LOG_LEVEL=warn eval "$command" 2>&1 || true)
test_error_message "$backend_name shows correct warning" "$output" "$expected_warning"
}

# Test npm backend
test_npm() {
echo "Testing npm backend with missing npm..."
check_command_missing "npm"

# Test ls-remote
test_backend_error "npm" "mise ls-remote npm:test-package" "npm is required but not found"
# Test ls-remote - should show warning but continue
test_backend_warning "npm" "mise ls-remote npm:test-package" "npm may be required but was not found"

# Check for helpful install instructions
local output
output=$(mise ls-remote npm:test-package 2>&1 || true)
output=$(MISE_LOG_LEVEL=warn mise ls-remote npm:test-package 2>&1 || true)
test_error_message "npm suggests installing node" "$output" "mise use node@latest"
test_error_message "npm mentions npm is needed for queries" "$output" "npm is required for querying package information"

# Test install
test_backend_error "npm" "mise install npm:test-package@latest" "npm is required but not found"
# Test install - should show warning but continue
test_backend_warning "npm" "mise install npm:test-package@latest" "npm may be required but was not found"

# Test with bun mode enabled but npm still missing
echo "Testing npm backend with bun mode enabled but npm missing..."
export MISE_NPM_BUN=true
test_backend_error "npm (bun mode)" "mise ls-remote npm:test-package" "npm is required but not found"
test_backend_warning "npm (bun mode)" "mise ls-remote npm:test-package" "npm may be required but was not found"
output=$(mise ls-remote npm:test-package 2>&1 || true)
test_error_message "npm (bun mode) shows npm is required for queries" "$output" "npm is required for querying package information"
unset MISE_NPM_BUN
Expand All @@ -97,7 +92,7 @@ test_cargo() {
export PATH="$new_path"

check_command_missing "cargo"
test_backend_error "cargo" "mise install cargo:tiny@latest" "cargo is required but not found"
test_backend_warning "cargo" "mise install cargo:tiny@latest" "cargo may be required but was not found"
}

# Test go backend
Expand All @@ -116,7 +111,7 @@ test_go() {
export PATH="$new_path"

check_command_missing "go"
test_backend_error "go" "mise ls-remote go:github.com/test/test" "go is required but not found"
test_backend_warning "go" "mise ls-remote go:github.com/test/test" "go may be required but was not found"
}

# Run all tests
Expand All @@ -125,7 +120,7 @@ test_cargo
test_go

if [ $TEST_FAILED -eq 0 ]; then
echo "✓ backend dependency error test passed"
echo "✓ backend dependency warning test passed"
else
echo "✗ Some tests failed"
exit 1
Expand Down
4 changes: 2 additions & 2 deletions src/backend/cargo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ impl Backend for CargoBackend {

async fn install_version_(&self, ctx: &InstallContext, tv: ToolVersion) -> Result<ToolVersion> {
// Check if cargo is available
self.ensure_dependency(
self.warn_if_dependency_missing(
&ctx.config,
"cargo",
"To use cargo packages with mise, you need to install Rust first:\n\
mise use rust@latest\n\n\
Or install Rust via https://rustup.rs/",
)
.await?;
.await;

let config = ctx.config.clone();
let install_arg = format!("{}@{}", self.tool_name(), tv.version);
Expand Down
4 changes: 2 additions & 2 deletions src/backend/dotnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@ impl Backend for DotnetBackend {
Settings::get().ensure_experimental("dotnet backend")?;

// Check if dotnet is available
self.ensure_dependency(
self.warn_if_dependency_missing(
&ctx.config,
"dotnet",
"To use dotnet tools with mise, you need to install .NET SDK first:\n\
mise use dotnet@latest\n\n\
Or install .NET SDK via https://dotnet.microsoft.com/download",
)
.await?;
.await;

let mut cli = CmdLineRunner::new("dotnet")
.arg("tool")
Expand Down
4 changes: 2 additions & 2 deletions src/backend/gem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ impl Backend for GemBackend {
Settings::get().ensure_experimental("gem backend")?;

// Check if gem is available
self.ensure_dependency(
self.warn_if_dependency_missing(
&ctx.config,
"gem",
"To use gem packages with mise, you need to install Ruby first:\n\
mise use ruby@latest",
)
.await?;
.await;

CmdLineRunner::new("gem")
.arg("install")
Expand Down
8 changes: 4 additions & 4 deletions src/backend/go.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ impl Backend for GoBackend {

async fn _list_remote_versions(&self, config: &Arc<Config>) -> eyre::Result<Vec<String>> {
// Check if go is available
self.ensure_dependency(
self.warn_if_dependency_missing(
config,
"go",
"To use go packages with mise, you need to install Go first:\n\
mise use go@latest\n\n\
Or install Go via https://go.dev/dl/",
)
.await?;
.await;

timeout::run_with_timeout_async(
async || {
Expand Down Expand Up @@ -100,14 +100,14 @@ impl Backend for GoBackend {
Settings::get().ensure_experimental("go backend")?;

// Check if go is available
self.ensure_dependency(
self.warn_if_dependency_missing(
&ctx.config,
"go",
"To use go packages with mise, you need to install Go first:\n\
mise use go@latest\n\n\
Or install Go via https://go.dev/dl/",
)
.await?;
.await;

let opts = self.ba.opts();

Expand Down
40 changes: 30 additions & 10 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -715,22 +715,42 @@ pub trait Backend: Debug + Send + Sync {
b.which(config, &tv, bin).await.ok().flatten()
}

/// Check if a required dependency is available and return a helpful error if not.
/// This provides a consistent error message format across all backends.
async fn ensure_dependency(
/// Check if a required dependency is available and show a warning if not.
/// This provides a consistent warning message format across all backends.
/// Changed to warning instead of error to avoid CI failures on Windows.
async fn warn_if_dependency_missing(
&self,
config: &Arc<Config>,
program: &str,
install_instructions: &str,
) -> eyre::Result<()> {
if self.dependency_which(config, program).await.is_none() {
bail!(
"{} is required but not found.\n\n{}",
program,
install_instructions
) {
let found = if self.dependency_which(config, program).await.is_some() {
true
} else if cfg!(windows) {
// On Windows, also check for program with Windows executable extensions
let settings = Settings::get();
let mut found = false;
for ext in &settings.windows_executable_extensions {
if self
.dependency_which(config, &format!("{}.{}", program, ext))
.await
.is_some()
{
found = true;
break;
}
}
found
} else {
false
};

if !found {
warn!(
"{} may be required but was not found.\n\n{}",
program, install_instructions
);
}
Ok(())
}

async fn dependency_env(&self, config: &Arc<Config>) -> eyre::Result<BTreeMap<String, String>> {
Expand Down
20 changes: 10 additions & 10 deletions src/backend/npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl Backend for NPMBackend {
// Currently bun info requires a package.json file, so we always use npm.
// Once bun provides a way to query registry without package.json, we can
// switch to using bun when npm.bun=true
self.ensure_npm_for_version_check(config).await?;
self.ensure_npm_for_version_check(config).await;
timeout::run_with_timeout_async(
async || {
// Always use npm for listing versions since bun info requires package.json
Expand All @@ -60,7 +60,7 @@ impl Backend for NPMBackend {
async fn latest_stable_version(&self, config: &Arc<Config>) -> eyre::Result<Option<String>> {
// TODO: Add bun support for getting latest version without npm
// See TODO in _list_remote_versions for details
self.ensure_npm_for_version_check(config).await?;
self.ensure_npm_for_version_check(config).await;
let cache = self.latest_version_cache.lock().await;
let this = self;
timeout::run_with_timeout_async(
Expand Down Expand Up @@ -88,7 +88,7 @@ impl Backend for NPMBackend {
}

async fn install_version_(&self, ctx: &InstallContext, tv: ToolVersion) -> Result<ToolVersion> {
self.check_install_deps(&ctx.config).await?;
self.check_install_deps(&ctx.config).await;
if Settings::get().npm.bun {
CmdLineRunner::new("bun")
.arg("install")
Expand Down Expand Up @@ -156,12 +156,12 @@ impl NPMBackend {
}

/// Check dependencies for version checking (always needs npm)
async fn ensure_npm_for_version_check(&self, config: &Arc<Config>) -> Result<()> {
async fn ensure_npm_for_version_check(&self, config: &Arc<Config>) {
// We always need npm for querying package versions
// TODO: Once bun supports querying packages without package.json, this can be updated
self.ensure_dependency(
self.warn_if_dependency_missing(
config,
NPM_PROGRAM,
"npm", // Use "npm" for dependency check, which will check npm.cmd on Windows
"To use npm packages with mise, you need to install Node.js first:\n\
mise use node@latest\n\n\
Note: npm is required for querying package information, even when using bun for installation.",
Expand All @@ -170,10 +170,10 @@ impl NPMBackend {
}

/// Check dependencies for package installation (npm or bun based on settings)
async fn check_install_deps(&self, config: &Arc<Config>) -> Result<()> {
async fn check_install_deps(&self, config: &Arc<Config>) {
if Settings::get().npm.bun {
// In bun mode, only bun is required for installation
self.ensure_dependency(
self.warn_if_dependency_missing(
config,
"bun",
"To use npm packages with bun, you need to install bun first:\n\
Expand All @@ -184,9 +184,9 @@ impl NPMBackend {
.await
} else {
// In npm mode, npm is required
self.ensure_dependency(
self.warn_if_dependency_missing(
config,
NPM_PROGRAM,
"npm", // Use "npm" for dependency check, which will check npm.cmd on Windows
"To use npm packages with mise, you need to install Node.js first:\n\
mise use node@latest\n\n\
Alternatively, you can use bun instead of npm by setting:\n\
Expand Down
4 changes: 2 additions & 2 deletions src/backend/pipx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,15 @@ impl Backend for PIPXBackend {
&& tv.request.options().get("uvx") != Some(&"false".to_string());

if !use_uvx {
self.ensure_dependency(
self.warn_if_dependency_missing(
&ctx.config,
"pipx",
"To use pipx packages with mise, you need to install pipx first:\n\
mise use pipx@latest\n\n\
Alternatively, you can use uv/uvx by installing uv:\n\
mise use uv@latest",
)
.await?;
.await;
}

let pipx_request = self
Expand Down
4 changes: 2 additions & 2 deletions src/backend/spm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ impl Backend for SPMBackend {
settings.ensure_experimental("spm backend")?;

// Check if swift is available
self.ensure_dependency(
self.warn_if_dependency_missing(
&ctx.config,
"swift",
"To use Swift Package Manager (spm) tools with mise, you need to install Swift first:\n\
mise use swift@latest\n\n\
Or install Swift via https://swift.org/download/",
)
.await?;
.await;

let repo = SwiftPackageRepo::new(&self.tool_name())?;
let revision = if tv.version == "latest" {
Expand Down
Loading