1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#![allow(clippy::unwrap_or_default)]
#![allow(clippy::useless_vec)]

use std::{env, path};

pub fn get_project_root_path() -> Option<String> {
    let current_dir = env::current_dir().unwrap();
    let dir = walk_reverse_dir(current_dir.as_path()).unwrap_or_default();

    Some(dir)
}

fn walk_reverse_dir(path: &path::Path) -> Option<String> {
    let current_path = path.to_path_buf();
    let map_files = vec![
        ("package-lock.json", "npm"),
        ("npm-shrinkwrap.json", "npm"),
        ("yarn.lock", "yarn"),
        ("pnpm-lock.yaml", "pnpm"),
        ("bun.lockb", "bun"),
    ];

    for (file, _) in map_files.iter() {
        let lock_file = current_path.join(file);

        if lock_file.exists() {
            return Some(current_path.to_str().unwrap().to_string());
        }
    }

    if let Some(parent) = path.parent() {
        return walk_reverse_dir(parent);
    }

    None
}

#[cfg(test)]
mod tests {
    use super::*;

    use std::fs::{remove_file, File};
    use std::path::Path;

    fn create_agent_file(path: &Path) -> File {
        File::create(path).expect("File not created")
    }

    fn delete_agent_file(path: &Path) {
        remove_file(path).expect("File not deleted");
    }

    #[test]
    fn npm_root_project() {
        let path = std::env::current_dir().expect("Current user home directory");
        let npm_lock = path.join("package-lock.json");

        create_agent_file(&npm_lock);

        let project_root = get_project_root_path();

        assert_eq!(project_root, Some(path.to_str().unwrap().to_string()));

        delete_agent_file(&npm_lock);
    }

    #[test]
    fn yarn_root_project() {
        let path = std::env::current_dir().expect("Current user home directory");
        let yarn_lock = path.join("yarn.lock");

        create_agent_file(&yarn_lock);

        let project_root = get_project_root_path();

        assert_eq!(project_root, Some(path.to_str().unwrap().to_string()));

        delete_agent_file(&yarn_lock);
    }

    #[test]
    fn pnpm_root_project() {
        let path = std::env::current_dir().expect("Current user home directory");
        let pnpm_lock = path.join("pnpm-lock.yaml");

        create_agent_file(&pnpm_lock);

        let project_root = get_project_root_path();

        assert_eq!(project_root, Some(path.to_str().unwrap().to_string()));

        delete_agent_file(&pnpm_lock);
    }

    #[test]
    fn bun_root_project() {
        let path = std::env::current_dir().expect("Current user home directory");
        let bun_lock = path.join("bun.lockb");

        create_agent_file(&bun_lock);

        let project_root = get_project_root_path();

        assert_eq!(project_root, Some(path.to_str().unwrap().to_string()));

        delete_agent_file(&bun_lock);
    }
}