use miette::{IntoDiagnostic, miette};
use std::path::{Path, PathBuf};
use std::sync::RwLock;
static CWD: RwLock<Option<PathBuf>> = RwLock::new(None);
pub fn cwd() -> miette::Result<PathBuf> {
if let Some(p) = CWD.read().expect("cwd lock poisoned").as_ref() {
return Ok(p.clone());
}
let mut cwd = CWD.write().expect("cwd lock poisoned");
if let Some(p) = cwd.as_ref() {
return Ok(p.clone());
}
let p = std::env::current_dir().into_diagnostic()?;
Ok(cwd.insert(p).clone())
}
pub fn find_project_root(start: &Path) -> Option<PathBuf> {
static CACHE: aube_util::cache::ProcessCache<PathBuf, PathBuf> =
aube_util::cache::ProcessCache::new();
let key = start.to_path_buf();
if let Some(hit) = CACHE.get(&key) {
return Some((*hit).clone());
}
let result = find_project_root_uncached(start)?;
Some((*CACHE.get_or_compute(key, || result)).clone())
}
fn find_project_root_uncached(start: &Path) -> Option<PathBuf> {
let stop = home_stop_boundary();
for dir in start.ancestors() {
if dir.join("package.json").is_file() {
return Some(dir.to_path_buf());
}
if stop.as_deref() == Some(dir) {
return None;
}
}
None
}
fn home_stop_boundary() -> Option<PathBuf> {
aube_util::env::home_dir()
}
fn package_json_has_workspaces(path: &Path) -> bool {
#[derive(serde::Deserialize)]
struct ShapeOnly {
#[serde(default)]
workspaces: Option<serde::de::IgnoredAny>,
}
let Ok(bytes) = std::fs::read(path) else {
return false;
};
serde_json::from_slice::<ShapeOnly>(&bytes)
.map(|s| s.workspaces.is_some())
.unwrap_or(false)
}
pub fn find_workspace_root(start: &Path) -> Option<PathBuf> {
static CACHE: aube_util::cache::ProcessCache<PathBuf, PathBuf> =
aube_util::cache::ProcessCache::new();
let key = start.to_path_buf();
if let Some(hit) = CACHE.get(&key) {
return Some((*hit).clone());
}
let result = find_workspace_root_uncached(start)?;
Some((*CACHE.get_or_compute(key, || result)).clone())
}
fn find_workspace_root_uncached(start: &Path) -> Option<PathBuf> {
let stop = home_stop_boundary();
for dir in start.ancestors() {
if aube_manifest::workspace::workspace_yaml_existing(dir).is_some() {
return Some(dir.to_path_buf());
}
let pkg = dir.join("package.json");
if pkg.is_file() && package_json_has_workspaces(&pkg) {
return Some(dir.to_path_buf());
}
if stop.as_deref() == Some(dir) {
return None;
}
}
None
}
pub fn find_workspace_yaml_root(start: &Path) -> Option<PathBuf> {
let stop = home_stop_boundary();
for dir in start.ancestors() {
if aube_manifest::workspace::workspace_yaml_existing(dir).is_some() {
return Some(dir.to_path_buf());
}
if stop.as_deref() == Some(dir) {
return None;
}
}
None
}
pub fn project_root() -> miette::Result<PathBuf> {
let initial_cwd = cwd()?;
find_project_root(&initial_cwd).ok_or_else(|| {
miette!(
"no package.json found in {} or any parent directory",
initial_cwd.display()
)
})
}
pub fn project_root_or_cwd() -> miette::Result<PathBuf> {
let initial_cwd = cwd()?;
Ok(find_project_root(&initial_cwd).unwrap_or(initial_cwd))
}
pub fn workspace_or_project_root() -> miette::Result<PathBuf> {
let initial_cwd = cwd()?;
if let Some(root) = find_workspace_root(&initial_cwd) {
return Ok(root);
}
if let Some(root) = find_project_root(&initial_cwd) {
return Ok(root);
}
Err(no_root_error(&initial_cwd))
}
fn no_root_error(initial_cwd: &Path) -> miette::Report {
miette!(
"no package.json or workspace yaml \
(pnpm-workspace.yaml / aube-workspace.yaml) found in {} \
or any parent directory",
initial_cwd.display()
)
}
pub fn set_cwd(path: &Path) -> miette::Result<()> {
let path = if path.is_absolute() {
path.to_path_buf()
} else {
std::env::current_dir().into_diagnostic()?.join(path)
};
*CWD.write().expect("cwd lock poisoned") = Some(path);
Ok(())
}
pub fn canonicalize(path: &Path) -> std::io::Result<PathBuf> {
let canon = std::fs::canonicalize(path)?;
Ok(aube_util::path::strip_verbatim_prefix(&canon))
}
#[cfg(test)]
mod tests {
use super::*;
fn write(path: &Path, content: &str) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap();
}
std::fs::write(path, content).unwrap();
}
#[test]
fn find_workspace_root_finds_pnpm_workspace_yaml() {
let dir = tempfile::tempdir().unwrap();
write(
&dir.path().join("pnpm-workspace.yaml"),
"packages:\n - 'packages/*'\n",
);
write(&dir.path().join("packages/a/package.json"), "{}");
let child = dir.path().join("packages/a");
assert_eq!(find_workspace_root(&child).unwrap(), dir.path());
}
#[test]
fn find_workspace_root_finds_package_json_workspaces_array() {
let dir = tempfile::tempdir().unwrap();
write(
&dir.path().join("package.json"),
r#"{"name":"root","workspaces":["packages/*"]}"#,
);
write(
&dir.path().join("packages/a/package.json"),
r#"{"name":"a"}"#,
);
let child = dir.path().join("packages/a");
assert_eq!(find_workspace_root(&child).unwrap(), dir.path());
}
#[test]
fn find_workspace_root_finds_package_json_workspaces_object() {
let dir = tempfile::tempdir().unwrap();
write(
&dir.path().join("package.json"),
r#"{"name":"root","workspaces":{"packages":["apps/*"]}}"#,
);
write(&dir.path().join("apps/a/package.json"), r#"{"name":"a"}"#);
let child = dir.path().join("apps/a");
assert_eq!(find_workspace_root(&child).unwrap(), dir.path());
}
#[test]
fn find_workspace_root_ignores_package_json_without_workspaces() {
let dir = tempfile::tempdir().unwrap();
write(
&dir.path().join("package.json"),
r#"{"name":"root","workspaces":["packages/*"]}"#,
);
write(
&dir.path().join("packages/a/package.json"),
r#"{"name":"a"}"#,
);
let child = dir.path().join("packages/a");
let root = find_workspace_root(&child).unwrap();
assert_eq!(root, dir.path());
assert_ne!(root, child);
}
#[test]
fn find_workspace_yaml_root_ignores_package_json_workspaces() {
let dir = tempfile::tempdir().unwrap();
write(
&dir.path().join("package.json"),
r#"{"name":"root","workspaces":["packages/*"]}"#,
);
write(
&dir.path().join("packages/a/package.json"),
r#"{"name":"a"}"#,
);
let child = dir.path().join("packages/a");
assert!(find_workspace_yaml_root(&child).is_none());
}
#[test]
fn find_workspace_root_returns_none_without_markers() {
let dir = tempfile::tempdir().unwrap();
write(&dir.path().join("package.json"), r#"{"name":"solo"}"#);
assert!(find_workspace_root(dir.path()).is_none());
}
#[test]
fn canonicalize_round_trips_an_existing_path() {
let dir = tempfile::tempdir().unwrap();
let canon = canonicalize(dir.path()).unwrap();
assert!(canon.is_absolute());
assert!(canon.exists());
}
#[cfg(windows)]
#[test]
fn canonicalize_strips_verbatim_drive_prefix() {
let dir = tempfile::tempdir().unwrap();
let canon = canonicalize(dir.path()).unwrap();
let s = canon.to_string_lossy();
assert!(
!s.starts_with(r"\\?\"),
"expected non-verbatim path, got {s}"
);
assert!(
s.chars().nth(1) == Some(':'),
"expected drive form, got {s}"
);
}
}