Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions crates/vite_path/src/absolute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,7 @@ impl AsRef<Self> 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)
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/vite_task_graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:?}")]
Expand Down
10 changes: 6 additions & 4 deletions crates/vite_task_plan/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<empty>")?;
return Ok(());
}
for (i, frame) in self.frames.iter().enumerate() {
if i > 0 {
write!(f, " -> ")?;
Expand Down
11 changes: 10 additions & 1 deletion crates/vite_task_plan/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,23 @@ 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,

#[source]
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 }
Expand Down
25 changes: 17 additions & 8 deletions crates/vite_workspace/src/error.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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<AbsolutePath>, workspace_root: Arc<AbsolutePath> },

#[error(
"The stripped path ({stripped_path:?}) is not a valid relative path because: {invalid_path_data_error}"
Expand All @@ -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<AbsolutePath>,
#[source]
serde_json_error: serde_json::Error,
},

#[error("Failed to parse YAML file at {file_path:?}")]
SerdeYml {
file_path: Arc<AbsolutePath>,
#[source]
serde_yml_error: serde_yml::Error,
},

#[error(transparent)]
WaxBuild(#[from] wax::BuildError),
Expand Down
43 changes: 32 additions & 11 deletions crates/vite_workspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -202,17 +204,29 @@ pub fn load_package_graph(
) -> Result<DiGraph<PackageInfo, DependencyType, PackageIx>, 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),
Expand All @@ -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<AbsolutePath> = 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),
});
};

Expand All @@ -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<AbsolutePath> = 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),
Expand Down
83 changes: 60 additions & 23 deletions crates/vite_workspace/src/package_manager.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,58 @@
use std::{
fs::File,
io::{BufReader, Seek, SeekFrom},
path::Path,
sync::Arc,
};

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<AbsolutePath>,
}

impl FileWithPath {
/// Open a file at the given path.
pub fn open(path: Arc<AbsolutePath>) -> Result<Self, Error> {
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<AbsolutePath>) -> Result<Option<Self>, 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<AbsolutePath> {
&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.
Expand All @@ -24,11 +62,12 @@ pub fn find_package_root(original_cwd: &AbsolutePath) -> Result<PackageRoot<'_>,
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<AbsolutePath> = 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,
});
}

Expand All @@ -50,11 +89,11 @@ pub fn find_package_root(original_cwd: &AbsolutePath) -> Result<PackageRoot<'_>,
#[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.
Expand Down Expand Up @@ -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<AbsolutePath> = 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<AbsolutePath> = 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,
));
Expand All @@ -129,12 +175,3 @@ pub fn find_workspace_root(
}
}
}

fn open_exists_file(path: impl AsRef<Path>) -> Result<Option<File>, 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()),
}
}