diff --git a/.changeset/cold-drinks-camp.md b/.changeset/cold-drinks-camp.md new file mode 100644 index 000000000..bd8a78d1e --- /dev/null +++ b/.changeset/cold-drinks-camp.md @@ -0,0 +1,5 @@ +--- +"fnm": minor +--- + +use XDG conventions in MacOS directories by default diff --git a/Cargo.lock b/Cargo.lock index 0f993a411..7a7642f74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -512,27 +512,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - [[package]] name = "displaydoc" version = "0.2.4" @@ -639,6 +618,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + [[package]] name = "fastrand" version = "2.1.0" @@ -677,11 +667,11 @@ dependencies = [ "clap_complete", "colored", "csv", - "dirs", "duct", "embed-resource", "encoding_rs_io", "env_logger", + "etcetera", "http", "indicatif", "indoc", @@ -821,6 +811,15 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "1.1.0" @@ -1039,16 +1038,6 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.5.0", - "libc", -] - [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1252,12 +1241,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "os_pipe" version = "1.1.5" @@ -1433,17 +1416,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" version = "1.10.4" diff --git a/Cargo.toml b/Cargo.toml index efa1599ea..d03577732 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ chrono = { version = "0.4.38", features = ["serde", "now"], default-features = f tar = "0.4.40" xz2 = "0.1.7" node-semver = "2.1.0" -dirs = "5.0.1" +etcetera = "0.8.0" colored = "2.1.0" zip = "2.1.0" tempfile = "3.10.1" diff --git a/src/commands/env.rs b/src/commands/env.rs index f0eacb799..e2346608b 100644 --- a/src/commands/env.rs +++ b/src/commands/env.rs @@ -1,6 +1,5 @@ use super::command::Command; use crate::config::FnmConfig; -use crate::directories; use crate::fs::symlink_dir; use crate::outln; use crate::path_ext::PathExt; @@ -36,7 +35,7 @@ fn generate_symlink_path() -> String { } fn make_symlink(config: &FnmConfig) -> Result { - let base_dir = directories::multishell_storage().ensure_exists_silently(); + let base_dir = config.multishell_storage().ensure_exists_silently(); let mut temp_dir = base_dir.join(generate_symlink_path()); while temp_dir.exists() { diff --git a/src/config.rs b/src/config.rs index 2f68a9d71..df6567527 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,8 +1,8 @@ use crate::arch::Arch; +use crate::directories::Directories; use crate::log_level::LogLevel; use crate::path_ext::PathExt; use crate::version_file_strategy::VersionFileStrategy; -use dirs::{data_dir, home_dir}; use url::Url; #[derive(clap::Parser, Debug)] @@ -87,6 +87,9 @@ pub struct FnmConfig { verbatim_doc_comment )] resolve_engines: bool, + + #[clap(skip)] + directories: Directories, } impl Default for FnmConfig { @@ -100,6 +103,7 @@ impl Default for FnmConfig { version_file_strategy: VersionFileStrategy::default(), corepack_enabled: false, resolve_engines: false, + directories: Directories::default(), } } } @@ -134,19 +138,7 @@ impl FnmConfig { return dir; } - let legacy = home_dir() - .map(|dir| dir.join(".fnm")) - .filter(|dir| dir.exists()); - - let modern = data_dir().map(|dir| dir.join("fnm")); - - if let Some(dir) = legacy { - return dir; - } - - modern - .expect("Can't get data directory") - .ensure_exists_silently() + self.directories.default_base_dir() } pub fn installations_dir(&self) -> std::path::PathBuf { @@ -165,6 +157,10 @@ impl FnmConfig { .ensure_exists_silently() } + pub fn multishell_storage(&self) -> std::path::PathBuf { + self.directories.multishell_storage() + } + #[cfg(test)] pub fn with_base_dir(mut self, base_dir: Option) -> Self { self.base_dir = base_dir; diff --git a/src/directories.rs b/src/directories.rs index 0f7c8c055..df6d357f2 100644 --- a/src/directories.rs +++ b/src/directories.rs @@ -1,26 +1,78 @@ +use etcetera::BaseStrategy; use std::path::PathBuf; +use crate::path_ext::PathExt; + fn xdg_dir(env: &str) -> Option { - let env_var = std::env::var(env).ok()?; - Some(PathBuf::from(env_var)) + if cfg!(windows) { + let env_var = std::env::var(env).ok()?; + Some(PathBuf::from(env_var)) + } else { + // On non-Windows platforms, `etcetera` already handles XDG variables + None + } } -fn state_dir() -> Option { - xdg_dir("XDG_STATE_HOME").or_else(dirs::state_dir) +fn runtime_dir(basedirs: &impl BaseStrategy) -> Option { + xdg_dir("XDG_RUNTIME_DIR").or_else(|| basedirs.runtime_dir()) } -fn cache_dir() -> Option { - xdg_dir("XDG_CACHE_HOME").or_else(dirs::cache_dir) +fn state_dir(basedirs: &impl BaseStrategy) -> Option { + xdg_dir("XDG_STATE_HOME").or_else(|| basedirs.state_dir()) } -fn runtime_dir() -> Option { - xdg_dir("XDG_RUNTIME_DIR").or_else(dirs::runtime_dir) +fn cache_dir(basedirs: &impl BaseStrategy) -> PathBuf { + xdg_dir("XDG_CACHE_HOME").unwrap_or_else(|| basedirs.cache_dir()) } -pub fn multishell_storage() -> PathBuf { - runtime_dir() - .or_else(state_dir) - .or_else(cache_dir) - .unwrap_or_else(std::env::temp_dir) - .join("fnm_multishells") +/// A helper struct for directories in fnm that uses XDG Base Directory Specification +/// if applicable for the platform. +#[derive(Debug)] +pub struct Directories( + #[cfg(windows)] etcetera::base_strategy::Windows, + #[cfg(not(windows))] etcetera::base_strategy::Xdg, +); + +impl Default for Directories { + fn default() -> Self { + Self(etcetera::choose_base_strategy().expect("choosing base strategy")) + } +} + +impl Directories { + pub fn strategy(&self) -> &impl BaseStrategy { + &self.0 + } + + pub fn default_base_dir(&self) -> PathBuf { + let strategy = self.strategy(); + let modern = strategy.data_dir().join("fnm"); + if modern.exists() { + return modern; + } + + let legacy = strategy.home_dir().join(".fnm"); + if legacy.exists() { + return legacy; + } + + #[cfg(target_os = "macos")] + { + let basedirs = etcetera::base_strategy::Apple::new().expect("Can't get home directory"); + let legacy = basedirs.data_dir().join("fnm"); + if legacy.exists() { + return legacy; + } + } + + modern.ensure_exists_silently() + } + + pub fn multishell_storage(&self) -> PathBuf { + let basedirs = self.strategy(); + let dir = runtime_dir(basedirs) + .or_else(|| state_dir(basedirs)) + .unwrap_or_else(|| cache_dir(basedirs)); + dir.join("fnm_multishells") + } }