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
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ lychee = "0.22.0"
node = "24.14.1"
"npm:renovate" = "43.92.1"
"github:koalaman/shellcheck" = "v0.11.0"
"github:mvdan/sh" = "v3.12.0"
"github:mvdan/sh" = "v3.13.1"
actionlint = "1.7.10"
editorconfig-checker = "v3.6.1"
"npm:markdownlint-cli2" = "0.22.0"
Expand Down
38 changes: 37 additions & 1 deletion src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,16 +763,52 @@ pub fn check_active(check: &Check, mise_tools: &HashMap<String, String>) -> bool
/// suffix in the installed binary name. The backend's binary-name cleaning logic matches
/// binaries against the repo name (e.g. `"mvdan/sh"`), so it cannot map `"shfmt"` β†’
/// `"mvdan/sh"` and leaves the name as `"shfmt_v3.12.0"` rather than stripping it.
///
/// When the exact constructed name is not found on PATH (e.g. after a version bump
/// where the declared version doesn't yet match the installed binary), the function
/// falls back to scanning PATH for any binary whose name starts with the prefix before
/// `{version}` in the format string (e.g. prefix `"shfmt_"` matches `"shfmt_v3.13.1"`).
/// This avoids needing to update fixture versions on every Renovate bump.
pub fn resolve_bin_name(check: &Check, mise_tools: &HashMap<String, String>) -> String {
if let Some(fmt) = check.versioned_bin_fmt {
let key = check.mise_tool_name.unwrap_or(check.bin_name);
if let Some(version) = mise_tools.get(key) {
return fmt.replace("{version}", version);
let exact = fmt.replace("{version}", version);
let path_var = std::env::var("PATH").unwrap_or_default();
if binary_on_path_var(&exact, &path_var) {
return exact;
}
// Exact name not found β€” scan PATH for any binary starting with the
// prefix before `{version}` in the format string.
if let Some(prefix) = fmt.split_once("{version}").map(|(p, _)| p)
&& let Some(found) = find_bin_with_prefix(prefix, &path_var)
{
return found;
}
return exact;
}
}
check.bin_name.to_string()
}

/// Scans each directory in `path_var` for the first file whose name starts with
/// `prefix`. Returns the file name (not the full path) of the first match found.
fn find_bin_with_prefix(prefix: &str, path_var: &str) -> Option<String> {
for dir in std::env::split_paths(path_var) {
let Ok(entries) = std::fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if name_str.starts_with(prefix) && entry.path().is_file() {
return Some(name_str.into_owned());
}
}
}
None
}

/// Returns true if `bin_name` exists as a file in any directory in `path_var`
/// (a `:`-separated PATH string). Accepts the PATH string as a parameter so
/// callers can substitute a test-controlled path without mutating env vars.
Expand Down
Loading