changepacks_node/
lib.rs

1pub mod finder;
2pub mod package;
3pub mod workspace;
4
5pub use finder::NodeProjectFinder;
6
7use std::path::Path;
8
9/// Represents the detected Node.js package manager
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PackageManager {
12    Npm,
13    Yarn,
14    Pnpm,
15    Bun,
16}
17
18impl PackageManager {
19    /// Returns the publish command for this package manager
20    pub fn publish_command(&self) -> &'static str {
21        match self {
22            PackageManager::Npm => "npm publish",
23            PackageManager::Yarn => "yarn npm publish",
24            PackageManager::Pnpm => "pnpm publish",
25            PackageManager::Bun => "bun publish",
26        }
27    }
28}
29
30/// Detects the package manager by checking for lock files in the given directory
31/// Priority: bun.lockb > pnpm-lock.yaml > yarn.lock > package-lock.json > npm (default)
32pub fn detect_package_manager(dir: &Path) -> PackageManager {
33    if dir.join("bun.lockb").exists() || dir.join("bun.lock").exists() {
34        PackageManager::Bun
35    } else if dir.join("pnpm-lock.yaml").exists() {
36        PackageManager::Pnpm
37    } else if dir.join("yarn.lock").exists() {
38        PackageManager::Yarn
39    } else if dir.join("package-lock.json").exists() {
40        PackageManager::Npm
41    } else {
42        // Default to npm if no lock file found
43        PackageManager::Npm
44    }
45}
46
47/// Detects the package manager by searching from the given path up to the root
48pub fn detect_package_manager_recursive(path: &Path) -> PackageManager {
49    let mut current = if path.is_file() {
50        path.parent()
51    } else {
52        Some(path)
53    };
54
55    while let Some(dir) = current {
56        let pm = detect_package_manager(dir);
57        if pm != PackageManager::Npm || dir.join("package-lock.json").exists() {
58            return pm;
59        }
60        current = dir.parent();
61    }
62
63    PackageManager::Npm
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use std::fs;
70    use tempfile::TempDir;
71
72    #[test]
73    fn test_detect_bun_lockb() {
74        let temp_dir = TempDir::new().unwrap();
75        fs::write(temp_dir.path().join("bun.lockb"), "").unwrap();
76        assert_eq!(detect_package_manager(temp_dir.path()), PackageManager::Bun);
77    }
78
79    #[test]
80    fn test_detect_bun_lock() {
81        let temp_dir = TempDir::new().unwrap();
82        fs::write(temp_dir.path().join("bun.lock"), "").unwrap();
83        assert_eq!(detect_package_manager(temp_dir.path()), PackageManager::Bun);
84    }
85
86    #[test]
87    fn test_detect_pnpm() {
88        let temp_dir = TempDir::new().unwrap();
89        fs::write(temp_dir.path().join("pnpm-lock.yaml"), "").unwrap();
90        assert_eq!(
91            detect_package_manager(temp_dir.path()),
92            PackageManager::Pnpm
93        );
94    }
95
96    #[test]
97    fn test_detect_yarn() {
98        let temp_dir = TempDir::new().unwrap();
99        fs::write(temp_dir.path().join("yarn.lock"), "").unwrap();
100        assert_eq!(
101            detect_package_manager(temp_dir.path()),
102            PackageManager::Yarn
103        );
104    }
105
106    #[test]
107    fn test_detect_npm() {
108        let temp_dir = TempDir::new().unwrap();
109        fs::write(temp_dir.path().join("package-lock.json"), "{}").unwrap();
110        assert_eq!(detect_package_manager(temp_dir.path()), PackageManager::Npm);
111    }
112
113    #[test]
114    fn test_detect_default_npm() {
115        let temp_dir = TempDir::new().unwrap();
116        assert_eq!(detect_package_manager(temp_dir.path()), PackageManager::Npm);
117    }
118
119    #[test]
120    fn test_bun_priority_over_others() {
121        let temp_dir = TempDir::new().unwrap();
122        fs::write(temp_dir.path().join("bun.lockb"), "").unwrap();
123        fs::write(temp_dir.path().join("pnpm-lock.yaml"), "").unwrap();
124        fs::write(temp_dir.path().join("yarn.lock"), "").unwrap();
125        assert_eq!(detect_package_manager(temp_dir.path()), PackageManager::Bun);
126    }
127
128    #[test]
129    fn test_pnpm_priority_over_yarn() {
130        let temp_dir = TempDir::new().unwrap();
131        fs::write(temp_dir.path().join("pnpm-lock.yaml"), "").unwrap();
132        fs::write(temp_dir.path().join("yarn.lock"), "").unwrap();
133        assert_eq!(
134            detect_package_manager(temp_dir.path()),
135            PackageManager::Pnpm
136        );
137    }
138
139    #[test]
140    fn test_publish_commands() {
141        assert_eq!(PackageManager::Npm.publish_command(), "npm publish");
142        assert_eq!(PackageManager::Yarn.publish_command(), "yarn npm publish");
143        assert_eq!(PackageManager::Pnpm.publish_command(), "pnpm publish");
144        assert_eq!(PackageManager::Bun.publish_command(), "bun publish");
145    }
146
147    #[test]
148    fn test_detect_recursive() {
149        let temp_dir = TempDir::new().unwrap();
150        let sub_dir = temp_dir.path().join("packages").join("core");
151        fs::create_dir_all(&sub_dir).unwrap();
152        fs::write(temp_dir.path().join("pnpm-lock.yaml"), "").unwrap();
153        fs::write(sub_dir.join("package.json"), "{}").unwrap();
154
155        assert_eq!(
156            detect_package_manager_recursive(&sub_dir.join("package.json")),
157            PackageManager::Pnpm
158        );
159    }
160}