diff --git a/e2e/backend/test_backend_missing_deps b/e2e/backend/test_backend_missing_deps index fc160b6f61..bb77baad68 100644 --- a/e2e/backend/test_backend_missing_deps +++ b/e2e/backend/test_backend_missing_deps @@ -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 @@ -38,21 +38,16 @@ 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 @@ -60,22 +55,22 @@ 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 @@ -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 @@ -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 @@ -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 diff --git a/src/backend/cargo.rs b/src/backend/cargo.rs index b985484118..a4f43ccbee 100644 --- a/src/backend/cargo.rs +++ b/src/backend/cargo.rs @@ -62,14 +62,14 @@ impl Backend for CargoBackend { async fn install_version_(&self, ctx: &InstallContext, tv: ToolVersion) -> Result { // 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); diff --git a/src/backend/dotnet.rs b/src/backend/dotnet.rs index a75b39725c..9722e28e53 100644 --- a/src/backend/dotnet.rs +++ b/src/backend/dotnet.rs @@ -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") diff --git a/src/backend/gem.rs b/src/backend/gem.rs index 0876a66b2c..e95fe0b4d6 100644 --- a/src/backend/gem.rs +++ b/src/backend/gem.rs @@ -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") diff --git a/src/backend/go.rs b/src/backend/go.rs index 4fb9bd92c1..4bc3e2e651 100644 --- a/src/backend/go.rs +++ b/src/backend/go.rs @@ -33,14 +33,14 @@ impl Backend for GoBackend { async fn _list_remote_versions(&self, config: &Arc) -> eyre::Result> { // 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 || { @@ -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(); diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 43db2813cf..16a33b4cc8 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -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, 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) -> eyre::Result> { diff --git a/src/backend/npm.rs b/src/backend/npm.rs index a2404ca571..3865933fc6 100644 --- a/src/backend/npm.rs +++ b/src/backend/npm.rs @@ -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 @@ -60,7 +60,7 @@ impl Backend for NPMBackend { async fn latest_stable_version(&self, config: &Arc) -> eyre::Result> { // 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( @@ -88,7 +88,7 @@ impl Backend for NPMBackend { } async fn install_version_(&self, ctx: &InstallContext, tv: ToolVersion) -> Result { - self.check_install_deps(&ctx.config).await?; + self.check_install_deps(&ctx.config).await; if Settings::get().npm.bun { CmdLineRunner::new("bun") .arg("install") @@ -156,12 +156,12 @@ impl NPMBackend { } /// Check dependencies for version checking (always needs npm) - async fn ensure_npm_for_version_check(&self, config: &Arc) -> Result<()> { + async fn ensure_npm_for_version_check(&self, config: &Arc) { // 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.", @@ -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) -> Result<()> { + async fn check_install_deps(&self, config: &Arc) { 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\ @@ -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\ diff --git a/src/backend/pipx.rs b/src/backend/pipx.rs index 65d9e7c8b1..e952bec420 100644 --- a/src/backend/pipx.rs +++ b/src/backend/pipx.rs @@ -164,7 +164,7 @@ 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\ @@ -172,7 +172,7 @@ impl Backend for PIPXBackend { Alternatively, you can use uv/uvx by installing uv:\n\ mise use uv@latest", ) - .await?; + .await; } let pipx_request = self diff --git a/src/backend/spm.rs b/src/backend/spm.rs index 003d7e3373..b9ec9c2a86 100644 --- a/src/backend/spm.rs +++ b/src/backend/spm.rs @@ -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" {