Skip to content

Commit 65368e2

Browse files
committed
Merge branch 'main' into refactor/consolidate-env
2 parents ac90afd + d8e77d7 commit 65368e2

File tree

7 files changed

+84
-318
lines changed

7 files changed

+84
-318
lines changed

crates/chat-cli/src/util/directories.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,6 @@ pub fn home_dir(#[cfg_attr(windows, allow(unused_variables))] os: &Os) -> Result
7373
}
7474
}
7575

76-
/// The q data directory
77-
///
78-
/// - Linux: `$XDG_DATA_HOME/amazon-q` or `$HOME/.local/share/amazon-q`
79-
/// - MacOS: `$HOME/Library/Application Support/amazon-q`
80-
pub fn fig_data_dir() -> Result<PathBuf> {
81-
crate::util::paths::app_data_dir().map_err(|e| DirectoryError::Io(std::io::Error::other(e)))
82-
}
83-
8476
/// Get the macos tempdir from the `confstr` function
8577
///
8678
/// See: <https://man7.org/linux/man-pages/man3/confstr.3.html>
@@ -125,7 +117,8 @@ pub fn logs_dir() -> Result<PathBuf> {
125117
if #[cfg(unix)] {
126118
Ok(runtime_dir()?.join("qlog"))
127119
} else if #[cfg(windows)] {
128-
Ok(std::env::temp_dir().join("amazon-q").join("logs"))
120+
use crate::util::paths::application::DATA_DIR_NAME;
121+
Ok(std::env::temp_dir().join(DATA_DIR_NAME).join("logs"))
129122
}
130123
}
131124
}
@@ -154,7 +147,8 @@ pub fn settings_path() -> Result<PathBuf> {
154147

155148
/// The path to the local sqlite database
156149
pub fn database_path() -> Result<PathBuf> {
157-
Ok(fig_data_dir()?.join("data.sqlite3"))
150+
crate::util::paths::ApplicationPaths::database_path_static()
151+
.map_err(|e| DirectoryError::Io(std::io::Error::other(e)))
158152
}
159153

160154
#[cfg(test)]
@@ -245,9 +239,11 @@ mod tests {
245239

246240
#[test]
247241
fn snapshot_fig_data_dir() {
248-
linux!(fig_data_dir(), @"$HOME/.local/share/amazon-q");
249-
macos!(fig_data_dir(), @"$HOME/Library/Application Support/amazon-q");
250-
windows!(fig_data_dir(), @r"C:\Users\$USER\AppData\Local\amazon-q");
242+
let app_data_dir =
243+
|| crate::util::paths::app_data_dir().map_err(|e| DirectoryError::Io(std::io::Error::other(e)));
244+
linux!(app_data_dir(), @"$HOME/.local/share/amazon-q");
245+
macos!(app_data_dir(), @"$HOME/Library/Application Support/amazon-q");
246+
windows!(app_data_dir(), @r"C:\Users\$USER\AppData\Local\AmazonQ");
251247
}
252248

253249
#[test]

crates/chat-cli/src/util/paths.rs

Lines changed: 14 additions & 252 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,38 @@
11
//! Hierarchical path management for the application
22
3-
#![allow(dead_code)] // Allow unused code during migration phase
4-
5-
use std::env::VarError;
6-
use std::path::{
7-
PathBuf,
8-
StripPrefixError,
9-
};
10-
11-
use globset::{
12-
Glob,
13-
GlobSetBuilder,
14-
};
15-
use thiserror::Error;
3+
use std::path::PathBuf;
164

175
use crate::os::Os;
186

19-
#[derive(Debug, Error)]
7+
#[derive(Debug, thiserror::Error)]
208
pub enum DirectoryError {
219
#[error("home directory not found")]
2210
NoHomeDirectory,
23-
#[cfg(unix)]
24-
#[error("runtime directory not found: neither XDG_RUNTIME_DIR nor TMPDIR were found")]
25-
NoRuntimeDirectory,
2611
#[error("IO Error: {0}")]
2712
Io(#[from] std::io::Error),
28-
#[error(transparent)]
29-
TimeFormat(#[from] time::error::Format),
30-
#[error(transparent)]
31-
Utf8FromPath(#[from] camino::FromPathError),
32-
#[error(transparent)]
33-
Utf8FromPathBuf(#[from] camino::FromPathBufError),
34-
#[error(transparent)]
35-
FromVecWithNul(#[from] std::ffi::FromVecWithNulError),
36-
#[error(transparent)]
37-
IntoString(#[from] std::ffi::IntoStringError),
38-
#[error(transparent)]
39-
StripPrefix(#[from] StripPrefixError),
40-
#[error(transparent)]
41-
PathExpand(#[from] shellexpand::LookupError<VarError>),
42-
#[error(transparent)]
43-
GlobCreation(#[from] globset::Error),
4413
}
4514

4615
pub mod workspace {
4716
//! Project-level paths (relative to current working directory)
48-
pub const AGENTS_DIR: &str = ".amazonq/cli-agents";
49-
pub const PROMPTS_DIR: &str = ".amazonq/prompts";
5017
pub const MCP_CONFIG: &str = ".amazonq/mcp.json";
51-
pub const TODO_LISTS_DIR: &str = ".amazonq/cli-todo-lists";
52-
pub const SUBAGENTS_DIR: &str = ".amazonq/.subagents";
5318
pub const RULES_PATTERN: &str = ".amazonq/rules/**/*.md";
54-
55-
// Default documentation files for agent resources
56-
pub const DEFAULT_AGENT_RESOURCES: &[&str] = &["file://AmazonQ.md", "file://AGENTS.md", "file://README.md"];
5719
}
5820

5921
pub mod global {
6022
//! User-level paths (relative to home directory)
61-
pub const AGENTS_DIR: &str = ".aws/amazonq/cli-agents";
62-
pub const PROMPTS_DIR: &str = ".aws/amazonq/prompts";
6323
pub const MCP_CONFIG: &str = ".aws/amazonq/mcp.json";
64-
pub const SHADOW_REPO_DIR: &str = ".aws/amazonq/cli-checkouts";
65-
pub const CLI_BASH_HISTORY: &str = ".aws/amazonq/.cli_bash_history";
6624
pub const GLOBAL_CONTEXT: &str = ".aws/amazonq/global_context.json";
6725
pub const PROFILES_DIR: &str = ".aws/amazonq/profiles";
68-
pub const KNOWLEDGE_BASES_DIR: &str = ".aws/amazonq/knowledge_bases";
6926
}
7027

7128
pub mod application {
7229
//! Application data paths (system-specific)
30+
#[cfg(unix)]
7331
pub const DATA_DIR_NAME: &str = "amazon-q";
32+
#[cfg(windows)]
33+
pub const DATA_DIR_NAME: &str = "AmazonQ";
7434
pub const SETTINGS_FILE: &str = "settings.json";
7535
pub const DATABASE_FILE: &str = "data.sqlite3";
76-
pub const UPDATE_LOCK_FILE: &str = "update.lock";
77-
pub const BACKUP_DIR_NAME: &str = ".amazon-q.dotfiles.bak";
7836
}
7937

8038
type Result<T, E = DirectoryError> = std::result::Result<T, E>;
@@ -121,116 +79,14 @@ pub fn home_dir(#[cfg_attr(windows, allow(unused_variables))] os: &Os) -> Result
12179
}
12280
}
12381

124-
/// Get the macos tempdir from the `confstr` function
125-
#[cfg(target_os = "macos")]
126-
fn macos_tempdir() -> Result<PathBuf> {
127-
let len = unsafe { libc::confstr(libc::_CS_DARWIN_USER_TEMP_DIR, std::ptr::null::<i8>().cast_mut(), 0) };
128-
let mut buf: Vec<u8> = vec![0; len];
129-
unsafe { libc::confstr(libc::_CS_DARWIN_USER_TEMP_DIR, buf.as_mut_ptr().cast(), buf.len()) };
130-
let c_string = std::ffi::CString::from_vec_with_nul(buf)?;
131-
let str = c_string.into_string()?;
132-
Ok(PathBuf::from(str))
133-
}
134-
135-
/// Runtime dir for logs and sockets
136-
#[cfg(unix)]
137-
pub fn runtime_dir() -> Result<PathBuf> {
138-
let mut dir = dirs::runtime_dir();
139-
dir = dir.or_else(|| std::env::var_os("TMPDIR").map(PathBuf::from));
140-
141-
#[cfg(target_os = "macos")]
142-
{
143-
let macos_tempdir = macos_tempdir()?;
144-
dir = dir.or(Some(macos_tempdir));
145-
}
146-
#[cfg(not(target_os = "macos"))]
147-
{
148-
dir = dir.or_else(|| Some(std::env::temp_dir()));
149-
}
150-
151-
dir.ok_or(DirectoryError::NoRuntimeDirectory)
152-
}
153-
15482
/// The application data directory
155-
/// - Linux: `$XDG_DATA_HOME/amazon-q` or `$HOME/.local/share/amazon-q`
156-
/// - MacOS: `$HOME/Library/Application Support/amazon-q`
157-
/// - Windows: `%LOCALAPPDATA%\AmazonQ`
83+
/// - Linux: `$XDG_DATA_HOME/{data_dir}` or `$HOME/.local/share/{data_dir}`
84+
/// - MacOS: `$HOME/Library/Application Support/{data_dir}`
85+
/// - Windows: `%LOCALAPPDATA%\{data_dir}`
15886
pub fn app_data_dir() -> Result<PathBuf> {
159-
#[cfg(unix)]
160-
{
161-
Ok(dirs::data_local_dir()
162-
.ok_or(DirectoryError::NoHomeDirectory)?
163-
.join(application::DATA_DIR_NAME))
164-
}
165-
#[cfg(windows)]
166-
{
167-
Ok(dirs::data_local_dir()
168-
.ok_or(DirectoryError::NoHomeDirectory)?
169-
.join("AmazonQ"))
170-
}
171-
}
172-
173-
/// The directory to all the logs
174-
pub fn logs_dir() -> Result<PathBuf> {
175-
#[cfg(unix)]
176-
{
177-
Ok(runtime_dir()?.join("qlog"))
178-
}
179-
#[cfg(windows)]
180-
{
181-
Ok(std::env::temp_dir().join(application::DATA_DIR_NAME).join("logs"))
182-
}
183-
}
184-
185-
/// Canonicalizes path given by expanding the path given
186-
pub fn canonicalizes_path(os: &Os, path_as_str: &str) -> Result<String> {
187-
let context = |input: &str| Ok(os.env.get(input).ok());
188-
let home_dir_fn = || os.env.home().map(|p| p.to_string_lossy().to_string());
189-
190-
let expanded = shellexpand::full_with_context(path_as_str, home_dir_fn, context)?;
191-
let path_buf = if !expanded.starts_with("/") {
192-
let current_dir = os.env.current_dir()?;
193-
current_dir.join(expanded.as_ref() as &str)
194-
} else {
195-
PathBuf::from(expanded.as_ref() as &str)
196-
};
197-
198-
match path_buf.canonicalize() {
199-
Ok(normalized) => Ok(normalized.as_path().to_string_lossy().to_string()),
200-
Err(_) => {
201-
let normalized = normalize_path(&path_buf);
202-
Ok(normalized.to_string_lossy().to_string())
203-
},
204-
}
205-
}
206-
207-
/// Manually normalize a path by resolving . and .. components
208-
fn normalize_path(path: &std::path::Path) -> std::path::PathBuf {
209-
let mut components = Vec::new();
210-
for component in path.components() {
211-
match component {
212-
std::path::Component::CurDir => {},
213-
std::path::Component::ParentDir => {
214-
components.pop();
215-
},
216-
_ => {
217-
components.push(component);
218-
},
219-
}
220-
}
221-
components.iter().collect()
222-
}
223-
224-
/// Given a globset builder and a path, build globs for both the file and directory patterns
225-
/// This is needed because by default glob does not match children of a dir so we need both
226-
/// patterns to exist in a globset.
227-
pub fn add_gitignore_globs(builder: &mut GlobSetBuilder, path: &str) -> Result<()> {
228-
let glob_for_file = Glob::new(path)?;
229-
let dir_pattern: String = format!("{}/**", path.trim_end_matches('/'));
230-
let glob_for_dir = Glob::new(&dir_pattern)?;
231-
builder.add(glob_for_file);
232-
builder.add(glob_for_dir);
233-
Ok(())
87+
Ok(dirs::data_local_dir()
88+
.ok_or(DirectoryError::NoHomeDirectory)?
89+
.join(application::DATA_DIR_NAME))
23490
}
23591

23692
/// Path resolver with hierarchy-aware methods
@@ -252,11 +108,6 @@ impl<'a> PathResolver<'a> {
252108
pub fn global(&self) -> GlobalPaths<'_> {
253109
GlobalPaths { os: self.os }
254110
}
255-
256-
/// Get application-scoped path resolver
257-
pub fn application(&self) -> ApplicationPaths<'_> {
258-
ApplicationPaths { os: self.os }
259-
}
260111
}
261112

262113
/// Workspace-scoped path methods
@@ -265,33 +116,9 @@ pub struct WorkspacePaths<'a> {
265116
}
266117

267118
impl<'a> WorkspacePaths<'a> {
268-
pub fn agents_dir(&self) -> Result<PathBuf> {
269-
Ok(self.os.env.current_dir()?.join(workspace::AGENTS_DIR))
270-
}
271-
272-
pub fn prompts_dir(&self) -> Result<PathBuf> {
273-
Ok(self.os.env.current_dir()?.join(workspace::PROMPTS_DIR))
274-
}
275-
276119
pub fn mcp_config(&self) -> Result<PathBuf> {
277120
Ok(self.os.env.current_dir()?.join(workspace::MCP_CONFIG))
278121
}
279-
280-
pub fn todo_lists_dir(&self) -> Result<PathBuf> {
281-
Ok(self.os.env.current_dir()?.join(workspace::TODO_LISTS_DIR))
282-
}
283-
284-
pub fn subagents_dir(&self) -> Result<PathBuf> {
285-
Ok(self.os.env.current_dir()?.join(workspace::SUBAGENTS_DIR))
286-
}
287-
288-
pub async fn ensure_subagents_dir(&self) -> Result<PathBuf> {
289-
let dir = self.subagents_dir()?;
290-
if !dir.exists() {
291-
self.os.fs.create_dir_all(&dir).await?;
292-
}
293-
Ok(dir)
294-
}
295122
}
296123

297124
/// Global-scoped path methods
@@ -300,88 +127,23 @@ pub struct GlobalPaths<'a> {
300127
}
301128

302129
impl<'a> GlobalPaths<'a> {
303-
pub fn agents_dir(&self) -> Result<PathBuf> {
304-
Ok(home_dir(self.os)?.join(global::AGENTS_DIR))
305-
}
306-
307-
pub fn prompts_dir(&self) -> Result<PathBuf> {
308-
Ok(home_dir(self.os)?.join(global::PROMPTS_DIR))
309-
}
310-
311130
pub fn mcp_config(&self) -> Result<PathBuf> {
312131
Ok(home_dir(self.os)?.join(global::MCP_CONFIG))
313132
}
314133

315-
pub fn shadow_repo_dir(&self) -> Result<PathBuf> {
316-
Ok(home_dir(self.os)?.join(global::SHADOW_REPO_DIR))
317-
}
318-
319-
pub fn cli_bash_history(&self) -> Result<PathBuf> {
320-
Ok(home_dir(self.os)?.join(global::CLI_BASH_HISTORY))
321-
}
322-
323134
pub fn global_context(&self) -> Result<PathBuf> {
324135
Ok(home_dir(self.os)?.join(global::GLOBAL_CONTEXT))
325136
}
326137

327138
pub fn profiles_dir(&self) -> Result<PathBuf> {
328139
Ok(home_dir(self.os)?.join(global::PROFILES_DIR))
329140
}
330-
331-
pub fn knowledge_bases_dir(&self) -> Result<PathBuf> {
332-
Ok(home_dir(self.os)?.join(global::KNOWLEDGE_BASES_DIR))
333-
}
334-
335-
pub fn mcp_auth_dir(&self) -> Result<PathBuf> {
336-
Ok(home_dir(self.os)?.join(".aws").join("sso").join("cache"))
337-
}
338-
339-
pub async fn ensure_agents_dir(&self) -> Result<PathBuf> {
340-
let dir = self.agents_dir()?;
341-
if !dir.exists() {
342-
self.os.fs.create_dir_all(&dir).await?;
343-
}
344-
Ok(dir)
345-
}
346-
}
347-
348-
/// Application-scoped path methods
349-
pub struct ApplicationPaths<'a> {
350-
os: &'a Os,
351141
}
352142

353-
#[allow(clippy::unused_self)] // Allow unused self during migration
354-
impl<'a> ApplicationPaths<'a> {
355-
/// The application data directory
356-
pub fn data_dir(&self) -> Result<PathBuf> {
357-
app_data_dir()
358-
}
359-
360-
/// The application settings file
361-
pub fn settings_path(&self) -> Result<PathBuf> {
362-
Ok(app_data_dir()?.join(application::SETTINGS_FILE))
363-
}
364-
365-
/// The application database file
366-
pub fn database_path(&self) -> Result<PathBuf> {
367-
Ok(app_data_dir()?.join(application::DATABASE_FILE))
368-
}
369-
370-
/// The application update lock file
371-
pub fn update_lock_path(&self) -> Result<PathBuf> {
372-
Ok(app_data_dir()?.join(application::UPDATE_LOCK_FILE))
373-
}
374-
375-
/// The backup directory
376-
pub fn backups_dir(&self) -> Result<PathBuf> {
377-
Ok(home_dir(self.os)?.join(application::BACKUP_DIR_NAME))
378-
}
379-
380-
/// The logs directory
381-
pub fn logs_dir(&self) -> Result<PathBuf> {
382-
logs_dir()
383-
}
143+
/// Application path static methods
144+
pub struct ApplicationPaths;
384145

146+
impl ApplicationPaths {
385147
/// Static method for settings path (to avoid circular dependency)
386148
pub fn settings_path_static() -> Result<PathBuf> {
387149
Ok(app_data_dir()?.join(application::SETTINGS_FILE))

0 commit comments

Comments
 (0)