From b5fddb419af834390893c66c04bf3ca372a64691 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 03:59:58 +0000 Subject: [PATCH] fix(runner): limit lockfile conflict checks to Node.js ecosystem Previously, `run` would throw a `LockfileConflict` error if multiple tools from the same ecosystem were detected and installed. This behavior was intended for Node.js (where `package-lock.json`, `yarn.lock`, etc., indicate competing tools), but it errantly applied to other ecosystems. For example, in Ruby, a project may have both a `Gemfile.lock` and a `Rakefile` representing `bundler` and `rake`, which gracefully coexist. This commit limits the lockfile conflict logic strictly to the Node.js ecosystem, allowing other ecosystems to correctly rely on priority resolution. Co-authored-by: insign <1113045+insign@users.noreply.github.com> --- src/main.rs | 14 ++++++-------- src/runner.rs | 51 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/main.rs b/src/main.rs index ee23013..842f4a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -96,12 +96,7 @@ fn main() { }; // Search for runners - let search_result = search_runners( - ¤t_dir, - max_levels, - &ignore_list, - verbose, - ); + let search_result = search_runners(¤t_dir, max_levels, &ignore_list, verbose); // Prepare to inject custom commands // Filter empty commands @@ -115,7 +110,7 @@ fn main() { let has_valid_commands = valid_config_commands .as_ref() - .map_or(false, |c| !c.is_empty()); + .is_some_and(|c| !c.is_empty()); let (mut runners, working_dir) = match search_result { Ok(result) => result, @@ -135,7 +130,10 @@ fn main() { if let Some(valid_config_commands) = valid_config_commands { if !valid_config_commands.is_empty() { // Check if we already have a custom runner - if let Some(idx) = runners.iter().position(|r| r.ecosystem == Ecosystem::Custom) { + if let Some(idx) = runners + .iter() + .position(|r| r.ecosystem == Ecosystem::Custom) + { // Merge config commands into existing runner (local overrides global) let mut merged_commands = valid_config_commands.clone(); if let Some(existing_cmds) = &runners[idx].custom_commands { diff --git a/src/runner.rs b/src/runner.rs index 9166862..c6bffc8 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -90,27 +90,25 @@ pub fn check_conflicts( // Check for conflicts within ecosystems for (ecosystem, eco_runners) in &by_ecosystem { - if eco_runners.len() > 1 { + if eco_runners.len() > 1 && *ecosystem == Ecosystem::NodeJs { // For Node.js ecosystem, try to use Corepack to resolve - if *ecosystem == Ecosystem::NodeJs { - if let Some(corepack_pm) = node::get_corepack_manager(working_dir) { - // Find the runner that matches the Corepack package manager - if let Some(runner) = eco_runners.iter().find(|r| r.name == corepack_pm) { - if verbose { - output::info(&format!( - "Using {} (specified by packageManager in package.json)", - corepack_pm - )); - } - return Ok((*runner).clone()); - } else { - // Corepack specifies a PM but we don't have a matching lockfile - if verbose { - output::warning(&format!( - "packageManager specifies '{}' but no matching lockfile found", - corepack_pm - )); - } + if let Some(corepack_pm) = node::get_corepack_manager(working_dir) { + // Find the runner that matches the Corepack package manager + if let Some(runner) = eco_runners.iter().find(|r| r.name == corepack_pm) { + if verbose { + output::info(&format!( + "Using {} (specified by packageManager in package.json)", + corepack_pm + )); + } + return Ok((*runner).clone()); + } else { + // Corepack specifies a PM but we don't have a matching lockfile + if verbose { + output::warning(&format!( + "packageManager specifies '{}' but no matching lockfile found", + corepack_pm + )); } } } @@ -378,4 +376,17 @@ mod tests { let result = check_conflicts(&runners, dir.path(), false).unwrap(); assert_eq!(result.name, "pnpm"); } + + #[test] + fn test_check_conflicts_non_nodejs_ecosystem() { + let dir = tempdir().unwrap(); + let runners = vec![ + DetectedRunner::new("bundler", "Gemfile.lock", Ecosystem::Ruby, 13), + DetectedRunner::new("rake", "Rakefile", Ecosystem::Ruby, 14), + ]; + + // Should not throw a LockfileConflict for non-NodeJs ecosystems + let result = check_conflicts(&runners, dir.path(), false).unwrap(); + assert_eq!(result.name, "bundler"); // Higher priority wins + } }