1use std::collections::{HashMap, HashSet};
4use std::path::Path;
5use std::sync::{Arc, Mutex};
6
7use crate::lockfile;
8use crate::registry;
9
10#[derive(Clone, Debug)]
12pub struct ResolvedPackage {
13 pub version: String,
14 pub resolved: String,
15 pub integrity: Option<String>,
16 pub dependencies: HashMap<String, String>,
17 pub peer_dependencies: HashMap<String, String>,
18 pub peer_dependencies_meta: HashMap<String, serde_json::Value>,
19}
20
21#[derive(Clone, Debug)]
22struct PeerRequirement {
23 requester: String,
24 spec: String,
25 optional: bool,
26}
27
28#[derive(Clone, Debug)]
29struct Requirement {
30 requester: String,
31 spec: String,
32}
33
34pub fn resolve_full_tree(package_json_path: &Path) -> Result<HashMap<String, ResolvedPackage>, String> {
38 let deps = lockfile::read_package_json_deps(package_json_path)
39 .ok_or("Could not read package.json dependencies.")?;
40 if deps.is_empty() {
41 return Ok(HashMap::new());
42 }
43
44 let direct_names: Vec<String> = deps.keys().cloned().collect();
45 let cache_arc = Arc::new(Mutex::new(HashMap::<String, serde_json::Value>::new()));
46 let results = registry::parallel_fetch_metadata(&direct_names, &cache_arc);
47 for (name, res) in results {
48 if let Err(e) = res {
49 return Err(format!("Failed to fetch metadata for {}: {}", name, e));
50 }
51 }
52
53 let mut tree: HashMap<String, ResolvedPackage> = HashMap::new();
54 let mut seen: HashSet<(String, String)> = HashSet::new();
55 let mut requirements: HashMap<String, Vec<Requirement>> = HashMap::new();
56 let mut peer_requirements: HashMap<String, Vec<PeerRequirement>> = HashMap::new();
57
58 let mut queue: Vec<(String, String, String, String)> = deps
59 .iter()
60 .map(|(name, spec)| {
61 requirements
62 .entry(name.clone())
63 .or_default()
64 .push(Requirement {
65 requester: "root".to_string(),
66 spec: spec.clone(),
67 });
68 (
69 format!("node_modules/{}", name),
70 name.clone(),
71 spec.clone(),
72 "root".to_string(),
73 )
74 })
75 .collect();
76
77 let mut conflicts: Vec<String> = Vec::new();
78 while let Some((key, name, spec, requester)) = queue.pop() {
79 let meta = {
80 let mut cache = cache_arc.lock().unwrap();
81 registry::fetch_metadata_cached(&name, &mut *cache)?
82 };
83
84 requirements
85 .entry(name.clone())
86 .or_default()
87 .push(Requirement { requester, spec });
88
89 let combined_specs = requirements
90 .get(&name)
91 .map(|reqs| reqs.iter().map(|r| r.spec.clone()).collect::<Vec<_>>())
92 .unwrap_or_default();
93
94 let version = resolve_highest_satisfying(&meta, &combined_specs).ok_or_else(|| {
95 let refs = requirements
96 .get(&name)
97 .map(|reqs| {
98 reqs.iter()
99 .map(|r| format!("{} -> {}", r.requester, r.spec))
100 .collect::<Vec<_>>()
101 .join(", ")
102 })
103 .unwrap_or_default();
104 format!("Dependency conflict for {} (no version satisfies all): {}", name, refs)
105 })?;
106
107 if let Some(existing) = tree.get(&key) {
108 if existing.version == version {
109 continue;
110 }
111 let combined_specs_str = combined_specs.join(", ");
112 conflicts.push(format!(
113 "{}: existing {} vs {} (specs: {})",
114 name, existing.version, version, combined_specs_str
115 ));
116 continue;
117 }
118 if seen.contains(&(name.clone(), version.clone())) {
119 continue;
120 }
121 seen.insert((name.clone(), version.clone()));
122
123 let resolved_url = registry::get_tarball_url(&meta, &version)
124 .ok_or_else(|| format!("No tarball URL for {}@{}", name, version))?;
125 let integrity = registry::get_integrity_for_version(&meta, &version);
126
127 let version_deps = registry::get_version_dependencies(&meta, &version);
128 let peer_deps = registry::get_version_peer_dependencies(&meta, &version);
129 let peer_deps_meta = registry::get_version_peer_dependencies_meta(&meta, &version);
130 let mut resolved_deps = HashMap::new();
131 for (dep_name, dep_spec) in &version_deps {
132 let dep_meta = {
133 let mut cache = cache_arc.lock().unwrap();
134 match registry::fetch_metadata_cached(dep_name, &mut *cache) {
135 Ok(m) => m,
136 Err(_) => continue,
137 }
138 };
139 requirements
140 .entry(dep_name.clone())
141 .or_default()
142 .push(Requirement {
143 requester: name.clone(),
144 spec: dep_spec.clone(),
145 });
146 if let Some(dep_version) = resolve_highest_satisfying(&dep_meta, &[dep_spec.clone()]) {
147 resolved_deps.insert(dep_name.clone(), dep_version.clone());
148 let dep_key = format!("node_modules/{}", dep_name);
149 if !seen.contains(&(dep_name.clone(), dep_version)) {
150 queue.push((dep_key, dep_name.clone(), dep_spec.clone(), name.clone()));
151 }
152 }
153 }
154
155 for (peer_name, peer_spec) in &peer_deps {
156 let optional = peer_deps_meta
157 .get(peer_name)
158 .and_then(|v| v.get("optional"))
159 .and_then(|v| v.as_bool())
160 .unwrap_or(false);
161 peer_requirements
162 .entry(peer_name.clone())
163 .or_default()
164 .push(PeerRequirement {
165 requester: name.clone(),
166 spec: peer_spec.clone(),
167 optional,
168 });
169 }
170
171 tree.insert(
172 key,
173 ResolvedPackage {
174 version,
175 resolved: resolved_url,
176 integrity,
177 dependencies: resolved_deps,
178 peer_dependencies: peer_deps,
179 peer_dependencies_meta: peer_deps_meta,
180 },
181 );
182 }
183
184 let mut peer_conflicts: Vec<String> = Vec::new();
185 for (peer_name, reqs) in &peer_requirements {
186 let resolved_version = tree
187 .get(&format!("node_modules/{}", peer_name))
188 .map(|p| p.version.clone());
189 if let Some(resolved) = resolved_version {
190 for req in reqs {
191 if !registry::version_satisfies(&req.spec, &resolved) {
192 peer_conflicts.push(format!(
193 "peer {} required by {} but resolved {} (spec {})",
194 peer_name, req.requester, resolved, req.spec
195 ));
196 }
197 }
198 } else {
199 let required_reqs: Vec<&PeerRequirement> = reqs.iter().filter(|r| !r.optional).collect();
200 if required_reqs.is_empty() {
201 continue;
202 }
203 let req_list = required_reqs
204 .iter()
205 .map(|r| format!("{} -> {}", r.requester, r.spec))
206 .collect::<Vec<_>>()
207 .join(", ");
208 peer_conflicts.push(format!("peer {} missing (requirements: {})", peer_name, req_list));
209 }
210 }
211
212 if !conflicts.is_empty() || !peer_conflicts.is_empty() {
213 let mut all = Vec::new();
214 all.extend(conflicts);
215 all.extend(peer_conflicts);
216 return Err(format!(
217 "Dependency conflict: {}. Consider updating dependencies or using a single version.",
218 all.join("; ")
219 ));
220 }
221 Ok(tree)
222}
223
224fn resolve_highest_satisfying(meta: &serde_json::Value, specs: &[String]) -> Option<String> {
225 let versions = meta.get("versions")?.as_object()?;
226 let mut parsed: Vec<semver::Version> = versions
227 .keys()
228 .filter_map(|v| semver::Version::parse(v).ok())
229 .collect();
230 parsed.sort();
231 parsed.reverse();
232 for ver in parsed {
233 let ver_str = ver.to_string();
234 if specs.iter().all(|s| registry::version_satisfies(s, &ver_str)) {
235 return Some(ver_str);
236 }
237 }
238 None
239}
240
241fn read_root_package_info(path: &Path) -> Result<(String, String), String> {
243 let s = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
244 let v: serde_json::Value = serde_json::from_str(&s).map_err(|e| e.to_string())?;
245 let name = v
246 .get("name")
247 .and_then(|n| n.as_str())
248 .unwrap_or("")
249 .to_string();
250 let version = v
251 .get("version")
252 .and_then(|v| v.as_str())
253 .unwrap_or("0.0.0")
254 .to_string();
255 Ok((name, version))
256}
257
258fn build_packages_json(
260 root_name: &str,
261 root_version: &str,
262 direct_dep_names: &[String],
263 tree: &HashMap<String, ResolvedPackage>,
264) -> serde_json::Value {
265 let mut packages = serde_json::Map::new();
266
267 let mut root_deps = serde_json::Map::new();
268 for name in direct_dep_names {
269 let key = format!("node_modules/{}", name);
270 if let Some(pkg) = tree.get(&key) {
271 root_deps.insert(name.clone(), serde_json::Value::String(pkg.version.clone()));
272 }
273 }
274 packages.insert(
275 "".to_string(),
276 serde_json::json!({
277 "name": root_name,
278 "version": root_version,
279 "dependencies": root_deps,
280 }),
281 );
282
283 for (key, pkg) in tree {
284 let mut entry = serde_json::Map::new();
285 entry.insert("version".to_string(), serde_json::Value::String(pkg.version.clone()));
286 entry.insert("resolved".to_string(), serde_json::Value::String(pkg.resolved.clone()));
287 if let Some(ref i) = pkg.integrity {
288 entry.insert("integrity".to_string(), serde_json::Value::String(i.clone()));
289 }
290 let deps: serde_json::Map<String, serde_json::Value> = pkg
291 .dependencies
292 .iter()
293 .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
294 .collect();
295 let peer_deps: serde_json::Map<String, serde_json::Value> = pkg
296 .peer_dependencies
297 .iter()
298 .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
299 .collect();
300 let peer_deps_meta: serde_json::Map<String, serde_json::Value> = pkg
301 .peer_dependencies_meta
302 .iter()
303 .map(|(k, v)| (k.clone(), v.clone()))
304 .collect();
305 entry.insert("dependencies".to_string(), serde_json::Value::Object(deps));
306 entry.insert("requires".to_string(), serde_json::Value::Bool(!pkg.dependencies.is_empty()));
307 if !peer_deps.is_empty() {
308 entry.insert("peerDependencies".to_string(), serde_json::Value::Object(peer_deps));
309 }
310 if !peer_deps_meta.is_empty() {
311 entry.insert("peerDependenciesMeta".to_string(), serde_json::Value::Object(peer_deps_meta));
312 }
313 packages.insert(key.clone(), serde_json::Value::Object(entry));
314 }
315
316 serde_json::Value::Object(packages)
317}
318
319pub fn write_package_lock(
321 lock_path: &Path,
322 package_json_path: &Path,
323 tree: &HashMap<String, ResolvedPackage>,
324) -> Result<(), String> {
325 let (root_name, root_version) = read_root_package_info(package_json_path)?;
326 let deps = lockfile::read_package_json_deps(package_json_path).unwrap_or_default();
327 let direct_dep_names: Vec<String> = deps.keys().cloned().collect();
328
329 let packages = build_packages_json(&root_name, &root_version, &direct_dep_names, tree);
330
331 let lockfile_content = serde_json::json!({
332 "name": root_name,
333 "version": root_version,
334 "lockfileVersion": 3,
335 "packages": packages,
336 });
337
338 let pretty = serde_json::to_string_pretty(&lockfile_content).map_err(|e| e.to_string())?;
339 std::fs::write(lock_path, pretty).map_err(|e| e.to_string())?;
340 Ok(())
341}