Skip to main content

jhol_core/
workspaces.rs

1//! Workspace detection and listing (package.json "workspaces" field).
2
3use std::path::{Path, PathBuf};
4
5/// List workspace package roots from package.json in dir. Returns paths relative to dir.
6/// Supports "workspaces": ["packages/*"] or "workspaces": { "packages": ["packages/*"] }.
7pub 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}