1use crate::adapters::PackageAdapter;
2use crate::errors::WorkspaceError;
3use crate::types::Workspace;
4use std::path::{Path, PathBuf};
5
6type Result<T> = std::result::Result<T, WorkspaceError>;
7
8pub fn discover_workspace(start_dir: &Path) -> Result<Workspace> {
10 let mut root = None;
11 let mut all_members = Vec::new();
12
13 for adapter in PackageAdapter::all() {
15 if adapter.can_discover(start_dir) {
16 let discovered_root = find_workspace_root_for_adapter(start_dir, *adapter)?;
18
19 let packages = adapter.discover(&discovered_root)?;
21
22 root.get_or_insert(discovered_root);
24 all_members.extend(packages);
25 }
26 }
27
28 let workspace_root = root.ok_or(WorkspaceError::NotFound)?;
29
30 Ok(Workspace {
31 root: workspace_root,
32 members: all_members,
33 })
34}
35
36fn find_workspace_root_for_adapter(start_dir: &Path, adapter: PackageAdapter) -> Result<PathBuf> {
38 let mut current = start_dir;
39 loop {
40 if adapter.can_discover(current) {
41 return Ok(current.to_path_buf());
42 }
43 current = current.parent().ok_or(WorkspaceError::NotFound)?;
44 }
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50 use crate::types::PackageKind;
51 use std::fs;
52
53 #[test]
54 fn discover_workspace_finds_cargo_packages() {
55 let temp = tempfile::tempdir().unwrap();
56 let root = temp.path();
57
58 fs::write(
60 root.join("Cargo.toml"),
61 "[workspace]\nmembers = [\"crates/*\"]\n",
62 )
63 .unwrap();
64
65 let crates_dir = root.join("crates");
67 fs::create_dir_all(crates_dir.join("pkg-a")).unwrap();
68 fs::create_dir_all(crates_dir.join("pkg-b")).unwrap();
69 fs::write(
70 crates_dir.join("pkg-a/Cargo.toml"),
71 "[package]\nname = \"pkg-a\"\nversion = \"0.1.0\"\n",
72 )
73 .unwrap();
74 fs::write(
75 crates_dir.join("pkg-b/Cargo.toml"),
76 "[package]\nname = \"pkg-b\"\nversion = \"0.2.0\"\n",
77 )
78 .unwrap();
79
80 let ws = discover_workspace(root).unwrap();
81 assert_eq!(ws.members.len(), 2);
82
83 let mut names: Vec<_> = ws.members.iter().map(|p| p.name.as_str()).collect();
84 names.sort();
85 assert_eq!(names, vec!["pkg-a", "pkg-b"]);
86 }
87
88 #[test]
89 fn discover_workspace_detects_internal_deps() {
90 let temp = tempfile::tempdir().unwrap();
91 let root = temp.path();
92
93 fs::write(
95 root.join("Cargo.toml"),
96 "[workspace]\nmembers = [\"crates/*\"]\n",
97 )
98 .unwrap();
99
100 let crates_dir = root.join("crates");
102 fs::create_dir_all(crates_dir.join("x")).unwrap();
103 fs::create_dir_all(crates_dir.join("y")).unwrap();
104 fs::create_dir_all(crates_dir.join("z")).unwrap();
105 fs::write(
106 crates_dir.join("x/Cargo.toml"),
107 format!(
108 "{}{}{}",
109 "[package]\nname=\"x\"\nversion=\"0.1.0\"\n",
110 "[dependencies]\n",
111 "y={ path=\"../y\" }\n z={ workspace=true }\n"
112 ),
113 )
114 .unwrap();
115 fs::write(
116 crates_dir.join("y/Cargo.toml"),
117 "[package]\nname=\"y\"\nversion=\"0.1.0\"\n",
118 )
119 .unwrap();
120 fs::write(
121 crates_dir.join("z/Cargo.toml"),
122 "[package]\nname=\"z\"\nversion=\"0.1.0\"\n",
123 )
124 .unwrap();
125
126 let ws = discover_workspace(root).unwrap();
127 let x = ws.members.iter().find(|c| c.name == "x").unwrap();
128 assert!(x.internal_deps.contains("cargo/y"));
129 assert!(x.internal_deps.contains("cargo/z"));
130 }
131
132 #[test]
133 fn discover_workspace_returns_only_cargo_packages() {
134 let temp = tempfile::tempdir().unwrap();
140 let root = temp.path();
141
142 fs::write(
144 root.join("Cargo.toml"),
145 "[workspace]\nmembers = [\"crates/*\"]\n",
146 )
147 .unwrap();
148
149 let crates_dir = root.join("crates");
150 fs::create_dir_all(crates_dir.join("cargo-pkg")).unwrap();
151 fs::write(
152 crates_dir.join("cargo-pkg/Cargo.toml"),
153 "[package]\nname = \"cargo-pkg\"\nversion = \"1.0.0\"\n",
154 )
155 .unwrap();
156
157 let ws = discover_workspace(root).unwrap();
158
159 assert_eq!(ws.members.len(), 1);
161 assert_eq!(ws.members[0].name, "cargo-pkg");
162 assert_eq!(ws.members[0].kind, PackageKind::Cargo);
163 }
164
165 #[test]
166 fn discover_workspace_handles_empty_workspace() {
167 let temp = tempfile::tempdir().unwrap();
169 let root = temp.path();
170
171 fs::write(root.join("Cargo.toml"), "[workspace]\nmembers = []\n").unwrap();
172
173 let ws = discover_workspace(root).unwrap();
174 assert_eq!(ws.members.len(), 0);
175 assert_eq!(ws.root, root);
176 }
177
178 #[test]
179 fn discover_workspace_fails_when_no_workspace_found() {
180 let temp = tempfile::tempdir().unwrap();
182 let root = temp.path();
183
184 let result = discover_workspace(root);
186 assert!(result.is_err());
187
188 match result {
190 Err(WorkspaceError::NotFound) => {}
191 _ => panic!("Expected WorkspaceError::NotFound"),
192 }
193 }
194}