cuenv_workspaces/discovery/
pnpm_workspace.rs1use crate::core::traits::WorkspaceDiscovery;
4use crate::core::types::{PackageManager, Workspace, WorkspaceMember};
5use crate::discovery::{read_json_file, read_yaml_file, resolve_glob_patterns};
6use crate::error::{Error, Result};
7use serde::Deserialize;
8use std::collections::HashMap;
9use std::path::Path;
10
11pub struct PnpmWorkspaceDiscovery;
13
14impl WorkspaceDiscovery for PnpmWorkspaceDiscovery {
15 fn discover(&self, root: &Path) -> Result<Workspace> {
16 let workspace_yaml_path = root.join("pnpm-workspace.yaml");
17 if !workspace_yaml_path.exists() {
18 return Err(Error::WorkspaceNotFound {
19 path: root.to_path_buf(),
20 });
21 }
22
23 let _: PnpmWorkspace = read_yaml_file(&workspace_yaml_path)?;
25
26 let members = self.find_members(root)?;
27
28 let mut workspace = Workspace::new(root.to_path_buf(), PackageManager::Pnpm);
29 workspace.members = members;
30
31 let lockfile = root.join("pnpm-lock.yaml");
32 if lockfile.exists() {
33 workspace.lockfile = Some(lockfile);
34 }
35
36 Ok(workspace)
37 }
38
39 fn find_members(&self, root: &Path) -> Result<Vec<WorkspaceMember>> {
40 let workspace_yaml_path = root.join("pnpm-workspace.yaml");
41 let workspace_config: PnpmWorkspace = read_yaml_file(&workspace_yaml_path)?;
42
43 let matched_paths = resolve_glob_patterns(root, &workspace_config.packages, &[])?;
44 let mut members = Vec::new();
45
46 for path in matched_paths {
47 if self.validate_member(&path)? {
48 let manifest_path = path.join("package.json");
49 let member_pkg: PackageJson = read_json_file(&manifest_path)?;
50
51 if let Some(name) = member_pkg.name {
52 let mut dependencies = Vec::new();
53 if let Some(deps) = member_pkg.dependencies {
54 dependencies.extend(deps.keys().cloned());
55 }
56 if let Some(dev_deps) = member_pkg.dev_dependencies {
57 dependencies.extend(dev_deps.keys().cloned());
58 }
59 if let Some(peer_deps) = member_pkg.peer_dependencies {
60 dependencies.extend(peer_deps.keys().cloned());
61 }
62
63 members.push(WorkspaceMember {
64 name,
65 path: path.strip_prefix(root).unwrap_or(&path).to_path_buf(),
66 manifest_path,
67 dependencies,
68 });
69 }
70 }
71 }
72
73 members.sort_by(|a, b| a.name.cmp(&b.name));
74 Ok(members)
75 }
76
77 fn validate_member(&self, member_path: &Path) -> Result<bool> {
90 let manifest_path = member_path.join("package.json");
91 if !manifest_path.exists() {
92 return Ok(false);
93 }
94
95 match read_json_file::<PackageJson>(&manifest_path) {
97 Ok(pkg) => {
98 if pkg.name.is_some() {
99 Ok(true)
100 } else {
101 Ok(false)
102 }
103 }
104 Err(Error::Json { .. }) => Ok(false), Err(e) => Err(e), }
107 }
108}
109
110#[derive(Deserialize)]
111struct PnpmWorkspace {
112 packages: Vec<String>,
113}
114
115#[derive(Deserialize)]
116#[serde(rename_all = "camelCase")]
117struct PackageJson {
118 name: Option<String>,
119 dependencies: Option<HashMap<String, String>>,
120 dev_dependencies: Option<HashMap<String, String>>,
121 peer_dependencies: Option<HashMap<String, String>>,
122}