spinne_core/
package_json.rs

1use serde_json::Value;
2use spinne_logger::Logger;
3use std::fs;
4use std::path::PathBuf;
5
6/// Handles interactions with package.json
7#[derive(Debug, Clone, Default)]
8pub struct PackageJson {
9    /// Path to `package.json`. Contains the `package.json` filename.
10    pub path: PathBuf,
11    /// The name of the project.
12    pub name: Option<String>,
13    /// The workspaces of the project.
14    pub workspaces: Option<Vec<String>>,
15}
16
17impl PackageJson {
18    /// Creates a new PackageJson instance by reading package.json from the given path
19    pub fn read(path: PathBuf) -> Option<Self> {
20        if !path.exists() {
21            Logger::error(&format!("No package.json found at {}", path.display()));
22            return None;
23        }
24
25        let mut package_json = Self::default();
26
27        match fs::read_to_string(&path) {
28            Ok(content) => match serde_json::from_str::<Value>(&content) {
29                Ok(mut parsed) => {
30                    if let Some(json_object) = parsed.as_object_mut() {
31                        // Remove large fields that are useless for pragmatic use.
32                        json_object.remove("description");
33                        json_object.remove("keywords");
34                        json_object.remove("scripts");
35                        json_object.remove("dependencies");
36                        json_object.remove("devDependencies");
37                        json_object.remove("peerDependencies");
38                        json_object.remove("optionalDependencies");
39
40                        // Add name
41                        package_json.name = json_object
42                            .get("name")
43                            .and_then(|field| field.as_str())
44                            .map(ToString::to_string);
45
46                        // Add workspaces
47                        package_json.workspaces = Self::get_workspaces(&parsed);
48                    }
49
50                    Some(package_json)
51                }
52                Err(e) => {
53                    Logger::error(&format!("Failed to parse package.json: {}", e));
54                    None
55                }
56            },
57            Err(e) => {
58                Logger::error(&format!("Failed to read package.json: {}", e));
59                None
60            }
61        }
62    }
63
64    // TODO: resolve workspaces with blob support
65    fn get_workspaces(json: &Value) -> Option<Vec<String>> {
66        let workspaces = json.get("workspaces").and_then(|field| field.as_array());
67
68        match workspaces {
69            Some(workspaces) => Some(
70                workspaces
71                    .iter()
72                    .map(|item| item.as_str().unwrap().to_string())
73                    .collect(),
74            ),
75            None => None,
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use tempfile::TempDir;
84
85    fn setup_test_dir() -> TempDir {
86        let temp_dir = TempDir::new().unwrap();
87
88        fs::write(
89            temp_dir.path().join("package.json"),
90            r#"{
91                "name": "test-project",
92                "version": "1.0.0",
93                "workspaces": ["packages/*"],
94                "config": {
95                    "components": {
96                        "path": "src/components"
97                    }
98                }
99            }"#,
100        )
101        .unwrap();
102
103        temp_dir
104    }
105
106    #[test]
107    fn test_read_package_json() {
108        let temp_dir = setup_test_dir();
109
110        let package_json = PackageJson::read(PathBuf::from(temp_dir.path().join("package.json")))
111            .expect("Failed to read package.json");
112        assert_eq!(package_json.name, Some("test-project".to_string()));
113    }
114
115    #[test]
116    fn test_missing_package_json() {
117        assert!(PackageJson::read(PathBuf::from("package.json")).is_none());
118    }
119
120    #[test]
121    fn test_resolves_workspaces() {
122        let temp_dir = setup_test_dir();
123
124        let package_json =
125            PackageJson::read(PathBuf::from(temp_dir.path().join("package.json"))).unwrap();
126
127        assert_eq!(
128            package_json.workspaces,
129            Some(vec!["packages/*".to_string()])
130        );
131    }
132}