cuenv_workspaces/
resolver.rs1use crate::core::traits::{DependencyGraph, DependencyResolver};
8use crate::core::types::{DependencyRef, LockfileEntry, PackageManager, Workspace};
9use crate::discovery::read_json_file;
10use crate::error::Result;
11use petgraph::graph::NodeIndex;
12use serde::Deserialize;
13use std::collections::HashMap;
14use std::path::Path;
15
16#[cfg(feature = "toml")]
17use crate::discovery::read_toml_file;
18
19pub struct GenericDependencyResolver;
25
26impl DependencyResolver for GenericDependencyResolver {
27 fn resolve_dependencies(
28 &self,
29 _workspace: &Workspace,
30 lockfile: &[LockfileEntry],
31 ) -> Result<DependencyGraph> {
32 let mut graph = DependencyGraph::new();
33 let mut node_map: HashMap<(String, String), NodeIndex> = HashMap::new();
35
36 for entry in lockfile {
38 let dep_ref = DependencyRef {
39 name: entry.name.clone(),
40 version_req: entry.version.clone(),
41 };
42 let idx = graph.add_node(dep_ref);
43 node_map.insert((entry.name.clone(), entry.version.clone()), idx);
44 }
45
46 for entry in lockfile {
48 if let Some(&source_idx) = node_map.get(&(entry.name.clone(), entry.version.clone())) {
49 for dep in &entry.dependencies {
50 if let Some(&target_idx) =
58 node_map.get(&(dep.name.clone(), dep.version_req.clone()))
59 {
60 graph.add_edge(source_idx, target_idx, ());
61 } else {
62 tracing::trace!(
67 "Could not find exact match for dependency {} {} -> {} {}",
68 entry.name,
69 entry.version,
70 dep.name,
71 dep.version_req
72 );
73 }
74 }
75 }
76 }
77
78 Ok(graph)
79 }
80
81 fn resolve_workspace_deps(&self, workspace: &Workspace) -> Result<Vec<DependencyRef>> {
82 let mut workspace_deps = Vec::new();
83
84 for member in &workspace.members {
85 let deps = match workspace.manager {
86 PackageManager::Npm
87 | PackageManager::Bun
88 | PackageManager::Pnpm
89 | PackageManager::YarnClassic
90 | PackageManager::YarnModern
91 | PackageManager::Deno => self.parse_js_deps(&member.manifest_path)?,
92 PackageManager::Cargo => Self::parse_rust_deps(&member.manifest_path)?,
93 };
94
95 workspace_deps.extend(deps);
96 }
97
98 Ok(workspace_deps)
99 }
100
101 fn resolve_external_deps(&self, lockfile: &[LockfileEntry]) -> Result<Vec<DependencyRef>> {
102 Ok(lockfile
103 .iter()
104 .filter(|entry| !entry.is_workspace_member)
105 .map(|entry| DependencyRef {
106 name: entry.name.clone(),
107 version_req: entry.version.clone(),
108 })
109 .collect())
110 }
111
112 fn detect_workspace_protocol(&self, spec: &str) -> bool {
113 spec.starts_with("workspace:") || spec == "workspace"
116 }
117}
118
119impl GenericDependencyResolver {
120 fn parse_js_deps(&self, path: &Path) -> Result<Vec<DependencyRef>> {
121 #[derive(Deserialize)]
122 struct PackageJsonDeps {
123 dependencies: Option<HashMap<String, String>>,
124 #[serde(rename = "devDependencies")]
125 dev_dependencies: Option<HashMap<String, String>>,
126 }
127
128 let pkg: PackageJsonDeps = read_json_file(path)?;
129 let mut result = Vec::new();
130
131 let mut add_deps = |deps: HashMap<String, String>| {
132 for (name, version) in deps {
133 if self.detect_workspace_protocol(&version) {
134 result.push(DependencyRef {
135 name,
136 version_req: version,
137 });
138 }
139 }
140 };
141
142 if let Some(deps) = pkg.dependencies {
143 add_deps(deps);
144 }
145 if let Some(deps) = pkg.dev_dependencies {
146 add_deps(deps);
147 }
148
149 Ok(result)
150 }
151
152 fn parse_rust_deps(path: &Path) -> Result<Vec<DependencyRef>> {
153 #[cfg(feature = "toml")]
154 {
155 #[derive(Deserialize)]
156 struct CargoTomlDeps {
157 dependencies: Option<HashMap<String, toml::Value>>,
158 #[serde(rename = "dev-dependencies")]
159 dev_dependencies: Option<HashMap<String, toml::Value>>,
160 }
161
162 let pkg: CargoTomlDeps = read_toml_file(path)?;
165 let mut result = Vec::new();
166
167 let mut add_deps = |deps: HashMap<String, toml::Value>| {
168 for (name, value) in deps {
169 let is_workspace = if let toml::Value::Table(t) = &value {
170 t.get("workspace")
171 .and_then(toml::Value::as_bool)
172 .unwrap_or(false)
173 } else {
174 false
175 };
176
177 if is_workspace {
178 result.push(DependencyRef {
179 name,
180 version_req: "workspace".to_string(),
181 });
182 }
183 }
184 };
185
186 if let Some(deps) = pkg.dependencies {
187 add_deps(deps);
188 }
189 if let Some(deps) = pkg.dev_dependencies {
190 add_deps(deps);
191 }
192
193 Ok(result)
194 }
195 #[cfg(not(feature = "toml"))]
196 {
197 let _ = path;
199 Ok(Vec::new())
200 }
201 }
202}