diff --git a/crates/chat-cli/src/cli/chat/context.rs b/crates/chat-cli/src/cli/chat/context.rs index 4aff145fc..10e2aa17d 100644 --- a/crates/chat-cli/src/cli/chat/context.rs +++ b/crates/chat-cli/src/cli/chat/context.rs @@ -628,9 +628,11 @@ async fn load_global_config(os: &Os) -> Result { Ok(config) } else { // Return default global configuration with predefined paths + use crate::util::paths::workspace; + Ok(ContextConfig { paths: vec![ - ".amazonq/rules/**/*.md".to_string(), + workspace::RULES_PATTERN.to_string(), "README.md".to_string(), AMAZONQ_FILENAME.to_string(), ], diff --git a/crates/chat-cli/src/cli/chat/tool_manager.rs b/crates/chat-cli/src/cli/chat/tool_manager.rs index f5e61e9c3..fb59f3b44 100644 --- a/crates/chat-cli/src/cli/chat/tool_manager.rs +++ b/crates/chat-cli/src/cli/chat/tool_manager.rs @@ -95,7 +95,7 @@ use crate::mcp_client::{ }; use crate::os::Os; use crate::telemetry::TelemetryThread; -use crate::util::directories::home_dir; +use crate::util::paths::PathResolver; const NAMESPACE_DELIMITER: &str = "___"; // This applies for both mcp server and tool name since in the end the tool name as seen by the @@ -104,11 +104,11 @@ const VALID_TOOL_NAME: &str = "^[a-zA-Z][a-zA-Z0-9_]*$"; const SPINNER_CHARS: [char; 10] = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; pub fn workspace_mcp_config_path(os: &Os) -> eyre::Result { - Ok(os.env.current_dir()?.join(".amazonq").join("mcp.json")) + Ok(PathResolver::new(os).workspace().mcp_config()?) } pub fn global_mcp_config_path(os: &Os) -> eyre::Result { - Ok(home_dir(os)?.join(".aws").join("amazonq").join("mcp.json")) + Ok(PathResolver::new(os).global().mcp_config()?) } /// Messages used for communication between the tool initialization thread and the loading @@ -158,12 +158,13 @@ pub struct McpServerConfig { impl McpServerConfig { pub async fn load_config(stderr: &mut impl Write) -> eyre::Result { - let mut cwd = std::env::current_dir()?; - cwd.push(".amazonq/mcp.json"); - let expanded_path = shellexpand::tilde("~/.aws/amazonq/mcp.json"); - let global_path = PathBuf::from(expanded_path.as_ref() as &str); + let os = Os::new().await?; + let resolver = PathResolver::new(&os); + let workspace_path = resolver.workspace().mcp_config()?; + let global_path = resolver.global().mcp_config()?; + let global_buf = tokio::fs::read(global_path).await.ok(); - let local_buf = tokio::fs::read(cwd).await.ok(); + let local_buf = tokio::fs::read(workspace_path).await.ok(); let conf = match (global_buf, local_buf) { (Some(global_buf), Some(local_buf)) => { let mut global_conf = Self::from_slice(&global_buf, stderr, "global")?; diff --git a/crates/chat-cli/src/util/directories.rs b/crates/chat-cli/src/util/directories.rs index 70dd7d9f6..525df4784 100644 --- a/crates/chat-cli/src/util/directories.rs +++ b/crates/chat-cli/src/util/directories.rs @@ -3,7 +3,9 @@ use std::path::PathBuf; use thiserror::Error; use crate::os::Os; +use crate::util::paths::PathResolver; +#[allow(dead_code)] // Allow unused variants during migration #[derive(Debug, Error)] pub enum DirectoryError { #[error("home directory not found")] @@ -32,6 +34,7 @@ type Result = std::result::Result; /// - Linux: /home/Alice /// - MacOS: /Users/Alice /// - Windows: C:\Users\Alice +#[allow(dead_code)] // Allow unused function during migration pub fn home_dir(#[cfg_attr(windows, allow(unused_variables))] os: &Os) -> Result { #[cfg(unix)] match cfg!(test) { @@ -70,16 +73,6 @@ pub fn home_dir(#[cfg_attr(windows, allow(unused_variables))] os: &Os) -> Result } } -/// The q data directory -/// -/// - Linux: `$XDG_DATA_HOME/amazon-q` or `$HOME/.local/share/amazon-q` -/// - MacOS: `$HOME/Library/Application Support/amazon-q` -pub fn fig_data_dir() -> Result { - Ok(dirs::data_local_dir() - .ok_or(DirectoryError::NoHomeDirectory)? - .join("amazon-q")) -} - /// Get the macos tempdir from the `confstr` function /// /// See: @@ -124,29 +117,38 @@ pub fn logs_dir() -> Result { if #[cfg(unix)] { Ok(runtime_dir()?.join("qlog")) } else if #[cfg(windows)] { - Ok(std::env::temp_dir().join("amazon-q").join("logs")) + use crate::util::paths::application::DATA_DIR_NAME; + Ok(std::env::temp_dir().join(DATA_DIR_NAME).join("logs")) } } } /// The directory to the directory containing config for the `/context` feature in `q chat`. pub fn chat_global_context_path(os: &Os) -> Result { - Ok(home_dir(os)?.join(".aws").join("amazonq").join("global_context.json")) + PathResolver::new(os) + .global() + .global_context() + .map_err(|e| DirectoryError::Io(std::io::Error::other(e))) } /// The directory to the directory containing config for the `/context` feature in `q chat`. pub fn chat_profiles_dir(os: &Os) -> Result { - Ok(home_dir(os)?.join(".aws").join("amazonq").join("profiles")) + PathResolver::new(os) + .global() + .profiles_dir() + .map_err(|e| DirectoryError::Io(std::io::Error::other(e))) } /// The path to the fig settings file pub fn settings_path() -> Result { - Ok(fig_data_dir()?.join("settings.json")) + crate::util::paths::ApplicationPaths::settings_path_static() + .map_err(|e| DirectoryError::Io(std::io::Error::other(e))) } /// The path to the local sqlite database pub fn database_path() -> Result { - Ok(fig_data_dir()?.join("data.sqlite3")) + crate::util::paths::ApplicationPaths::database_path_static() + .map_err(|e| DirectoryError::Io(std::io::Error::other(e))) } #[cfg(test)] @@ -237,9 +239,11 @@ mod tests { #[test] fn snapshot_fig_data_dir() { - linux!(fig_data_dir(), @"$HOME/.local/share/amazon-q"); - macos!(fig_data_dir(), @"$HOME/Library/Application Support/amazon-q"); - windows!(fig_data_dir(), @r"C:\Users\$USER\AppData\Local\amazon-q"); + let app_data_dir = + || crate::util::paths::app_data_dir().map_err(|e| DirectoryError::Io(std::io::Error::other(e))); + linux!(app_data_dir(), @"$HOME/.local/share/amazon-q"); + macos!(app_data_dir(), @"$HOME/Library/Application Support/amazon-q"); + windows!(app_data_dir(), @r"C:\Users\$USER\AppData\Local\AmazonQ"); } #[test] diff --git a/crates/chat-cli/src/util/mod.rs b/crates/chat-cli/src/util/mod.rs index 576ba37ac..041373df0 100644 --- a/crates/chat-cli/src/util/mod.rs +++ b/crates/chat-cli/src/util/mod.rs @@ -2,6 +2,7 @@ pub mod consts; pub mod directories; pub mod knowledge_store; pub mod open; +pub mod paths; pub mod process; pub mod spinner; pub mod system_info; diff --git a/crates/chat-cli/src/util/paths.rs b/crates/chat-cli/src/util/paths.rs new file mode 100644 index 000000000..ea141448b --- /dev/null +++ b/crates/chat-cli/src/util/paths.rs @@ -0,0 +1,156 @@ +//! Hierarchical path management for the application + +use std::path::PathBuf; + +use crate::os::Os; + +#[derive(Debug, thiserror::Error)] +pub enum DirectoryError { + #[error("home directory not found")] + NoHomeDirectory, + #[error("IO Error: {0}")] + Io(#[from] std::io::Error), +} + +pub mod workspace { + //! Project-level paths (relative to current working directory) + pub const MCP_CONFIG: &str = ".amazonq/mcp.json"; + pub const RULES_PATTERN: &str = ".amazonq/rules/**/*.md"; +} + +pub mod global { + //! User-level paths (relative to home directory) + pub const MCP_CONFIG: &str = ".aws/amazonq/mcp.json"; + pub const GLOBAL_CONTEXT: &str = ".aws/amazonq/global_context.json"; + pub const PROFILES_DIR: &str = ".aws/amazonq/profiles"; +} + +pub mod application { + //! Application data paths (system-specific) + #[cfg(unix)] + pub const DATA_DIR_NAME: &str = "amazon-q"; + #[cfg(windows)] + pub const DATA_DIR_NAME: &str = "AmazonQ"; + pub const SETTINGS_FILE: &str = "settings.json"; + pub const DATABASE_FILE: &str = "data.sqlite3"; +} + +type Result = std::result::Result; + +/// The directory of the users home +/// - Linux: /home/Alice +/// - MacOS: /Users/Alice +/// - Windows: C:\Users\Alice +pub fn home_dir(#[cfg_attr(windows, allow(unused_variables))] os: &Os) -> Result { + #[cfg(unix)] + match cfg!(test) { + true => os + .env + .get("HOME") + .map_err(|_err| DirectoryError::NoHomeDirectory) + .and_then(|h| { + if h.is_empty() { + Err(DirectoryError::NoHomeDirectory) + } else { + Ok(h) + } + }) + .map(PathBuf::from) + .map(|p| os.fs.chroot_path(p)), + false => dirs::home_dir().ok_or(DirectoryError::NoHomeDirectory), + } + + #[cfg(windows)] + match cfg!(test) { + true => os + .env + .get("USERPROFILE") + .map_err(|_err| DirectoryError::NoHomeDirectory) + .and_then(|h| { + if h.is_empty() { + Err(DirectoryError::NoHomeDirectory) + } else { + Ok(h) + } + }) + .map(PathBuf::from) + .map(|p| os.fs.chroot_path(p)), + false => dirs::home_dir().ok_or(DirectoryError::NoHomeDirectory), + } +} + +/// The application data directory +/// - Linux: `$XDG_DATA_HOME/{data_dir}` or `$HOME/.local/share/{data_dir}` +/// - MacOS: `$HOME/Library/Application Support/{data_dir}` +/// - Windows: `%LOCALAPPDATA%\{data_dir}` +pub fn app_data_dir() -> Result { + Ok(dirs::data_local_dir() + .ok_or(DirectoryError::NoHomeDirectory)? + .join(application::DATA_DIR_NAME)) +} + +/// Path resolver with hierarchy-aware methods +pub struct PathResolver<'a> { + os: &'a Os, +} + +impl<'a> PathResolver<'a> { + pub fn new(os: &'a Os) -> Self { + Self { os } + } + + /// Get workspace-scoped path resolver + pub fn workspace(&self) -> WorkspacePaths<'_> { + WorkspacePaths { os: self.os } + } + + /// Get global-scoped path resolver + pub fn global(&self) -> GlobalPaths<'_> { + GlobalPaths { os: self.os } + } +} + +/// Workspace-scoped path methods +pub struct WorkspacePaths<'a> { + os: &'a Os, +} + +impl<'a> WorkspacePaths<'a> { + pub fn mcp_config(&self) -> Result { + Ok(self.os.env.current_dir()?.join(workspace::MCP_CONFIG)) + } +} + +/// Global-scoped path methods +pub struct GlobalPaths<'a> { + os: &'a Os, +} + +impl<'a> GlobalPaths<'a> { + pub fn mcp_config(&self) -> Result { + Ok(home_dir(self.os)?.join(global::MCP_CONFIG)) + } + + pub fn global_context(&self) -> Result { + Ok(home_dir(self.os)?.join(global::GLOBAL_CONTEXT)) + } + + pub fn profiles_dir(&self) -> Result { + Ok(home_dir(self.os)?.join(global::PROFILES_DIR)) + } +} + +/// Application path static methods +pub struct ApplicationPaths; + +impl ApplicationPaths { + /// Static method for settings path (to avoid circular dependency) + pub fn settings_path_static() -> Result { + Ok(app_data_dir()?.join(application::SETTINGS_FILE)) + } + + /// Static method for database path (to avoid circular dependency) + pub fn database_path_static() -> Result { + Ok(app_data_dir()?.join(application::DATABASE_FILE)) + } +} diff --git a/crates/fig_install/src/macos.rs b/crates/fig_install/src/macos.rs index a48380df5..7384746e3 100644 --- a/crates/fig_install/src/macos.rs +++ b/crates/fig_install/src/macos.rs @@ -16,6 +16,7 @@ use std::path::{ use fig_util::consts::{ APP_BUNDLE_ID, CLI_BINARY_NAME, + system_paths, }; use fig_util::macos::BUNDLE_CONTENTS_MACOS_PATH; use fig_util::{ @@ -187,7 +188,7 @@ pub(crate) async fn update( let installed_app_path = if same_bundle_name { fig_util::app_bundle_path() } else { - Path::new("/Applications").join(app_name) + Path::new(system_paths::APPLICATIONS_DIR).join(app_name) }; let installed_app_path_cstr = CString::new(installed_app_path.as_os_str().as_bytes())?; diff --git a/crates/fig_util/src/consts.rs b/crates/fig_util/src/consts.rs index 58287f4a5..547e3612f 100644 --- a/crates/fig_util/src/consts.rs +++ b/crates/fig_util/src/consts.rs @@ -25,6 +25,15 @@ pub const PRODUCT_NAME: &str = "Amazon Q"; pub const RUNTIME_DIR_NAME: &str = "cwrun"; +/// Data directory name used in paths like ~/.local/share/{DATA_DIR_NAME} +#[cfg(unix)] +pub const DATA_DIR_NAME: &str = "amazon-q"; +#[cfg(windows)] +pub const DATA_DIR_NAME: &str = "AmazonQ"; + +/// Backup directory name +pub const BACKUP_DIR_NAME: &str = ".amazon-q.dotfiles.bak"; + // These are the old "CodeWhisperer" branding, used anywhere we will not update to Amazon Q pub const OLD_PRODUCT_NAME: &str = "CodeWhisperer"; pub const OLD_CLI_BINARY_NAMES: &[&str] = &["cw"]; @@ -134,6 +143,14 @@ pub mod env_var { } } +pub mod system_paths { + /// System installation paths + pub const APPLICATIONS_DIR: &str = "/Applications"; + pub const USR_LOCAL_BIN: &str = "/usr/local/bin"; + pub const USR_SHARE: &str = "/usr/share"; + pub const OPT_HOMEBREW_BIN: &str = "/opt/homebrew/bin"; +} + #[cfg(test)] mod tests { use time::OffsetDateTime; diff --git a/crates/fig_util/src/directories.rs b/crates/fig_util/src/directories.rs index 20fdb8d05..db02361cf 100644 --- a/crates/fig_util/src/directories.rs +++ b/crates/fig_util/src/directories.rs @@ -16,7 +16,6 @@ use time::OffsetDateTime; #[cfg(unix)] use crate::RUNTIME_DIR_NAME; -use crate::TAURI_PRODUCT_NAME; use crate::env_var::{ Q_BUNDLE_METADATA_PATH, Q_PARENT, @@ -26,6 +25,11 @@ use crate::system_info::{ in_cloudshell, is_remote, }; +use crate::{ + BACKUP_DIR_NAME, + DATA_DIR_NAME, + TAURI_PRODUCT_NAME, +}; macro_rules! utf8_dir { ($name:ident, $($arg:ident: $type:ty),*) => { @@ -136,21 +140,13 @@ pub fn old_fig_data_dir() -> Result { /// The q data directory /// -/// - Linux: `$XDG_DATA_HOME/amazon-q` or `$HOME/.local/share/amazon-q` -/// - MacOS: `$HOME/Library/Application Support/amazon-q` -/// - Windows: `%LOCALAPPDATA%\AmazonQ` +/// - Linux: `$XDG_DATA_HOME/{data_dir}` or `$HOME/.local/share/{data_dir}` +/// - MacOS: `$HOME/Library/Application Support/{data_dir}` +/// - Windows: `%LOCALAPPDATA%\{data_dir}` pub fn fig_data_dir() -> Result { - cfg_if::cfg_if! { - if #[cfg(unix)] { - Ok(dirs::data_local_dir() - .ok_or(DirectoryError::NoHomeDirectory)? - .join("amazon-q")) - } else if #[cfg(windows)] { - Ok(dirs::data_local_dir() - .ok_or(DirectoryError::NoHomeDirectory)? - .join("AmazonQ")) - } - } + Ok(dirs::data_local_dir() + .ok_or(DirectoryError::NoHomeDirectory)? + .join(DATA_DIR_NAME)) } pub fn fig_data_dir_ctx(fs: &impl FsProvider) -> Result { @@ -184,21 +180,13 @@ pub fn local_data_dir(ctx: &Ct /// The q cache directory /// -/// - Linux: `$XDG_CACHE_HOME/amazon-q` or `$HOME/.cache/amazon-q` -/// - MacOS: `$HOME/Library/Caches/amazon-q` -/// - Windows: `%LOCALAPPDATA%\AmazonQ\cache` +/// - Linux: `$XDG_CACHE_HOME/{data_dir}` or `$HOME/.cache/{data_dir}` +/// - MacOS: `$HOME/Library/Caches/{data_dir}` +/// - Windows: `%LOCALAPPDATA%\{data_dir}\cache` pub fn cache_dir() -> Result { - cfg_if::cfg_if! { - if #[cfg(unix)] { - Ok(dirs::cache_dir() - .ok_or(DirectoryError::NoHomeDirectory)? - .join("amazon-q")) - } else if #[cfg(windows)] { - Ok(dirs::cache_dir() - .ok_or(DirectoryError::NoHomeDirectory)? - .join("AmazonQ")) - } - } + Ok(dirs::cache_dir() + .ok_or(DirectoryError::NoHomeDirectory)? + .join(DATA_DIR_NAME)) } /// Get the macos tempdir from the `confstr` function @@ -246,13 +234,13 @@ pub fn runtime_dir() -> Result { /// /// - Linux: $XDG_RUNTIME_DIR/cwrun /// - MacOS: $TMPDIR/cwrun -/// - Windows: %TEMP%\sockets +/// - Windows: %TEMP%\{data_dir}\sockets pub fn sockets_dir() -> Result { cfg_if::cfg_if! { if #[cfg(unix)] { Ok(runtime_dir()?.join(RUNTIME_DIR_NAME)) } else if #[cfg(windows)] { - Ok(runtime_dir()?.join("AmazonQ").join("sockets")) + Ok(runtime_dir()?.join(DATA_DIR_NAME).join("sockets")) } } } @@ -303,24 +291,24 @@ pub fn autocomplete_specs_dir() -> Result { /// The directory to all the fig logs /// - Linux: `/tmp/fig/$USER/logs` /// - MacOS: `$TMPDIR/logs` -/// - Windows: `%TEMP%\AmazonQ\logs` +/// - Windows: `%TEMP%\{data_dir}\logs` pub fn logs_dir() -> Result { cfg_if::cfg_if! { if #[cfg(unix)] { use crate::CLI_BINARY_NAME; Ok(runtime_dir()?.join(format!("{CLI_BINARY_NAME}log"))) } else if #[cfg(windows)] { - Ok(std::env::temp_dir().join("AmazonQ").join("logs")) + Ok(std::env::temp_dir().join(DATA_DIR_NAME).join("logs")) } } } /// The directory where fig places all data-sensitive backups /// -/// - Linux/MacOS: `$HOME/.amazon-q.dotfiles.bak` -/// - Windows: `%USERPROFILE%\.amazon-q.dotfiles.bak` +/// - Linux/MacOS: `$HOME/{backup_dir}` +/// - Windows: `%USERPROFILE%\{backup_dir}` pub fn backups_dir() -> Result { - Ok(home_dir()?.join(".amazon-q.dotfiles.bak")) + Ok(home_dir()?.join(BACKUP_DIR_NAME)) } /// The directory for time based data-sensitive backups @@ -397,13 +385,13 @@ pub fn figterm_socket_path(session_id: impl Display) -> Result { /// The path to the resources directory /// -/// - MacOS: "/Applications/Amazon Q.app/Contents/Resources" +/// - MacOS: "/Applications/{app_name}.app/Contents/Resources" /// - Linux: "/usr/share/fig" -/// - Windows: "%LOCALAPPDATA%\AmazonQ\resources" +/// - Windows: "%LOCALAPPDATA%\{data_dir}\resources" pub fn resources_path() -> Result { cfg_if::cfg_if! { if #[cfg(all(unix, not(target_os = "macos")))] { - Ok(std::path::Path::new("/usr/share/fig").into()) + Ok(std::path::Path::new("/usr/share").join(PACKAGE_NAME)) } else if #[cfg(target_os = "macos")] { Ok(crate::app_bundle_path().join(crate::macos::BUNDLE_CONTENTS_RESOURCE_PATH)) } else if #[cfg(windows)] { @@ -433,9 +421,9 @@ pub fn resources_path_ctx(ctx: &Ctx) -> Res /// The path to the fig install manifest /// -/// - MacOS: "/Applications/Amazon Q.app/Contents/Resources/manifest.json" +/// - MacOS: "/Applications/{app_name}.app/Contents/Resources/manifest.json" /// - Linux: "/usr/share/fig/manifest.json" -/// - Windows: "%LOCALAPPDATA%\AmazonQ\resources\bin\manifest.json" +/// - Windows: "%LOCALAPPDATA%\{data_dir}\resources\bin\manifest.json" pub fn manifest_path() -> Result { cfg_if::cfg_if! { if #[cfg(unix)] { @@ -460,18 +448,18 @@ pub fn bundle_metadata_path(ctx: &Ctx) -> R /// The path to the fig settings file /// -/// - Linux: `$HOME/.local/share/amazon-q/settings.json` -/// - MacOS: `$HOME/Library/Application Support/amazon-q/settings.json` -/// - Windows: `%LOCALAPPDATA%\AmazonQ\settings.json` +/// - Linux: `$HOME/.local/share/{data_dir}/settings.json` +/// - MacOS: `$HOME/Library/Application Support/{data_dir}/settings.json` +/// - Windows: `%LOCALAPPDATA%\{data_dir}\settings.json` pub fn settings_path() -> Result { Ok(fig_data_dir()?.join("settings.json")) } /// The path to the lock file used to indicate that the app is updating /// -/// - Linux: `$HOME/.local/share/amazon-q/update.lock` -/// - MacOS: `$HOME/Library/Application Support/amazon-q/update.lock` -/// - Windows: `%LOCALAPPDATA%\AmazonQ\update.lock` +/// - Linux: `$HOME/.local/share/{data_dir}/update.lock` +/// - MacOS: `$HOME/Library/Application Support/{data_dir}/update.lock` +/// - Windows: `%LOCALAPPDATA%\{data_dir}\update.lock` pub fn update_lock_path(ctx: &impl FsProvider) -> Result { Ok(fig_data_dir_ctx(ctx)?.join("update.lock")) } diff --git a/crates/fig_util/src/lib.rs b/crates/fig_util/src/lib.rs index 9d535f02f..5dac44be9 100644 --- a/crates/fig_util/src/lib.rs +++ b/crates/fig_util/src/lib.rs @@ -126,7 +126,7 @@ fn app_bundle_path_opt() -> Option { #[must_use] pub fn app_bundle_path() -> PathBuf { - app_bundle_path_opt().unwrap_or_else(|| Path::new("/Applications").join(APP_BUNDLE_NAME)) + app_bundle_path_opt().unwrap_or_else(|| Path::new(consts::system_paths::APPLICATIONS_DIR).join(APP_BUNDLE_NAME)) } pub fn partitioned_compare(lhs: &str, rhs: &str, by: char) -> Ordering { diff --git a/crates/q_cli/src/cli/doctor/mod.rs b/crates/q_cli/src/cli/doctor/mod.rs index 408cc469b..f8034c619 100644 --- a/crates/q_cli/src/cli/doctor/mod.rs +++ b/crates/q_cli/src/cli/doctor/mod.rs @@ -93,6 +93,7 @@ use fig_util::{ Shell, Terminal, directories, + system_paths, }; use futures::FutureExt; use futures::future::BoxFuture; @@ -1144,7 +1145,8 @@ impl DoctorCheck for BundlePathCheck { async fn check(&self, diagnostics: &DiagnosticsResponse) -> Result<(), DoctorError> { let path = diagnostics.path_to_bundle.clone(); - if path.contains(&format!("/Applications/{APP_BUNDLE_NAME}")) || path.contains(".toolbox") { + if path.contains(&format!("{}/{APP_BUNDLE_NAME}", system_paths::APPLICATIONS_DIR)) || path.contains(".toolbox") + { Ok(()) } else if path.contains(&format!("/Build/Products/Debug/{APP_BUNDLE_NAME}")) { Err(DoctorError::Warning( @@ -1154,7 +1156,7 @@ impl DoctorCheck for BundlePathCheck { Err(DoctorError::Error { reason: format!("App is installed in {}", path.bold()).into(), info: vec![ - "You need to install the app into /Applications.".into(), + format!("You need to install the app into {}.", system_paths::APPLICATIONS_DIR).into(), "To fix: uninstall and reinstall in the correct location.".into(), "Remember to drag the installed app into the Applications folder.".into(), ], @@ -1251,8 +1253,8 @@ impl DoctorCheck for CliPathCheck { .join(CLI_BINARY_NAME); if path == local_bin_path - || path == Path::new("/usr/local/bin").join(CLI_BINARY_NAME) - || path == Path::new("/opt/homebrew/bin").join(CLI_BINARY_NAME) + || path == Path::new(system_paths::USR_LOCAL_BIN).join(CLI_BINARY_NAME) + || path == Path::new(system_paths::OPT_HOMEBREW_BIN).join(CLI_BINARY_NAME) { Ok(()) } else if path.ends_with(Path::new("target/debug").join(CLI_BINARY_NAME))