nodus 0.9.2

Local-first CLI for managing project-scoped agent packages.
Documentation
use std::env;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};

pub const APP_STORAGE_DIR: &str = "nodus";

pub fn resolve_store_root(override_path: Option<&Path>) -> Result<PathBuf> {
    match override_path {
        Some(path) => absolutize(path),
        None => default_store_root(),
    }
}

fn absolutize(path: &Path) -> Result<PathBuf> {
    if path.is_absolute() {
        return Ok(path.to_path_buf());
    }

    let cwd = env::current_dir().context("failed to determine the current directory")?;
    Ok(cwd.join(path))
}

fn default_store_root() -> Result<PathBuf> {
    #[cfg(target_os = "macos")]
    {
        let home = env::var_os("HOME").ok_or_else(|| {
            anyhow::anyhow!("failed to determine the home directory for the default storage path")
        })?;
        Ok(PathBuf::from(home)
            .join("Library")
            .join("Application Support")
            .join(APP_STORAGE_DIR))
    }

    #[cfg(target_os = "windows")]
    {
        if let Some(local_app_data) = env::var_os("LOCALAPPDATA") {
            return Ok(PathBuf::from(local_app_data).join(APP_STORAGE_DIR));
        }
        if let Some(app_data) = env::var_os("APPDATA") {
            return Ok(PathBuf::from(app_data).join(APP_STORAGE_DIR));
        }
        anyhow::bail!("failed to determine the default local application data path on Windows");
    }

    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
    {
        let home = env::var_os("HOME").ok_or_else(|| {
            anyhow::anyhow!("failed to determine the home directory for the default storage path")
        })?;
        Ok(default_unix_store_root(
            Path::new(&home),
            env::var_os("XDG_STATE_HOME").as_deref().map(Path::new),
        ))
    }
}

#[cfg(not(any(target_os = "macos", target_os = "windows")))]
fn default_unix_store_root(home: &Path, xdg_state_home: Option<&Path>) -> PathBuf {
    if let Some(path) = xdg_state_home.filter(|path| path.is_absolute()) {
        return path.join(APP_STORAGE_DIR);
    }

    home.join(".local").join("state").join(APP_STORAGE_DIR)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn resolves_relative_overrides_from_the_current_directory() {
        let original_cwd = env::current_dir().unwrap();
        let temp = tempfile::TempDir::new().unwrap();
        env::set_current_dir(temp.path()).unwrap();

        let resolved = resolve_store_root(Some(Path::new("store-root"))).unwrap();

        env::set_current_dir(original_cwd).unwrap();
        assert_eq!(resolved.file_name().unwrap(), "store-root");
        assert_eq!(
            resolved.parent().unwrap().canonicalize().unwrap(),
            temp.path().canonicalize().unwrap()
        );
    }

    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
    #[test]
    fn prefers_absolute_xdg_state_home_over_the_home_fallback() {
        let resolved = default_unix_store_root(
            Path::new("/home/tester"),
            Some(Path::new("/var/lib/tester-state")),
        );

        assert_eq!(resolved, PathBuf::from("/var/lib/tester-state/nodus"));
    }

    #[cfg(not(any(target_os = "macos", target_os = "windows")))]
    #[test]
    fn ignores_relative_xdg_state_home_values() {
        let resolved =
            default_unix_store_root(Path::new("/home/tester"), Some(Path::new("relative-state")));

        assert_eq!(resolved, PathBuf::from("/home/tester/.local/state/nodus"));
    }
}