1use std::path::{Path, PathBuf};
4
5pub fn list_workspace_roots(dir: &Path) -> Result<Vec<PathBuf>, String> {
8 let pj = dir.join("package.json");
9 if !pj.exists() {
10 return Ok(Vec::new());
11 }
12 let s = std::fs::read_to_string(&pj).map_err(|e| e.to_string())?;
13 let v: serde_json::Value = serde_json::from_str(&s).map_err(|e| e.to_string())?;
14 let workspaces = v
15 .get("workspaces")
16 .and_then(|w| {
17 if w.is_array() {
18 w.as_array().map(|a| a.to_vec())
19 } else {
20 w.get("packages").and_then(|p| p.as_array()).map(|a| a.to_vec())
21 }
22 })
23 .unwrap_or_default();
24 let mut roots = Vec::new();
25 for pattern in workspaces {
26 let pattern = pattern.as_str().unwrap_or("").trim();
27 if pattern.is_empty() {
28 continue;
29 }
30 if pattern.contains('*') {
31 let (prefix, _) = pattern.split_once('*').unwrap_or((pattern, ""));
32 let prefix = prefix.trim_end_matches('/');
33 let base = dir.join(prefix);
34 if base.exists() {
35 if let Ok(entries) = std::fs::read_dir(&base) {
36 for e in entries.flatten() {
37 let path = e.path();
38 if path.is_dir() && path.join("package.json").exists() {
39 roots.push(path.strip_prefix(dir).unwrap_or(&path).to_path_buf());
40 }
41 }
42 }
43 }
44 } else if dir.join(pattern).join("package.json").exists() {
45 roots.push(PathBuf::from(pattern));
46 }
47 }
48 roots.sort();
49 roots.dedup();
50 Ok(roots)
51}