From b7a65c3ee79742b64915eadc498a7cff59285eb2 Mon Sep 17 00:00:00 2001 From: Okiki Ojo Date: Wed, 3 Jun 2026 20:44:24 +0000 Subject: [PATCH 1/2] feat(vite_workspace): support aube-workspace.yaml Allow workspace discovery and package-graph generation to treat aube-workspace.yaml as a first-class workspace marker. This keeps pnpm-workspace.yaml as the higher-priority marker when both are present (to support migration scenarios), and reuses the existing pnpm workspace YAML schema for the package globs. Adds unit tests for workspace-root detection and graph discovery to lock in the behavior. --- crates/vite_workspace/src/lib.rs | 62 +++++++++++++++++++- crates/vite_workspace/src/package_manager.rs | 31 ++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index ab145a3e1..33479c415 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -247,7 +247,8 @@ pub fn load_package_graph( ) -> Result, Error> { let mut graph_builder = PackageGraphBuilder::default(); let workspaces = match &workspace_root.workspace_file { - WorkspaceFile::PnpmWorkspaceYaml(file_with_path) => { + WorkspaceFile::AubeWorkspaceYaml(file_with_path) + | WorkspaceFile::PnpmWorkspaceYaml(file_with_path) => { let workspace: PnpmWorkspace = serde_norway::from_slice(file_with_path.content()) .map_err(|e| Error::SerdeYaml { file_path: Arc::clone(file_with_path.path()), @@ -422,6 +423,65 @@ mod tests { assert!(found_edge, "Should have found edge from pkg-b to pkg-a"); } + #[test] + fn test_get_package_graph_aube_workspace() { + let temp_dir = TempDir::new().unwrap(); + let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); + + // Create aube-workspace.yaml + let workspace_yaml = r#"packages: + - "packages/*" +"#; + fs::write(temp_dir_path.join("aube-workspace.yaml"), workspace_yaml).unwrap(); + + // Create root package.json + let root_package = serde_json::json!({ + "name": "monorepo-root", + "private": true + }); + fs::write(temp_dir_path.join("package.json"), root_package.to_string()).unwrap(); + + // Create packages directory + fs::create_dir_all(temp_dir_path.join("packages")).unwrap(); + + // Create package A + fs::create_dir_all(temp_dir_path.join("packages/pkg-a")).unwrap(); + let pkg_a = serde_json::json!({ + "name": "pkg-a", + "dependencies": {} + }); + fs::write(temp_dir_path.join("packages/pkg-a/package.json"), pkg_a.to_string()).unwrap(); + + // Create package B that depends on A + fs::create_dir_all(temp_dir_path.join("packages/pkg-b")).unwrap(); + let pkg_b = serde_json::json!({ + "name": "pkg-b", + "dependencies": { + "pkg-a": "workspace:*" + } + }); + fs::write(temp_dir_path.join("packages/pkg-b/package.json"), pkg_b.to_string()).unwrap(); + + let graph = discover_package_graph(temp_dir_path).unwrap(); + + // Should have 3 nodes: root + pkg-a + pkg-b + assert_eq!(graph.node_count(), 3); + // Should have 1 edge: pkg-b -> pkg-a + assert_eq!(graph.edge_count(), 1); + + // Verify the dependency edge exists + let mut found_edge = false; + for edge_ref in graph.edge_references() { + let source = &graph[edge_ref.source()]; + let target = &graph[edge_ref.target()]; + if source.package_json.name == "pkg-b" && target.package_json.name == "pkg-a" { + found_edge = true; + assert_eq!(*edge_ref.weight(), DependencyType::Normal); + } + } + assert!(found_edge, "Should have found edge from pkg-b to pkg-a"); + } + #[test] fn test_get_package_graph_workspace_exclusions() { let temp_dir = TempDir::new().unwrap(); diff --git a/crates/vite_workspace/src/package_manager.rs b/crates/vite_workspace/src/package_manager.rs index 995d3762c..a4287ddef 100644 --- a/crates/vite_workspace/src/package_manager.rs +++ b/crates/vite_workspace/src/package_manager.rs @@ -100,6 +100,8 @@ pub fn find_package_root(original_cwd: &AbsolutePath) -> Result, /// - `NonWorkspacePackage` is the package.json file of a non-workspace package. #[derive(Debug)] pub enum WorkspaceFile { + /// The aube-workspace.yaml file of an aube workspace. + AubeWorkspaceYaml(FileWithPath), /// The pnpm-workspace.yaml file of a pnpm workspace. PnpmWorkspaceYaml(FileWithPath), /// The package.json file of a yarn/npm workspace. @@ -152,6 +154,24 @@ pub fn find_workspace_root( )); } + // Check for aube-workspace.yaml for aube workspaces. + // + // Aube can operate in repositories that still use pnpm's workspace YAML during + // migration. We therefore keep pnpm-workspace.yaml as the highest-priority + // workspace marker when both are present. + let aube_workspace_path: Arc = cwd.join("aube-workspace.yaml").into(); + if let Some(file_with_path) = FileWithPath::open_if_exists(aube_workspace_path)? { + let relative_cwd = + original_cwd.strip_prefix(cwd)?.expect("cwd must be within the aube workspace"); + return Ok(( + WorkspaceRoot { + path: Arc::from(cwd), + workspace_file: WorkspaceFile::AubeWorkspaceYaml(file_with_path), + }, + relative_cwd, + )); + } + // Check for package.json with workspaces field for npm/yarn workspace let package_json_path: Arc = cwd.join("package.json").into(); if let Some(file_with_path) = FileWithPath::open_if_exists(package_json_path)? { @@ -258,6 +278,17 @@ mod tests { assert_eq!(&**file_with_path.path(), &*path); } + #[test] + fn find_workspace_root_detects_aube_workspace_yaml() { + let temp_dir = TempDir::new().unwrap(); + let temp_dir_path = AbsolutePath::new(temp_dir.path()).unwrap(); + fs::write(temp_dir_path.join("aube-workspace.yaml"), b"packages:\n - apps/*\n").unwrap(); + + let (workspace_root, relative) = find_workspace_root(temp_dir_path).unwrap(); + assert!(relative.as_str().is_empty()); + assert!(matches!(workspace_root.workspace_file, WorkspaceFile::AubeWorkspaceYaml(_))); + } + #[test] fn file_with_path_open_if_exists_returns_none_when_missing() { let temp_dir = TempDir::new().unwrap(); From abcce496d2b59dbddfba40f1cfbd434b835eb7e8 Mon Sep 17 00:00:00 2001 From: Okiki Ojo Date: Thu, 4 Jun 2026 05:11:56 +0000 Subject: [PATCH 2/2] docs(vite_workspace): clarify aube yaml handling --- crates/vite_workspace/src/lib.rs | 3 +++ crates/vite_workspace/src/package_manager.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index 33479c415..59d253cc7 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -249,6 +249,9 @@ pub fn load_package_graph( let workspaces = match &workspace_root.workspace_file { WorkspaceFile::AubeWorkspaceYaml(file_with_path) | WorkspaceFile::PnpmWorkspaceYaml(file_with_path) => { + // NOTE: `aube-workspace.yaml` is intentionally compatible with pnpm's workspace YAML + // format for the fields we currently read (specifically `packages: [...]` glob + // patterns). We deserialize both via `PnpmWorkspace` to reuse the same expansion logic. let workspace: PnpmWorkspace = serde_norway::from_slice(file_with_path.content()) .map_err(|e| Error::SerdeYaml { file_path: Arc::clone(file_with_path.path()), diff --git a/crates/vite_workspace/src/package_manager.rs b/crates/vite_workspace/src/package_manager.rs index a4287ddef..53bab907d 100644 --- a/crates/vite_workspace/src/package_manager.rs +++ b/crates/vite_workspace/src/package_manager.rs @@ -100,7 +100,7 @@ pub fn find_package_root(original_cwd: &AbsolutePath) -> Result, /// - `NonWorkspacePackage` is the package.json file of a non-workspace package. #[derive(Debug)] pub enum WorkspaceFile { - /// The aube-workspace.yaml file of an aube workspace. + /// The `aube-workspace.yaml` file of an Aube workspace. AubeWorkspaceYaml(FileWithPath), /// The pnpm-workspace.yaml file of a pnpm workspace. PnpmWorkspaceYaml(FileWithPath),