spinne_core/traverse/
workspace.rs

1use ignore::{DirEntry, WalkBuilder};
2use spinne_logger::Logger;
3use std::path::PathBuf;
4
5use super::Project;
6
7/// Represents a workspace containing multiple projects
8pub struct Workspace {
9    workspace_root: PathBuf,
10    projects: Vec<Project>,
11}
12
13impl Workspace {
14    /// Creates a new Workspace instance from a given path
15    pub fn new(workspace_root: PathBuf) -> Self {
16        Self {
17            workspace_root,
18            projects: Vec::new(),
19        }
20    }
21
22    /// Discovers and analyzes all projects in the workspace
23    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) // We want to find .git folders
31            .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    /// Traverses all discovered projects to analyze their components
45    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    /// Gets a reference to all discovered projects
52    pub fn get_projects(&self) -> &Vec<Project> {
53        &self.projects
54    }
55
56    /// Checks if a directory entry is a project root by looking for package.json and .git
57    fn check_project_root(&mut self, entry: &DirEntry) {
58        let path = entry.path();
59
60        // Only check directories
61        if !path.is_dir() {
62            return;
63        }
64
65        // Check if this is a .git directory
66        if path.file_name().map_or(false, |name| name == ".git") {
67            let project_root = path.parent().unwrap_or(path).to_path_buf();
68
69            // Check if package.json exists in the project root
70            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                // Create and add the project
75                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            // Project 1
91            ("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            // Project 2 in subdirectory
101            ("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}