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