1use std::{
2 collections::VecDeque,
3 ffi::OsStr,
4 ops::{Index, IndexMut},
5 path::Path,
6};
7
8use indexmap::IndexMap;
9use kdl::KdlDocument;
10use nassun::{package::Package, PackageResolution, PackageSpec};
11use oro_common::CorgiManifest;
12use petgraph::stable_graph::{EdgeIndex, NodeIndex, StableGraph};
13#[cfg(not(target_arch = "wasm32"))]
14use petgraph::Direction;
15use unicase::UniCase;
16
17use crate::{error::NodeMaintainerError, Lockfile, LockfileNode};
18
19#[cfg(debug_assertions)]
20use NodeMaintainerError::GraphValidationError;
21
22#[derive(Debug, Hash, PartialEq, Eq)]
23pub(crate) struct DemotionTarget {
24 pub(crate) target_idx: NodeIndex,
26
27 pub(crate) dependent_idx: NodeIndex,
29
30 pub(crate) edge_idx: EdgeIndex,
32}
33
34#[derive(Debug, Clone)]
35pub struct Node {
36 pub(crate) idx: NodeIndex,
38 pub(crate) name: UniCase<String>,
40 pub(crate) package: Package,
42 pub(crate) root: NodeIndex,
44 pub(crate) dependencies: IndexMap<UniCase<String>, EdgeIndex>,
46 pub(crate) dependency_reqs: IndexMap<UniCase<String>, (PackageSpec, DepType)>,
48 pub(crate) parent: Option<NodeIndex>,
50 pub(crate) children: IndexMap<UniCase<String>, NodeIndex>,
54}
55
56impl Node {
57 pub(crate) fn new(
58 name: UniCase<String>,
59 package: Package,
60 manifest: CorgiManifest,
61 is_root: bool,
62 ) -> Result<Self, NodeMaintainerError> {
63 let deps = manifest
64 .dependencies
65 .iter()
66 .map(|x| (x, DepType::Prod))
67 .chain(
68 manifest
69 .optional_dependencies
70 .iter()
71 .map(|x| (x, DepType::Opt)),
72 );
80
81 let deps: Box<dyn Iterator<Item = ((&String, &String), DepType)> + Send> = if is_root {
82 Box::new(deps.chain(manifest.dev_dependencies.iter().map(|x| (x, DepType::Dev))))
83 } else {
84 Box::new(deps)
85 };
86 let mut dependency_reqs = IndexMap::new();
87 for ((name, spec), dep_type) in deps {
88 dependency_reqs.insert(
89 UniCase::new(name.clone()),
90 (format!("{name}@{spec}").parse()?, dep_type),
91 );
92 }
93 Ok(Self {
94 package,
95 name,
96 idx: NodeIndex::new(0),
97 root: NodeIndex::new(0),
98 parent: None,
99 children: IndexMap::new(),
100 dependencies: IndexMap::new(),
101 dependency_reqs,
102 })
103 }
104
105 pub(crate) fn depth(&self, graph: &Graph) -> usize {
107 graph.node_parent_iter(self.idx).count() - 1
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
112pub enum DepType {
113 Prod,
114 Dev,
115 Peer,
116 Opt,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct Edge {
121 pub(crate) requested: PackageSpec,
122 pub(crate) dep_type: DepType,
123}
124
125impl Edge {
126 pub(crate) fn new(requested: PackageSpec, dep_type: DepType) -> Self {
127 Self {
128 requested,
129 dep_type,
130 }
131 }
132}
133
134#[derive(Debug, Default)]
135pub(crate) struct Graph {
136 pub(crate) root: NodeIndex,
137 pub(crate) inner: StableGraph<Node, Edge>,
138}
139
140impl Index<NodeIndex> for Graph {
141 type Output = Node;
142
143 fn index(&self, index: NodeIndex) -> &Self::Output {
144 &self.inner[index]
145 }
146}
147
148impl IndexMut<NodeIndex> for Graph {
149 fn index_mut(&mut self, index: NodeIndex) -> &mut Self::Output {
150 &mut self.inner[index]
151 }
152}
153
154impl Index<EdgeIndex> for Graph {
155 type Output = Edge;
156
157 fn index(&self, index: EdgeIndex) -> &Self::Output {
158 &self.inner[index]
159 }
160}
161
162impl IndexMut<EdgeIndex> for Graph {
163 fn index_mut(&mut self, index: EdgeIndex) -> &mut Self::Output {
164 &mut self.inner[index]
165 }
166}
167
168impl Graph {
169 #[cfg(not(target_arch = "wasm32"))]
170 pub fn is_optional(&self, node: NodeIndex) -> bool {
171 for edge_ref in self.inner.edges_directed(node, Direction::Incoming) {
172 if edge_ref.weight().dep_type != DepType::Opt {
173 return false;
174 }
175 }
176 true
177 }
178
179 pub fn resolve_dep(&self, node: NodeIndex, dep: &UniCase<String>) -> Option<NodeIndex> {
180 for parent in self.node_parent_iter(node) {
181 if let Some(resolved) = parent.children.get(dep) {
182 return Some(*resolved);
183 }
184 }
185 None
186 }
187
188 pub fn is_ancestor(&self, ancestor: NodeIndex, descendant: NodeIndex) -> bool {
189 self.node_parent_iter(descendant)
190 .any(|parent| parent.idx == ancestor)
191 }
192
193 pub fn to_lockfile(&self) -> Result<Lockfile, NodeMaintainerError> {
194 let root = self.node_lockfile_node(self.root, true)?;
195 let packages = self
196 .inner
197 .node_indices()
198 .filter(|idx| *idx != self.root)
199 .map(|idx| {
200 let node = self.node_lockfile_node(idx, false)?;
201 Ok((
202 UniCase::from(
203 node.path
204 .iter()
205 .map(|x| x.to_string())
206 .collect::<Vec<_>>()
207 .join("/node_modules/"),
208 ),
209 node,
210 ))
211 })
212 .collect::<Result<IndexMap<_, _>, NodeMaintainerError>>()?;
213 Ok(Lockfile {
214 version: 1,
215 root,
216 packages,
217 })
218 }
219
220 pub fn to_kdl(&self) -> Result<KdlDocument, NodeMaintainerError> {
221 Ok(self.to_lockfile()?.to_kdl())
222 }
223
224 pub(crate) fn node_parent_iter(&self, idx: NodeIndex) -> NodeParentIterator {
225 NodeParentIterator {
226 graph: self,
227 current: Some(idx),
228 }
229 }
230
231 pub(crate) fn node_at_path(&self, path: &Path) -> Option<&Node> {
232 let mut current = Some(self.root);
233 let mut in_nm = true;
234 let mut scope = None;
235 let slash = OsStr::new("/");
236 let backslash = OsStr::new("\\");
237 let nm = UniCase::new("node_modules".to_owned());
238 for raw_segment in path {
239 let str_segment = raw_segment.to_string_lossy().to_string();
240 let segment = UniCase::new(str_segment.clone());
241 if (segment == nm && scope.is_none())
242 || slash == raw_segment
243 || backslash == raw_segment
244 {
245 in_nm = true;
246 continue;
247 } else if let Some(curr_idx) = current {
248 if !in_nm {
249 break;
250 } else if segment.starts_with('@') {
251 scope = Some(segment.to_string());
252 } else if let Some(curr_scope) = scope.as_deref() {
253 let scoped_seg = UniCase::new(format!("{curr_scope}/{segment}"));
254 if let Some(child) = self.inner[curr_idx].children.get(&scoped_seg) {
255 current = Some(*child);
256 }
257 in_nm = false;
258 scope = None;
259 } else if let Some(child) = self.inner[curr_idx].children.get(&segment) {
260 current = Some(*child);
261 in_nm = false;
262 } else {
263 break;
264 }
265 } else {
266 break;
267 }
268 }
269 if current == Some(self.root) {
270 None
271 } else {
272 current.map(|idx| &self.inner[idx])
273 }
274 }
275
276 pub(crate) fn package_at_path(&self, path: &Path) -> Option<Package> {
277 Some(self.node_at_path(path)?.package.clone())
278 }
279
280 pub(crate) fn find_by_name(
281 &self,
282 parent: NodeIndex,
283 name: &UniCase<String>,
284 ) -> Result<Option<NodeIndex>, NodeMaintainerError> {
285 Ok(self.node_parent_iter(parent).find_map(|node| {
286 if node.children.contains_key(name) {
287 Some(node.children[name])
288 } else {
289 None
290 }
291 }))
292 }
293
294 pub(crate) fn node_path(&self, node_idx: NodeIndex) -> VecDeque<UniCase<String>> {
295 let node = &self.inner[node_idx];
296 let mut path = VecDeque::new();
297 if node_idx != self.root {
298 path.push_front(node.name.clone());
299 let mut parent = node.parent;
300 while let Some(parent_idx) = parent {
301 if parent_idx == self.root {
302 break;
303 }
304 path.push_front(self.inner[parent_idx].name.clone());
305 parent = self.inner[parent_idx].parent;
306 }
307 };
308 path
309 }
310
311 pub(crate) fn node_path_string(&self, node_idx: NodeIndex) -> String {
312 self.node_path(node_idx)
313 .iter()
314 .map(|x| x.to_string())
315 .collect::<Vec<_>>()
316 .join("/node_modules/")
317 }
318
319 #[cfg(debug_assertions)]
322 pub(crate) fn validate(&self) -> Result<(), NodeMaintainerError> {
323 let mut q = VecDeque::new();
325 q.push_back(self.root);
326 while let Some(node) = q.pop_front() {
327 if !self.inner.contains_node(node) {
328 return Err(GraphValidationError(format!(
329 "Missing node in the graph for: {node:?}"
330 )));
331 }
332
333 q.extend(self.inner[node].children.values());
334 }
335
336 for dependent in self.inner.node_weights() {
338 for (dep_name, edge_idx) in &dependent.dependencies {
339 let edge = &self.inner[*edge_idx];
340
341 if let Some(dep_idx) = self.resolve_dep(dependent.idx, dep_name) {
342 let dependency = &self.inner[dep_idx];
343
344 if !dependency.package.resolved().satisfies(&edge.requested)? {
345 return Err(GraphValidationError(format!(
346 "Dependency {:?} does not satisfy requirement {} from {:?}",
347 dependency.package.resolved(),
348 edge.requested,
349 dependent.package.resolved(),
350 )));
351 }
352 } else {
353 return Err(GraphValidationError(format!(
354 "Dependency {:?} {} not reachable from {:?}",
355 dep_name,
356 edge.requested,
357 dependent.package.resolved(),
358 )));
359 }
360 }
361 }
362
363 Ok(())
364 }
365
366 pub(crate) fn node_lockfile_node(
367 &self,
368 node: NodeIndex,
369 is_root: bool,
370 ) -> Result<LockfileNode, NodeMaintainerError> {
371 let path = self.node_path(node);
372 let node = &self.inner[node];
373 let resolved = match node.package.resolved() {
374 PackageResolution::Npm { tarball, .. } => tarball.to_string(),
375 PackageResolution::Dir { path, .. } => path.to_string_lossy().into(),
376 PackageResolution::Git { info, .. } => info.to_string(),
377 };
378 let version = if let PackageResolution::Npm { version, .. } = node.package.resolved() {
379 Some(version.clone())
380 } else {
381 None
382 };
383
384 let mut prod_deps = IndexMap::new();
385 let mut dev_deps = IndexMap::new();
386 let mut peer_deps = IndexMap::new();
387 let mut opt_deps = IndexMap::new();
388 let dependencies = node.dependencies.iter().map(|(name, edge_idx)| {
389 let edge = &self.inner[*edge_idx];
390 (name, &edge.requested, &edge.dep_type)
391 });
392 for (name, requested, dep_type) in dependencies {
393 use DepType::*;
394 let deps = match dep_type {
395 Prod => &mut prod_deps,
396 Dev => &mut dev_deps,
397 Peer => &mut peer_deps,
398 Opt => &mut opt_deps,
399 };
400 deps.insert(name.to_string(), requested.requested().clone());
401 }
402 Ok(LockfileNode {
403 name: UniCase::new(node.package.name().to_string()),
404 is_root,
405 path: path.into(),
406 resolved: Some(resolved),
407 version,
408 dependencies: prod_deps,
409 dev_dependencies: dev_deps,
410 peer_dependencies: peer_deps,
411 optional_dependencies: opt_deps,
412 integrity: match node.package.resolved() {
413 PackageResolution::Npm { ref integrity, .. } => integrity.clone(),
414 _ => None,
415 },
416 })
417 }
418}
419
420pub(crate) struct NodeParentIterator<'a> {
421 graph: &'a Graph,
422 current: Option<NodeIndex>,
423}
424
425impl<'a> Iterator for NodeParentIterator<'a> {
426 type Item = &'a Node;
427
428 fn next(&mut self) -> Option<Self::Item> {
429 if let Some(idx) = self.current {
430 let res = &self.graph[idx];
431 self.current = res.parent;
432 Some(res)
433 } else {
434 None
435 }
436 }
437}