diff --git a/benches/resolver.rs b/benches/resolver.rs index ac56c0ba..f5aa12e5 100644 --- a/benches/resolver.rs +++ b/benches/resolver.rs @@ -716,5 +716,33 @@ mod memory_fs { )) }) } + + fn canonicalize(&self, path: &Path) -> io::Result { + // Follow symlinks to resolve the canonical path + let mut current = path.to_path_buf(); + let mut visited = FxHashSet::default(); + + while let Some(target) = self.symlinks.get(¤t) { + if !visited.insert(current.clone()) { + return Err(io::Error::other("Circular symlink")); + } + + current = if target.is_relative() { + current.parent().unwrap().join(target) + } else { + target.clone() + }; + } + + // Verify the final path exists + if self.files.contains_key(¤t) || self.directories.contains(¤t) { + Ok(current) + } else { + Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Path not found: {}", path.display()), + )) + } + } } } diff --git a/src/cache/cache_impl.rs b/src/cache/cache_impl.rs index 57d11518..4c5784ca 100644 --- a/src/cache/cache_impl.rs +++ b/src/cache/cache_impl.rs @@ -235,9 +235,16 @@ impl Cache { }); result.as_ref().map_err(Clone::clone).and_then(|weak| { - weak.upgrade().map(CachedPath).ok_or_else(|| { - io::Error::new(io::ErrorKind::NotFound, "Path no longer exists").into() - }) + weak.upgrade() + .map(CachedPath) + .or_else(|| { + // Cache was cleared while canonicalizing. Fall back to direct FS canonicalize + // without caching the result to ensure we still return the resolved path. + self.fs.canonicalize(path.path()).ok().map(|canonical| self.value(&canonical)) + }) + .ok_or_else(|| { + io::Error::new(io::ErrorKind::NotFound, "Path no longer exists").into() + }) }) } diff --git a/src/file_system.rs b/src/file_system.rs index 0fde005b..44043a59 100644 --- a/src/file_system.rs +++ b/src/file_system.rs @@ -64,6 +64,13 @@ pub trait FileSystem: Send + Sync { /// /// See [std::fs::read_link] fn read_link(&self, path: &Path) -> Result; + + /// Returns the canonical, absolute form of a path with all intermediate components normalized. + /// + /// # Errors + /// + /// See [std::fs::canonicalize] + fn canonicalize(&self, path: &Path) -> io::Result; } /// Metadata information about a file @@ -208,6 +215,14 @@ impl FileSystemOs { } } } + + /// # Errors + /// + /// See [std::fs::canonicalize] + #[inline] + pub fn canonicalize(path: &Path) -> io::Result { + fs::canonicalize(path) + } } impl FileSystem for FileSystemOs { @@ -281,6 +296,21 @@ impl FileSystem for FileSystemOs { } Self::read_link(path) } + + fn canonicalize(&self, path: &Path) -> io::Result { + cfg_if! { + if #[cfg(feature = "yarn_pnp")] { + if self.yarn_pnp { + return match VPath::from(path)? { + VPath::Zip(info) => Self::canonicalize(&info.physical_base_path().join(info.zip_path)), + VPath::Virtual(info) => Self::canonicalize(&info.physical_base_path()), + VPath::Native(path) => Self::canonicalize(&path), + } + } + } + } + Self::canonicalize(path) + } } #[test] diff --git a/src/tests/memory_fs.rs b/src/tests/memory_fs.rs index 6044d8a6..b0be28f6 100644 --- a/src/tests/memory_fs.rs +++ b/src/tests/memory_fs.rs @@ -86,4 +86,13 @@ impl FileSystem for MemoryFS { fn read_link(&self, _path: &Path) -> Result { Err(io::Error::new(io::ErrorKind::NotFound, "not a symlink").into()) } + + fn canonicalize(&self, path: &Path) -> io::Result { + // MemoryFS doesn't support symlinks, so just verify path exists and return it + use vfs::FileSystem; + self.fs + .metadata(path.to_string_lossy().as_ref()) + .map_err(|err| io::Error::new(io::ErrorKind::NotFound, err))?; + Ok(path.to_path_buf()) + } }