use std::collections::HashSet;
use std::path::{Path, PathBuf};
use crate::error::Result;
use crate::fs::Fs;
pub fn build_node_module_import_paths(main_module_path: &Path) -> Vec<PathBuf> {
let mut paths = Vec::new();
let mut current = main_module_path.to_path_buf();
paths.push(current.clone());
while current.pop() {
paths.push(current.clone());
}
paths
}
pub async fn build_node_module_paths(
fs: &dyn Fs,
import_paths: &[PathBuf],
) -> Result<Vec<PathBuf>> {
let mut result = Vec::new();
let mut visited = HashSet::new();
for path in import_paths {
build_node_module_paths_inner(fs, path, &mut result, &mut visited).await?;
}
Ok(result)
}
async fn build_node_module_paths_inner(
fs: &dyn Fs,
path: &Path,
result: &mut Vec<PathBuf>,
visited: &mut HashSet<PathBuf>,
) -> Result<()> {
let canonical = match fs.canonicalize(path).await {
Ok(p) => p,
Err(_) => return Ok(()),
};
if visited.contains(&canonical) {
return Ok(());
}
visited.insert(canonical.clone());
let has_package_json = fs.is_file(&canonical.join("package.json")).await;
let node_modules_dir = canonical.join("node_modules");
let has_node_modules = fs.is_dir(&node_modules_dir).await;
if has_package_json || has_node_modules {
result.push(canonical.clone());
if has_node_modules {
if let Ok(entries) = fs.read_dir(&node_modules_dir).await {
for entry in entries {
if entry.name.starts_with('.') {
continue;
}
if entry.name.starts_with('@') {
if let Ok(scoped_entries) = fs.read_dir(&entry.path).await {
for scoped_entry in scoped_entries {
Box::pin(build_node_module_paths_inner(
fs,
&scoped_entry.path,
result,
visited,
))
.await?;
}
}
} else {
Box::pin(build_node_module_paths_inner(
fs,
&entry.path,
result,
visited,
))
.await?;
}
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_import_paths() {
let paths = build_node_module_import_paths(Path::new("/a/b/c"));
assert_eq!(
paths,
vec![
PathBuf::from("/a/b/c"),
PathBuf::from("/a/b"),
PathBuf::from("/a"),
PathBuf::from("/"),
]
);
}
}