spinne_core/traverse/
workspace.rs1use ignore::{DirEntry, WalkBuilder};
2use spinne_logger::Logger;
3use std::path::PathBuf;
4
5use super::Project;
6
7pub struct Workspace {
9 workspace_root: PathBuf,
10 projects: Vec<Project>,
11}
12
13impl Workspace {
14 pub fn new(workspace_root: PathBuf) -> Self {
16 Self {
17 workspace_root,
18 projects: Vec::new(),
19 }
20 }
21
22 pub fn discover_projects(&mut self) {
24 Logger::info(&format!(
25 "Traversing workspace: {}",
26 self.workspace_root.display()
27 ));
28
29 let walker = WalkBuilder::new(&self.workspace_root)
30 .hidden(false) .git_ignore(true)
32 .build();
33
34 for entry in walker {
35 match entry {
36 Ok(entry) => self.check_project_root(&entry),
37 Err(e) => Logger::error(&format!("Error while walking directory: {}", e)),
38 }
39 }
40
41 Logger::info(&format!("Found {} projects", self.projects.len()));
42 }
43
44 pub fn traverse_projects(&mut self, exclude: &Vec<String>, include: &Vec<String>) {
46 for project in &mut self.projects {
47 project.traverse(exclude, include);
48 }
49 }
50
51 pub fn get_projects(&self) -> &Vec<Project> {
53 &self.projects
54 }
55
56 fn check_project_root(&mut self, entry: &DirEntry) {
58 let path = entry.path();
59
60 if !path.is_dir() {
62 return;
63 }
64
65 if path.file_name().map_or(false, |name| name == ".git") {
67 let project_root = path.parent().unwrap_or(path).to_path_buf();
68
69 let package_json_path = project_root.join("package.json");
71 if package_json_path.exists() {
72 Logger::info(&format!("Found project at: {}", project_root.display()));
73
74 let project = Project::new(project_root);
76 self.projects.push(project);
77 }
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::util::test_utils;
86
87 #[test]
88 fn test_workspace_discovery() {
89 let temp_dir = test_utils::create_mock_project(&vec![
90 ("projects/project1/.git/HEAD", "ref: refs/heads/main"),
92 ("projects/project1/package.json", r#"{"name": "project1"}"#),
93 (
94 "projects/project1/src/components/Button.tsx",
95 r#"
96 import React from 'react';
97 export const Button = () => <button>Click me</button>;
98 "#,
99 ),
100 ("projects/project2/.git/HEAD", "ref: refs/heads/main"),
102 ("projects/project2/package.json", r#"{"name": "project2"}"#),
103 (
104 "projects/project2/src/App.tsx",
105 r#"
106 import React from 'react';
107 export const App = () => <div>Hello</div>;
108 "#,
109 ),
110 ]);
111
112 let mut workspace = Workspace::new(temp_dir.path().to_path_buf());
113 workspace.discover_projects();
114
115 assert_eq!(workspace.get_projects().len(), 2);
116 }
117}