pub(crate) fn resolve_shell() -> String {
if let Ok(shell) = std::env::var("APTU_SHELL") {
return shell;
}
#[cfg(unix)]
{
if std::env::var("PATH").is_ok_and(|p| {
std::env::split_paths(&p).any(|dir| {
use std::os::unix::fs::PermissionsExt as _;
let candidate = dir.join("bash");
candidate.is_file()
&& candidate
.metadata()
.map(|m| m.permissions().mode() & 0o111 != 0)
.unwrap_or(false)
})
}) {
return "bash".to_string();
}
"/bin/sh".to_string()
}
#[cfg(not(unix))]
{
"cmd".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[serial_test::serial]
fn test_resolve_shell_aptu_shell_env_takes_priority() {
unsafe { std::env::set_var("APTU_SHELL", "zsh") };
let shell = resolve_shell();
unsafe { std::env::remove_var("APTU_SHELL") };
assert_eq!(
shell, "zsh",
"APTU_SHELL must take priority over PATH detection"
);
}
#[test]
#[cfg(unix)]
#[serial_test::serial]
fn test_resolve_shell_falls_back_when_path_empty() {
unsafe { std::env::remove_var("APTU_SHELL") };
let saved_path = std::env::var("PATH").ok();
unsafe { std::env::set_var("PATH", "") };
let shell = resolve_shell();
match saved_path {
Some(p) => unsafe { std::env::set_var("PATH", p) },
None => unsafe { std::env::remove_var("PATH") },
}
assert_eq!(
shell, "/bin/sh",
"must fall back to /bin/sh when bash is not on PATH"
);
}
}