components_rs/discovery/
node_modules.rs1use std::collections::HashSet;
2use std::path::{Path, PathBuf};
3
4use crate::error::Result;
5use crate::fs::Fs;
6
7pub fn build_node_module_import_paths(main_module_path: &Path) -> Vec<PathBuf> {
11 let mut paths = Vec::new();
12 let mut current = main_module_path.to_path_buf();
13 paths.push(current.clone());
14 while current.pop() {
15 paths.push(current.clone());
16 }
17 paths
18}
19
20pub async fn build_node_module_paths(
23 fs: &dyn Fs,
24 import_paths: &[PathBuf],
25) -> Result<Vec<PathBuf>> {
26 let mut result = Vec::new();
27 let mut visited = HashSet::new();
28 for path in import_paths {
29 build_node_module_paths_inner(fs, path, &mut result, &mut visited).await?;
30 }
31 Ok(result)
32}
33
34async fn build_node_module_paths_inner(
35 fs: &dyn Fs,
36 path: &Path,
37 result: &mut Vec<PathBuf>,
38 visited: &mut HashSet<PathBuf>,
39) -> Result<()> {
40 let canonical = match fs.canonicalize(path).await {
41 Ok(p) => p,
42 Err(_) => return Ok(()),
43 };
44
45 if visited.contains(&canonical) {
46 return Ok(());
47 }
48 visited.insert(canonical.clone());
49
50 let has_package_json = fs.is_file(&canonical.join("package.json")).await;
51 let node_modules_dir = canonical.join("node_modules");
52 let has_node_modules = fs.is_dir(&node_modules_dir).await;
53
54 if has_package_json || has_node_modules {
55 result.push(canonical.clone());
56
57 if has_node_modules {
58 if let Ok(entries) = fs.read_dir(&node_modules_dir).await {
59 for entry in entries {
60 if entry.name.starts_with('.') {
61 continue;
62 }
63
64 if entry.name.starts_with('@') {
65 if let Ok(scoped_entries) = fs.read_dir(&entry.path).await {
67 for scoped_entry in scoped_entries {
68 Box::pin(build_node_module_paths_inner(
69 fs,
70 &scoped_entry.path,
71 result,
72 visited,
73 ))
74 .await?;
75 }
76 }
77 } else {
78 Box::pin(build_node_module_paths_inner(
79 fs,
80 &entry.path,
81 result,
82 visited,
83 ))
84 .await?;
85 }
86 }
87 }
88 }
89 }
90
91 Ok(())
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_build_import_paths() {
100 let paths = build_node_module_import_paths(Path::new("/a/b/c"));
101 assert_eq!(
102 paths,
103 vec![
104 PathBuf::from("/a/b/c"),
105 PathBuf::from("/a/b"),
106 PathBuf::from("/a"),
107 PathBuf::from("/"),
108 ]
109 );
110 }
111}