socket_patch_core/utils/
global_packages.rs1use std::path::PathBuf;
2use std::process::Command;
3
4pub fn get_npm_global_prefix() -> Result<String, String> {
10 let output = Command::new("npm")
11 .args(["root", "-g"])
12 .stdin(std::process::Stdio::null())
13 .stdout(std::process::Stdio::piped())
14 .stderr(std::process::Stdio::piped())
15 .output()
16 .map_err(|e| format!("Failed to run `npm root -g`: {e}"))?;
17
18 if !output.status.success() {
19 return Err(
20 "Failed to determine npm global prefix. Ensure npm is installed and in PATH."
21 .to_string(),
22 );
23 }
24
25 let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
26 if path.is_empty() {
27 return Err("npm root -g returned empty output".to_string());
28 }
29
30 Ok(path)
31}
32
33pub fn get_yarn_global_prefix() -> Option<String> {
35 let output = Command::new("yarn")
36 .args(["global", "dir"])
37 .stdin(std::process::Stdio::null())
38 .stdout(std::process::Stdio::piped())
39 .stderr(std::process::Stdio::piped())
40 .output()
41 .ok()?;
42
43 if !output.status.success() {
44 return None;
45 }
46
47 let dir = String::from_utf8_lossy(&output.stdout).trim().to_string();
48 if dir.is_empty() {
49 return None;
50 }
51
52 Some(
53 PathBuf::from(dir)
54 .join("node_modules")
55 .to_string_lossy()
56 .to_string(),
57 )
58}
59
60pub fn get_pnpm_global_prefix() -> Option<String> {
62 let output = Command::new("pnpm")
63 .args(["root", "-g"])
64 .stdin(std::process::Stdio::null())
65 .stdout(std::process::Stdio::piped())
66 .stderr(std::process::Stdio::piped())
67 .output()
68 .ok()?;
69
70 if !output.status.success() {
71 return None;
72 }
73
74 let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
75 if path.is_empty() {
76 return None;
77 }
78
79 Some(path)
80}
81
82pub fn get_bun_global_prefix() -> Option<String> {
84 let output = Command::new("bun")
85 .args(["pm", "bin", "-g"])
86 .stdin(std::process::Stdio::null())
87 .stdout(std::process::Stdio::piped())
88 .stderr(std::process::Stdio::piped())
89 .output()
90 .ok()?;
91
92 if !output.status.success() {
93 return None;
94 }
95
96 let bin_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
97 if bin_path.is_empty() {
98 return None;
99 }
100
101 let bun_root = PathBuf::from(&bin_path);
102 let parent = bun_root.parent()?;
103
104 Some(
105 parent
106 .join("install")
107 .join("global")
108 .join("node_modules")
109 .to_string_lossy()
110 .to_string(),
111 )
112}
113
114pub fn get_global_prefix(custom: Option<&str>) -> Result<String, String> {
123 if let Some(custom_path) = custom {
124 return Ok(custom_path.to_string());
125 }
126 get_npm_global_prefix()
127}
128
129pub fn get_global_node_modules_paths(custom: Option<&str>) -> Vec<String> {
134 if let Some(custom_path) = custom {
135 return vec![custom_path.to_string()];
136 }
137
138 let mut paths = Vec::new();
139
140 if let Ok(npm_path) = get_npm_global_prefix() {
141 paths.push(npm_path);
142 }
143
144 if let Some(pnpm_path) = get_pnpm_global_prefix() {
145 paths.push(pnpm_path);
146 }
147
148 if let Some(yarn_path) = get_yarn_global_prefix() {
149 paths.push(yarn_path);
150 }
151
152 if let Some(bun_path) = get_bun_global_prefix() {
153 paths.push(bun_path);
154 }
155
156 paths
157}
158
159pub fn is_global_path(pkg_path: &str) -> bool {
161 let paths = get_global_node_modules_paths(None);
162 let normalized = PathBuf::from(pkg_path);
163 let normalized_str = normalized.to_string_lossy();
164
165 paths.iter().any(|global_path| {
166 let gp = PathBuf::from(global_path);
167 normalized_str.starts_with(&*gp.to_string_lossy())
168 })
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_get_global_prefix_custom() {
177 let result = get_global_prefix(Some("/custom/node_modules"));
178 assert_eq!(result.unwrap(), "/custom/node_modules");
179 }
180
181 #[test]
182 fn test_get_global_node_modules_paths_custom() {
183 let paths = get_global_node_modules_paths(Some("/my/custom/path"));
184 assert_eq!(paths, vec!["/my/custom/path".to_string()]);
185 }
186}