changepacks_node/
finder.rs

1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use changepacks_core::{Project, ProjectFinder};
4use std::{
5    collections::HashMap,
6    path::{Path, PathBuf},
7};
8use tokio::fs::read_to_string;
9
10use crate::{package::NodePackage, workspace::NodeWorkspace};
11
12#[derive(Debug)]
13pub struct NodeProjectFinder {
14    projects: HashMap<PathBuf, Project>,
15    project_files: Vec<&'static str>,
16}
17
18impl Default for NodeProjectFinder {
19    fn default() -> Self {
20        Self::new()
21    }
22}
23
24impl NodeProjectFinder {
25    pub fn new() -> Self {
26        Self {
27            projects: HashMap::new(),
28            project_files: vec!["package.json"],
29        }
30    }
31}
32
33#[async_trait]
34impl ProjectFinder for NodeProjectFinder {
35    fn projects(&self) -> Vec<&Project> {
36        self.projects.values().collect::<Vec<_>>()
37    }
38    fn projects_mut(&mut self) -> Vec<&mut Project> {
39        self.projects.values_mut().collect::<Vec<_>>()
40    }
41
42    fn project_files(&self) -> &[&str] {
43        &self.project_files
44    }
45
46    async fn visit(&mut self, path: &Path, relative_path: &Path) -> Result<()> {
47        // glob all the package.json in the root without .gitignore
48        if path.is_file()
49            && self.project_files().contains(
50                &path
51                    .file_name()
52                    .context(format!("File name not found - {}", path.display()))?
53                    .to_str()
54                    .context(format!("File name not found - {}", path.display()))?,
55            )
56        {
57            if self.projects.contains_key(path) {
58                return Ok(());
59            }
60            // read package.json
61            let package_json = read_to_string(path).await?;
62            let package_json: serde_json::Value = serde_json::from_str(&package_json)?;
63            // if workspaces
64            if package_json.get("workspaces").is_some()
65                || path
66                    .parent()
67                    .context(format!("Parent not found - {}", path.display()))?
68                    .join("pnpm-workspace.yaml")
69                    .is_file()
70            {
71                let version = package_json["version"].as_str().map(|v| v.to_string());
72                let name = package_json["name"].as_str().map(|v| v.to_string());
73                self.projects.insert(
74                    path.to_path_buf(),
75                    Project::Workspace(Box::new(NodeWorkspace::new(
76                        name,
77                        version,
78                        path.to_path_buf(),
79                        relative_path.to_path_buf(),
80                    ))),
81                );
82            } else {
83                let version = package_json["version"]
84                    .as_str()
85                    .context(format!("Version not found - {}", path.display()))?
86                    .to_string();
87                let name = package_json["name"]
88                    .as_str()
89                    .context(format!("Name not found - {}", path.display()))?
90                    .to_string();
91
92                self.projects.insert(
93                    path.to_path_buf(),
94                    Project::Package(Box::new(NodePackage::new(
95                        name,
96                        version,
97                        path.to_path_buf(),
98                        relative_path.to_path_buf(),
99                    ))),
100                );
101            }
102        }
103        Ok(())
104    }
105}