diff --git a/crates/vite_workspace/src/lib.rs b/crates/vite_workspace/src/lib.rs index ab145a3e1..59d253cc7 100644 --- a/crates/vite_workspace/src/lib.rs +++ b/crates/vite_workspace/src/lib.rs @@ -247,7 +247,11 @@ 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) => { + // 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()), @@ -422,6 +426,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..53bab907d 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();