use super::{NodePathKind, PathUtils, types::PathExt};
use crate::error::{Error, FileSystemError, FileSystemResult, Result};
use std::path::{Component, Path, PathBuf};
impl NodePathKind {
#[must_use]
pub fn default_path(self) -> &'static str {
match self {
Self::NodeModules => "node_modules",
Self::PackageJson => "package.json",
Self::Src => "src",
Self::Dist => "dist",
Self::Test => "test",
}
}
}
impl PathUtils {
#[must_use]
pub fn find_project_root(start: &Path) -> Option<PathBuf> {
let mut current = Some(start);
let lock_files = [
"package-lock.json", "npm-shrinkwrap.json", "yarn.lock", "pnpm-lock.yaml", "bun.lockb", "jsr.json", ];
while let Some(path) = current {
for lock_file in &lock_files {
if path.join(lock_file).exists() {
return Some(path.to_path_buf());
}
}
current = path.parent();
}
None
}
pub fn current_dir() -> FileSystemResult<PathBuf> {
std::env::current_dir().map_err(std::convert::Into::into)
}
pub fn make_relative(path: &Path, base: &Path) -> FileSystemResult<PathBuf> {
path.strip_prefix(base).map(std::path::Path::to_path_buf).map_err(|e| {
FileSystemError::Validation {
path: path.to_path_buf(),
reason: format!("Path is not a child of base path: {e}"),
}
})
}
}
impl PathExt for Path {
fn normalize(&self) -> PathBuf {
let mut components = Vec::new();
for component in self.components() {
match component {
Component::Prefix(_) | Component::RootDir => {
components.push(component);
}
Component::CurDir => {}
Component::ParentDir => {
components.pop();
}
Component::Normal(name) => {
components.push(Component::Normal(name));
}
}
}
components.into_iter().collect()
}
fn is_in_project(&self) -> bool {
let mut current = Some(self);
while let Some(path) = current {
if path.join("package.json").exists() {
return true;
}
current = path.parent();
}
false
}
fn relative_to_project(&self) -> Option<PathBuf> {
let mut current = Some(self);
while let Some(path) = current {
if path.join("package.json").exists() {
return self.strip_prefix(path).ok().map(std::path::Path::to_path_buf);
}
current = path.parent();
}
None
}
fn node_path(&self, kind: NodePathKind) -> PathBuf {
self.join(kind.default_path())
}
fn canonicalize(&self) -> Result<PathBuf> {
if self.is_symlink() {
return Path::canonicalize(self)
.map_err(|e| Error::FileSystem(FileSystemError::from_io(e, self)));
}
Ok(self.to_path_buf())
}
}