diff --git a/crates/vite_path/src/absolute/mod.rs b/crates/vite_path/src/absolute/mod.rs index 909a2d52..98ffe860 100644 --- a/crates/vite_path/src/absolute/mod.rs +++ b/crates/vite_path/src/absolute/mod.rs @@ -27,14 +27,7 @@ impl AsRef for AbsolutePath { impl Debug for AbsolutePath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut debug_tuple = f.debug_tuple("AbsolutePath"); - #[cfg(feature = "absolute-redaction")] - if let Some(redacted_path) = self.try_redact().unwrap() { - debug_tuple.field(&redacted_path); - return debug_tuple.finish(); - } - debug_tuple.field(&&self.0); - debug_tuple.finish() + Debug::fmt(&self.0, f) } } diff --git a/crates/vite_task_graph/src/lib.rs b/crates/vite_task_graph/src/lib.rs index 044496de..a71b8beb 100644 --- a/crates/vite_task_graph/src/lib.rs +++ b/crates/vite_task_graph/src/lib.rs @@ -88,7 +88,7 @@ impl vite_graph_ser::GetKey for TaskNode { #[derive(Debug, thiserror::Error)] pub enum TaskGraphLoadError { - #[error("Failed to load package graph: {0}")] + #[error("Failed to load package graph")] PackageGraphLoadError(#[from] vite_workspace::Error), #[error("Failed to load task config file for package at {package_path:?}")] diff --git a/crates/vite_task_plan/src/context.rs b/crates/vite_task_plan/src/context.rs index dec3d47a..5fa02e70 100644 --- a/crates/vite_task_plan/src/context.rs +++ b/crates/vite_task_plan/src/context.rs @@ -64,12 +64,14 @@ pub struct TaskCallStackDisplay { frames: Arc<[TaskCallStackFrameDisplay]>, } +impl TaskCallStackDisplay { + pub fn is_empty(&self) -> bool { + self.frames.is_empty() + } +} + impl Display for TaskCallStackDisplay { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.frames.is_empty() { - write!(f, "")?; - return Ok(()); - } for (i, frame) in self.frames.iter().enumerate() { if i > 0 { write!(f, " -> ")?; diff --git a/crates/vite_task_plan/src/error.rs b/crates/vite_task_plan/src/error.rs index 225f9e7a..f313f7fb 100644 --- a/crates/vite_task_plan/src/error.rs +++ b/crates/vite_task_plan/src/error.rs @@ -126,7 +126,6 @@ pub enum TaskPlanErrorKind { } #[derive(Debug, thiserror::Error)] -#[error("Failed to plan execution, task call stack: {task_call_stack}")] pub struct Error { task_call_stack: TaskCallStackDisplay, @@ -134,6 +133,16 @@ pub struct Error { kind: TaskPlanErrorKind, } +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to plan execution")?; + if !self.task_call_stack.is_empty() { + write!(f, ", task call stack: {}", self.task_call_stack)? + } + Ok(()) + } +} + impl TaskPlanErrorKind { pub fn with_empty_call_stack(self) -> Error { Error { task_call_stack: TaskCallStackDisplay::default(), kind: self } diff --git a/crates/vite_workspace/src/error.rs b/crates/vite_workspace/src/error.rs index 63317ffa..10dc01fe 100644 --- a/crates/vite_workspace/src/error.rs +++ b/crates/vite_workspace/src/error.rs @@ -1,7 +1,8 @@ -use std::{io, path::Path}; +use std::{io, path::Path, sync::Arc}; use vite_path::{ - AbsolutePathBuf, RelativePathBuf, absolute::StripPrefixError, relative::InvalidPathDataError, + AbsolutePath, AbsolutePathBuf, RelativePathBuf, absolute::StripPrefixError, + relative::InvalidPathDataError, }; use vite_str::Str; @@ -14,7 +15,7 @@ pub enum Error { PackageJsonNotFound(AbsolutePathBuf), #[error("Package at `{package_path:?}` is outside workspace root `{workspace_root:?}`")] - PackageOutsideWorkspace { package_path: AbsolutePathBuf, workspace_root: AbsolutePathBuf }, + PackageOutsideWorkspace { package_path: Arc, workspace_root: Arc }, #[error( "The stripped path ({stripped_path:?}) is not a valid relative path because: {invalid_path_data_error}" @@ -25,11 +26,19 @@ pub enum Error { #[error(transparent)] Io(#[from] io::Error), - #[error(transparent)] - Serde(#[from] serde_json::Error), - - #[error(transparent)] - SerdeYml(#[from] serde_yml::Error), + #[error("Failed to parse JSON file at {file_path:?}")] + SerdeJson { + file_path: Arc, + #[source] + serde_json_error: serde_json::Error, + }, + + #[error("Failed to parse YAML file at {file_path:?}")] + SerdeYml { + file_path: Arc, + #[source] + serde_yml_error: serde_yml::Error, + }, #[error(transparent)] WaxBuild(#[from] wax::BuildError), diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index 23c3cb1e..615ab86f 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -16,7 +16,9 @@ use wax::Glob; pub use crate::{ error::Error, package::{DependencyType, PackageJson}, - package_manager::{WorkspaceFile, WorkspaceRoot, find_package_root, find_workspace_root}, + package_manager::{ + FileWithPath, WorkspaceFile, WorkspaceRoot, find_package_root, find_workspace_root, + }, }; /// The workspace configuration for pnpm. @@ -202,17 +204,29 @@ pub fn load_package_graph( ) -> Result, Error> { let mut graph_builder = PackageGraphBuilder::default(); let workspaces = match &workspace_root.workspace_file { - WorkspaceFile::PnpmWorkspaceYaml(file) => { - let workspace: PnpmWorkspace = serde_yml::from_reader(file)?; + WorkspaceFile::PnpmWorkspaceYaml(file_with_path) => { + let workspace: PnpmWorkspace = + serde_yml::from_reader(file_with_path.file()).map_err(|e| Error::SerdeYml { + file_path: Arc::clone(file_with_path.path()), + serde_yml_error: e, + })?; workspace.packages } - WorkspaceFile::NpmWorkspaceJson(file) => { - let workspace: NpmWorkspace = serde_json::from_reader(file)?; + WorkspaceFile::NpmWorkspaceJson(file_with_path) => { + let workspace: NpmWorkspace = + serde_json::from_reader(file_with_path.file()).map_err(|e| Error::SerdeJson { + file_path: Arc::clone(file_with_path.path()), + serde_json_error: e, + })?; workspace.workspaces } - WorkspaceFile::NonWorkspacePackage(file) => { + WorkspaceFile::NonWorkspacePackage(file_with_path) => { // For non-workspace packages, add the package.json to the graph as a root package - let package_json: PackageJson = serde_json::from_reader(file)?; + let package_json: PackageJson = serde_json::from_reader(file_with_path.file()) + .map_err(|e| Error::SerdeJson { + file_path: Arc::clone(file_with_path.path()), + serde_json_error: e, + })?; graph_builder.add_package( RelativePathBuf::default(), Arc::clone(&workspace_root.path), @@ -226,12 +240,16 @@ pub fn load_package_graph( let member_globs = WorkspaceMemberGlobs::new(workspaces); let mut has_root_package = false; for package_json_path in member_globs.get_package_json_paths(&*workspace_root.path)? { - let package_json: PackageJson = serde_json::from_slice(&fs::read(&package_json_path)?)?; + let package_json_path: Arc = package_json_path.clone().into(); + let package_json: PackageJson = + serde_json::from_slice(&fs::read(&*package_json_path)?).map_err(|e| { + Error::SerdeJson { file_path: Arc::clone(&package_json_path), serde_json_error: e } + })?; let absolute_path = package_json_path.parent().unwrap(); let Some(package_path) = absolute_path.strip_prefix(&*workspace_root.path)? else { return Err(Error::PackageOutsideWorkspace { package_path: package_json_path, - workspace_root: workspace_root.path.to_absolute_path_buf(), + workspace_root: Arc::clone(&workspace_root.path), }); }; @@ -242,8 +260,11 @@ pub fn load_package_graph( if !has_root_package { let package_json_path = workspace_root.path.join("package.json"); match fs::read(&package_json_path) { - Ok(package_json) => { - let package_json: PackageJson = serde_json::from_slice(&package_json)?; + Ok(content) => { + let package_json_path: Arc = package_json_path.into(); + let package_json: PackageJson = serde_json::from_slice(&content).map_err(|e| { + Error::SerdeJson { file_path: package_json_path, serde_json_error: e } + })?; graph_builder.add_package( RelativePathBuf::default(), Arc::clone(&workspace_root.path), diff --git a/crates/vite_workspace/src/package_manager.rs b/crates/vite_workspace/src/package_manager.rs index a564ab56..21110434 100644 --- a/crates/vite_workspace/src/package_manager.rs +++ b/crates/vite_workspace/src/package_manager.rs @@ -1,7 +1,6 @@ use std::{ fs::File, io::{BufReader, Seek, SeekFrom}, - path::Path, sync::Arc, }; @@ -9,12 +8,51 @@ use vite_path::{AbsolutePath, RelativePathBuf}; use crate::Error; +/// A file handle bundled with its absolute path for error context. +#[derive(Debug)] +pub struct FileWithPath { + file: File, + path: Arc, +} + +impl FileWithPath { + /// Open a file at the given path. + pub fn open(path: Arc) -> Result { + let file = File::open(&*path)?; + Ok(Self { file, path }) + } + + /// Try to open a file, returning None if it doesn't exist. + pub fn open_if_exists(path: Arc) -> Result, Error> { + match File::open(&*path) { + Ok(file) => Ok(Some(Self { file, path })), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(e.into()), + } + } + + /// Get a reference to the file handle. + pub fn file(&self) -> &File { + &self.file + } + + /// Get a mutable reference to the file handle. + pub fn file_mut(&mut self) -> &mut File { + &mut self.file + } + + /// Get the file path. + pub fn path(&self) -> &Arc { + &self.path + } +} + /// The package root directory and its package.json file. #[derive(Debug)] pub struct PackageRoot<'a> { pub path: &'a AbsolutePath, pub cwd: RelativePathBuf, - pub package_json: File, + pub package_json: FileWithPath, } /// Find the package root directory from the current working directory. `original_cwd` must be absolute. @@ -24,11 +62,12 @@ pub fn find_package_root(original_cwd: &AbsolutePath) -> Result, let mut cwd = original_cwd; loop { // Check for package.json - if let Some(file) = open_exists_file(cwd.join("package.json"))? { + let package_json_path: Arc = cwd.join("package.json").into(); + if let Some(file_with_path) = FileWithPath::open_if_exists(package_json_path)? { return Ok(PackageRoot { path: cwd, cwd: original_cwd.strip_prefix(cwd)?.expect("cwd must be within the package root"), - package_json: file, + package_json: file_with_path, }); } @@ -50,11 +89,11 @@ pub fn find_package_root(original_cwd: &AbsolutePath) -> Result, #[derive(Debug)] pub enum WorkspaceFile { /// The pnpm-workspace.yaml file of a pnpm workspace. - PnpmWorkspaceYaml(File), + PnpmWorkspaceYaml(FileWithPath), /// The package.json file of a yarn/npm workspace. - NpmWorkspaceJson(File), + NpmWorkspaceJson(FileWithPath), /// The package.json file of a non-workspace package. - NonWorkspacePackage(File), + NonWorkspacePackage(FileWithPath), } /// The workspace root directory and its workspace file. @@ -82,31 +121,38 @@ pub fn find_workspace_root( loop { // Check for pnpm-workspace.yaml for pnpm workspace - if let Some(file) = open_exists_file(cwd.join("pnpm-workspace.yaml"))? { + let pnpm_workspace_path: Arc = cwd.join("pnpm-workspace.yaml").into(); + if let Some(file_with_path) = FileWithPath::open_if_exists(pnpm_workspace_path)? { let relative_cwd = original_cwd.strip_prefix(cwd)?.expect("cwd must be within the pnpm workspace"); return Ok(( WorkspaceRoot { path: Arc::from(cwd), - workspace_file: WorkspaceFile::PnpmWorkspaceYaml(file), + workspace_file: WorkspaceFile::PnpmWorkspaceYaml(file_with_path), }, relative_cwd, )); } // Check for package.json with workspaces field for npm/yarn workspace - let package_json_path = cwd.join("package.json"); - if let Some(mut file) = open_exists_file(&package_json_path)? { - let package_json: serde_json::Value = serde_json::from_reader(BufReader::new(&file))?; + let package_json_path: Arc = cwd.join("package.json").into(); + if let Some(mut file_with_path) = FileWithPath::open_if_exists(package_json_path)? { + let package_json: serde_json::Value = + serde_json::from_reader(BufReader::new(file_with_path.file())).map_err(|e| { + Error::SerdeJson { + file_path: Arc::clone(file_with_path.path()), + serde_json_error: e, + } + })?; if package_json.get("workspaces").is_some() { // Reset the file cursor since we consumed it reading - file.seek(SeekFrom::Start(0))?; + file_with_path.file_mut().seek(SeekFrom::Start(0))?; let relative_cwd = original_cwd.strip_prefix(cwd)?.expect("cwd must be within the workspace"); return Ok(( WorkspaceRoot { path: Arc::from(cwd), - workspace_file: WorkspaceFile::NpmWorkspaceJson(file), + workspace_file: WorkspaceFile::NpmWorkspaceJson(file_with_path), }, relative_cwd, )); @@ -129,12 +175,3 @@ pub fn find_workspace_root( } } } - -fn open_exists_file(path: impl AsRef) -> Result, Error> { - match File::open(path) { - Ok(file) => Ok(Some(file)), - // if the file does not exist, return None - Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), - Err(e) => Err(e.into()), - } -}