workspace_node_tools/
paths.rs

1#![allow(clippy::all)]
2
3//! #Paths module
4//!
5//! The `paths` module is used to get the project root path.
6use super::utils::strip_trailing_newline;
7use execute::Execute;
8use std::{
9    env,
10    path::{Path, PathBuf},
11    process::{Command, Stdio},
12};
13
14/// Get the project root path.
15pub fn get_project_root_path(root: Option<PathBuf>) -> Option<String> {
16    let env_dir = match root {
17        Some(dir) => Ok(dir),
18        None => env::current_dir(),
19    };
20
21    let current_dir = match env_dir {
22        Ok(dir) => dir,
23        _ => PathBuf::from("./"),
24    };
25    let current_path = current_dir.as_path();
26
27    let git_root_dir = walk_reverse_dir(&current_path);
28
29    let project_root = match git_root_dir {
30        Some(current) => current,
31        None => {
32            let search_root = get_git_root_dir(&current_path);
33            search_root.unwrap_or(current_path.to_str().unwrap().to_string())
34        }
35    };
36
37    let canonic_path = &std::fs::canonicalize(Path::new(&project_root)).unwrap();
38    let root = canonic_path.as_path().display().to_string();
39
40    Some(root)
41}
42
43/// Get the git root directory.
44fn get_git_root_dir(dir: &Path) -> Option<String> {
45    let mut command = Command::new("git");
46    command.arg("rev-parse").arg("--show-toplevel");
47
48    command.current_dir(dir);
49
50    command.stdout(Stdio::piped());
51    command.stderr(Stdio::piped());
52
53    let output = command.execute_output().unwrap();
54
55    if output.status.success() {
56        let output = String::from_utf8(output.stdout).unwrap();
57        return Some(strip_trailing_newline(&output));
58    }
59
60    None
61}
62
63/// Walk reverse directory to find the root project.
64fn walk_reverse_dir(path: &Path) -> Option<String> {
65    let current_path = path.to_path_buf();
66    let map_files = vec![
67        ("package-lock.json", "npm"),
68        ("npm-shrinkwrap.json", "npm"),
69        ("yarn.lock", "yarn"),
70        ("pnpm-lock.yaml", "pnpm"),
71        ("bun.lockb", "bun"),
72    ];
73
74    for (file, _) in map_files.iter() {
75        let lock_file = current_path.join(file);
76
77        if lock_file.exists() {
78            return Some(current_path.to_str().unwrap().to_string());
79        }
80    }
81
82    if let Some(parent) = path.parent() {
83        return walk_reverse_dir(parent);
84    }
85
86    None
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    use crate::manager::PackageManager;
94    use crate::utils::create_test_monorepo;
95    use std::fs::{remove_dir_all, rename};
96    use std::path::Path;
97
98    fn git_dir_rename(from: &Path, to: &Path) {
99        rename(from, to).expect("Rename dir");
100    }
101
102    #[test]
103    fn npm_root_project() -> Result<(), Box<dyn std::error::Error>> {
104        let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
105        let git_home = monorepo_dir.join(".git");
106        let no_git = monorepo_dir.join(".no_git");
107
108        git_dir_rename(&git_home, &no_git);
109
110        let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
111
112        assert_eq!(
113            project_root,
114            Some(monorepo_dir.to_str().unwrap().to_string())
115        );
116
117        remove_dir_all(&monorepo_dir)?;
118        Ok(())
119    }
120
121    #[test]
122    fn yarn_root_project() -> Result<(), Box<dyn std::error::Error>> {
123        let ref monorepo_dir = create_test_monorepo(&PackageManager::Yarn)?;
124        let git_home = monorepo_dir.join(".git");
125        let no_git = monorepo_dir.join(".no_git");
126
127        git_dir_rename(&git_home, &no_git);
128
129        let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
130
131        assert_eq!(
132            project_root,
133            Some(monorepo_dir.to_str().unwrap().to_string())
134        );
135
136        remove_dir_all(&monorepo_dir)?;
137        Ok(())
138    }
139
140    #[test]
141    fn pnpm_root_project() -> Result<(), Box<dyn std::error::Error>> {
142        let ref monorepo_dir = create_test_monorepo(&PackageManager::Pnpm)?;
143        let git_home = monorepo_dir.join(".git");
144        let no_git = monorepo_dir.join(".no_git");
145
146        git_dir_rename(&git_home, &no_git);
147
148        let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
149
150        assert_eq!(
151            project_root,
152            Some(monorepo_dir.to_str().unwrap().to_string())
153        );
154
155        remove_dir_all(&monorepo_dir)?;
156        Ok(())
157    }
158
159    #[test]
160    fn bun_root_project() -> Result<(), Box<dyn std::error::Error>> {
161        let ref monorepo_dir = create_test_monorepo(&PackageManager::Bun)?;
162        let git_home = monorepo_dir.join(".git");
163        let no_git = monorepo_dir.join(".no_git");
164
165        git_dir_rename(&git_home, &no_git);
166
167        let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
168
169        assert_eq!(
170            project_root,
171            Some(monorepo_dir.to_str().unwrap().to_string())
172        );
173
174        remove_dir_all(&monorepo_dir)?;
175        Ok(())
176    }
177
178    #[test]
179    fn git_root_project() -> Result<(), Box<dyn std::error::Error>> {
180        let ref monorepo_dir = create_test_monorepo(&PackageManager::Npm)?;
181        let project_root = get_project_root_path(Some(monorepo_dir.to_path_buf()));
182
183        assert_eq!(project_root.is_some(), true);
184        remove_dir_all(&monorepo_dir)?;
185        Ok(())
186    }
187}