Skip to content
Open
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
65 changes: 64 additions & 1 deletion crates/vite_workspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,11 @@ 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_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 {
Comment thread
okikio marked this conversation as resolved.
file_path: Arc::clone(file_with_path.path()),
Expand Down Expand Up @@ -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();
Expand Down
31 changes: 31 additions & 0 deletions crates/vite_workspace/src/package_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ pub fn find_package_root(original_cwd: &AbsolutePath) -> Result<PackageRoot<'_>,
/// - `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.
Expand Down Expand Up @@ -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<AbsolutePath> = 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<AbsolutePath> = cwd.join("package.json").into();
if let Some(file_with_path) = FileWithPath::open_if_exists(package_json_path)? {
Expand Down Expand Up @@ -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();
Expand Down