aboutsummaryrefslogtreecommitdiff
path: root/src/update.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-03 18:35:56 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-03 18:35:56 +0300
commit03f68b7275f07c98769bf699d2aa14805b921f8d (patch)
tree67fa6239de5dbba7abf53118c9495e2e07db37da /src/update.rs
parent903662fcd822e50d875066ecc20811e3c9945e66 (diff)
fix: handle mise-managed updates
Diffstat (limited to 'src/update.rs')
-rw-r--r--src/update.rs197
1 files changed, 190 insertions, 7 deletions
diff --git a/src/update.rs b/src/update.rs
index 6d1ad49b..c5dab475 100644
--- a/src/update.rs
+++ b/src/update.rs
@@ -22,7 +22,9 @@ const PREVIEW_UPDATE_MANIFEST_URL: &str = "https://herdr.dev/preview.json";
const HOMEBREW_FORMULA_API_URL: &str = "https://formulae.brew.sh/api/formula/herdr.json";
const HERDR_UPDATE_COMMAND: &str = "herdr update";
const HOMEBREW_UPDATE_COMMAND: &str = "brew update && brew upgrade herdr";
+const MISE_UPDATE_COMMAND: &str = "mise upgrade herdr";
const NIX_UPDATE_COMMAND: &str = "update through Nix";
+const MISE_INSTALLS_DIR_ENV: &str = "MISE_INSTALLS_DIR";
const FAKE_UPDATE_VERSION_ENV: &str = "HERDR_FAKE_UPDATE_VERSION";
const FAKE_UPDATE_NOTES_VERSION_ENV: &str = "HERDR_FAKE_UPDATE_NOTES_VERSION";
const DEFAULT_FAKE_UPDATE_NOTES_VERSION: &str = "0.3.0";
@@ -1624,6 +1626,8 @@ fn print_running_session_update_outcomes(
pub(crate) fn update_install_command() -> &'static str {
if is_homebrew_managed_install() {
HOMEBREW_UPDATE_COMMAND
+ } else if is_mise_managed_install() {
+ MISE_UPDATE_COMMAND
} else if is_nix_managed_install() {
NIX_UPDATE_COMMAND
} else {
@@ -1639,6 +1643,10 @@ pub(crate) fn update_install_instruction(install_command: &str) -> String {
HOMEBREW_UPDATE_COMMAND => {
"detach, run `brew update && brew upgrade herdr`, then restart this Herdr session when ready".to_string()
}
+ MISE_UPDATE_COMMAND => {
+ "detach, run `mise upgrade herdr`, then restart this Herdr session when ready"
+ .to_string()
+ }
NIX_UPDATE_COMMAND => {
"detach, update through Nix, then restart this Herdr session when ready".to_string()
}
@@ -1662,6 +1670,14 @@ fn is_nix_managed_install() -> bool {
is_nix_store_exe_path_following_links(&current_exe)
}
+fn is_mise_managed_install() -> bool {
+ let Ok(current_exe) = env::current_exe() else {
+ return false;
+ };
+
+ is_mise_managed_exe_path_following_links(&current_exe)
+}
+
pub(crate) fn preview_channel_rejection_for_current_install() -> Option<&'static str> {
let Ok(current_exe) = env::current_exe() else {
return None;
@@ -1674,6 +1690,8 @@ pub(crate) fn package_manager_channel_update_guidance_for_current_install() -> O
{
if is_homebrew_managed_install() {
Some("Use `brew update && brew upgrade herdr` to update Homebrew installs.")
+ } else if is_mise_managed_install() {
+ Some("Use `mise upgrade herdr` to update mise installs.")
} else if is_nix_managed_install() {
Some("Update through Nix to update Nix-managed Herdr installs.")
} else {
@@ -1686,6 +1704,10 @@ fn preview_channel_rejection_for_exe_path(path: &Path) -> Option<&'static str> {
Some(
"preview channel is only available for direct Herdr installs; Homebrew installs update through `brew update && brew upgrade herdr`",
)
+ } else if is_mise_managed_exe_path_following_links(path) {
+ Some(
+ "preview channel is only available for direct Herdr installs; mise installs update through `mise upgrade herdr`",
+ )
} else if is_nix_store_exe_path_following_links(path) {
Some("preview channel is only available for direct Herdr installs; Nix installs update through Nix")
} else {
@@ -1695,6 +1717,7 @@ fn preview_channel_rejection_for_exe_path(path: &Path) -> Option<&'static str> {
pub(crate) fn is_package_manager_managed_exe_path(path: &Path) -> bool {
is_homebrew_managed_exe_path_following_links(path)
+ || is_mise_managed_exe_path_following_links(path)
|| is_nix_store_exe_path_following_links(path)
}
@@ -1716,10 +1739,80 @@ fn is_nix_store_exe_path_following_links(path: &Path) -> bool {
.is_ok_and(|path| is_nix_store_exe_path(&path))
}
+fn is_mise_managed_exe_path_following_links(path: &Path) -> bool {
+ if is_mise_managed_exe_path(path) {
+ return true;
+ }
+
+ path.canonicalize()
+ .is_ok_and(|path| is_mise_managed_exe_path(&path))
+}
+
fn is_nix_store_exe_path(path: &Path) -> bool {
path.starts_with("/nix/store")
}
+fn is_mise_managed_exe_path(path: &Path) -> bool {
+ mise_install_root(path).is_some()
+}
+
+fn mise_install_root(path: &Path) -> Option<PathBuf> {
+ if let Some(root) = mise_install_root_under_configured_installs_dir(path) {
+ return Some(root);
+ }
+
+ mise_install_root_under_named_installs_dir(path)
+}
+
+fn mise_install_root_under_configured_installs_dir(path: &Path) -> Option<PathBuf> {
+ let installs_dir = env::var_os(MISE_INSTALLS_DIR_ENV)
+ .map(PathBuf::from)
+ .filter(|path| !path.as_os_str().is_empty())?;
+ let version_dir = mise_tool_version_dir(path)?;
+ let tool_dir = version_dir.parent()?;
+ paths_match(tool_dir.parent()?, &installs_dir).then_some(version_dir.to_path_buf())
+}
+
+fn mise_install_root_under_named_installs_dir(path: &Path) -> Option<PathBuf> {
+ let version_dir = mise_tool_version_dir(path)?;
+ let tool_dir = version_dir.parent()?;
+ let installs_dir = tool_dir.parent()?;
+ if installs_dir.file_name()? != "installs" {
+ return None;
+ }
+ Some(version_dir.to_path_buf())
+}
+
+fn mise_tool_version_dir(path: &Path) -> Option<&Path> {
+ if path.file_name()? != "herdr" {
+ return None;
+ }
+ let bin_dir = path.parent()?;
+ if bin_dir.file_name()? != "bin" {
+ return None;
+ }
+ let version_dir = bin_dir.parent()?;
+ let tool_dir = version_dir.parent()?;
+ if tool_dir.file_name()? != "herdr" {
+ return None;
+ }
+ Some(version_dir)
+}
+
+fn paths_match(left: &Path, right: &Path) -> bool {
+ if left == right {
+ return true;
+ }
+
+ let Ok(left) = left.canonicalize() else {
+ return false;
+ };
+ let Ok(right) = right.canonicalize() else {
+ return false;
+ };
+ left == right
+}
+
fn is_homebrew_managed_exe_path(path: &Path) -> bool {
homebrew_cellar_keg_root(path).is_some()
}
@@ -1762,6 +1855,17 @@ pub fn self_update(options: SelfUpdateOptions) -> Result<Version, String> {
));
}
+ if is_mise_managed_install() {
+ if channel == UpdateChannel::Preview {
+ return Err(
+ "self-update is disabled for mise installs; preview is only available for direct Herdr installs".into(),
+ );
+ }
+ return Err(format!(
+ "self-update is disabled for mise installs; run `{MISE_UPDATE_COMMAND}`"
+ ));
+ }
+
if is_nix_managed_install() {
if channel == UpdateChannel::Preview {
return Err(
@@ -1873,6 +1977,11 @@ pub fn auto_update(events: tokio::sync::mpsc::Sender<crate::events::AppEvent>) {
return;
}
+ if is_mise_managed_install() && configured_channel == UpdateChannel::Preview {
+ crate::logging::update_check_failed("preview channel is not available for mise installs");
+ return;
+ }
+
let nix_managed_install = is_nix_managed_install();
if nix_managed_install && configured_channel == UpdateChannel::Preview {
crate::logging::update_check_failed("preview channel is not available for Nix installs");
@@ -1905,15 +2014,9 @@ pub fn auto_update(events: tokio::sync::mpsc::Sender<crate::events::AppEvent>) {
);
// Notify the TUI — blocking_send is safe from a std::thread
- let install_command = if nix_managed_install {
- NIX_UPDATE_COMMAND
- } else {
- HERDR_UPDATE_COMMAND
- };
-
let _ = events.blocking_send(crate::events::AppEvent::UpdateReady {
version: release.label().to_string(),
- install_command: install_command.to_string(),
+ install_command: update_install_command().to_string(),
});
}
@@ -2155,6 +2258,58 @@ mod tests {
}
#[test]
+ fn mise_install_path_is_detected() {
+ let path = Path::new("/home/user/.local/share/mise/installs/herdr/0.6.6/bin/herdr");
+
+ assert!(is_mise_managed_exe_path(path));
+ assert_eq!(
+ mise_install_root(path).unwrap(),
+ PathBuf::from("/home/user/.local/share/mise/installs/herdr/0.6.6")
+ );
+ }
+
+ #[test]
+ fn mise_alias_install_path_is_detected() {
+ let path = Path::new("/home/user/.local/share/mise/installs/herdr/latest/bin/herdr");
+
+ assert!(is_mise_managed_exe_path(path));
+ }
+
+ #[test]
+ fn mise_custom_installs_dir_path_is_detected() {
+ let path = Path::new("/opt/mise-tools/installs/herdr/0.6.6/bin/herdr");
+
+ assert!(is_mise_managed_exe_path(path));
+ }
+
+ #[test]
+ fn mise_configured_installs_dir_path_is_detected() {
+ let _guard = env_lock().lock().unwrap();
+ let previous = std::env::var_os(MISE_INSTALLS_DIR_ENV);
+ std::env::set_var(MISE_INSTALLS_DIR_ENV, "/opt/mise-tools");
+ let path = Path::new("/opt/mise-tools/herdr/0.6.6/bin/herdr");
+
+ assert!(is_mise_managed_exe_path(path));
+ assert_eq!(
+ mise_install_root(path).unwrap(),
+ PathBuf::from("/opt/mise-tools/herdr/0.6.6")
+ );
+
+ if let Some(previous) = previous {
+ std::env::set_var(MISE_INSTALLS_DIR_ENV, previous);
+ } else {
+ std::env::remove_var(MISE_INSTALLS_DIR_ENV);
+ }
+ }
+
+ #[test]
+ fn non_mise_install_path_is_not_detected() {
+ let path = Path::new("/home/user/.local/bin/herdr");
+
+ assert!(!is_mise_managed_exe_path(path));
+ }
+
+ #[test]
fn package_manager_path_detection_follows_homebrew_symlink() {
#[cfg(unix)]
{
@@ -2178,6 +2333,27 @@ mod tests {
}
#[test]
+ fn package_manager_path_detection_follows_mise_symlink() {
+ #[cfg(unix)]
+ {
+ let root = std::env::temp_dir()
+ .join(format!("herdr-mise-symlink-test-{}", std::process::id()));
+ let version_bin = root.join("installs/herdr/0.6.2/bin");
+ let latest_bin = root.join("installs/herdr/latest/bin");
+ fs::create_dir_all(&version_bin).unwrap();
+ fs::create_dir_all(&latest_bin).unwrap();
+ let version_binary = version_bin.join("herdr");
+ let latest_binary = latest_bin.join("herdr");
+ fs::write(&version_binary, b"").unwrap();
+ std::os::unix::fs::symlink(&version_binary, &latest_binary).unwrap();
+
+ assert!(is_package_manager_managed_exe_path(&latest_binary));
+
+ let _ = fs::remove_dir_all(root);
+ }
+ }
+
+ #[test]
fn nix_store_path_is_detected() {
let path = Path::new("/nix/store/abc123-herdr-0.6.1/bin/herdr");
@@ -2188,11 +2364,14 @@ mod tests {
#[test]
fn preview_channel_is_rejected_for_package_manager_paths() {
let homebrew = Path::new("/opt/homebrew/Cellar/herdr/0.6.6/bin/herdr");
+ let mise = Path::new("/home/user/.local/share/mise/installs/herdr/0.6.6/bin/herdr");
let nix = Path::new("/nix/store/abc123-herdr-0.6.6/bin/herdr");
let direct = Path::new("/home/user/.local/bin/herdr");
assert!(preview_channel_rejection_for_exe_path(homebrew)
.is_some_and(|message| message.contains("Homebrew")));
+ assert!(preview_channel_rejection_for_exe_path(mise)
+ .is_some_and(|message| message.contains("mise")));
assert!(preview_channel_rejection_for_exe_path(nix)
.is_some_and(|message| message.contains("Nix")));
assert!(preview_channel_rejection_for_exe_path(direct).is_none());
@@ -2278,6 +2457,10 @@ mod tests {
update_install_instruction(HOMEBREW_UPDATE_COMMAND),
"detach, run `brew update && brew upgrade herdr`, then restart this Herdr session when ready"
);
+ assert_eq!(
+ update_install_instruction(MISE_UPDATE_COMMAND),
+ "detach, run `mise upgrade herdr`, then restart this Herdr session when ready"
+ );
}
#[test]