npm_run_scripts/utils/
paths.rs

1//! Path utilities.
2
3use std::path::{Path, PathBuf};
4
5use anyhow::{Context, Result};
6
7use crate::error::NrsError;
8
9/// Maximum number of parent directories to search.
10pub const MAX_SEARCH_DEPTH: usize = 10;
11
12/// Find the package.json file starting from the given directory.
13///
14/// Searches the given directory and up to 10 parent directories.
15///
16/// # Errors
17///
18/// Returns an error if no package.json is found.
19pub fn find_package_json(start_dir: &Path) -> Result<PathBuf> {
20    let start = start_dir.canonicalize().with_context(|| {
21        format!(
22            "Cannot access directory '{}': path does not exist or is not accessible",
23            start_dir.display()
24        )
25    })?;
26
27    let mut current = start.as_path();
28    let mut depth = 0;
29
30    while depth < MAX_SEARCH_DEPTH {
31        let package_json = current.join("package.json");
32        if package_json.exists() {
33            return Ok(package_json);
34        }
35
36        match current.parent() {
37            Some(parent) if parent != current => {
38                current = parent;
39                depth += 1;
40            }
41            _ => break,
42        }
43    }
44
45    Err(NrsError::NoPackageJson {
46        path: start,
47        depth: MAX_SEARCH_DEPTH,
48    }
49    .into())
50}
51
52/// Find the project root (directory containing package.json).
53///
54/// # Errors
55///
56/// Returns an error if no package.json is found.
57pub fn find_project_root(start_dir: &Path) -> Result<PathBuf> {
58    let package_json = find_package_json(start_dir)?;
59    Ok(package_json
60        .parent()
61        .expect("package.json should have parent")
62        .to_path_buf())
63}
64
65/// Get the config directory for nrs.
66///
67/// Returns `~/.config/nrs` on Unix-like systems.
68pub fn config_dir() -> Option<PathBuf> {
69    dirs::config_dir().map(|p| p.join("nrs"))
70}
71
72/// Get the history file path.
73///
74/// Returns `~/.config/nrs/history.json`.
75pub fn history_file() -> Option<PathBuf> {
76    config_dir().map(|p| p.join("history.json"))
77}
78
79/// Get the global config file path.
80///
81/// Returns `~/.config/nrs/config.toml`.
82pub fn global_config_file() -> Option<PathBuf> {
83    config_dir().map(|p| p.join("config.toml"))
84}
85
86/// Find local config file in project directory.
87///
88/// Looks for `.nrsrc.toml` in the given directory.
89pub fn local_config_file(project_dir: &Path) -> Option<PathBuf> {
90    let config_file = project_dir.join(".nrsrc.toml");
91    if config_file.exists() {
92        Some(config_file)
93    } else {
94        None
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use tempfile::TempDir;
102
103    #[test]
104    fn test_find_package_json_in_current_dir() {
105        let temp = TempDir::new().unwrap();
106        std::fs::write(temp.path().join("package.json"), "{}").unwrap();
107
108        let result = find_package_json(temp.path());
109        assert!(result.is_ok());
110        assert!(result.unwrap().ends_with("package.json"));
111    }
112
113    #[test]
114    fn test_find_package_json_in_parent() {
115        let temp = TempDir::new().unwrap();
116        std::fs::write(temp.path().join("package.json"), "{}").unwrap();
117
118        let subdir = temp.path().join("src");
119        std::fs::create_dir(&subdir).unwrap();
120
121        let result = find_package_json(&subdir);
122        assert!(result.is_ok());
123    }
124
125    #[test]
126    fn test_find_package_json_not_found() {
127        let temp = TempDir::new().unwrap();
128        let result = find_package_json(temp.path());
129        assert!(result.is_err());
130    }
131}